diff --git a/Apps/FiniteDefElasticity/Makefile b/Apps/FiniteDefElasticity/Makefile new file mode 100644 index 00000000..a66d36c6 --- /dev/null +++ b/Apps/FiniteDefElasticity/Makefile @@ -0,0 +1,71 @@ +# $Id: Makefile,v 1.7 2011-02-08 09:32:18 kmo Exp $ +#=============================================================================== +# +# File: Makefile +# +# Created: Jan 12 2010 +# +# Author: Knut Morten Okstad / SINTEF +# +# Description: +# Makefile for spline-based isogeometric finite element simulation applications. +# +#=============================================================================== + +CSRC = main_NonLinEl.C NonlinearElasticityTL.C NonlinearElasticityUL.C \ + NonlinearElasticityULMX.C NonlinearElasticityULMixed.C \ + NonlinearElasticity.C NeoHookeElasticity.C \ + SIMFiniteDefEl.C ../SIMLinEl2D.C ../SIMLinEl3D.C +FSRC = accKM.f accKMx.f stiff_TL.f +OBJS = $(CSRC:.C=.o) $(FSRC:.f=.o) + +APP = nonlinel + +CXX = g++ +FC = gfortran + +MYLIB = ../../libSplineFEM.a libMaterial.a +GOTLIB = -L../../GoTools/lib -lGoTrivariate -lGoToolsCore +VTFLIB = -L../../GLviewExpressWriter/lib -lVTFExpressAPI +BLASLIB = -larpack -llapack -lblas +SLULIB = -lsuperlu +#SLULIB = -L../../SuperLU_MT/lib -lsuperlu_mt_PTHREAD -lpthread +#SAMGLIB = -L../../SAMG/samg -lamg_coo +LIBS = $(MYLIB) $(GOTLIB) $(VTFLIB) $(SAMGLIB) $(SLULIB) $(BLASLIB) + +#CDEBUG = -g -DINDEX_CHECK=2 -DINT_DEBUG=2 +CDEBUG = -O2 + +FFLAGS = -O3 -funroll-loops +CXXFLAGS = $(CDEBUG) -Dreal=double -DUSE_CBLAS -DUSE_FTNMAT \ + -I. -I.. -I../../Integrands -I../../SIM -I../../ASM \ + -I../../LinAlg -I../../Utility + +all: $(APP) + +$(APP): $(OBJS) $(MYLIB) Makefile + $(CXX) -o $@ $(OBJS) $(LIBS) -lgfortran + +../../libSplineFEM.a: + cd ../..; make + +libMaterial.a: + cd Material; make + +clean: + rm -f $(OBJS) + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile > /dev/null + mv Makefile.tmp Makefile + +depend: + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile > /dev/null + $(CXX) -M $(CXXFLAGS) *.C >> Makefile.tmp + mv Makefile Makefile.bak + mv Makefile.tmp Makefile + +tar: clean + tar cfv FDSolve.tar Makefile $(FSRC) $(CSRC) *.h \ + Material/Makefile Material/*.[fh] + gzip FDSolve.tar + +# DO NOT DELETE diff --git a/Apps/FiniteDefElasticity/Material/Makefile b/Apps/FiniteDefElasticity/Material/Makefile new file mode 100644 index 00000000..2a84abf4 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/Makefile @@ -0,0 +1,23 @@ +# $Id: Makefile,v 1.1 2010-12-18 20:03:54 kmo Exp $ + +LIB = ../libMaterial.a + +SRC = amat3d.f cons2d.f cons3d.f eigs3d.f ieps3d.f liel3d.f mdma3d.f \ + monh3d.f pcst3d.f push3d.f stnh3d.f vfnh3d.f wlog3d.f rprin0.f +OBJS = $(SRC:.f=.o) + +FC = gfortran +FFLAGS = -O3 +#FFLAGS = -g + +all: feninc $(LIB) + +clean: + -rm $(OBJS) + +$(LIB): $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + +feninc: + ln -s . include + ln -s . feninc diff --git a/Apps/FiniteDefElasticity/Material/amat3d.f b/Apps/FiniteDefElasticity/Material/amat3d.f new file mode 100644 index 00000000..9d80edcc --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/amat3d.f @@ -0,0 +1,162 @@ + subroutine amat3d (taup, aap, q, tau, aa, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: AMAT3D NTNU/SINTEF/DNV +C +C PURPOSE: +C AMAT3D: Transform Kirchhoff stresses and material moduli from +C principal to standard basis +C +C METHOD: +C +C +C ARGUMENTS INPUT: +C taup(3) - Principal deviatoric Kirchhoff stresses +C aap(6,6) - Deviatoric tangent moduli in principal basis +C q(6,6) - Transformation matrix: [principal]-->[std] basis +C +C ARGUMENTS OUTPUT: +C tau(3) - Deviatoric Kirchhoff stresses (ref to standard basis) +C aa(6,6) - Tangent moduli in standard basis +C ierr - Error flag +C = 1 : +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C +C +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C November 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer i, j, k, ipswb3, iwr, ierr +C + real*8 taup(3), aap(6,6), q(6,6), tau(6), aa(6,6), vi(6) +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE AMAT3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + call rprin0(taup, 1, 3, 'taup ', iwr) + call rprin0(aap , 6, 6, 'aap ', iwr) + call rprin0(q , 6, 6, 'q ', iwr) + endif + endif +C +C Compute stress tensor +C + do j = 1,6 + tau(j) = q(j,1)*taup(1) + q(j,2)*taup(2) + q(j,3)*taup(3) + end do ! j +C +C Compute tangent matrix in standard basis +C + do i = 1,6 +C +C Left multiplication: V = Q * AL +C + do k = 1,3 + vi(k) = zero + do j = 1,3 + vi(k) = vi(k) + q(i,j)*aap(j,k) + end do ! j + end do ! k +C + do k = 4,6 + vi(k) = q(i,k)*aap(k,k) + end do ! k +C +C Right multiplication: AA = V * Q_t +C + do j = 1,i + aa(i,j) = zero + do k = 1,6 + aa(i,j) = aa(i,j) + vi(k)*q(j,k) + end do ! k +C +C Fill in symmetric part +C + aa(j,i) = aa(i,j) +C + end do ! j +C + end do ! i + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE AMAT3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + call rprin0(taup, 1, 3, 'taup ', iwr) + call rprin0(aap , 6, 6, 'aap ', iwr) + call rprin0(q , 6, 6, 'q ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + call rprin0(tau, 1, 3, 'tau ', iwr) + call rprin0(aa , 6, 6, 'aa ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE AMAT3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + call rprin0(tau, 1, 3, 'tau ', iwr) + call rprin0(aa , 6, 6, 'aa ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/cons2d.f b/Apps/FiniteDefElasticity/Material/cons2d.f new file mode 100644 index 00000000..0404a8d3 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/cons2d.f @@ -0,0 +1,29 @@ + subroutine cons2d (mTYP,mVER,detF,F,pmat,U,Sig,Cst,ipsw,iwr,ierr) +C +C CONS2D: 2D wrapper on the 3D material of FENRIS. +C + implicit none +C + integer mTYP, mVER, ipsw, iwr, ierr + real*8 detF, U, F(4), pmat(*), Sig(3), Cst(3,3) + real*8 F3D(3,3), S3D(6), C3D(6,6) +C + F3D = 0.0D0 + F3D(1,1) = F(1) + F3D(2,1) = F(2) + F3D(1,2) = F(3) + F3D(2,2) = F(4) + F3D(3,3) = 1.0D0 + + call cons3d (mTYP,mVER,detF,F3D,pmat,U,S3D,C3D,ipsw,iwr,ierr) + + Cst(1:2,1:2) = C3D(1:2,1:2) + Cst(1:2,3) = C3D(1:2,4) + Cst(3,1:2) = C3D(4,1:2) + Cst(3,3) = C3D(4,4) + Sig(1:2) = S3D(1:2) + Sig(3) = S3D(4) +C + return + end + diff --git a/Apps/FiniteDefElasticity/Material/cons3d.f b/Apps/FiniteDefElasticity/Material/cons3d.f new file mode 100644 index 00000000..3a09c207 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/cons3d.f @@ -0,0 +1,226 @@ + subroutine cons3d (mTYP, mVER, detF, F, pmat, Engy, Sig, Cst, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: CONS3D NTNU/SINTEF/DNV +C +C PURPOSE: +C CONS3D: Driver for finite deformation constitutive models for +C 3D solids +C +C METHOD: +C Compute Cauchy stresses and spatial constitutive tensor +C for finite deformation constitutive models for 3D solids. +C +C The following material models are available: +C mTYP = 10 Linear elastic material, 3-D stress +C mTYP = 20 Hyperelastic compressible neoHookean model, 3-D stress +C mTYP = 40 Elastic-plastic strain hardening material, +C combined isotropic and kinematic hardening, +C 3-D stress +C +C ARGUMENTS INPUT: +C mTYP - Material type number +C mVER - Material version number +C detF - Determinant of the deformation gradient +C F - Deformation gradient +C pmat(2) - Material property array +C +C ARGUMENTS OUTPUT: +C Engy - Strain energy density +C Sig(6) - Cauchy stresses (spatial stresses) +C Cst(6,6) - Spatial constitutive tensor +C ierr - Error flag +C = -1 : Illegal number of element nodes +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C Bmod - Material property (Bulk modulus) +C Emod - Material property (Youngs modulus) +C Pratio - Material property (Poissons ratio) +C Smod - Material property (Shear modulus) +C incomp - logical variable +C = .true. if incompressible material ): Pratio = 0 +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer mTYP, mVER, ipswb3, iwr, ierr + real*8 detF, Engy, F(9), pmat(2), Sig(6), Cst(6,6) + real*8 Bmod, Emod, Pratio, Smod + logical incomp +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE CONS3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'mTYP =', mTYP + write(iwr,9020) 'mVER =', mVER + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'Pmat =', pmat + call rprin0(F , 3, 3, 'F ', iwr) + endif + endif +C +C +C Compute Shear and Bulk modulus +C + if (mTYP .lt. 0) then + Bmod = pmat(1) + Smod = pmat(2) + else + Emod = pmat(1) + Pratio = pmat(2) + Smod = one2 * Emod / (one + Pratio) + incomp = Pratio .eq. one2 + if (incomp) then +C +C Incompressible material +C + Bmod = zero + else + Bmod = one3 * Emod / (one - two * Pratio) + end if + end if +C +C Compute Cauchy stresses and spatial constitutive tensor +C + if (abs(mTYP) .eq. 10) then +C + if (mVER .eq. 0) then +C +C Linear elastic isotropic material +C + call liel3d(detF, F, Bmod, Smod, Engy, Sig, Cst, + & ipswb3, iwr, ierr) + if (ierr .lt. 0) go to 7000 +C + elseif (mVER .gt. 0 .and. mVER .lt. 5) then +C +C Standard hyperelastic neoHookean model +C +C Compute Lame's parameter for compressibel material +C + if (.not. incomp) Bmod = Bmod - two3 * Smod +C + call stnh3d(mVER, detF, F, Bmod, Smod, Engy, Sig, Cst, + & ipswb3, iwr, ierr) + if (ierr .lt. 0) go to 7000 +C + elseif (mVER .gt. 10 .and. mVER .lt. 15) then +C +C Modified hyperelastic neoHookean model +C + call monh3d(mVER-10, detF, F, Bmod, Smod, Engy, Sig, Cst, + & ipswb3, iwr, ierr) + if (ierr .lt. 0) go to 7000 +C + elseif (mVER .eq. 20) then +C +C Isotropic elasticity formulated in terms of +C principal logarithmic stretches +C + call ieps3d(detF, F, Bmod, Smod, Engy, Sig, Cst, + & ipswb3, iwr, ierr) + if (ierr .lt. 0) go to 7000 +C + else +C +C Illegal material version +C + go to 7100 + endif +C + else +C +C Illegal material type +C + go to 7200 + endif + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 + 7200 continue + ierr =-3 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE CONS3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'mTYP =', mTYP + write(iwr,9020) 'mVER =', mVER + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'Pmat =', pmat + call rprin0(F , 3, 3, 'F ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig , 1, 6, 'Sig ', iwr) + call rprin0(Cst , 6, 6, 'Cst ', iwr) + call flush(iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE CONS3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig , 1, 6, 'Sig ', iwr) + call rprin0(Cst , 6, 6, 'Cst ', iwr) + endif + call flush(iwr) + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,1P,2E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/const.h b/Apps/FiniteDefElasticity/Material/const.h new file mode 100644 index 00000000..5c21567a --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/const.h @@ -0,0 +1,6 @@ + double precision zero,one,two,four + double precision one2,one3,one4,one6,onep5,two3,four3 + parameter ( zero=0.0D0, one=1.0D0, two=2.0D0, four=4.0D0 ) + parameter ( one2=0.5D0, one3=one/3.0D0, one4=0.25D0 ) + parameter ( one6=one/6.0D0, onep5=1.5D0 ) + parameter ( two3=two/3.0D0, four3=four/3.0D0 ) diff --git a/Apps/FiniteDefElasticity/Material/eigs3d.f b/Apps/FiniteDefElasticity/Material/eigs3d.f new file mode 100644 index 00000000..349a3ca7 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/eigs3d.f @@ -0,0 +1,257 @@ + subroutine eigs3d (v, d, nrotd, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: EIGS3D NTNU/SINTEF/DNV +C +C PURPOSE: +C EIGS3D: Compute eigenvalues/vectors for 3x3 symmetric matrix +C +C METHOD: +C Storage done as follows: +C +C | v(1,1) v(1,2) v(1,3) | | d(1) a(1) a(3) | +C | v(2,1) v(2,2) v(2,3) | = | a(1) d(2) a(2) | +C | v(3,1) v(3,2) v(3,3) | | a(3) a(2) d(3) | +C +C Transformations performed on d(i) and a(i) and v(i,j) become +C eigenvectors. Thus, original array is destroyed. +C +C ARGUMENTS INPUT: +C v(3,3) - Matrix for which eigenvalues/vectors should be +C computed for +C +C ARGUMENTS OUTPUT: +C v(3,3) - Matrix of eigenvectors (by column) +C d(3) - Eigenvalues associated with the columns of v +C nrotd - number of rotations to diagonalize +C ierr - Error flag +C = -1 : +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C +C +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C November 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer nrotd, i, its, j, k, ipswb3, iwr, ierr +C + real*8 g, h, aij, sm, thresh, t, c, s, tau, + * v(3,3), d(3), a(3), b(3), z(3) +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE EIGS3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + call rprin0(v, 3, 3, 'v ', iwr) + endif + endif +C +C Move array into one-d arrays +C + a(1) = v(1,2) + a(2) = v(2,3) + a(3) = v(1,3) +C + do i = 1,3 +C + d(i) = v(i,i) + b(i) = d(i) + z(i) = zero +C + do j = 1,3 +C + v(i,j) = zero +C + end do ! j +C + v(i,i) = one +C + end do ! i +C +C Check for diagonal case +C + sm = abs(a(1)) + abs(a(2)) + abs(a(3)) + g = abs(d(1)) + abs(d(2)) + abs(d(3)) +C + if (sm .lt. 1.d-13*g) go to 8000 +C + nrotd = 0 +C + do its = 1, 50 +C +C Set convergence test and threshold +C + sm = abs(a(1)) + abs(a(2)) + abs(a(3)) + if (sm .eq. zero) go to 8000 +C + if (its .lt. 4) then +C + thresh = 0.011d0*sm +C + else +C + thresh = zero +C + end if +C +C Perform sweeps for rotations +C + do i = 1,3 +C + j = mod(i,3) + 1 + k = mod(j,3) + 1 +C + aij = a(i) + g = 100.d0*abs(aij) +C + if (abs(d(i))+g .ne. abs(d(i)) .or. + & abs(d(j))+g .ne. abs(d(j))) then +C + if (abs(aij) .gt. thresh) then +C + a(i) = zero + h = d(j) - d(i) +C + if (abs(h)+g .eq. abs(h)) then + t = aij/h + else + t = sign(two,h/aij)/(abs(h/aij) + & + sqrt(four+(h/aij)**2)) + end if +C +C Set rotation parameters +C + c = one/sqrt(one+t*t) + s = t*c + tau = s/(one+c) +C +C Rotate diagonal terms +C + h = t*aij + z(i) = z(i) - h + z(j) = z(j) + h + d(i) = d(i) - h + d(j) = d(j) + h +C +C Rotate off-diagonal terms +C + h = a(j) + g = a(k) + a(j) = h + s*(g - h*tau) + a(k) = g - s*(h + g*tau) +C +C Rotate eigenvectors +C + do k = 1,3 +C + g = v(k,i) + h = v(k,j) + v(k,i) = g - s*(h + g*tau) + v(k,j) = h + s*(g - h*tau) +C + end do ! k +C + nrotd = nrotd + 1 +C + end if +C + else +C + a(i) = zero +C + end if +C + end do ! i +C +C Update diagonal terms +C + do i = 1,3 +C + b(i) = b(i) + z(i) + d(i) = b(i) + z(i) = zero +C + end do ! i +C + end do ! its + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE EIGS3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + call rprin0(v, 3, 3, 'v ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9020) 'nrotd =', nrotd + call rprin0(v, 3, 3, 'v ', iwr) + call rprin0(d, 1, 3, 'd ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE EIGS3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9020) 'nrotd =', nrotd + call rprin0(v, 3, 3, 'v ', iwr) + call rprin0(d, 1, 3, 'd ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/ieps3d.f b/Apps/FiniteDefElasticity/Material/ieps3d.f new file mode 100644 index 00000000..c1226cfc --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/ieps3d.f @@ -0,0 +1,247 @@ + subroutine ieps3d (detF, F, lambda, mu, Engy, Sig, Cst, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: IEPS3D NTNU/SINTEF/DNV +C +C PURPOSE: +C IEPS3D: Compute Cauchy stresses and spatial constitutive tensor +C for a isotropic elastic 3D material formulated in +C principal stretches +C +C METHOD: +C +C +C +C ARGUMENTS INPUT: +C detF - Determinant of the deformation gradient +C F(3,3) - Deformation gradient +C lambda - Lame's constant (=K-2*G/3) +C mu - Shear modulus +C +C ARGUMENTS OUTPUT: +C Engy - Strain energy density +C Sig(6) - Cauchy stresses (spatial stresses) +C Cst(6,6) - Spatial constitutive tensor +C ierr - Error flag +C = 1 : +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C b(6) - Left Cauchy-Green deformation tensor +C +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C November 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer ipswb3, iwr, ierr + integer i, j, k, l, iVF, i3, ierrl, nrotd +C + real*8 detF, Engy, F(3,3), lambda, mu, Sig(6), Cst(6,6) + real*8 aap(6,6), b(6), bpr(6), Cstv, detFi, EngU, EngW, + & epsd(3), Press, q(6,6), qen(3,3), taup(3) +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE IEPS3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'lambda =', lambda + write(iwr,9030) 'mu =', mu + call rprin0(F , 3, 3, 'F ', iwr) + endif + endif +C +C Initialize Cauchy stresses and spatial constitutive tensor +C + Sig = zero + Cst = zero +C +C Compute Left Cauchy-Green deformation tensor +C +C b = F * F^T +C + b(1) = F(1,1)*F(1,1) + F(1,2)*F(1,2) + F(1,3)*F(1,3) + b(2) = F(2,1)*F(2,1) + F(2,2)*F(2,2) + F(2,3)*F(2,3) + b(3) = F(3,1)*F(3,1) + F(3,2)*F(3,2) + F(3,3)*F(3,3) + b(4) = F(1,1)*F(2,1) + F(1,2)*F(2,2) + F(1,3)*F(2,3) + b(5) = F(2,1)*F(3,1) + F(2,2)*F(3,2) + F(2,3)*F(3,3) + b(6) = F(1,1)*F(3,1) + F(1,2)*F(3,2) + F(1,3)*F(3,3) +C +C Compute principal stretches and directions +C +C Initialization: Upon entry to EIGS3D array qen(3,3) contains +C left Cauchy-Green tensor b; upon exit, +C qen(3,3) contains eigenvectors +C + do i = 1,3 +C + i3 = i + 3 + j = 1 + mod(i,3) +C + qen(i,i) = b(i) + qen(i,j) = b(i3) + qen(j,i) = b(i3) +C + end do ! i +C + call eigs3d(qen, bpr, nrotd, ipswb3, iwr, ierr) + if (ierrl .lt. 0) go to 7000 +C +C Compute transformation matrix from +C Principal to Standard Basis +C + do i = 1,3 +C + k = 1 + mod(i,3) +C + do j = 1,3 +C + l = 1 + mod(j,3) +C + q(i,j) = qen(i,j)*qen(i,j) +C + q(i+3,j) = qen(i,j)*qen(k,j) +C + q(j,i+3) = qen(j,i)*qen(j,k)*two +C + q(j+3,i+3) = qen(j,i)*qen(l,k) + qen(j,k)*qen(l,i) +C + end do ! j +C + end do ! i +C +C Compute Deviatoric Strains, deviatoric Kirchhoff +C Stresses and Material Moduli in Principal Basis +C + call wlog3d (mu, bpr, epsd, taup, aap, EngW, ipswb3, iwr, ierrl) + if (ierrl .lt. 0) go to 7000 +C +C Transform deviatoric Kirchhoff Stresses and Material Moduli +C to Cauchy stresses and spatial material moduli +C + call amat3d (taup, aap, q, Sig, Cst, ipswb3, iwr, ierrl) + if (ierrl .lt. 0) go to 7000 +C +C Transform deviatoric Kirchhoff Stresses and Material Moduli +C to Cauchy stresses and spatial material moduli +C + detFi = one / detF + Sig = Sig * detFi + Cst = Cst * detFi +C +C Compute volumetric stresses (pressure) and material moduli +C + iVF = 1 +C + call vfnh3d (iVF, detF, detFi, lambda, EngU, Press, Cstv, + & ipswb3, iwr, ierrl) + if (ierrl .lt. 0) go to 7000 +C +C Accumulate deviatoric and volumetric contribution to +C spatial stresses and material moduli +C + Sig(1:3) = Sig(1:3) + Press +C + Cstv = Cstv * detF +C + Cst(1,1) = Cst(1,1) - Press + Cstv + Cst(1,2) = Cst(1,2) + Press + Cstv + Cst(1,3) = Cst(1,3) + Press + Cstv + + Cst(2,1) = Cst(2,1) + Press + Cstv + Cst(2,2) = Cst(2,2) - Press + Cstv + Cst(2,3) = Cst(2,3) + Press + Cstv + + Cst(3,1) = Cst(3,1) + Press + Cstv + Cst(3,2) = Cst(3,2) + Press + Cstv + Cst(3,3) = Cst(3,3) - Press + Cstv + + Cst(4,4) = Cst(4,4) - Press + Cst(5,5) = Cst(5,5) - Press + Cst(6,6) = Cst(6,6) - Press +C +C Compute strain energy desnity +C + Engy = EngU + EngW + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE IEPS3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'lambda =', lambda + write(iwr,9030) 'mu =', mu + call rprin0(F , 3, 3, 'F ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE IEPS3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/liel3d.f b/Apps/FiniteDefElasticity/Material/liel3d.f new file mode 100644 index 00000000..ac12e53b --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/liel3d.f @@ -0,0 +1,183 @@ + subroutine liel3d (detF, F, Bmod, Smod, Engy, Sig, Cst, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: LIEL3D NTNU/SINTEF/DNV +C +C PURPOSE: +C LIEL3D: Compute Cauchy stresses and spatial constitutive tensor +C for linear elastic isotropic 3D materials +C +C METHOD: +C Material and spatial stress components are stored as: +C +C | Stress(1) Stress(4) Stress(6) | +C Stress(3,3) = | Stress(4) Stress(2) Stress(5) | +C | Stress(6) Stress(5) Stress(3) | +C +C +C ARGUMENTS INPUT: +C detF - Determinant of the deformation gradient +C F - Deformation gradients +C Bmod - Bulk modulus +C Smod - Shear modulus +C +C ARGUMENTS OUTPUT: +C Engy - Strain energy density +C Sig(6) - Cauchy stresses (spatial stresses) +C Cst(6,6) - Spatial constitutive tensor +C ierr - Error flag +C = 1 : Illegal number of element nodes +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C GLe(6) - Green-Lagrangse strains +C PK2(6) - 2nd Piola-Kirchhoff stresses (material stresses) +C Cmt(6,6) - Material constitutive tensor +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer ipswb3, iwr, ierr + integer i, j, i3 +C + real*8 detF, Engy, F(9), Bmod, Smod, Sig(6), Cst(6,6) + real*8 c1, c2 + real*8 GLe(6), PK2(6), Cmt(6,6) +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE LIEL3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'Bmod =', Bmod + write(iwr,9030) 'Smod =', Smod + call rprin0(F , 3, 3, 'F ', iwr) + endif + endif +C + Cmt = zero +C +C Express the material constitutive tensor +C in terms of the Shear and Bulk modulus +C + c1 = Bmod + four3 * Smod + c2 = Bmod - two3 * Smod +C + do i = 1, 3 + j = mod(i,3) + 1 + i3 = i + 3 + Cmt(i ,i ) = c1 + Cmt(i ,j ) = c2 + Cmt(j ,i ) = c2 + Cmt(i3,i3) = Smod + end do ! i +C +C Compute Green-Lagrange strains from the deformation gradient +C +C E = (F^T * F - 1) / 2 +C + GLe(1) = one2 * (F(1)*F(1) + F(2)*F(2) + F(3)*F(3) - one) + GLe(2) = one2 * (F(4)*F(4) + F(5)*F(5) + F(6)*F(6) - one) + GLe(3) = one2 * (F(7)*F(7) + F(8)*F(8) + F(9)*F(9) - one) + GLe(4) = (F(1)*F(4) + F(2)*F(5) + F(3)*F(6)) + GLe(5) = (F(4)*F(7) + F(5)*F(8) + F(6)*F(9)) + GLe(6) = (F(1)*F(7) + F(2)*F(8) + F(3)*F(9)) +C +C Compute 2nd Piola Kirchhoff stresses from product of +C material constitutive tensor and Green-Lagrange strains +C +C PK2 = C_mt * GLe +C + PK2 = matmul(Cmt,GLe) +C +C Compute strain energy density +C + Engy = one2 * dot_product(PK2,GLe) +C +C Push stresses and constitutive tensor forward from +C material to spatial coordinates in current configuration +C + call push3D (detF, F, PK2, Cmt, Sig, Cst, ipswb3, iwr) +C + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE LIEL3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'Bmod =', Bmod + write(iwr,9030) 'Smod =', Smod + call rprin0(F , 3, 3, 'F ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE LIEL3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/mdma3d.f b/Apps/FiniteDefElasticity/Material/mdma3d.f new file mode 100644 index 00000000..8baa0a79 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/mdma3d.f @@ -0,0 +1,132 @@ + subroutine mdma3d (P_bar, P_mix, Sig, DD, ipswb3, iwr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: MDMA3D NTNU/SINTEF/DNV +C +C PURPOSE: +C MDMA3D: Compute finite deformation mixed D arrays +C +C METHOD: +C | D_11 D_12 | +C DD = | | +C | D_21 D_22 | +C where: +C D_11 = 6 x 6 Deviatoric part of matrix +C D_12 = 6 x 1 Coupling part of matrix +C D_21 = 1 x 6 Coupling part of matrix +C D_22 = 1 x 1 Volumetric part of matrix +C +C ARGUMENTS INPUT: +C P_bar - Constitutive pressure +C P_mix - Mixed pressure +C Sig(6) - Constitutive Cauchy stresses +C DD(7,7) - Modified constitutive array +C +C ARGUMENTS OUTPUT: +C DD(7,7) - Mixed material tangent terms +C +C COMMON BLOCKS: +C None +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C None +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer ipswb3, iwr, j + real*8 DD(7,7), P_bar, P_mix, Sig(6) + real*8 fac1, Sig_D(6) +C + include 'include/feninc/const.h' +C +C Entry section +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE MDMA3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'P_bar =', P_bar + write(iwr,9030) 'P_mix =', P_mix + call rprin0(Sig, 1, 6, 'Sig ', iwr) + call rprin0(DD , 7, 7, 'DD ', iwr) + endif + endif +C +C +C Compute deviatoric stress +C + Sig_D(1:3) = two3 *(Sig(1:3) - P_bar) + Sig_D(4:6) = two3 * Sig(4:6) +C +C D_11: B_matrix part +C + fac1 = P_mix - two3 * P_bar +C + DD(1:3,1:3) = DD(1:3,1:3) + fac1 +C + do j = 1,6 + DD(1:3,j) = DD(1:3,j) - Sig_D(j) + DD(j,1:3) = DD(j,1:3) - Sig_D(j) + end do +C + fac1 = P_bar - P_mix +C + do j = 1,3 + DD(j ,j ) = DD(j ,j ) + fac1 * two + DD(j+3,j+3) = DD(j+3,j+3) + fac1 + end do ! j +C +C D_12: Coupling matrix with +C + DD(7,1:6) = DD(7,1:6) + Sig_D + DD(1:6,7) = DD(1:6,7) + Sig_D +C +C D_22: Volumetric part +C + DD(7,7) = DD(7,7) - one3 * P_bar +C +C +C Closing section +C + 8000 continue + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE MDMA3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + call rprin0(DD , 7, 7, 'DD ', iwr) + endif + call flush(iwr) + endif +C + return +C + 9010 format(3X,A) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/monh3d.f b/Apps/FiniteDefElasticity/Material/monh3d.f new file mode 100644 index 00000000..1b072b5c --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/monh3d.f @@ -0,0 +1,274 @@ + subroutine monh3d (iVF, detF, F, Bmod, Smod, Engy, Sig, Cst, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: MONH3D NTNU/SINTEF/DNV +C +C PURPOSE: +C MONH3D: Compute Cauchy stresses and spatial constitutive tensor +C for a modified NeoHookean isotropic 3D material +C +C METHOD: +C Energy function: Compressible Neohookean model +C with J_2/3 regularization +C _ __ _ _ __ +C W(J,be) = U(J) + 0.5*mu*(J^(-2/3)be:1 - 3) +C +C Volumetric function type: U_1 = K*[ 0.25*( J^2 - 1 )-0.5*ln(J)] +C U_2 = K*[ 0.5*( J - 1 )^2 ] +C U_3 = K*[ 0.5*( ln(J) )^2 ] +C U_4 = K*[ 2.0*(J - 1 - ln(J)) ] +C Free energy function: +C +C Psi = U(J) -3*alpha*J*U'(J)*[T - T_ref] +C +C up = partial_J ( Psi ) +C upp = [ partial_J ( J partial_J Psi ) - up ]/J +C = partial^2_J ( Psi )/JC +C +C +C ARGUMENTS INPUT: +C iVF - Volumetric function type +C detF - Determinant of the deformation gradient +C F - Deformation gradients +C Bmod - Bulk modulus +C Smod - Shear modulus +C +C ARGUMENTS OUTPUT: +C Engy - Strain energy density +C Sig(6) - Cauchy stresses (spatial stresses) +C Cst(6,6) - Spatial constitutive tensor +C ierr - Error flag +C = 1 : Illegal number of element nodes +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C nSTR - Number of stress/strain components +C bm(6) - Modified Left Cauchy-Green deformation tensor +C +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer iVF, ipswb3, iwr, ierr + integer i, j, ierrl +C + real*8 detF, Engy, F(3,3), Bmod, Smod, Sig(6), Cst(6,6) + real*8 c1, c2, c3, Cstv, bm(6), detFi, Press, trbm3 +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE MONH3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'iVF =', iVF + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'Bmod =', Bmod + write(iwr,9030) 'Smod =', Smod + call rprin0(F , 3, 3, 'F ', iwr) + endif + endif +C +C Initialize spatial constitutive tensor +C + Cst = zero +C +C Compute Left Cauchy-Green deformation tensor +C +C b = F * F^T +C + bm(1) = F(1,1)*F(1,1) + F(1,2)*F(1,2) + F(1,3)*F(1,3) + bm(2) = F(2,1)*F(2,1) + F(2,2)*F(2,2) + F(2,3)*F(2,3) + bm(3) = F(3,1)*F(3,1) + F(3,2)*F(3,2) + F(3,3)*F(3,3) + bm(4) = F(1,1)*F(2,1) + F(1,2)*F(2,2) + F(1,3)*F(2,3) + bm(5) = F(2,1)*F(3,1) + F(2,2)*F(3,2) + F(2,3)*F(3,3) + bm(6) = F(1,1)*F(3,1) + F(1,2)*F(3,2) + F(1,3)*F(3,3) +C + if (ipswb3 .gt. 3) then + call rprin0(bm, 1, 6,'bm ', iwr) + endif +C +C Compute modified Left Cauchy-Green deformation tensor +C _ +C bm = b = J^(-2/3) * b +C + detFi = one / detF + bm = bm * (detFi ** two3) +C + if (ipswb3 .gt. 3) then + call rprin0(bm, 1, 6,'bm ', iwr) + endif +C +C Compute one third of the trace of the modified +C Left Cauchy-Green deformation tensor +C _ +C trbm3 = tr( b ) / 3 +C + trbm3 = (bm(1) + bm(2) + bm(3)) * one3 + bm(1) = bm(1) - trbm3 + bm(2) = bm(2) - trbm3 + bm(3) = bm(3) - trbm3 +C + if (ipswb3 .gt. 3) then + call rprin0(bm, 1, 6,'bm ', iwr) + endif +C +C Compute deviatoric part of the constitutive tensor +C +C Part 1: _ _ _ +C Rank one update: -2/3 G * ( b x g + g x b ) / J +C + c1 = two3 * Smod +C + do i = 1, 6 + c2 = c1 * bm(i) + do j = 1,3 + Cst(i,j) = Cst(i,j) - c2 + Cst(j,i) = Cst(j,i) - c2 + end do ! j + end do ! i +C +C Part 2: __ _ +C Deviatoric term 2 mu [ I - 1/3 g x g ] / J +C + c1 = Smod * trbm3 + c2 = c1 + c1 + c3 = c2 * one3 +C + do i = 1,3 + Cst(i ,i ) = Cst(i ,i ) + c2 + Cst(i+3,i+3) = Cst(i+3,i+3) + c1 + do j = 1,3 + Cst(i ,j ) = Cst(i ,j ) - c3 + end do ! j + end do ! i +C +C Compute deviatoric Kirchhoff stress tensor +C _ _ +C tau_dev = b - tr( b ) / 3 +C + Sig = Smod * bm +C +C Compute spatial deviatoric (isochoric) +C stresses and material moduli +C + Sig = Sig * detFi + Cst = Cst * detFi +C + if (ipswb3 .gt. 3) then + write(iwr,9030) 'Press =', Press + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + endif +C +C Compute volumetric stresses (presssure) and material moduli +C according to the volumetric function type +C + call vfnh3d (iVF, detF, detFi, Bmod, Engy, Press, Cstv, + & ipswb3, iwr, ierrl) + if (ierrl .lt. 0) go to 7000 +C + Sig(1:3) = Sig(1:3) + Press +C + Cstv = Cstv * detF +C + Cst(1,1) = Cst(1,1) - Press + Cstv + Cst(1,2) = Cst(1,2) + Press + Cstv + Cst(1,3) = Cst(1,3) + Press + Cstv + + Cst(2,1) = Cst(2,1) + Press + Cstv + Cst(2,2) = Cst(2,2) - Press + Cstv + Cst(2,3) = Cst(2,3) + Press + Cstv + + Cst(3,1) = Cst(3,1) + Press + Cstv + Cst(3,2) = Cst(3,2) + Press + Cstv + Cst(3,3) = Cst(3,3) - Press + Cstv + + Cst(4,4) = Cst(4,4) - Press + Cst(5,5) = Cst(5,5) - Press + Cst(6,6) = Cst(6,6) - Press +C +C Compute strain energy density +C + Engy = Engy + onep5 * Smod * (trbm3 - one) + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE MONH3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'iVF =', iVF + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'Bmod =', Bmod + write(iwr,9030) 'Smod =', Smod + call rprin0(F , 3, 3, 'F ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE MONH3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/pcst3d.f b/Apps/FiniteDefElasticity/Material/pcst3d.f new file mode 100644 index 00000000..7355612a --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/pcst3d.f @@ -0,0 +1,121 @@ + subroutine pcst3d (aa,dd,ipswb3,iwr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: PCST3D NTNU/SINTEF/DNV +C +C PURPOSE: +C PCST3D: Project 6x6 constitutive tensor (AA) onto a 7x7 equivalent +C constitutive tensor for mixed model (DD) +C +C METHOD: +C | D_11 D_12 | +C DD = | | +C | D_21 D_22 | +C where: +C D_11 = 6 x 6 Deviatoric part of matrix +C D_12 = 6 x 1 Coupling part of matrix +C D_21 = 1 x 6 Coupling part of matrix +C D_22 = 1 x 1 Volumetric part of matrix +C +C +C ARGUMENTS INPUT: +C aa(6,6) - Material tangent matrix (based on F) +C +C ARGUMENTS OUTPUT: +C dd(7,7) - Mixed material tangent for stiffness computations +C +C COMMON BLOCKS: +C None +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C None +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer ipswb3, iwr, j + real*8 aa(6,6), dd(7,7) +C + include 'include/feninc/const.h' +C +C +C Entry section +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE PCST3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + call rprin0(aa, 6, 6, 'aa ', iwr) + endif + endif +C +C +C Load moduli from constitution +C + dd(1:6,1:6) = aa +C +C Compute left and right multiples with trace +C + dd(1:6,7) = (aa(:,1) + aa(:,2) + aa(:,3))*one3 + dd(7,1:6) = (aa(1,:) + aa(2,:) + aa(3,:))*one3 +C +C Convert upper 6 x 6 to a deviatoric D_11 +C + do j = 1, 3 + dd(1:6,j) = dd(1:6,j) - dd(1:6,7) + dd(j,1:6) = dd(j,1:6) - dd(7,1:6) + end do +C +C Form last term, D_22 +C + dd(7,7) = (dd(1,7) + dd(2,7) + dd(3,7))*one3 +C +C Final update to form D_12 and D_21 +C + dd(1:3, 7) = dd(1:3, 7) - dd(7,7) + dd( 7,1:3) = dd( 7,1:3) - dd(7,7) + dd(1:3,1:3) = dd(1:3,1:3) + dd(7,7) +C +C +C Closing section +C + 8000 continue + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE PCST3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + call rprin0(dd, 7, 7, 'dd ', iwr) + endif + call flush(iwr) + endif +C + return +C + 9010 format(3X,A) +C + end diff --git a/Apps/FiniteDefElasticity/Material/push3d.f b/Apps/FiniteDefElasticity/Material/push3d.f new file mode 100644 index 00000000..929b2130 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/push3d.f @@ -0,0 +1,162 @@ + subroutine push3d (detF, F, PK2, Cmt, Sig, Cst, ipswb3, iwr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: PUSH3D NTNU/SINTEF/DNV +C +C PURPOSE: +C PUSH3D: Push stresses and constitutive tensor forward from +C material to spatial coordinates in current configuration +C +C METHOD: +C Material and spatial stress components are stored as: +C +C | Stress(1) Stress(4) Stress(6) | +C Stress(3,3) = | Stress(4) Stress(2) Stress(5) | +C | Stress(6) Stress(5) Stress(3) | +C +C ARGUMENTS INPUT: +C detF - Determinant of deformation gradient +C F(3,3) - Deformation gradient +C PK2(6) - 2nd Piola-Kirchhoff stresses (material stresses) +C Cmt(6,6) - Material constitutive tensor +C +C ARGUMENTS OUTPUT: +C Sig(6) - Cauchy stresses (spatial stresses) +C Cst(6,6) - Spatial constitutive tensor +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C Tmat(6,6) - Transformation matrix +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer ipswb3, iwr + integer i, j, i1(6), i2(6) + real*8 detF, F(3,3), PK2(6), Cmt(6,6), Sig(6), Cst(6,6) + real*8 detFi, Tmat(6,6) +C + include 'include/feninc/const.h' +C + data i1 /1,2,3,1,2,3/ + data i2 /1,2,3,2,3,1/ +C +C Entry section +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE PUSH3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'detF =', detF + call rprin0(F , 3, 3,'F ',iwr) + call rprin0(PK2, 1, 6,'PK2 ',iwr) + call rprin0(Cmt, 6, 6,'Cmt ',iwr) + endif + endif +C +C Form transformation array for a 4th rank tensor in matrix form +C +C Tmat(a,b) = F(i,I)*F(j,J) : a -> I,J ; b -> i,j +C a,b | 1 2 3 4 5 6 +C ------+----------------------------- +C (I,J) | 1,1 2,2 3,3 1,2 2,3 3,1 +C or (i,j) | 2,1 3,2 1,3 +C + do i = 1,3 + do j = 1,3 + Tmat(i,j) = F(i1(j),i1(i))*F(i2(j),i2(i)) + end do ! j + do j = 4,6 + Tmat(i,j) = (F(i1(j),i1(i))*F(i2(j),i2(i)) + & + F(i2(j),i2(i))*F(i1(j),i1(i)))*one2 + end do ! j + end do ! i + + do i = 4,6 + do j = 1,3 + Tmat(i,j) = F(i1(j),i1(i))*F(i2(j),i2(i)) + & + F(i2(j),i2(i))*F(i1(j),i1(i)) + end do ! j + do j = 4,6 + Tmat(i,j) = (F(i1(j),i1(i))*F(i2(j),i2(i)) + & + F(i2(j),i1(i))*F(i1(j),i2(i)) + & + F(i1(j),i2(i))*F(i2(j),i1(i)) + & + F(i2(j),i2(i))*F(i1(j),i1(i)))*one2 + end do ! j + end do ! i +C +C +C Compute matrix product: Cst = Tmat^t * Cmt * Tmat +C + Cst = matmul(transpose(Tmat),matmul(Cmt,Tmat)) +C +C Push forward material stress (PK2) to current configuration +C +C Sig(i,j) = F(i,k)*PK2(k,l)*F(j,l)/detf +C +C +C Tmat = F^T * PK2 +C + do i = 1,3 + Tmat(i,1) = F(i,1)*PK2(1) + F(i,2)*PK2(4) + F(i,3)*PK2(6) + Tmat(i,2) = F(i,1)*PK2(4) + F(i,2)*PK2(2) + F(i,3)*PK2(5) + Tmat(i,3) = F(i,1)*PK2(6) + F(i,2)*PK2(5) + F(i,3)*PK2(3) + end do ! i +C +C Sig = Tmat*F +C + Sig(1) = Tmat(1,1)*F(1,1) + Tmat(1,2)*F(1,2) + Tmat(1,3)*F(1,3) + Sig(2) = Tmat(2,1)*F(2,1) + Tmat(2,2)*F(2,2) + Tmat(2,3)*F(2,3) + Sig(3) = Tmat(3,1)*F(3,1) + Tmat(3,2)*F(3,2) + Tmat(3,3)*F(3,3) + Sig(4) = Tmat(1,1)*F(2,1) + Tmat(1,2)*F(2,2) + Tmat(1,3)*F(2,3) + Sig(5) = Tmat(2,1)*F(3,1) + Tmat(2,2)*F(3,2) + Tmat(2,3)*F(3,3) + Sig(6) = Tmat(3,1)*F(1,1) + Tmat(3,2)*F(1,2) + Tmat(3,3)*F(1,3) +C + detFi = one / detf + Cst = detFi * Cst + Sig = detFi * Sig +C +C Closing section +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE PUSH3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + call rprin0(Sig, 1, 6,'Sig ',iwr) + call rprin0(Cst, 6, 6,'Cst ',iwr) + endif + endif +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/rprin0.f b/Apps/FiniteDefElasticity/Material/rprin0.f new file mode 100644 index 00000000..e19b4cef --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/rprin0.f @@ -0,0 +1,93 @@ + SUBROUTINE RPRIN0 (A,MA,NA,CHHEAD,IW) + INTEGER MA,NA, IW + DOUBLE PRECISION A(MA,* ) + CHARACTER CHHEAD*(*) +C +C @(#)rprin0.f 1.3 90/11/19 Copyright (c) 1989 by A.S Veritas Research. +C----------------------------------------------------------------------- +C +C F E N R I S ROUTINE : RPRIN0 NTH/SINTEF/DNV +C +C PURPOSE: +C RPRIN0: Print the real matrix A(MA,NA). +C +C METHOD: +C THE MATRIX IS PRINTED AS A TWO-DIMENSIONAL MATRIX.ROWS AND COLUMNS +C ARE NUMBERED,AND EACH ELEMENT IS PRINTED WITH THE FORMAT 1PE14.6 , +C I.E. 7 SIGNIFICANTS DIGITS. +C A HEADING ( E.G. THE SYMBOL NAME "A" OF THE MATRIX ) CONSISTING OF +C UP TO 6 CHARACTERS IS PRINTED. +C EXAMPLE OF A CALL TO RPRIN0: CALL RPRIN0(A,MA,NA,'A ',IW) +C +C ARGUMENTS INPUT: +C A - MATRIX TO BE PRINTED +C MA - NUMBER OF ROWS +C NA - NUMBER OF COLUMNS +C CHHEAD - HEADING, E.G. "NAME" OF MATRIX +C IW - LINE PRINTER UNIT NUMBER +C +C ARGUMENTS OUTPUT: +C NONE +C +C COMMON BLOCKS: +C NONE +C +C PERIPHERAL UNITS: +C NONE +C +C LIMITATIONS: +C NONE +C +C CODING: +C STANDARD ADHERED TO: COMPUTAS REPORT NO 78-949 +C CODED: DECEMBER 5, 1974 K.BELL SINTEF - DIV. OF STRUCT. ENG. +C +C REVISIONS: +C DATE: AUGUST 27,1980 ALF ENGSETH SINTEF - DIV. OF STRUCT. ENG. +C DATE: DECEMBER 20,1982 MAGNE NYGAARD VERITAS +C 12-MAR-87 M.K.Nygaard VERITEC 86.1-0 +C Changed format 9100 +C 2-DEC-87 K.M.Mathisen NTH 5.2-04 +C Included test on the value of NA and changed dimensioning of A. +C 16-NOV-88 Ole Stroem / Robert O. Bjaerum, Veritas Research +C Any dimension of CHHEAD OK. Please keep it under 120. +C +C----------------------------------------------------------------------- +C + INTEGER NCOL,J,I,K,L + PARAMETER ( NCOL = 9 ) +C +C EXECUTABLE SECTION +C +C PROGRAM LOGIC SECTION +C + IF (NA .LE. 0 .OR. MA .LE. 0) GO TO 8000 +C +C PRINT HEADING +C + WRITE(IW,9050) CHHEAD +C +C PRINT MATRIX +C + DO 4000 J=1,NA,NCOL + K = J+(NCOL-1) + IF ( K .GT. NA ) THEN + K = NA + END IF +C + WRITE(IW,9000) (L,L=J,K) +C + DO 3000 I=1,MA + WRITE(IW,9100) I,(A(I,L),L=J,K) + 3000 CONTINUE + 4000 CONTINUE +C + 8000 CONTINUE +C + RETURN +C + 9000 FORMAT ( / 3X , 9( I9 , 5X ) ) + 9050 FORMAT ( / 5X , A , ' = ' ) + 9100 FORMAT ( 1X , I3 , 1P , 9E14.6 ) +C + END diff --git a/Apps/FiniteDefElasticity/Material/stnh3d.f b/Apps/FiniteDefElasticity/Material/stnh3d.f new file mode 100644 index 00000000..4b0c0767 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/stnh3d.f @@ -0,0 +1,210 @@ + subroutine stnh3d (iVF, detF, F, lambda, mu, Engy, Sig, Cst, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: STNH3D NTNU/SINTEF/DNV +C +C PURPOSE: +C MONH3D: Compute Cauchy stresses and spatial constitutive tensor +C for a standard NeoHookean isotropic 3D material +C +C METHOD: +C Energy function: Standard Neohookean model +C +C W(J,b) = lambda*U(J) + 0.5*mu*(I_c - 3 - 2*ln(J)) +C +C Sig = [mu*(b - 1) + lambda*Press]/detF +C = mu*(b - 1)/detF + lambda*partial_U/partial_J +C +C Volumetric function type: U_1 = K*[ 0.25*( J^2 - 1 )-0.5*ln(J)] +C U_2 = K*[ 0.5*( J - 1 )^2 ] +C U_3 = K*[ 0.5*( ln(J) )^2 ] +C U_4 = K*[ 2.0*(J - 1 - ln(J)) ] +C Free energy function: +C +C Psi = U(J) -3*alpha*J*U'(J)*[T - T_ref] +C +C up = partial_J ( Psi ) +C upp = [ partial_J ( J partial_J Psi ) - up ]/J +C = partial^2_J ( Psi )/JC +C +C +C ARGUMENTS INPUT: +C iVF - Volumetric function type +C detF - Determinant of the deformation gradient +C F - Deformation gradients +C lambda - Lame's constant (=K-2*G/3) +C mu - Shear modulus +C +C ARGUMENTS OUTPUT: +C Engy - Strain energy density +C Sig(6) - Cauchy stresses (spatial stresses) +C Cst(6,6) - Spatial constitutive tensor +C ierr - Error flag +C = 1 : Illegal number of element nodes +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C b(6) - Left Cauchy-Green deformation tensor +C +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer iVF, ipswb3, iwr, ierr + integer i, i3, ierrl +C + real*8 detF, Engy, F(3,3), lambda, mu, Sig(6), Cst(6,6) + real*8 c1, c2, b(6), Cstv, detFi, Press +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE STNH3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'iVF =', iVF + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'lambda =', lambda + write(iwr,9030) 'mu =', mu + call rprin0(F , 3, 3, 'F ', iwr) + endif + endif +C +C Compute Left Cauchy-Green deformation tensor +C +C b = F * F^T +C + b(1) = F(1,1)*F(1,1) + F(1,2)*F(1,2) + F(1,3)*F(1,3) + b(2) = F(2,1)*F(2,1) + F(2,2)*F(2,2) + F(2,3)*F(2,3) + b(3) = F(3,1)*F(3,1) + F(3,2)*F(3,2) + F(3,3)*F(3,3) + b(4) = F(1,1)*F(2,1) + F(1,2)*F(2,2) + F(1,3)*F(2,3) + b(5) = F(2,1)*F(3,1) + F(2,2)*F(3,2) + F(2,3)*F(3,3) + b(6) = F(1,1)*F(3,1) + F(1,2)*F(3,2) + F(1,3)*F(3,3) +C +C Compute volumetric stresses (pressure) and material moduli +C according to the volumetric function type +C + detFi = one / detF +C + call vfnh3d (iVF, detF, detFi, lambda, Engy, Press, Cstv, + & ipswb3, iwr, ierrl) + if (ierrl .lt. 0) go to 7000 +C +C Initialize spatial constitutive tensor +C + Cst = zero +C +C Accumulate deviatoric and volumetric contribution to +C spatial stresses and material moduli +C + c1 = mu * detFi +C c2 = two * mu + c2 = two * c1 + Cstv = Cstv * detF +C +C Diagonal terms +C + do i = 1, 3 + i3 = i + 3 + Sig(i) = c1 * b(i) - c1 + Press + Sig(i3) = c1 * b(i3) + Cst(i ,i ) = c2 - Press + Cstv + Cst(i3,i3) = c1 - Press + end do ! i +C +C Off-diagonal terms +C + c1 = Press + Cstv +C + Cst(1,2) = c1 + Cst(1,3) = c1 + Cst(2,1) = c1 + Cst(2,3) = c1 + Cst(3,1) = c1 + Cst(3,2) = c1 +C +C Compute strain energy desnity +C + Engy = Engy + mu*(one2*(b(1)+b(2)+b(3)) - onep5 - log(abs(detF))) +C + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE STNH3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'iVF =', iVF + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'lambda =', lambda + write(iwr,9030) 'mu =', mu + call rprin0(F , 3, 3, 'F ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE STNH3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'Engy =', Engy + call rprin0(Sig, 1, 6,'Sig ', iwr) + call rprin0(Cst, 6, 6,'Cst ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/vfnh3d.f b/Apps/FiniteDefElasticity/Material/vfnh3d.f new file mode 100644 index 00000000..128acbe1 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/vfnh3d.f @@ -0,0 +1,187 @@ + subroutine vfnh3d (iVF, detF, detFi, lambda, U, Up, Upp, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: VFNH3D NTNU/SINTEF/DNV +C +C PURPOSE: +C VFNH3D: Compute volumetric stresses (pressure) and material moduli +C according to the volumetric function type +C +C METHOD: +C Volumetric function type: U_1 = K*[ 0.25*( J^2 - 1 )-0.5*ln(J)] +C U_2 = K*[ 0.5*( J - 1 )^2 ] +C U_3 = K*[ 0.5*( ln(J) )^2 ] +C U_4 = K*[ 2.0*(J - 1 - ln(J)) ] +C Free energy function: +C U = U(J) +C Up = partial_J ( Psi ) +C Upp = [ partial_J ( J partial_J Psi ) - up ]/J +C = partial^2_J ( Psi )/JC +C +C +C ARGUMENTS INPUT: +C iVF - Volumetric function type +C detF - Determinant of the deformation gradient +C detFi - Inverse of the determinant of the Jacobian gradient +C lambda - Lame's constant +C +C ARGUMENTS OUTPUT: +C U - Strain energy density +C Up - Mean stress (pressure) +C Upp - Volumetric constitutive tensor +C ierr - Error flag +C = 1 : Illegal number of element nodes +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C U = U(J) +C Up = partial_J ( Psi ) +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C September 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer iVF, ipswb3, iwr, ierr +C + real*8 detF, detFi, lambda, + & detF2, detFi2, detFm1, U, Up, Upp +C + include 'include/feninc/const.h' +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE VFNH3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9020) 'iVF =', iVF + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'detFi =', detFi + write(iwr,9030) 'lambda =', lambda + endif + endif +C +C Compute volumetric stresses (pressure) and material moduli +C according to the volumetric function type +C + if (iVF . eq. 1) then +C +C Volumetric function type 1: +C U(J) = lambda*0.25*(J^2 - 1 - 2*(log J)) +C + detF2 = detF**2 + detFi2 = detFi**2 + U = one4 * lambda * ( detF2 - one - two*log(abs(detF)) ) + Up = one2 * lambda * ( detF - detFi ) + Upp = one2 * lambda * ( one + detFi2 ) +C + else if (iVF .eq. 2) then +C +C Volumetric function type 2: +C U(J) = lambda*0.5*(J-1)^2 +C + detFm1 = detF - one + U = one2 * lambda * (detFm1)**2 + Up = lambda * detFm1 + Upp = lambda +C + else if (IVF .eq. 3) then +C +C Volumetric function type 3: +C U(J) = lambda*0.5*(log J)^2 +C + U = one2 * lambda * log(abs(detF))**2 + Up = lambda * log(abs(detF)) * detFi + Upp = ( lambda * detFi - Up ) * detFi +C + else if (iVF .eq. 4) then +C +C Volumetric function type 4: +C U(J) = lambda*2.0*(J - 1 - log J) +C + detFi2 = detFi**2 + U = two * lambda * ( detF - one - log(abs(detF)) ) + Up = lambda * ( two - detFi ) + Upp = lambda * detFi2 +C +C Illegal volumetric function +C + else + go to 7000 + end if + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE VFNH3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'detF =', detF + write(iwr,9030) 'detFi =', detFi + write(iwr,9030) 'lambda =', lambda +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'U =', U + write(iwr,9030) 'Up =', Up + write(iwr,9030) 'Upp =', Upp + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE VFNH3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'U =', U + write(iwr,9030) 'Up =', Up + write(iwr,9030) 'Upp =', Upp + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/Material/wlog3d.f b/Apps/FiniteDefElasticity/Material/wlog3d.f new file mode 100644 index 00000000..4cd5b2e7 --- /dev/null +++ b/Apps/FiniteDefElasticity/Material/wlog3d.f @@ -0,0 +1,184 @@ + subroutine wlog3d (mu, bpr, epsd, taup, aap, w, + & ipswb3, iwr, ierr) +C +C --------------------------------------------------------------------- +C +C F E N R I S ROUTINE: WLOG3D NTNU/SINTEF/DNV +C +C PURPOSE: +C WLOG3D: Compute stress and tangent in principal stretch form +C +C METHOD: +C Stored Energy Function in Principal Stretches: +C W = w(lam_1) + w(lam_2) + w(lam_3) +C taup_i = [w(lam_i)]' * lambda_i +C gamm_ij = lambda_j * [taup_i],j +C +C Logarithmic strain energy function +C w = mu * [log(lambda)]**2 +C +C ARGUMENTS INPUT: +C mu - Shear modulus +C bpr(3) - Principal stretch (squared) +C +C ARGUMENTS OUTPUT: +C epsd(3) - Principal deviatoric strains +C taup(3) - Principal deviatoric Kirchhoff stresses +C aap(6,6) - Deviatoric tangent moduli in principal basis +C ierr - Error flag +C = 1 : +C +C COMMON BLOCKS: +C const +C +C PERIPHERAL UNITS: +C None +C +C INTERNAL VARIABLES: +C +C +C +C PRINT SWITCH: +C ipswb3 = 0 Gives no print +C ipswb3 = 2 Gives enter and leave +C ipswb3 = 3 Gives in addition parameters on input +C ipswb3 = 5 Gives in addition parameters on output +C +C LIMITS: +C +C CODING: +C STANDART ADHERED TO: Computas report no 78-949 +C AUTHOR: Kjell Magne Mathisen +C NTNU, Department of structural engineering +C November 14, 2010 +C +C INSPECTED: +C +C REVISIONS: +C xx-OCT-2010 K.M.Mathisen NTNU +C Changed XG1-XG8 to XG and UG1-UG8 to UG. +C +C --------------------------------------------------------------------- +C + implicit none +C + integer i, j, ipswb3, iwr, ierr +C + real*8 jthird, mu, twomu, fourmu, w, tol, + & bpr(3), lamt(3), epsd(3), taup(3), aap(6,6) +C + include 'include/feninc/const.h' +C + data tol / 1.0d-08 / +C +C Entry section +C + ierr = 0 +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'ENTERING SUBROUTINE WLOG3D' + if (ipswb3 .gt. 2) then + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'mu =', mu + call rprin0(bpr, 1, 3, 'bpr ', iwr) + endif + endif +C +C Compute Je**(-1/3) and volume preserving stretches +C + jthird = (bpr(1)*bpr(2)*bpr(3))**(-one6) + do i = 1,3 + lamt(i) = jthird*sqrt(bpr(i)) + end do ! i +C +C Compute predictor strain energy function derivatives +C + twomu = two * mu + w = zero + do i = 1,3 + epsd(i) = log(lamt(i)) + taup(i) = twomu * epsd(i) + w = w + mu * epsd(i)**2 + end do ! i +C +C Load material part of moduli in principal stretches +C + aap = zero +C + twomu = twomu * one3 + fourmu = twomu + twomu +C + do i = 1,3 + aap(i,i) = fourmu - two * taup(i) + end do ! i +C + aap(1,2) = - twomu + aap(2,1) = - twomu +C + aap(2,3) = - twomu + aap(3,2) = - twomu +C + aap(3,1) = - twomu + aap(1,3) = - twomu +C +C Compute deviatoric tangent moduli in principal basis +C + do i = 1,3 + j = mod(i,3) + 1 +C + if (abs(bpr(i) - bpr(j)) .gt. tol) then + aap(i+3,i+3) = ( bpr(i)*taup(j) - bpr(j)*taup(i) ) + & / ( bpr(j) - bpr(i) ) + else + aap(i+3,i+3) = (aap(i,i) - aap(j,i))*one2 + endif +C + end do ! i + go to 8000 +C +C Error section +C + 7000 continue + ierr =-1 + go to 7900 + 7100 continue + ierr =-2 + go to 7900 +C ---------- + 7900 continue + write(iwr,9020) '*** ERROR IN SUBROUTINE WLOG3D *** IERR = ', ierr + write(iwr,9010) 'WITH INPUT ARGUMENTS' + write(iwr,9030) 'mu =', mu + call rprin0(bpr, 1, 3, 'bpr ', iwr) +C + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'w =', w + call rprin0(epsd, 1, 3, 'epsd ', iwr) + call rprin0(taup, 1, 3, 'taup ', iwr) + call rprin0(aap , 6, 6, 'aap ', iwr) + go to 8010 +C +C Closing section +C + 8000 continue +C + if (ipswb3 .gt. 0) then + write(iwr,9010) 'LEAVING SUBROUTINE WLOG3D' + if (ipswb3 .gt. 3) then + write(iwr,9010) 'WITH OUTPUT ARGUMENTS' + write(iwr,9030) 'w =', w + call rprin0(epsd, 1, 3, 'epsd ', iwr) + call rprin0(taup, 1, 3, 'taup ', iwr) + call rprin0(aap , 6, 6, 'aap ', iwr) + endif + endif +C + 8010 continue +C + return +C + 9010 format(3X,A) + 9020 format(3X,A,I6) + 9030 format(3X,A,E15.6) +C + end diff --git a/Apps/FiniteDefElasticity/NeoHookeElasticity.C b/Apps/FiniteDefElasticity/NeoHookeElasticity.C new file mode 100644 index 00000000..6ffb5139 --- /dev/null +++ b/Apps/FiniteDefElasticity/NeoHookeElasticity.C @@ -0,0 +1,187 @@ +// $Id: NeoHookeElasticity.C,v 1.2 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NeoHookeElasticity.C +//! +//! \date Jul 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for Neo-Hooke elasticity problems. +//! +//============================================================================== + +#include "NeoHookeElasticity.h" + + +void NeoHookeElasticity::print (std::ostream& os) const +{ + this->NonlinearElasticity::print(os); + std::cout <<"NeoHookeElasticity: Neo-Hooke material formulation"<< std::endl; +} + + +void NeoHookeElasticity::setMaterial (double Emod, double Poiss, double Density) +{ + this->Elasticity::setMaterial(Emod,Poiss,Density); + + // Define the Lame elasticity parameters + lambda = Emod*Poiss / ((1.0+Poiss)*(1.0-Poiss-Poiss)); + mu = lambda * (0.5/Poiss-1.0); + kappa = lambda + (mu+mu)/3.0; +} + + +bool NeoHookeElasticity::formStressTensor (const Matrix& dNdX, const Vec3&, + SymmTensor& S) const +{ + if (!eV || eV->empty()) + { + // Initial state (zero stresses) + S.zero(); + return true; + } + + // Evaluate the kinematic quantities, F, E, Ci, and J at this point + if (!this->kinematics(dNdX,E)) + return false; + + // Set up the 2nd Piola-Kirchhoff stress tensor, S + this->formStressTensor(S); + + return true; +} + + +bool NeoHookeElasticity::formTangent (Matrix& Ctan, SymmTensor& S, + const Vec3& X) const +{ + if (!this->formCmatrix(Ctan,X)) + return false; + else if (eV && !eV->empty()) + this->formStressTensor(S); + + return true; +} + + +bool NeoHookeElasticity::kinematics (const Matrix& dNdX, SymmTensor& _E) const +{ + // Form the deformation gradient, F + if (!this->NonlinearElasticity::kinematics(dNdX,_E)) + return false; + else + J = F.det(); + + // Form the right Cauchy-Green tensor, C = F^T*F + static Tensor dUdX(nsd); + C.rightCauchyGreen(dUdX=F); + + // Invert the right Cauchy-Green tensor, Ci = C^-1 + Ci = C; + return Ci.inverse() > 0.0; +} + + +void NeoHookeElasticity::formStressTensor (SymmTensor& S) const +{ + // Set up the 2nd Piola-Kirchhoff stress tensor. + // See equation (3.119) in P. Wriggers book: + // S = mu*(1-Ci) + 0.5*lambda*(J^2-1)*Ci + + S = 0.5*lambda*(J*J-1.0)*Ci; + S += -mu*(Ci-1.0); +} + + +bool NeoHookeElasticity::formCmatrix (Matrix& Ctan, const Vec3&, bool) const +{ + // Set up the incremental consitutive tensor + size_t nst = nsd*(nsd+1)/2; + Ctan.resize(nst,nst,true); + SymmTensor4 D(Ctan,nsd); // fourth-order material tensor + + double a = lambda*J*J; + double b = mu - 0.5*(a-lambda); + + unsigned short int i, j, k, l; + for (i = 1; i <= nsd; i++) + for (j = i; j <= nsd; j++) + for (k = 1; k <= nsd; k++) + for (l = k; l <= nsd; l++) + // See equation (3.268) in P. Wriggers book + D(i,j,k,l) = a*Ci(i,j)*Ci(k,l) + b*(Ci(i,k)*Ci(j,l) + Ci(i,l)*C(j,k)); + + return true; +} + + +void NeoHookeElasticityIV::print (std::ostream& os) const +{ + this->NonlinearElasticity::print(os); + std::cout <<"NeoHookeElasticity: Neo-Hooke material formulation" + <<" with isochoric/volumetric decomposition"<< std::endl; +} + + +bool NeoHookeElasticityIV::formTangent (Matrix& Ctan, SymmTensor& S, + const Vec3& X) const +{ + // Set up the stress tensor first because Ctan also depends on Siso + if (eV && !eV->empty()) + this->formStressTensor(S); + else + { + // Initial state (zero stresses) + S.zero(); + Siso.zero(); + } + + return this->formCmatrix(Ctan,X); +} + + +void NeoHookeElasticityIV::formStressTensor (SymmTensor& S) const +{ + // Set up the 2nd Piola-Kirchhoff stress tensor. + // See equation (3.127) or (3.274) in P. Wriggers book: + // S = mu*J^(-2/3)*(1-tr(C)/3)*Ci + + S = -(C.trace()/3.0)*Ci; + S += 1.0; + S *= mu*pow(J,-2.0/3.0); + Siso = S; // Isochoric part + S += 0.5*kappa*(J*J-1.0)*Ci; // Volumetric part +} + + +bool NeoHookeElasticityIV::formCmatrix (Matrix& Ctan, const Vec3&, bool) const +{ + // Set up the incremental consitutive tensor + size_t nst = nsd*(nsd+1)/2; + Ctan.resize(nst,nst,true); + SymmTensor4 D(Ctan,nsd); // fourth-order material tensor + + double t = mu*pow(J,-2.0/3.0)*C.trace(); + double a = kappa*J*J + (t+t)/9.0; + double b = 0.5*kappa*(1.0-J*J) + t/3.0; + double c = (t+t)/3.0; // Yuri + //double c = 2.0/3.0; // Wriggers + + unsigned short int i, j, k, l; + for (i = 1; i <= nsd; i++) + for (j = i; j <= nsd; j++) + for (k = 1; k <= nsd; k++) + for (l = k; l <= nsd; l++) + { + // See equation (3.275) in P. Wriggers book + //D(i,j,k,l) = a*Ci(i,j)*Ci(k,l) + b*(Ci(i,k)*Ci(j,l) + Ci(i,l)*C(j,k)) + // - c*(Ci(k,l)*Siso(i,j) + Ci(i,j)*Siso(k,l)); + // See equation (81) In Yuri's Comput. Mech. paper + D(i,j,k,l) = a*Ci(i,j)*Ci(k,l) + b*(Ci(i,k)*Ci(j,l) + Ci(i,l)*C(j,k)); + if (i == j) D(i,j,k,l) += c*Ci(k,l); + if (k == l) D(i,j,k,l) += c*Ci(i,j); + } + + return true; +} diff --git a/Apps/FiniteDefElasticity/NeoHookeElasticity.h b/Apps/FiniteDefElasticity/NeoHookeElasticity.h new file mode 100644 index 00000000..2f97d2c4 --- /dev/null +++ b/Apps/FiniteDefElasticity/NeoHookeElasticity.h @@ -0,0 +1,128 @@ +// $Id: NeoHookeElasticity.h,v 1.1 2010-12-18 20:03:53 kmo Exp $ +//============================================================================== +//! +//! \file NeoHookeElasticity.h +//! +//! \date Jul 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for Neo-Hooke elasticity problems. +//! +//============================================================================== + +#ifndef _NEO_HOOKE_ELASTICITY_H +#define _NEO_HOOKE_ELASTICITY_H + +#include "NonlinearElasticity.h" + + +/*! + \brief Class representing the integrand of nonlinear hyperelastic problems. + \details This class implements the Neo-Hooke hyperelastic material behavior. +*/ + +class NeoHookeElasticity : public NonlinearElasticity +{ +public: + //! \brief The default constructor invokes the parent class constructor only. + //! \param[in] n Number of spatial dimensions + NeoHookeElasticity(unsigned short int n = 3) + : NonlinearElasticity(n), C(n), Ci(n) { fullCmat = true; } + + //! \brief Empty destructor. + virtual ~NeoHookeElasticity() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines material properties for current volume patch. + //! \param[in] Emod Young's modulus + //! \param[in] Poiss Poisson's ratio + //! \param[in] Density Mass density + virtual void setMaterial(double Emod, double Poiss, double Density); + +protected: + //! \brief Calculates some kinematic quantities at current point. + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] _E Green-Lagrange strain tensor at current point + //! + //! \details The deformation gradient \b F and the Green-Lagrange tensor \b E + //! are established. In addition, the right Cauchy-Green tensor \b C and its + //! inverse \b Ci are formed. + //! All quantities are stored internally in mutable data members. + virtual bool kinematics(const Matrix& dNdX, SymmTensor& _E) const; + + //! \brief Forms tangential tensorial quantities needed by the evalInt method. + //! \param[in] X Cartesian coordinates of current integration point + //! \param[out] Ctan Tangential constitutive tensor at current point + //! \param[out] S 2nd Piola-Kirchhoff stress tensor at current point + virtual bool formTangent(Matrix& Ctan, SymmTensor& S, const Vec3& X) const; + + //! \brief Forms the 2nd Piola-Kirchhoff stress tensor. + //! \param[in] dNdX Basis function gradients at current integration point + //! \param[in] X Cartesian coordinates of current integration point + //! \param[out] S 2nd Piola-Kirchhoff stress tensor at current point + virtual bool formStressTensor(const Matrix& dNdX, const Vec3& X, + SymmTensor& S) const; + + //! \brief Forms the 2nd Piola-Kirchhoff stress tensor. + virtual void formStressTensor(SymmTensor& S) const; + +public: + //! \brief Sets up the tangential constitutive matrix at current point. + //! \param[out] Ctan \f$6\times6\f$-matrix (in 3D) or \f$3\times3\f$-matrix + //! (in 2D), representing the tangential constitutive tensor + //! \param[in] X Cartesian coordinates of current point + virtual bool formCmatrix(Matrix& Ctan, const Vec3& X, bool = false) const; + +protected: + double lambda; //!< First Lame parameter + double mu; //!< Second Lame parameter (shear modulus) + double kappa; //!< Bulk modulus + mutable double J; //!< Determinant of the deformation gradient + mutable SymmTensor C; //!< Right Cauchy-Green deformation tensor + mutable SymmTensor Ci; //!< Inverse of the right Cauchy-Green tensor +}; + + +/*! + \brief Neo-Hooke hyperelastic material with isochoric and volumetric split. +*/ + +class NeoHookeElasticityIV : public NeoHookeElasticity +{ +public: + //! \brief The default constructor invokes the parent class constructor only. + //! \param[in] n Number of spatial dimensions + NeoHookeElasticityIV(unsigned short int n = 3) + : NeoHookeElasticity(n), Siso(n) {} + + //! \brief Empty destructor. + virtual ~NeoHookeElasticityIV() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + +protected: + //! \brief Forms tangential tensorial quantities needed by the evalInt method. + //! \param[in] X Cartesian coordinates of current integration point + //! \param[out] Ctan Tangential constitutive tensor at current point + //! \param[out] S 2nd Piola-Kirchhoff stress tensor at current point + virtual bool formTangent(Matrix& Ctan, SymmTensor& S, const Vec3& X) const; + + //! \brief Forms the 2nd Piola-Kirchhoff stress tensor. + virtual void formStressTensor(SymmTensor& S) const; + +public: + //! \brief Sets up the tangential constitutive matrix at current point. + //! \param[out] Ctan \f$6\times6\f$-matrix (in 3D) or \f$3\times3\f$-matrix + //! (in 2D), representing the tangential constitutive tensor + //! \param[in] X Cartesian coordinates of current point + virtual bool formCmatrix(Matrix& Ctan, const Vec3& X, bool = false) const; + +protected: + mutable SymmTensor Siso; //!< Isochoric part of the 2nd PK stress tensor +}; + +#endif diff --git a/Apps/FiniteDefElasticity/NonlinearElasticity.C b/Apps/FiniteDefElasticity/NonlinearElasticity.C new file mode 100644 index 00000000..01b3ace5 --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticity.C @@ -0,0 +1,250 @@ +// $Id: NonlinearElasticity.C,v 1.3 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticity.C +//! +//! \date May 25 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity problems. +//! +//============================================================================== + +#include "NonlinearElasticity.h" +#include "Utilities.h" +#include "Profiler.h" + +#ifndef NO_FORTRAN +#if defined(_WIN32) +#define stiff_tl2d_ STIFF_TL2D +#define stiff_tl3d_ STIFF_TL3D +#define stiff_tl3d_isoel_ STIFF_TL3D_ISOEL +#define stiff_tl3d_isoel_ STIFF_TL3D_ISOEL +#elif defined(_AIX) +#define stiff_tl2d_ stiff_tl2d +#define stiff_tl3d_ stiff_tl3d +#define stiff_tl2d_isoel_ stiff_tl2d_isoel +#define stiff_tl3d_isoel_ stiff_tl3d_isoel +#endif + +extern "C" { + //! \brief Calculates material stiffness contributions for 2D problems. + void stiff_tl2d_(const int& nenod, const double& detJW, + const double* dNdX, const double* F, const double* Cmat, + double* EM); + //! \brief Calculates material stiffness contributions for 3D problems. + void stiff_tl3d_(const int& nenod, const double& detJW, + const double* dNdX, const double* F, const double* Cmat, + double* EM); + //! \brief Optimized for isotropic linear elastic materials in 2D. + void stiff_tl2d_isoel_(const int& nenod, const double& detJW, + const double* dNdX, const double* F, + const double& C1, const double& C2,const double& C3, + double* EM); + //! \brief Optimized for isotropic linear elastic materials in 3D. + void stiff_tl3d_isoel_(const int& nenod, const double& detJW, + const double* dNdX, const double* F, + const double& C1, const double& C2,const double& C3, + double* EM); +} +#endif + + +NonlinearElasticity::NonlinearElasticity (unsigned short int n, bool ps) + : NonlinearElasticityTL(n,ps), E(n) +{ + fullCmat = false; +} + + +void NonlinearElasticity::print (std::ostream& os) const +{ + this->Elasticity::print(os); + std::cout <<"NonlinearElasticity: Total Lagrangian formulation " + <<" (tensorial form)"<< std::endl; +} + + +void NonlinearElasticity::setMode (SIM::SolutionMode mode) +{ + this->NonlinearElasticityTL::setMode(mode); + formB = false; // We don't need the B-matrix in the tensor formulation +} + + +bool NonlinearElasticity::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + PROFILE3("NonlinearEl::evalInt"); + + // Evaluate the kinematic quantities, F and E, at this point + if (!this->kinematics(dNdX,E)) + return false; + + // Evaluate current tangent at this point, that is + // the incremental constitutive matrix, Cmat, and + // the 2nd Piola-Kirchhoff stress tensor, S + static SymmTensor S(nsd); + if (!this->formTangent(Cmat,S,X)) + return false; + + bool haveStrains = !E.isZero(1.0e-16); + + size_t a, b; + unsigned short int i, j, k; + + if (eKm) + { + // Integrate the material stiffness matrix +#ifndef NO_FORTRAN + // Using Fortran routines optimized for symmetric constitutive tensors + PROFILE4("stiff_TL_"); + if (nsd == 3) + if (fullCmat) + stiff_tl3d_(N.size(),detJW,dNdX.ptr(),F.ptr(), + Cmat.ptr(),eKm->ptr()); + else + stiff_tl3d_isoel_(N.size(),detJW,dNdX.ptr(),F.ptr(), + Cmat(1,1),Cmat(1,2),Cmat(4,4),eKm->ptr()); + else if (nsd == 2) + if (fullCmat) + stiff_tl2d_(N.size(),detJW,dNdX.ptr(),F.ptr(), + Cmat.ptr(),eKm->ptr()); + else + stiff_tl2d_isoel_(N.size(),detJW,dNdX.ptr(),F.ptr(), + Cmat(1,1),Cmat(1,2),Cmat(3,3),eKm->ptr()); + else if (nsd == 1) + for (a = 1; a <= N.size(); a++) + for (b = 1; b <= N.size(); b++) + (*eKm)(a,b) += dNdX(a,1)*F(1,1)*Cmat(1,1)*F(1,1)*dNdX(b,1); +#else + // This is too costly, but is basically what is done in the fortran routines + PROFILE4("dNdX^t*F^t*C*F*dNdX"); + unsigned short int l, m, n; + SymmTensor4 D(Cmat,nsd); // fourth-order material tensor + Matrix& EM = *eKm; + for (a = 1; a <= N.size(); a++) + for (b = 1; b <= N.size(); b++) + for (m = 1; m <= nsd; m++) + for (n = 1; n <= nsd; n++) + { + double km = 0.0; + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + km += dNdX(a,i)*F(m,j)*D(i,j,k,l)*F(n,k)*dNdX(b,l); + + EM(nsd*(a-1)+m,nsd*(b-1)+n) += km*detJW; + } +#endif + } + + if (eKg && haveStrains) + // Integrate the geometric stiffness matrix + this->formKG(*eKg,dNdX,S,detJW); + + if (eM) + // Integrate the mass matrix + this->formMassMatrix(*eM,N,X,detJW); + + if (iS && haveStrains) + { + // Integrate the internal forces + Vector& ES = *iS; + for (a = 1; a <= N.size(); a++) + for (k = 1; k <= nsd; k++) + { + double f = 0.0; + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + f -= dNdX(a,i)*F(k,j)*S(i,j); + ES(nsd*(a-1)+k) += f*detJW; + } + } + + if (eS) + // Integrate the load vector due to gravitation and other body forces + this->formBodyForce(*eS,N,X,detJW); + + return this->getIntegralResult(elmInt); +} + + +bool NonlinearElasticity::evalSol (Vector& s, const Vector&, + const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const +{ + PROFILE3("NonlinearEl::evalSol"); + + int ierr = 0; + if (eV && !primsol.front().empty()) + if (ierr = utl::gather(MNPC,nsd,primsol.front(),*eV)) + { + std::cerr <<" *** NonlinearElasticity::evalSol: Detected " + << ierr <<" node numbers out of range."<< std::endl; + return false; + } + + // Evaluate the stress state at this point + static SymmTensor Sigma(nsd); + if (!this->formStressTensor(dNdX,X,Sigma)) + return false; + + // Congruence transformation to local coordinate system at current point + if (locSys) Sigma.transform(locSys->getTmat(X)); + + s = Sigma; + s.push_back(Sigma.vonMises()); + return true; +} + + +bool NonlinearElasticity::evalSol (Vector& s, + const Matrix& dNdX, const Vec3& X) const +{ + PROFILE3("NonlinearEl::evalSol"); + + static SymmTensor Sigma(nsd); + if (!this->formStressTensor(dNdX,X,Sigma)) + return false; + + s = Sigma; + return true; +} + + +bool NonlinearElasticity::formStressTensor (const Matrix& dNdX, const Vec3& X, + SymmTensor& S) const +{ + if (!eV || eV->empty()) + { + // Initial state (zero stresses) + S.zero(); + return true; + } + + // Evaluate the kinematic quantities, F and E, at this point + if (!this->kinematics(dNdX,E)) + return false; + + // Evaluate the constitutive matrix, C, at this point + if (!this->formCmatrix(Cmat,X)) + return false; + + return Cmat.multiply(E,S); // S = C*E +} + + +bool NonlinearElasticity::formTangent (Matrix& Ctan, SymmTensor& S, + const Vec3& X) const +{ + if (!this->formCmatrix(Ctan,X)) + return false; + else if (eV && !eV->empty()) + return Ctan.multiply(E,S); // S = C*E + else + return true; // Initial state (no stresses) +} diff --git a/Apps/FiniteDefElasticity/NonlinearElasticity.h b/Apps/FiniteDefElasticity/NonlinearElasticity.h new file mode 100644 index 00000000..5e7bad24 --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticity.h @@ -0,0 +1,96 @@ +// $Id: NonlinearElasticity.h,v 1.2 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticity.h +//! +//! \date May 25 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity problems. +//! +//============================================================================== + +#ifndef _NONLINEAR_ELASTICITY_H +#define _NONLINEAR_ELASTICITY_H + +#include "NonlinearElasticityTL.h" +#include "Tensor.h" + + +/*! + \brief Class representing the integrand of the nonlinear elasticity problem. + \details This class implements a Total Lagrangian formulation, tensorial form. + It reimplements most of the Elasticity methods, except for the methods + \a kinematics and \a evalBou, which are inherited from NonlinearElasticityTL. + + \note This class is obsolete, as it gives exactly the same results as the + NonlinearElasticityTL class, but is less efficient. It is retained mostly for + educational purposes and historical reasons, and such that the performance of + the two ways of implementing the total Lagrangian formulation can be compared + on a varity of problems. +*/ + +class NonlinearElasticity : public NonlinearElasticityTL +{ +public: + //! \brief The default constructor invokes the parent class constructor only. + //! \param[in] n Number of spatial dimensions + //! \param[in] ps If \e true, assume plane stress in 2D + NonlinearElasticity(unsigned short int n = 3, bool ps = true); + //! \brief Empty destructor. + virtual ~NonlinearElasticity() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s Array of solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the finite element (FE) solution at an integration point. + //! \param[out] s The FE stress values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSol(Vector& s, const Matrix& dNdX, const Vec3& X) const; + +protected: + //! \brief Forms tangential tensorial quantities needed by the evalInt method. + //! \param[in] X Cartesian coordinates of current integration point + //! \param[out] Ctan Tangential constitutive tensor at current point + //! \param[out] S 2nd Piola-Kirchhoff stress tensor at current point + virtual bool formTangent(Matrix& Ctan, SymmTensor& S, const Vec3& X) const; + + //! \brief Forms the 2nd Piola-Kirchhoff stress tensor. + //! \param[in] dNdX Basis function gradients at current integration point + //! \param[in] X Cartesian coordinates of current integration point + //! \param[out] S 2nd Piola-Kirchhoff stress tensor at current point + virtual bool formStressTensor(const Matrix& dNdX, const Vec3& X, + SymmTensor& S) const; + +protected: + bool fullCmat; //!< If \e true, assume a full (but symmetric) C-matrix + mutable SymmTensor E; //!< Green-Lagrange strain tensor +}; + +#endif diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityTL.C b/Apps/FiniteDefElasticity/NonlinearElasticityTL.C new file mode 100644 index 00000000..5bc7db70 --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityTL.C @@ -0,0 +1,221 @@ +// $Id: NonlinearElasticityTL.C,v 1.4 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityTL.C +//! +//! \date May 25 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity problems. +//! +//============================================================================== + +#include "NonlinearElasticity.h" +#include "ElmMats.h" +#include "Tensor.h" +#include "Vec3Oper.h" + + +NonlinearElasticityTL::NonlinearElasticityTL (unsigned short int n, bool ps) + : Elasticity(n,ps) +{ + // Only the current solution is needed + primsol.resize(1); +} + + +void NonlinearElasticityTL::print (std::ostream& os) const +{ + this->Elasticity::print(os); + std::cout <<"NonlinearElasticityTL: Total Lagrangian formulation"<< std::endl; +} + + +void NonlinearElasticityTL::setMode (SIM::SolutionMode mode) +{ + if (!myMats) return; + + formB = true; + myMats->rhsOnly = false; + eM = eKm = eKg = 0; + iS = eS = eV = 0; + + switch (mode) + { + case SIM::STATIC: + myMats->resize(1,2); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + break; + + case SIM::DYNAMIC: + myMats->resize(2,2); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + eM = &myMats->A[1]; + break; + + case SIM::RHS_ONLY: + if (myMats->A.empty()) + myMats->resize(1,2); + else + myMats->b.resize(2); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + myMats->rhsOnly = true; + break; + + default: + formB = false; + this->Elasticity::setMode(mode); + return; + } + + // We always need the force+displacement vectors in nonlinear simulations + iS = &myMats->b[0]; + eS = &myMats->b[0]; + eV = &myMats->b[1]; + tracVal.clear(); +} + + +bool NonlinearElasticityTL::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + if (!tracFld) + { + std::cerr <<" *** NonlinearElasticityTL::evalBou: No tractions." + << std::endl; + return false; + } + else if (!eS) + { + std::cerr <<" *** NonlinearElasticityTL::evalBou: No load vector." + << std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + // Check for with-rotated pressure load + unsigned short int i, j; + if (tracFld->isNormalPressure()) + { + // Compute the deformation gradient, F + SymmTensor dummy(0); + if (!this->kinematics(dNdX,dummy)) return false; + + // Compute its inverse and determinant, J + double J = F.inverse(); + if (J <= 0.0) return false; + + // Pull-back the normal traction to the initial configuration. + // See equation (3.4.5) on page 102 in T. Belytschko et. al (2000): + // p*n*dS ==> J*p*n0*F^-1*dS0 + Vec3 t = J*T; T = 0.0; + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + T[i-1] += t[j-1]*F(j,i); + } + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (i = 1; i <= nsd; i++) + ES(nsd*(a-1)+i) += T[i-1]*N(a)*detJW; + + return this->getIntegralResult(elmInt); +} + + +bool NonlinearElasticityTL::kinematics (const Matrix& dNdX, SymmTensor& E) const +{ + if (!eV || eV->empty()) + { + // Initial state, unit deformation gradient and linear B-matrix + F.diag(1.0,nsd); + return this->Elasticity::kinematics(dNdX,E); + } + + const size_t nenod = dNdX.rows(); + const size_t nstrc = nsd*(nsd+1)/2; + if (eV->size() != nenod*nsd || dNdX.cols() < nsd) + { + std::cerr <<" *** NonlinearElasticityTL::kinematics: Invalid dimension," + <<" dNdX("<< nenod <<","<< dNdX.cols() <<")"<< std::endl; + return false; + } + + // Compute the deformation gradient, [F] = [I] + [dudX] = [I] + [dNdX]*[u], + // and the Green-Lagrange strains, E_ij = 0.5(F_ij+F_ji+F_ki*F_kj). + + // Notice that the matrix multiplication method used here treats the + // element displacement vector, *eV, as a matrix whose number of columns + // equals the number of rows in the matrix dNdX. + if (!F.multiplyMat(*eV,dNdX)) // F = Grad{u} = eV*dNdX + return false; + + unsigned short int i, j, k; + + // Now form the Green-Lagrange strain tensor. + // Note that for the shear terms (i/=j) we actually compute 2*E_ij + // to be consistent with the engineering strain style constitutive matrix. + for (i = 1; i <= E.dim(); i++) + for (j = 1; j <= i; j++) + { + double Eij = F(i,j) + F(j,i); + for (k = 1; k <= nsd; k++) + Eij += F(k,i)*F(k,j); + E(i,j) = i == j ? 0.5*Eij : Eij; + } + + // Add the unit tensor to F to form the deformation gradient + for (i = 1; i <= nsd; i++) + F(i,i) += 1.0; + +#ifdef INT_DEBUG + std::cout <<"NonlinearElasticityTL::F ="<< F; +#endif + + if (!formB || E.dim() < nsd) return true; + + // Form the nonlinear B-matrix + + Bmat.resize(nstrc*nsd,nenod,true); + +#define INDEX(i,j) i+nstrc*(j-1) + + // Normal strain part + size_t a; + for (a = 1; a <= nenod; a++) + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + Bmat(INDEX(j,i),a) = F(i,j)*dNdX(a,j); + + // Shear strain part + if (nsd == 3) + for (a = 1; a <= nenod; a++) + for (i = 1; i <= nsd; i++) + { + Bmat(INDEX(4,i),a) = F(i,1)*dNdX(a,2) + F(i,2)*dNdX(a,1); + Bmat(INDEX(5,i),a) = F(i,2)*dNdX(a,3) + F(i,3)*dNdX(a,2); + Bmat(INDEX(6,i),a) = F(i,3)*dNdX(a,1) + F(i,1)*dNdX(a,3); + } + + else if (nsd == 2) + for (a = 1; a <= nenod; a++) + for (i = 1; i <= nsd; i++) + Bmat(INDEX(3,i),a) = F(i,1)*dNdX(a,2) + F(i,2)*dNdX(a,1); + + Bmat.resize(nstrc,nsd*nenod); +#ifdef INT_DEBUG + std::cout <<"NonlinearElasticityTL::B ="<< Bmat; +#endif + return true; +} diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityTL.h b/Apps/FiniteDefElasticity/NonlinearElasticityTL.h new file mode 100644 index 00000000..7b48fff3 --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityTL.h @@ -0,0 +1,76 @@ +// $Id: NonlinearElasticityTL.h,v 1.2 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityTL.h +//! +//! \date May 25 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity problems. +//! +//============================================================================== + +#ifndef _NONLINEAR_ELASTICITY_TL_H +#define _NONLINEAR_ELASTICITY_TL_H + +#include "Elasticity.h" + + +/*! + \brief Class representing the integrand of the nonlinear elasticity problem. + \details This class implements a Total Lagrangian formulation in matrix form. + It inherits most of the Elasticity methods, but reimplements the \a kinematics + method, for calculating the nonlinear variant of the strain-displacement + matrix, \b B, and the associated Green-Lagrange strain tensor, \b E. + The \a evalBou method is also reimplemented to account for with-rotated loads. +*/ + +class NonlinearElasticityTL : public Elasticity +{ +public: + //! \brief The default constructor invokes the parent class constructor only. + //! \param[in] n Number of spatial dimensions + //! \param[in] ps If \e true, assume plane stress in 2D + NonlinearElasticityTL(unsigned short int n = 3, bool ps = true); + //! \brief Empty destructor. + virtual ~NonlinearElasticityTL() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details This method is reimplemented in this class to account for + //! possibly with-rotated traction fields in the Total-Lagrangian setting. + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + +protected: + //! \brief Calculates some kinematic quantities at current point. + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] E Green-Lagrange strain tensor at current point + //! + //! \details The deformation gradient \b F and the nonlinear + //! strain-displacement matrix \b B are established. The latter matrix + //! is stored in the mutable class member \a Bmat of the parent class. + //! The B-matrix is formed only when the variable \a formB is true. + virtual bool kinematics(const Matrix& dNdX, SymmTensor& E) const; + +protected: + bool formB; //!< Flag determining whether we need to form the B-matrix + mutable Matrix F; //!< Deformation gradient +}; + +#endif diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityUL.C b/Apps/FiniteDefElasticity/NonlinearElasticityUL.C new file mode 100644 index 00000000..ebc5903a --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityUL.C @@ -0,0 +1,468 @@ +// $Id: NonlinearElasticityUL.C,v 1.5 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityUL.C +//! +//! \date Sep 21 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity problems. +//! +//============================================================================== + +#include "NonlinearElasticityUL.h" +#include "ElmMats.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3Oper.h" + +#ifdef USE_FTNMAT +extern "C" { + //! \brief Interface to 2D nonlinear material routines (FORTRAN-77 code). + void cons2d_(const int& mTYP, const int& mVER, const double& detF, + const double* F, const double* pmat, double& Engy, + const double* Sig, double* Cst, + const int& ipsw, const int& iwr, int& ierr); + //! \brief Interface to 3D nonlinear material routines (FORTRAN-77 code). + void cons3d_(const int& mTYP, const int& mVER, const double& detF, + const double* F, const double* pmat, double& Engy, + const double* Sig, double* Cst, + const int& ipsw, const int& iwr, int& ierr); +} +#ifndef INT_DEBUG +#define INT_DEBUG 0 +#endif +#endif + + +NonlinearElasticityUL::NonlinearElasticityUL (unsigned short int n, + bool ps, int mver, char lop) + : Elasticity(n,ps) +{ + mVER = -1; +#ifdef USE_FTNMAT + if (n == 3 || (n == 2 && !ps)) mVER = mver; +#endif + loadOp = lop; + + // Only the current solution is needed + primsol.resize(1); +} + + +void NonlinearElasticityUL::print (std::ostream& os) const +{ + this->Elasticity::print(os); + std::cout <<"NonlinearElasticityUL: Updated Lagrangian formulation"; + if (mVER >= 0) std::cout <<", mVER="<< mVER; + if (mVER >= 1) std::cout <<" (Neo-Hooke)"; + std::cout << std::endl; +} + + +void NonlinearElasticityUL::setMaterial (double Young, double Poiss, + double Density) +{ + mTYP = 10; + pmat[0] = Young; + pmat[1] = Poiss; + if (Poiss > 0.5) + { + mTYP *= -1; // Assume Lame' parameters (kappa and mu are specified) + double kappa = Young; + double mu = Poiss; + // Calculate Young's modulus and Poisson's ratio + Young = 9.0*kappa*mu/(3.0*kappa + mu); + Poiss = (1.5*kappa - mu)/(3.0*kappa + mu); + } + this->Elasticity::setMaterial(Young,Poiss,Density); +} + + +void NonlinearElasticityUL::setMode (SIM::SolutionMode mode) +{ + if (!myMats) return; + + myMats->rhsOnly = false; + eM = eKm = eKg = 0; + iS = eS = eV = 0; + + switch (mode) + { + case SIM::STATIC: + myMats->resize(1,2); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + break; + + case SIM::DYNAMIC: + myMats->resize(2,2); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + eM = &myMats->A[1]; + break; + + case SIM::RHS_ONLY: + if (myMats->A.empty()) + myMats->resize(1,2); + else + myMats->b.resize(2); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + myMats->rhsOnly = true; + break; + + default: + this->Elasticity::setMode(mode); + return; + } + + // We always need the force+displacement vectors in nonlinear simulations + iS = &myMats->b[0]; + eS = &myMats->b[0]; + eV = &myMats->b[1]; + tracVal.clear(); +} + + +bool NonlinearElasticityUL::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + elmInt = myMats; + + // Evaluate the deformation gradient, F, and the Green-Lagrange strains, E + SymmTensor E(nsd); + if (!this->kinematics(dNdX,E)) + return false; + + bool lHaveStrains = !E.isZero(); + if (lHaveStrains) + { + // Invert the deformation gradient ==> Fi + Matrix Fi(F); + double J = Fi.inverse(); + if (J == 0.0) return false; + + // Scale with J=|F| since we are integrating over current configuration + detJW *= J; + + if (eKm || iS) + { + // Push-forward the basis function gradients to current configuration + dNdx.multiply(dNdX,Fi); // dNdx = dNdX * F^-1 + // Compute the small-deformation strain-displacement matrix B from dNdx + if (!this->formBmatrix(dNdx)) return false; +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityUL::dNdx ="<< dNdx; + std::cout <<"NonlinearElasticityUL::B ="<< Bmat; +#endif + } + } + else if (eKm || iS) + // Initial state, no deformation yet + if (!this->formBmatrix(dNdX)) return false; + + // Evaluate the constitutive relation + SymmTensor sigma(nsd); + double U = 0.0; + if (eKm || eKg || iS) + if (!this->constitutive(Cmat,sigma,U,E,X, (eKg || iS) && lHaveStrains)) + return false; + + if (eKm) + { + // Integrate the material stiffness matrix + CB.multiply(Cmat,Bmat).multiply(detJW); // CB = C*B*|J|*w + eKm->multiply(Bmat,CB,true,false,true); // EK += B^T * CB + } + + if (eKg && lHaveStrains) + // Integrate the geometric stiffness matrix + this->formKG(*eKg,dNdx,sigma,detJW); + + if (eM) + // Integrate the mass matrix + this->formMassMatrix(*eM,N,X,detJW); + + if (iS && lHaveStrains) + { + // Integrate the internal forces + sigma *= -detJW; + if (!Bmat.multiply(sigma,*iS,true,true)) // ES -= B^T*sigma + return false; + } + + if (eS) + // Integrate the load vector due to gravitation and other body forces + this->formBodyForce(*eS,N,X,detJW); + + return true; +} + + +bool NonlinearElasticityUL::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + elmInt = myMats; + if (!tracFld) + { + std::cerr <<" *** NonlinearElasticityUL::evalBou: No tractions." + << std::endl; + return false; + } + else if (!eS) + { + std::cerr <<" *** NonlinearElasticityUL::evalBou: No load vector." + << std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + unsigned short int i, j; + if (loadOp == 1) + { + // Compute the deformation gradient, F + if (!this->formDefGradient(dNdX)) return false; + + // Check for with-rotated pressure load + if (tracFld->isNormalPressure()) + { + // Compute its inverse and determinant, J + double J = F.inverse(); + if (J == 0.0) return false; + + // Pull-back the normal traction to the initial configuration. + // See equation (3.4.5) on page 102 in T. Belytschko et. al (2000): + // p*n*dS ==> J*p*n0*F^-1*dS0 + Vec3 t = J*T; T = 0.0; + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + T[i-1] += t[j-1]*F(j,i); + } + else + // Scale with J=|F| since we are integrating over current configuration + detJW *= F.det(); + } + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (i = 1; i <= nsd; i++) + ES(nsd*(a-1)+i) += T[i-1]*N(a)*detJW; + + return true; +} + + +bool NonlinearElasticityUL::formDefGradient (const Matrix& dNdX) const +{ + static SymmTensor dummy(0); + return this->kinematics(dNdX,dummy); +} + + +bool NonlinearElasticityUL::kinematics (const Matrix& dNdX, SymmTensor& E) const +{ + if (!eV || eV->empty()) + { + // Initial state, unit deformation gradient and zero strains + F.diag(1.0,nsd); + E.zero(); + return true; + } + + const size_t nenod = dNdX.rows(); + const size_t nstrc = nsd*(nsd+1)/2; + if (eV->size() != nenod*nsd || dNdX.cols() < nsd) + { + std::cerr <<" *** NonlinearElasticityUL::kinematics: Invalid dimension," + <<" dNdX("<< nenod <<","<< dNdX.cols() <<")"<< std::endl; + return false; + } + + // Compute the deformation gradient, [F] = [I] + [dudX] = [I] + [dNdX]*[u], + // and the Green-Lagrange strains, E_ij = 0.5(F_ij+F_ji+F_ki*F_kj). + + // Notice that the matrix multiplication method used here treats the + // element displacement vector, *eV, as a matrix whose number of columns + // equals the number of rows in the matrix dNdX. + if (!F.multiplyMat(*eV,dNdX)) // F = Grad{u} = eV*dNdX + return false; + + unsigned short int i, j, k; + + // Now form the Green-Lagrange strain tensor. + // Note that for the shear terms (i/=j) we actually compute 2*E_ij + // to be consistent with the engineering strain style constitutive matrix. + for (i = 1; i <= E.dim(); i++) + for (j = 1; j <= i; j++) + { + double Eij = F(i,j) + F(j,i); + for (k = 1; k <= nsd; k++) + Eij += F(k,i)*F(k,j); + E(i,j) = i == j ? 0.5*Eij : Eij; + } + + // Add the unit tensor to F to form the deformation gradient + for (i = 1; i <= nsd; i++) + F(i,i) += 1.0; + +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityUL::eV ="<< *eV; + std::cout <<"NonlinearElasticityUL::F ="<< F; +#endif + + return true; +} + + +bool NonlinearElasticityUL::constitutive (Matrix& C, SymmTensor& sigma, + double& U, const SymmTensor& eps, + const Vec3& X, + char calcStress) const +{ + double J = F.det(); + if (J == 0.0) + { + std::cerr <<" *** NonlinearElasticityUL::constitutive: " + <<" Singular/zero deformation gradient"<< F; + return false; + } + + size_t ndim = F.rows(); +#ifdef USE_FTNMAT + if (mVER >= 0) + { + // Invoke the FORTRAN routine for constitutive relations. + // This also includes the Neo-Hookean hyperelastic material models. + size_t ncmp = ndim*(ndim+1)/2; + C.resize(ncmp,ncmp); + int ipsw = INT_DEBUG > 1 ? 9 : 0; + int ierr = 0; + if (ndim == 2) + cons2d_(mTYP,mVER,J,F.ptr(),pmat,U,sigma.ptr(),C.ptr(),ipsw,6,ierr); + else + cons3d_(mTYP,mVER,J,F.ptr(),pmat,U,sigma.ptr(),C.ptr(),ipsw,6,ierr); + if (calcStress == 2) + { + // Transform to 2nd Piola-Kirchhoff stresses, + // via pull-back to reference configuration + F.inverse(); + Tensor Fi(ndim); + sigma.transform(Fi=F); // sigma = F^-1 * sigma * F^-t + sigma *= J; + } +#if INT_DEBUG > 0 + if (calcStress) + std::cout <<"NonlinearElasticityUL::sigma =\n"<< sigma; + std::cout <<"NonlinearElasticityUL::C ="<< C; +#endif + return ierr == 0; + } +#endif + + // We are only doing pure isotrophic linear elastic material in this method. + // Evaluate the constitutive matrix at this point, but first reset nsd to ndim + // since it is used to set the dimension on C. + unsigned short int tmp = nsd; + const_cast(this)->nsd = ndim; + if (!this->formCmatrix(C,X)) return false; + const_cast(this)->nsd = tmp; + + if (calcStress) + // Evaluate the symmetric stress tensor + C.multiply(eps,sigma); // sigma = C*eps + + if (calcStress == 1) + { + // Push-forward the stress tensor to current configuration + Tensor dUdX(ndim); + sigma.transform(dUdX=F); // sigma = F * sigma * F^t + sigma *= 1.0/J; +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityUL::sigma =\n"<< sigma; +#endif + } + else if (calcStress == 2) + return true; + + // Push-forward the constitutive matrix to current configuration + + size_t i, j, k, l, m = C.rows(); + Matrix T(m,m), Ctmp; + + for (i = 1; i <= ndim; i++) + { + for (j = 1; j <= ndim; j++) + T(i,j) = F(j,i)*F(j,i); + for (j = ndim+1, k = 1; j <= m; j++, k++) + T(i,j) = 0.5*(F(k,i)*F(k%3+1,i) + F(k%3+1,i)*F(k,i)); + } + + for (i = ndim+1, k = 1; i <= m; i++, k++) + { + for (j = 1; j <= ndim; j++) + T(i,j) = F(j,k)*F(j,k%3+1) + F(j,k%3+1)*F(j,k); + for (j = ndim+1, l = 1; j <= m; j++, l++) + T(i,j) = 0.5*(F(l,k)*F(l%3+1,k%3+1) + F(l%3+1,k)*F(l,k%3+1) + + F(l,k%3+1)*F(l%3+1,k) + F(l%3+1,k%3+1)*F(l,k)); + } + + // C = 1/J * T^t * C * T + Ctmp.multiply(C,T); + C.multiply(T,Ctmp,true); + C *= 1.0/J; +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityUL::C ="<< C; +#endif + + return true; +} + + +NormBase* NonlinearElasticityUL::getNormIntegrand (TensorFunc*) const +{ + return new ElasticityNormUL(*const_cast(this)); +} + + +bool ElasticityNormUL::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector&, const Matrix& dNdX, + const Vec3& X) const +{ + ElmNorm& pnorm = ElasticityNorm::getElmNormBuffer(elmInt); + + NonlinearElasticityUL* ulp = static_cast(&problem); + + // Evaluate the deformation gradient, F, and the Green-Lagrange strains, E + SymmTensor E(dNdX.cols()); + if (!ulp->kinematics(dNdX,E)) + return false; + + // Evaluate the 2nd Piola Kirchhoff stresses, S + Matrix C; + SymmTensor S(E.dim()); + double U = 0.0; + if (!ulp->constitutive(C,S,U,E,X,2)) + return false; + + if (U == 0.0) + { + // Integrate the energy norm a(u^h,u^h) = Int_Omega0 (S:E) dV0 + const RealArray& sigma = S; + const RealArray& epsil = E; + for (size_t i = 0; i < sigma.size(); i++) + U += sigma[i]*epsil[i]; + } + + pnorm[0] += U*detJW; + return true; +} diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityUL.h b/Apps/FiniteDefElasticity/NonlinearElasticityUL.h new file mode 100644 index 00000000..6d52ad5f --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityUL.h @@ -0,0 +1,204 @@ +// $Id: NonlinearElasticityUL.h,v 1.7 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityUL.h +//! +//! \date Sep 21 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity problems. +//! +//============================================================================== + +#ifndef _NONLINEAR_ELASTICITY_UL_H +#define _NONLINEAR_ELASTICITY_UL_H + +#include "Elasticity.h" + + +/*! + \brief Class representing the integrand of the nonlinear elasticity problem. + \details This class implements an Updated Lagrangian formulation. It inherits + most of the Elasticity methods, but reimplements the \a kinematics method + for calculating the deformation gradient and the associated Green-Lagrange + strain tensor, and the method \a constitutive for calculating the tangent + constitutive matrix and the associated stress tensor. The \a evalInt and + \a evalBou methods are also reimplemented to account for the updated geometry. + + This class supports isotrophic linear-elastic and neo-Hookean hyperelastic + material laws. The material properties are assumed constant in space. +*/ + +class NonlinearElasticityUL : public Elasticity +{ +public: + //! \brief The default constructor invokes the parent class constructor. + //! \param[in] n Number of spatial dimensions + //! \param[in] ps If \e true, assume plane stress in 2D + //! \param[in] mver Material version parameter + //! \param[in] lop Load option (0=on initial length, 1=on updated length) + NonlinearElasticityUL(unsigned short int n = 3, + bool ps = false, int mver = 0, char lop = 0); + //! \brief Empty destructor. + virtual ~NonlinearElasticityUL() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines the material properties to use. + //! \param[in] Young Young's modulus + //! \param[in] Poiss Poisson's ratio + //! \param[in] Density Mass density + //! + //! \details If \a Poiss > 0.5, it is assumed that \a Young and \a Poiss + //! are the Lame's parameters instead (bulk and shear moduli). + virtual void setMaterial(double Young, double Poiss, double Density); + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details This method is reimplemented in this class to account for + //! possibly with-rotated traction fields (non-conservative loads). + //! For uni-directional (conservative) loads, it is similar to the + //! \a LinearElasticity::evalBou method. + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \note The Integrand object is allocated dynamically and has to be deleted + //! manually when leaving the scope of the pointer variable receiving the + //! returned pointer value. + virtual NormBase* getNormIntegrand(TensorFunc* = 0) const; + + //! \brief Calculates some kinematic quantities at current point. + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] E Green-Lagrange strain tensor at current point + //! + //! \details The deformation gradient is established and stored in + //! the mutable class member \a F. + virtual bool kinematics(const Matrix& dNdX, SymmTensor& E) const; + + //! \brief Calculates the deformation gradient at current point. + //! \param[in] dNdX Basis function gradients at current point + bool formDefGradient(const Matrix& dNdX) const; + + //! \brief Evaluates the constitutive relation at current point. + //! \param[out] C Constitutive matrix at current point + //! \param[out] sigma Stress tensor at current point + //! \param[out] U Strain energy density + //! \param[in] eps Strain tensor at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] calcStress Stress calculation option. + //! 0: No stress calculation, only output the C-matrix, + //! 1: Calculate Cauchy stresses, + //! 2: Calculate Second Piola Kirchhoff stresses. + virtual bool constitutive(Matrix& C, SymmTensor& sigma, double& U, + const SymmTensor& eps, const Vec3& X, + char calcStress = 1) const; + +protected: + mutable Matrix F; //!< Deformation gradient + mutable Matrix dNdx; //!< Basis function gradients in current configuration + mutable Matrix CB; //!< Result of the matrix-matrix product C*B + +private: + char loadOp; //!< Load option + int mTYP; //!< Material type + int mVER; //!< Material version + double pmat[2]; //!< Material properties +}; + + +/*! + \brief Class representing the integrand of the elasticity energy norm. + \details This class reimplements the \a evalInt method to use the strain + energy density value returned by the nonlinear constitutive model. + It also forwards the mixed interface methods to the corresponding single-field + methods of the parent class. +*/ + +class ElasticityNormUL : public ElasticityNorm +{ +public: + //! \brief The only constructor initializes its data members. + //! \param[in] p The linear elasticity problem to evaluate norms for + ElasticityNormUL(NonlinearElasticityUL& p) : ElasticityNorm(p,0) {} + //! \brief Empty destructor. + virtual ~ElasticityNormUL() {} + + //! \brief Initializes current element for numerical integration (mixed). + //! \param[in] MNPC1 Nodal point correspondance for the displacement field + virtual bool initElement(const std::vector& MNPC1, + const std::vector&, size_t) + { + return this->ElasticityNorm::initElement(MNPC1); + } + //! \brief Initializes current element for boundary integration (mixed). + //! \param[in] MNPC1 Nodal point correspondance for the displacement field + virtual bool initElementBou(const std::vector& MNPC1, + const std::vector&, size_t) + { + return this->ElasticityNorm::initElement(MNPC1); + } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + //! \brief Evaluates the integrand at an interior point (mixed). + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values for the displacement field + //! \param[in] dN1dX Basis function gradients for the displacement field + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N1, const Vector&, + const Matrix& dN1dX, const Matrix&, + const Vec3& X) const + { + return this->evalInt(elmInt,detJW,N1,dN1dX,X); + } + + //! \brief Evaluates the integrand at a boundary point (mixed). + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values for the displacement field + //! \param[in] dN1dX Basis function gradients for the displacement field + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N1, const Vector&, + const Matrix& dN1dX, const Matrix&, + const Vec3& X, const Vec3& normal) const + { + return this->ElasticityNorm::evalBou(elmInt,detJW,N1,dN1dX,X,normal); + } +}; + +#endif diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityULMX.C b/Apps/FiniteDefElasticity/NonlinearElasticityULMX.C new file mode 100644 index 00000000..333a9d5a --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityULMX.C @@ -0,0 +1,595 @@ +// $Id: NonlinearElasticityULMX.C,v 1.7 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityULMX.C +//! +//! \date Dec 14 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity mixed problems. +//! +//============================================================================== + +#include "NonlinearElasticityULMX.h" +#include "ElmMats.h" +#include "ElmNorm.h" +#include "Utilities.h" +#include "Vec3Oper.h" + +#ifdef USE_FTNMAT +extern "C" { + //! \brief Project the constitutive matrix for mixed models. + void pcst3d_(const double* C, double* D, + const int& ipsw, const int& iwr); + //! \brief Compute finite deformation mixed tangent material matrix. + void mdma3d_(const double& P_bar, const double& P_mix, + const double* Sig, double* D, + const int& ipsw, const int& iwr); + //! \brief Accumulates material stiffness contributions for mixed 2D problems. + void acckmx2d_(const int& nEN, const double* dNdx, const double* dNdxBar, + const double* D, double* eKt); + //! \brief Accumulates material stiffness contributions for mixed 3D problems. + void acckmx3d_(const int& nEN, const double* dNdx, const double* dNdxBar, + const double* D, double* eKt); +} +#ifndef INT_DEBUG +#define INT_DEBUG 0 +#endif +#endif + + +NonlinearElasticityULMX::NonlinearElasticityULMX (unsigned short int n, + int mver, int pp) + : NonlinearElasticityUL(n, false, mver < 0 ? 0 : mver) +{ + p = pp; + iP = 0; + + // Both the current and previous solutions are needed + primsol.resize(2); +} + + +void NonlinearElasticityULMX::print (std::ostream& os) const +{ + this->NonlinearElasticityUL::print(os); + std::cout <<"NonlinearElasticityULMX: Mixed formulation," + <<" discontinuous pressure, p="<< p << std::endl; +} + + +void NonlinearElasticityULMX::setMode (SIM::SolutionMode mode) +{ + if (!myMats) return; + + myMats->rhsOnly = false; + Hh = eM = eKm = eKg = 0; + iS = eS = eV = 0; + + switch (mode) + { + case SIM::STATIC: + myMats->resize(2,3); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + Hh = &myMats->A[1]; + break; + + case SIM::DYNAMIC: + myMats->resize(3,3); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + eM = &myMats->A[1]; + Hh = &myMats->A[2]; + break; + + case SIM::RHS_ONLY: + if (myMats->A.size() < 2) + myMats->resize(2,3); + else + myMats->b.resize(3); + eKm = &myMats->A[0]; + eKg = &myMats->A[0]; + Hh = &myMats->A.back(); + myMats->rhsOnly = true; + break; + + case SIM::RECOVERY: + if (myMats->A.size() < 1) + myMats->resize(1,3); + else + myMats->b.resize(3); + Hh = &myMats->A.back(); + eV = &myMats->b[1]; + myMats->rhsOnly = true; + return; + + default: + this->Elasticity::setMode(mode); + return; + } + + // We always need the force+displacement vectors in nonlinear simulations + iS = &myMats->b[0]; + eS = &myMats->b[0]; + eV = &myMats->b[1]; + tracVal.clear(); +} + + +bool NonlinearElasticityULMX::initElement (const std::vector& MNPC, + const Vec3& Xc, size_t nPt) +{ +#if INT_DEBUG > 0 + std::cout <<"\n\n *** Entering NonlinearElasticityULMX::initElement"; + std::cout <<", Xc = "<< Xc << std::endl; +#endif + + iP = 0; + X0 = Xc; + myData.resize(nPt); + size_t nPm = utl::Pascal(p,nsd); + if (Hh) Hh->resize(nPm,nPm,true); + + // Extract the element level displacements of previous increment + int ierr = 0; + if (!primsol[1].empty()) + if (ierr = utl::gather(MNPC,nsd,primsol[1],myMats->b[2])) + std::cerr <<" *** NonlinearElasticityULMX::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + // The other element matrices are initialized by the parent class method + return this->NonlinearElasticityUL::initElement(MNPC) && ierr == 0; +} + + +bool NonlinearElasticityULMX::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + if (myMats->b.size() < 3) return false; + + ItgPtData& ptData = myData[iP++]; + unsigned short int i, j; + +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityUL::dNdX ="<< dNdX; +#endif + + // Evaluate the deformation gradient, Fp, at previous configuration + const_cast(this)->eV = &myMats->b[2]; + if (this->formDefGradient(dNdX)) + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + ptData.Fp(i,j) = F(i,j); + else + return false; + + // Evaluate the deformation gradient, F, at current configuration + const_cast(this)->eV = &myMats->b[1]; + if (this->formDefGradient(dNdX)) + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + ptData.F(i,j) = F(i,j); + else + return false; + + if (nsd == 2) // In 2D we always assume plane strain so set F(3,3)=1 + ptData.F(3,3) = ptData.Fp(3,3) = 1.0; + + // Invert the deformation gradient ==> Fi + double J = F.inverse(); + if (J == 0.0) return false; + + // Push-forward the basis function gradients to current configuration + ptData.dNdx.multiply(dNdX,F); // dNdx = dNdX * F^-1 + + ptData.X.assign(X); + ptData.detJW = detJW; + + // Evaluate the pressure modes (generalized coordinates) + Vec3 Xg = X-X0; + if (nsd == 3) + utl::Pascal(p,Xg.x,Xg.y,Xg.z,ptData.Phi); + else if (nsd == 2) + utl::Pascal(p,Xg.x,Xg.y,ptData.Phi); + else + return false; + + if (Hh) + // Integrate the Hh-matrix + Hh->outer_product(ptData.Phi,ptData.Phi*detJW,true); + + if (eM) + // Integrate the mass matrix + this->formMassMatrix(*eM,N,X,J*detJW); + + if (eS) + // Integrate the load vector due to gravitation and other body forces + this->formBodyForce(*eS,N,X,J*detJW); + + return this->getIntegralResult(elmInt); +} + + +bool NonlinearElasticityULMX::finalizeElement (LocalIntegral*& elmInt) +{ + if (!iS && !eKm && !eKg) return true; + if (!Hh) return false; + +#if INT_DEBUG > 0 + std::cout <<"\n\n *** Entering NonlinearElasticityULMX::finalizeElement\n"; + for (iP = 1; iP <= myData.size(); iP++) + { + const ItgPtData& pt = myData[iP-1]; + std::cout <<"\n X" << iP <<" = "<< pt.X; + std::cout <<"\n detJ"<< iP <<"W = "<< pt.detJW; + std::cout <<"\n F" << iP <<" =\n"<< pt.F; + std::cout <<" dNdx"<< iP <<" ="<< pt.dNdx; + std::cout <<" Phi" << iP <<" ="<< pt.Phi; + } + std::cout <<"\n H ="<< *Hh << std::endl; +#endif + + // 1. Eliminate the internal pressure DOFs by static condensation. + + size_t i, j; + size_t nPM = Hh->rows(); + size_t nEN = myData.front().dNdx.rows(); + size_t nGP = myData.size(); + + // Invert the H-matrix + if (!utl::invert(*Hh)) return false; + + const Matrix& Hi = *Hh; + + Matrix Theta(2,nGP), dNdxBar[nGP]; + + if (nPM == 1) + { + // Simplified calculation when the only pressure mode is Phi=1.0 + + double h1 = 0.0; + double h2 = 0.0; + dNdxBar->resize(nEN,nsd,true); + + for (iP = 0; iP < nGP; iP++) + { + const ItgPtData& pt = myData[iP]; + h1 += pt.F.det() * pt.detJW; + h2 += pt.Fp.det()* pt.detJW; + dNdxBar->add(pt.dNdx,pt.F.det()*pt.detJW); + } + dNdxBar->multiply(1.0/h1); + + // All gauss point values are identical + Theta(1,1) = h1 * Hi(1,1); + Theta(2,1) = h2 * Hi(1,1); + for (iP = 2; iP <= nGP; iP++) + { + Theta(1,iP) = Theta(1,1); + Theta(2,iP) = Theta(2,1); + dNdxBar[iP-1] = dNdxBar[0]; + } + } + else // higher order elements with nPM > 1 + { + Matrix Gg[nPM], Hg[nPM]; + Vector J1(nPM), J2(nPM), Hj1, Hj2; + for (i = 0; i < nPM; i++) + { + Gg[i].resize(nEN,nsd,true); + Hg[i].resize(nEN,nsd,true); + } + + // Integrate the Ji- and G-matrices + for (iP = 0; iP < nGP; iP++) + { + const ItgPtData& pt = myData[iP]; + for (i = 0; i < nPM; i++) + { + double h1 = pt.Phi[i] * pt.F.det() * pt.detJW; + double h2 = pt.Phi[i] * pt.Fp.det()* pt.detJW; + J1[i] += h1; + J2[i] += h2; + Gg[i].add(pt.dNdx,h1); + } + } + +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityULMX::J1 ="<< J1; + std::cout <<"NonlinearElasticityULMX::J2 ="<< J2; + for (i = 0; i < nPM; i++) + std::cout <<"NonlinearElasticityULMX::G"<< i+1 <<" ="<< Gg[i]; +#endif + + // Calculate Hji = Hi*Ji + if (!Hi.multiply(J1,Hj1)) return false; + if (!Hi.multiply(J2,Hj2)) return false; + + // Calculate Hg = Hi*Gg + for (i = 1; i <= nPM; i++) + for (j = 1; j <= nPM; j++) + Hg[i-1].add(Gg[j-1],Hi(i,j)); + +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityULMX::Hj1 ="<< Hj1; + std::cout <<"NonlinearElasticityULMX::Hj2 ="<< Hj2; + for (i = 0; i < nPM; i++) + std::cout <<"NonlinearElasticityULMX::Hg"<< i+1 <<" ="<< Hg[i]; +#endif + + for (iP = 0; iP < nGP; iP++) + { + i = iP+1; + ItgPtData& pt = myData[iP]; + + // Calculate Theta = Hj*Phi + Theta(1,i) = Hj1.dot(pt.Phi); + Theta(2,i) = Hj2.dot(pt.Phi); + + // Calculate dNdxBar = Hg*Phi * 1/Theta + dNdxBar[iP].resize(nEN,nsd,true); + for (j = 0; j < nPM; j++) + dNdxBar[iP].add(Hg[j],pt.Phi[j]/Theta(1,i)); + } + } + + // Modify the deformation gradients + Vector detF(nGP); + for (iP = 1; iP <= nGP; iP++) + { + ItgPtData& pt = myData[iP-1]; + detF(iP) = pt.F.det(); + pt.F *= pow(fabs(Theta(1,iP)/detF(iP)),1.0/3.0); + pt.Fp *= pow(fabs(Theta(2,iP)/pt.Fp.det()),1.0/3.0); + } + +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityULMX::detF ="<< detF; + std::cout <<"NonlinearElasticityULMX::Theta ="<< Theta; + for (iP = 0; iP < nGP; iP++) + std::cout <<"NonlinearElasticityULMX::dNdxBar"<< iP+1 <<" ="<< dNdxBar[iP]; +#endif + + // 2. Evaluate the constitutive relation and calculate mixed pressure state. + + Matrix D[nGP]; + Vector Sigm(nPM), Hsig; + std::vector Sig; + Sig.reserve(nGP); + F.resize(3,3); + + bool lHaveStress = false; + for (iP = 0; iP < nGP; iP++) + { +#if INT_DEBUG > 0 + std::cout <<"\n iGP = "<< iP+1 << std::endl; +#endif + const ItgPtData& pt = myData[iP]; + + // Evaluate the constitutive relation + double U = 0.0; + F.fill(pt.F.ptr()); + Sig.push_back(SymmTensor(3)); + if (!this->constitutive(Cmat,Sig.back(),U,Sig.back(),pt.X)) + return false; + +#ifdef USE_FTNMAT + // Project the constitutive matrix for the mixed model + D[iP].resize(7,7); + int ipsw = INT_DEBUG > 1 ? 9 : 0; + pcst3d_(Cmat.ptr(),D[iP].ptr(),ipsw,6); +#else + std::cerr <<" *** NonlinearElasticityULMX::finalizeElement: " + <<" Does not work when compiled without USE_FTNMAT"<< std::endl; + return false; +#endif + + if (U > 0.0) + { + // Integrate mean pressure + lHaveStress = true; + double Mpress = Sig.back().trace()*pt.detJW/3.0; + for (i = 1; i <= nPM; i++) + Sigm(i) += Mpress * pt.Phi(i); + } + } + + // Divide pressure by reference volume; Hsig = Hi*Sigm + if (lHaveStress) + if (!Hh->multiply(Sigm,Hsig)) return false; + + // 3. Evaluate tangent matrices. + + double Press = 0.0, Bpres = 0.0; + SymmTensor Sigma(nsd); + for (iP = 0; iP < nGP; iP++) + { + const ItgPtData& pt = myData[iP]; + double dFmix = Theta(1,iP+1); + if (lHaveStress) + { + Press = Hsig.dot(pt.Phi) * detF[iP]/dFmix; + Bpres = Sig[iP].trace()/3.0; + Sigma = Sig[iP] + (Press-Bpres); +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityULMX::Sigma"<< iP+1 <<"\n"<< Sigma; +#endif + if (eKg) + // Integrate the geometric stiffness matrix + this->formKG(*eKg,pt.dNdx,Sigma,dFmix*pt.detJW); + } + + if (eKm) + { +#ifdef USE_FTNMAT + int ipsw = INT_DEBUG > 1 ? 9 : 0; + mdma3d_(Bpres,Press,Sig[iP].ptr(),D[iP].ptr(),ipsw,6); + + // Integrate the material stiffness matrix + D[iP] *= dFmix*pt.detJW; + if (nsd == 2) + acckmx2d_(nEN,pt.dNdx.ptr(),dNdxBar[iP].ptr(),D[iP].ptr(),eKm->ptr()); + else + acckmx3d_(nEN,pt.dNdx.ptr(),dNdxBar[iP].ptr(),D[iP].ptr(),eKm->ptr()); +#endif + } + + if (iS && lHaveStress) + { + // Compute the small-deformation strain-displacement matrix B from dNdx + if (!this->formBmatrix(pt.dNdx)) return false; + + // Integrate the internal forces + Sigma *= -dFmix*pt.detJW; + if (!Bmat.multiply(Sigma,*iS,true,true)) // ES -= B^T*sigma + return false; + } + } + +#if INT_DEBUG > 0 + std::cout <<"\n *** Leaving NonlinearElasticityULMX::finalizeElement"; + if (eKm) std::cout <<", eKm ="<< *eKm; + if (iS) std::cout <<" iS ="<< *iS; + std::cout << std::endl; +#endif + return true; +} + + +NormBase* NonlinearElasticityULMX::getNormIntegrand (TensorFunc*) const +{ + return new ElasticityNormULMX(*const_cast(this)); +} + + +bool ElasticityNormULMX::initElement (const std::vector& MNPC, + const Vec3& Xc, size_t nPt) +{ + NonlinearElasticityULMX& elp = static_cast(problem); + + elp.iP = 0; + elp.X0 = Xc; + elp.myData.resize(nPt); + size_t nPm = utl::Pascal(elp.p,elp.nsd); + if (elp.Hh) elp.Hh->resize(nPm,nPm,true); + + // The other element matrices are initialized by the parent class method + return elp.NonlinearElasticityUL::initElement(MNPC); +} + + +bool ElasticityNormULMX::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + NonlinearElasticityULMX& elp = static_cast(problem); + return elp.evalInt(elmInt,detJW,N,dNdX,X); +} + + +bool ElasticityNormULMX::finalizeElement (LocalIntegral*& elmInt) +{ + NonlinearElasticityULMX& elp = static_cast(problem); + if (!elp.Hh) return false; + + size_t iP; +#if INT_DEBUG > 0 + std::cout <<"\n\n *** Entering ElasticityNormULMX::finalizeElement\n"; + for (iP = 1; iP <= elp.myData.size(); iP++) + { + const NonlinearElasticityULMX::ItgPtData& pt = elp.myData[iP-1]; + std::cout <<"\n X" << iP <<" = "<< pt.X; + std::cout <<"\n detJ"<< iP <<"W = "<< pt.detJW; + std::cout <<"\n F" << iP <<" =\n"<< pt.F; + std::cout <<" dNdx"<< iP <<" ="<< pt.dNdx; + std::cout <<" Phi" << iP <<" ="<< pt.Phi; + } + std::cout <<"\n H ="<< *(elp.Hh) << std::endl; +#endif + + // 1. Eliminate the internal pressure DOFs by static condensation. + + size_t nPM = elp.Hh->rows(); + size_t nGP = elp.myData.size(); + + // Invert the H-matrix + Matrix& Hi = *elp.Hh; + if (!utl::invert(Hi)) return false; + + Vector Theta(nGP); + + if (nPM == 1) + { + // Simplified calculation when the only pressure mode is Phi=1.0 + + double h1 = 0.0; + for (iP = 0; iP < nGP; iP++) + h1 += elp.myData[iP].F.det() * elp.myData[iP].detJW; + + // All gauss point values are identical + Theta.front() = h1 * Hi(1,1); + for (iP = 1; iP < nGP; iP++) + Theta[iP] = Theta.front(); + } + else // higher order elements with nPM > 1 + { + Vector Ji(nPM), Hj; + + // Integrate the Ji-matrix + for (iP = 0; iP < nGP; iP++) + { + const NonlinearElasticityULMX::ItgPtData& pt = elp.myData[iP]; + for (size_t i = 0; i < nPM; i++) + Ji[i] += pt.Phi[i] * pt.F.det() * pt.detJW; + } + + // Calculate Hj = Hi*Ji + if (!Hi.multiply(Ji,Hj)) return false; + +#if INT_DEBUG > 0 + std::cout <<"ElasticityNormULMX::Ji ="<< Ji; + std::cout <<"ElasticityNormULMX::Hj ="<< Hj; +#endif + + // Calculate Theta = Hj*Phi + for (iP = 0; iP < nGP; iP++) + Theta[iP] = Hj.dot(elp.myData[iP].Phi); + } + +#if INT_DEBUG > 0 + std::cout <<"ElasticityNormULMX::Theta ="<< Theta; +#endif + + // 2. Evaluate the constitutive relation and integrate energy. + + ElmNorm& pnorm = ElasticityNorm::getElmNormBuffer(elmInt); + + Matrix C; + SymmTensor Sig(3); + elp.F.resize(3,3); + + for (iP = 0; iP < nGP; iP++) + { +#if INT_DEBUG > 0 + std::cout <<"\n iGP = "<< iP+1 << std::endl; +#endif + NonlinearElasticityULMX::ItgPtData& pt = elp.myData[iP]; + + // Modify the deformation gradient + pt.F *= pow(fabs(Theta[iP]/pt.F.det()),1.0/3.0); + + // Compute the strain energy density + double U = 0.0; + elp.F.fill(pt.F.ptr()); + if (!elp.constitutive(C,Sig,U,Sig,pt.X,false)) + return false; + + // Integrate strain energy + pnorm[0] += U*pt.detJW; + } + + return true; +} diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityULMX.h b/Apps/FiniteDefElasticity/NonlinearElasticityULMX.h new file mode 100644 index 00000000..39f0ca0f --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityULMX.h @@ -0,0 +1,156 @@ +// $Id: NonlinearElasticityULMX.h,v 1.3 2011-01-08 16:29:10 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityULMX.h +//! +//! \date Dec 14 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity mixed problems. +//! +//============================================================================== + +#ifndef _NONLINEAR_ELASTICITY_UL_MX_H +#define _NONLINEAR_ELASTICITY_UL_MX_H + +#include "NonlinearElasticityUL.h" +#include "Tensor.h" + + +/*! + \brief Class representing the integrand of the nonlinear elasticity problem. + \details This class implements a mixed Updated Lagrangian formulation with + internal pressure and volumetric change modes. +*/ + +class NonlinearElasticityULMX : public NonlinearElasticityUL +{ + //! \brief A struct with integration point data needed by \a finalizeElement. + struct ItgPtData + { + Tensor F; //!< Deformation gradient at current step/iteration + Tensor Fp; //!< Deformation gradient at previous (converged) step + Matrix dNdx; //!< Basis function gradients at current configuration + Vector Phi; //!< Internal modes for the pressure/volumetric-change fields + Vec4 X; //!< Cartesian coordinates of current integration point + double detJW; //!< Jacobian determinant times integration point weight + //! \brief Default constructor. + ItgPtData() : F(3), Fp(3) { detJW = 0.0; } + }; + +public: + //! \brief The default constructor invokes the parent class constructor. + //! \param[in] n Number of spatial dimensions + //! \param[in] mver Material version parameter + //! \param[in] pp Polynomial order of the pressure/volumetric-change field + NonlinearElasticityULMX(unsigned short int n = 3, int mver = 0, int pp = 1); + //! \brief Empty destructor. + virtual ~NonlinearElasticityULMX() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + //! \param[in] Xc Cartesian coordinates of the element center + //! \param[in] nPt Number of integration points in this element + virtual bool initElement(const std::vector& MNPC, + const Vec3& Xc, size_t nPt); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This method mainly stores the integration point quantities + //! depending on the basis function values in the internal member \a myData. + //! The actual numerical integration of the tangent stiffness and internal + //! forces is then performed by the \a finalizeElement method. + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Finalizes the element matrices after the numerical integration. + //! \details This method is used to implement the multiple integration point + //! loops within an element, required by the mixed formulation with internal + //! modes. All data needed during the integration has been stored internally + //! in the \a myData member by the \a evalInt method. + //! \param elmInt The local integral object to receive the contributions + virtual bool finalizeElement(LocalIntegral*&); + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \note The Integrand object is allocated dynamically and has to be deleted + //! manually when leaving the scope of the pointer variable receiving the + //! returned pointer value. + virtual NormBase* getNormIntegrand(TensorFunc* = 0) const; + + //! \brief Returns which integrand to be used. + virtual int getIntegrandType() const { return 4; } + +private: + int p; //!< Polynomial order of the internal pressure field + Vec3 X0; //!< Cartesian coordinates of the element center + + Matrix* Hh; //!< Pointer to element Hh-matrix associated with pressure modes + + mutable size_t iP; //!< Local integration point counter + mutable std::vector myData; //!< Local integration point data + + friend class ElasticityNormULMX; +}; + + +/*! + \brief Class representing the integrand of the mixed elasticity energy norm. +*/ + +class ElasticityNormULMX : public ElasticityNormUL +{ +public: + //! \brief The only constructor initializes its data members. + //! \param[in] p The linear elasticity problem to evaluate norms for + ElasticityNormULMX(NonlinearElasticityULMX& p) : ElasticityNormUL(p) {} + //! \brief Empty destructor. + virtual ~ElasticityNormULMX() {} + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + //! \param[in] Xc Cartesian coordinates of the element center + //! \param[in] nPt Number of integration points in this element + virtual bool initElement(const std::vector& MNPC, + const Vec3& Xc, size_t nPt); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This method just forwards to the \a evalInt method of the + //! corresponding NonlinearElasticityULMX object, for which this class is + //! declared as friend such that it can access the data members. The actual + //! norm integration us then performed by the \a finalizeElement method. + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Finalizes the element norms after the numerical integration. + //! \details This method is used to implement the multiple integration point + //! loops within an element required by the mixed formulation. It performs + //! a subset of the tasks done by NonlinearElasticityULMX::finalizeElement. + //! \param elmInt The local integral object to receive the contributions + virtual bool finalizeElement(LocalIntegral*& elmInt); + + //! \brief Returns which integrand to be used. + virtual int getIntegrandType() const { return 4; } +}; + +#endif diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityULMixed.C b/Apps/FiniteDefElasticity/NonlinearElasticityULMixed.C new file mode 100644 index 00000000..a04d712e --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityULMixed.C @@ -0,0 +1,435 @@ +// $Id: NonlinearElasticityULMixed.C,v 1.4 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityULMixed.C +//! +//! \date Dec 22 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for nonlinear elasticity mixed problems. +//! +//============================================================================== + +#include "NonlinearElasticityULMixed.h" +#include "ElmMats.h" +#include "Utilities.h" +#include "Tensor.h" + +#ifdef USE_FTNMAT +extern "C" { + //! \brief Project the constitutive matrix for mixed models. + void pcst3d_(const double* C, double* D, + const int& ipsw, const int& iwr); + //! \brief Compute finite deformation mixed tangent material matrix. + void mdma3d_(const double& P_bar, const double& P_mix, + const double* Sig, double* D, + const int& ipsw, const int& iwr); + //! \brief Accumulates material stiffness contributions for 2D problems. + void acckm2d_(const int& nEN, const double* dNdx, + const double* D, double* eKt); + //! \brief Accumulates material stiffness contributions for 3D problems. + void acckm3d_(const int& nEN, const double* dNdx, + const double* D, double* eKt); +} +#ifndef INT_DEBUG +#define INT_DEBUG 0 +#endif +#endif + + +//! \brief Enum for element-level solution vectors +enum DisplacementVectors { + U = 0, // displacement + T = 1, // volumetric change (theta) + P = 2 // pressure +}; + +//! \brief Enum for element-level right-hand-side vectors +enum ResidualVectors { + Ru = 3, + Rt = 4, + Rp = 5, + Rres = 6, + NVEC = 7 +}; + +//! \brief Enum for element-level left-hand-side matrices +enum TangentMatrices { + Kuu = 0, + Kut = 1, + Kup = 2, + Ktt = 3, + Ktp = 4, + Ktan = 5, + NMAT = 6 +}; + + +NonlinearElasticityULMixed::MixedElmMats::MixedElmMats () +{ + this->resize(NMAT,NVEC); +} + + +/*! + Tangent matrix for the mixed formulation: + \f[{\bf K}_{\rm T} = \left[\begin{array}{ccc} + {\bf K}_{uu} & {\bf K}_{u\theta} & {\bf K}_{up} \\ + {\bf K}_{u\theta}^T & {\bf K}_{\theta\theta} & {\bf K}_{\theta p} \\ + {\bf K}_{up}^T & {\bf K}_{\theta p}^T & {\bf 0} + \end{array}\right]\f] + where + -# \f${\bf K}_{uu}\f$ : + tangent stiffness matrix of the displacement formulation + -# \f${\bf K}_{\theta\theta}\f$ : + tangent stiffness matrix related to volumetric change + -# \f${\bf K}_{u\theta}\f$ : + coupling stiffness between displacements and volumetric change + -# \f${\bf K}_{up}\f$ : + coupling stiffness between displacements and pressure + -# \f${\bf K}_{\theta p}\f$ : + coupling stiffness between volumetric change and pressure +*/ + +const Matrix& NonlinearElasticityULMixed::MixedElmMats::getNewtonMatrix () const +{ +#if INT_DEBUG > 0 + std::cout <<"\nNonlinearElasticityULMixed::MixedElmMats::getNewtonMatrix:" + <<"\nKuu ="<< A[Kuu] + <<"\nKut ="<< A[Kut] + <<"\nKup ="<< A[Kup] + <<"\nKtt ="<< A[Ktt] + <<"\nKtp ="<< A[Ktp]; +#endif + + Matrix& N = const_cast(A.back()); + + size_t i, j; + size_t n = A[Kuu].rows(); + size_t m = A[Ktt].rows(); + + for (i = 1; i <= n; i++) + { + for (j = 1; j <= n; j++) + N(i,j) = A[Kuu](i,j); + for (j = 1; j <= m; j++) + { + size_t k = n+2*j-1; + size_t l = n+2*j; + N(i,k) = A[Kut](i,j); + N(k,i) = A[Kut](i,j); + N(i,l) = A[Kup](i,j); + N(l,i) = A[Kup](i,j); + } + } + + for (i = 1; i <= m; i++) + for (j = 1; j <= m; j++) + { + size_t ki = n+2*i-1; + size_t kj = n+2*j-1; + size_t lj = n+2*j; + N(ki,kj) = A[Ktt](i,j); + N(ki,lj) = A[Ktp](i,j); + N(lj,ki) = A[Ktp](i,j); + } + +#if INT_DEBUG > 0 + std::cout <<"\nNewton matrix ="<< A.back(); +#endif + return A.back(); +} + + +/*! + Right-hand-side vector for the mixed formulation: + \f[{\bf R} = \left\{\begin{array}{c} + {\bf R}_u \\ {\bf R}_\theta \\ {\bf R}_p + \end{array}\right\}\f] + where + -# \f${\bf R}_{u}\f$ : + residual forces of the displacement DOFs + -# \f${\bf R}_\theta\f$ : + residual forces of the volumetric change DOFs + -# \f${\bf R}_p\f$ : + residual forces of the pressure DOFs +*/ + +const Vector& NonlinearElasticityULMixed::MixedElmMats::getRHSVector () const +{ +#if INT_DEBUG > 0 + std::cout <<"\nNonlinearElasticityULMixed::MixedElmMats::getRHSVector:" + <<"\nRu ="<< b[Ru]; + if (withLHS) + std::cout <<"\nRt ="<< b[Rt] <<"\nRp ="<< b[Rp]; +#endif + Vector& R = const_cast(b.back()); + + size_t i; + size_t n = b[Ru].size(); + size_t m = b[Rt].size(); + + for (i = 1; i <= n; i++) + R(i) = b[Ru](i); + + if (withLHS) + for (i = 1; i <= m; i++) + { + R(n+2*i-1) = b[Rt](i); + R(n+2*i ) = b[Rp](i); + } + else + std::fill(R.begin()+n,R.end(),0.0); + +#if INT_DEBUG > 0 + std::cout <<"\nRHS ="<< b.back(); +#endif + return b.back(); +} + + +NonlinearElasticityULMixed::NonlinearElasticityULMixed (unsigned short int n, + int mver) + : NonlinearElasticityUL(n,false,mver) +{ + Fbar.resize(3,3); + Dmat.resize(7,7); + + if (myMats) delete myMats; + myMats = new MixedElmMats(); + + eKm = &myMats->A[Kuu]; + eKg = &myMats->A[Kuu]; + + iS = &myMats->b[Ru]; + eS = &myMats->b[Ru]; + eV = &myMats->b[U]; +} + + +void NonlinearElasticityULMixed::print (std::ostream& os) const +{ + this->NonlinearElasticityUL::print(os); + + std::cout <<"NonlinearElasticityULMixed: " + <<"Continuous volumetric change and pressure fields"<< std::endl; +} + + +void NonlinearElasticityULMixed::setMode (SIM::SolutionMode mode) +{ + switch (mode) + { + case SIM::INIT: + case SIM::STATIC: + tracVal.clear(); + myMats->rhsOnly = false; + break; + + case SIM::RHS_ONLY: + tracVal.clear(); + case SIM::RECOVERY: + myMats->rhsOnly = true; + break; + + default: + std::cerr <<"\n *** NonlinearElasticityULMixed::setMode: Invalid mode " + << mode << std::endl; + } +} + + +bool NonlinearElasticityULMixed::initElement (const std::vector& MNPC1, + const std::vector& MNPC2, + size_t n1) +{ + const size_t nen1 = MNPC1.size(); + const size_t nen2 = MNPC2.size(); + const size_t nedof = nsd*nen1 + 2*nen2; + + myMats->A[Kut].resize(nsd*nen1,nen2,true); + myMats->A[Kup].resize(nsd*nen1,nen2,true); + myMats->A[Ktt].resize(nen2,nen2,true); + myMats->A[Ktp].resize(nen2,nen2,true); + myMats->A.back().resize(nedof,nedof,true); + + myMats->b[Rt].resize(nen2,true); + myMats->b[Rp].resize(nen2,true); + myMats->b.back().resize(nedof,true); + + // Extract the element level volumetric change and pressure vectors + int ierr = 0; + if (!primsol.front().empty()) + if (ierr = (utl::gather(MNPC2,0,2,primsol.front(),myMats->b[T],nsd*n1,n1) + + utl::gather(MNPC2,1,2,primsol.front(),myMats->b[P],nsd*n1,n1))) + std::cerr <<" *** NonlinearElasticityULMixed::initElement: Detected " + << ierr/2 <<" node numbers out of range."<< std::endl; + + // The other element matrices are initialized by the parent class method + return this->NonlinearElasticityUL::initElement(MNPC1) && ierr == 0; +} + + +bool NonlinearElasticityULMixed::initElementBou (const std::vector& MNPC1, + const std::vector& MNPC2, + size_t) +{ + const size_t nen1 = MNPC1.size(); + const size_t nen2 = MNPC2.size(); + const size_t nedof = nsd*nen1 + 2*nen2; + + myMats->b[Ru].resize(nsd*nen1,true); + myMats->b.back().resize(nedof,true); + + // Extract the element level displacement vector + int ierr = 0; + if (!primsol.front().empty()) + if (ierr = utl::gather(MNPC1,nsd,primsol.front(),myMats->b[U])) + std::cerr <<" *** NonlinearElasticityULMixed::initElementBou: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats->withLHS = false; + return ierr == 0; +} + + +bool NonlinearElasticityULMixed::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N1, + const Vector& N2, + const Matrix& dNdX1, + const Matrix& dNdX2, + const Vec3& X) const +{ + // Evaluate the deformation gradient, F, and the Green-Lagrange strains, E + SymmTensor E(nsd); + if (!this->kinematics(dNdX1,E)) + return false; + + bool lHaveStrains = !E.isZero(); + + // Evaluate the volumetric change and pressure fields + double Theta = N2.dot(myMats->b[T]) + 1.0; + double Press = N2.dot(myMats->b[P]); +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityULMixed::b_theta ="<< myMats->b[T]; + std::cout <<"NonlinearElasticityULMixed::b_p ="<< myMats->b[P]; + std::cout <<"Theta = "<< Theta <<" Press = "<< Press << std::endl; +#endif + + // Compute the mixed integration point volume + double dVol = Theta*detJW; + + // Compute the mixed model deformation gradient, F_bar + double r1 = pow(fabs(Theta/F.det()),1.0/3.0); + + unsigned short int i, j, k; + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + Fbar(i,j) = r1*F(i,j); + + if (nsd == 2) // In 2D we always assume plane strain so set F(3,3)=1 + Fbar(3,3) = r1; + else if (nsd != 3) + return false; + + // Invert the deformation gradient ==> Fi + double J = F.inverse(); + if (J == 0.0) return false; + + // Push-forward the basis function gradients to current configuration + dNdx.multiply(dNdX1,F); // dNdx = dNdX * F^-1 +#if INT_DEBUG > 0 + std::cout <<"NonlinearElasticityULMixed::dNdX ="<< dNdX1; + std::cout <<"NonlinearElasticityULMixed::dNdx ="<< dNdx; + std::cout <<"NonlinearElasticityULMixed::Fbar ="<< Fbar; +#endif + + if (eM) + // Integrate the mass matrix + this->formMassMatrix(*eM,N1,X,J*detJW); + + if (eS) + // Integrate the load vector due to gravitation and other body forces + this->formBodyForce(*eS,N1,X,J*detJW); + + // Evaluate the constitutive relation + F = Fbar; + SymmTensor Eps(3), Sig(3), Sigma(nsd); + double U, Bpres = 0.0, Mpres = 0.0; + if (!this->constitutive(Cmat,Sig,U,Eps=E,X,lHaveStrains)) + return false; + +#ifdef USE_FTNMAT + // Project the constitutive matrix for the mixed model + int ipsw = INT_DEBUG > 1 ? 9 : 0; + pcst3d_(Cmat.ptr(),Dmat.ptr(),ipsw,6); +#if INT_DEBUG == 1 + std::cout <<"NonlinearElasticityULMixed::Dmat ="<< Dmat; +#endif +#else + return false; +#endif + + if (lHaveStrains) + { + // Compute mixed strees + Bpres = Sig.trace()/3.0; + Mpres = Press * J/Theta; + Sigma = Sig + (Mpres-Bpres); + + // Integrate the geometric stiffness matrix + this->formKG(*eKg,dNdx,Sigma,dVol); + } + +#ifdef USE_FTNMAT + mdma3d_(Bpres,Mpres,Sig.ptr(),Dmat.ptr(),ipsw,6); + + // Integrate the material stiffness matrix + Dmat *= dVol; + if (nsd == 2) + acckm2d_(N1.size(),dNdx.ptr(),Dmat.ptr(),eKm->ptr()); + else + acckm3d_(N1.size(),dNdx.ptr(),Dmat.ptr(),eKm->ptr()); +#endif + + // Integrate the volumetric change and pressure tangent terms + size_t a, b; + for (a = 1; a <= N1.size(); a++) + for (b = 1; b <= N2.size(); b++) + for (i = 1; i <= nsd; i++) + { + myMats->A[Kut](nsd*(a-1)+i,b) += dNdx(a,i)*N2(b) * Dmat(i,7); + if (nsd == 2) + myMats->A[Kut](nsd*(a-1)+i,b) += dNdx(a,3-i)*N2(b) * Dmat(4,7); + else if (i < 3) + { + j = i + 1; + k = j%3 + 1; + myMats->A[Kut](nsd*(a-1)+i,b) += dNdx(a,3-i)*N2(b) * Dmat(4,7); + myMats->A[Kut](nsd*(a-1)+j,b) += dNdx(a,5-j)*N2(b) * Dmat(5,7); + myMats->A[Kut](nsd*(a-1)+k,b) += dNdx(a,4-k)*N2(b) * Dmat(6,7); + } + myMats->A[Kup](nsd*(a-1)+i,b) += dNdx(a,i)*N2(b) * dVol; + } + + myMats->A[Ktt].outer_product(N2,N2*Dmat(7,7),true); // Ktt += N2*N2^T * D77 + myMats->A[Ktp].outer_product(N2,N2*(-detJW),true); // Ktp -= N2*N2^T * detJW + + if (lHaveStrains) + { + // Compute the small-deformation strain-displacement matrix B from dNdx + if (!this->Elasticity::formBmatrix(dNdx)) return false; + + // Integrate the internal forces + Sigma *= -dVol; + if (!Bmat.multiply(Sigma,myMats->b[Ru],true,true)) // Ru -= B^T * sigma + return false; + + // Integrate the volumetric change and pressure forces + myMats->b[Rt].add(N2,(Press - Bpres)*detJW); // Rt += N2 * (p-pBar) + myMats->b[Rp].add(N2,(Theta - J)*detJW); // Rp += N2 * (Theta-J) + } + + return this->getIntegralResult(elmInt); +} diff --git a/Apps/FiniteDefElasticity/NonlinearElasticityULMixed.h b/Apps/FiniteDefElasticity/NonlinearElasticityULMixed.h new file mode 100644 index 00000000..524e5ed1 --- /dev/null +++ b/Apps/FiniteDefElasticity/NonlinearElasticityULMixed.h @@ -0,0 +1,108 @@ +// $Id: NonlinearElasticityULMixed.h,v 1.2 2010-12-30 15:25:14 kmo Exp $ +//============================================================================== +//! +//! \file NonlinearElasticityULMixed.h +//! +//! \date Dec 22 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for mixed nonlinear elasticity problems. +//! +//============================================================================== + +#ifndef _NONLINEAR_ELASTICITY_UL_MIXED_H +#define _NONLINEAR_ELASTICITY_UL_MIXED_H + +#include "NonlinearElasticityUL.h" +#include "ElmMats.h" + + +/*! + \brief Class representing the integrand of the nonlinear elasticity problem. + \details This class implements a mixed Updated Lagrangian formulation with + continuous pressure and volumetric change fields. +*/ + +class NonlinearElasticityULMixed : public NonlinearElasticityUL +{ + //! \brief Class representing the element matrices of the mixed formulation. + class MixedElmMats : public ElmMats + { + public: + //! \brief Default constructor. + MixedElmMats(); + //! \brief Empty destructor. + virtual ~MixedElmMats() {} + //! \brief Returns the element-level Newton matrix. + virtual const Matrix& getNewtonMatrix() const; + //! \brief Returns the element-level right-hand-side vector + //! associated with the Newton matrix. + virtual const Vector& getRHSVector() const; + }; + +public: + //! \brief The default constructor invokes the parent class constructor. + //! \param[in] n Number of spatial dimensions + //! \param[in] mver Material version parameter + NonlinearElasticityULMixed(unsigned short int n = 3, int mver = 0); + //! \brief Empty destructor. + virtual ~NonlinearElasticityULMixed() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + //! \param[in] n1 Number of nodes in basis 1 on this patch + virtual bool initElement(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1); + //! \brief Initializes current element for numerical boundary integration. + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + virtual bool initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, size_t); + + //! \brief Evaluates the mixed field problem integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field 1 (displacements) + //! \param[in] N2 Basis function values, field 2 (pressure and volume change) + //! \param[in] dN1dX Basis function gradients, field 1 + //! \param[in] dN2dX Basis function gradients, field 2 + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field 1 + //! \param[in] dN1dX Basis function gradients, field 1 + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details The boundary integral is the same as that of the parent class. + //! It does not depend on the pressure and volumetric change fields. + //! Thus, this call is forwarded to the single-field parent class method. + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N1, const Vector&, + const Matrix& dN1dX, const Matrix&, + const Vec3& X, const Vec3& normal) const + { + return this->NonlinearElasticityUL::evalBou(elmInt,detJW,N1,dN1dX,X,normal); + } + +protected: + mutable Matrix Fbar; //!< Mixed model deformation gradient + mutable Matrix Dmat; //!< Projected mixed constitutive matrix +}; + +#endif diff --git a/Apps/FiniteDefElasticity/SIMFiniteDefEl.C b/Apps/FiniteDefElasticity/SIMFiniteDefEl.C new file mode 100644 index 00000000..4bcf57b4 --- /dev/null +++ b/Apps/FiniteDefElasticity/SIMFiniteDefEl.C @@ -0,0 +1,89 @@ +// $Id: SIMFiniteDefEl.C,v 1.3 2010-12-29 18:51:53 kmo Exp $ +//============================================================================== +//! +//! \file SIMFiniteDefEl.C +//! +//! \date Dec 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution drivers for NURBS-based finite deformation analysis. +//! +//============================================================================== + +#include "SIMFiniteDefEl.h" +#include "NonlinearElasticityULMixed.h" +#include "NonlinearElasticityULMX.h" +#include "NeoHookeElasticity.h" + + +SIMFiniteDefEl2D::SIMFiniteDefEl2D (int form, bool planeStress, + const std::vector& options) + : SIMLinEl2D(SIM::NONE) +{ + int mVER = options.size() > 0 ? options[0] : -1; // material version + int pOrd = options.size() > 1 ? options[1] : 0; // pressure field order + + switch (form) + { + case SIM::MIXED_QnQn1: + nf[1] = 2; // continuous volumetric change and pressure fields + myProblem = new NonlinearElasticityULMixed(2,mVER); + break; + case SIM::MIXED_QnPn1: + // Local discontinuous volumetric change and pressure fields + myProblem = new NonlinearElasticityULMX(2,mVER,pOrd); + break; + case SIM::UPDATED_LAGRANGE: + myProblem = new NonlinearElasticityUL(2,planeStress,mVER); + break; + case SIM::TOTAL_LAGRANGE: + myProblem = new NonlinearElasticityTL(2,planeStress); + break; + case SIM::NONLINEAR: // Old tensor-based TL-formulation + myProblem = new NonlinearElasticity(2,planeStress); + break; + default: + std::cerr <<" *** SIMFiniteDefEl2D: Unknown problem formulation " + << form << std::endl; + } +} + + +SIMFiniteDefEl3D::SIMFiniteDefEl3D (bool checkRHS, int form, + const std::vector& options) + : SIMLinEl3D(checkRHS,SIM::NONE) +{ + int mVER = options.size() > 0 ? options[0] : -1; // material version + int pOrd = options.size() > 1 ? options[1] : 0; // pressure field order + + switch (form) + { + case SIM::MIXED_QnQn1: + nf[1] = 2; // continuous volumetric change and pressure fields + myProblem = new NonlinearElasticityULMixed(3,mVER); + break; + case SIM::MIXED_QnPn1: + // Local discontinuous volumetric change and pressure fields + myProblem = new NonlinearElasticityULMX(3,mVER,pOrd); + break; + case SIM::UPDATED_LAGRANGE: + myProblem = new NonlinearElasticityUL(3,false,mVER); + break; + case SIM::TOTAL_LAGRANGE: + myProblem = new NonlinearElasticityTL(); + break; + case SIM::NONLINEAR: // Old tensor-based TL-formulation + myProblem = new NonlinearElasticity(); + break; + case SIM::NEOHOOKE: // NeoHookean TL-formulation (not working) + myProblem = new NeoHookeElasticity(); + break; + case SIM::NEOHOOKE_IV: // NeoHookean isochoric/volumetric TL-formulation + myProblem = new NeoHookeElasticityIV(); + break; + default: + std::cerr <<" *** SIMFiniteDefEl3D: Unknown problem formulation " + << form << std::endl; + } +} diff --git a/Apps/FiniteDefElasticity/SIMFiniteDefEl.h b/Apps/FiniteDefElasticity/SIMFiniteDefEl.h new file mode 100644 index 00000000..a0aa35ff --- /dev/null +++ b/Apps/FiniteDefElasticity/SIMFiniteDefEl.h @@ -0,0 +1,71 @@ +// $Id: SIMFiniteDefEl.h,v 1.2 2010-12-29 18:51:53 kmo Exp $ +//============================================================================== +//! +//! \file SIMFiniteDefEl.h +//! +//! \date Dec 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution drivers for NURBS-based finite deformation analysis. +//! +//============================================================================== + +#ifndef _SIM_FINITE_DEF_EL_H +#define _SIM_FINITE_DEF_EL_H + +#include "SIMLinEl2D.h" +#include "SIMLinEl3D.h" + + +namespace SIM +{ + //! \brief Enum defining various finite deformation formulations. + enum NlFormulation + { + TOTAL_LAGRANGE = 3, + NEOHOOKE = 4, + NEOHOOKE_IV = 5, + UPDATED_LAGRANGE = 6, + MIXED_QnPn1 = 7, + MIXED_QnQn1 = 8 + }; +}; + + +/*! + \brief Driver class for 2D isogeometric finite deformation analysis. +*/ + +class SIMFiniteDefEl2D : public SIMLinEl2D +{ +public: + //! \brief Default constructor. + //! \param[in] form Problem formulation option + //! \param[in] planeStress Plane stress/plane strain option + //! \param[in] options Additional solution formulation options + SIMFiniteDefEl2D(int form = SIM::TOTAL_LAGRANGE, bool planeStress = false, + const std::vector& options = std::vector()); + //! \brief Empty destructor. + virtual ~SIMFiniteDefEl2D() {} +}; + + +/*! + \brief Driver class for 3D isogeometric finite deformation analysis. +*/ + +class SIMFiniteDefEl3D : public SIMLinEl3D +{ +public: + //! \brief Default constructor. + //! \param[in] checkRHS If \e true, ensure the model is in a right-hand system + //! \param[in] form Problem formulation option + //! \param[in] options Additional solution formulation options + SIMFiniteDefEl3D(bool checkRHS = false, int form = SIM::TOTAL_LAGRANGE, + const std::vector& options = std::vector()); + //! \brief Empty destructor. + virtual ~SIMFiniteDefEl3D() {} +}; + +#endif diff --git a/Apps/FiniteDefElasticity/accKM.f b/Apps/FiniteDefElasticity/accKM.f new file mode 100644 index 00000000..9a5c7325 --- /dev/null +++ b/Apps/FiniteDefElasticity/accKM.f @@ -0,0 +1,135 @@ +C $Id: accKM.f,v 1.1 2010-12-29 18:51:53 kmo Exp $ +C======================================================================= +C! +C! \file accKM.f +C! +C! \date Dec 18 2010 +C! +C! \author Knut Morten Okstad / SINTEF +C! +C! \brief Material stiffness accumulation routines. +C! +C======================================================================= +C + subroutine accKM2D (NENOD,Shpf,D,EKt) +C + implicit none +C + integer NENOD + double precision Shpf(NENOD,2), D(7,7) + double precision EKt(2,NENOD,2,NENOD) +C + integer a, b, i, j + double precision BBC(2,4) +C + do a = 1, NENOD +C + do j = 1, 4 + BBC(1,j) = Shpf(a,1)*D(1,j) + & + Shpf(a,2)*D(4,j) + BBC(2,j) = Shpf(a,1)*D(4,j) + & + Shpf(a,2)*D(2,j) + end do +C + do b = 1, NENOD +C + EKt(1,a,1,b) = EKt(1,a,1,b) + & + BBC(1,1) * Shpf(b,1) + & + BBC(1,4) * Shpf(b,2) +C + EKt(1,a,2,b) = EKt(1,a,2,b) + & + BBC(1,4) * Shpf(b,1) + & + BBC(1,2) * Shpf(b,2) +C + EKt(2,a,1,b) = EKt(2,a,1,b) + & + BBC(2,1) * Shpf(b,1) + & + BBC(2,4) * Shpf(b,2) +C + EKt(2,a,2,b) = EKt(2,a,2,b) + & + BBC(2,4) * Shpf(b,1) + & + BBC(2,2) * Shpf(b,2) +C + end do +C + end do +C + end +C +C + subroutine accKM3D (NENOD,Shpf,D,EKt) +C + implicit none +C + integer NENOD + double precision Shpf(NENOD,3), D(7,7) + double precision EKt(3,NENOD,3,NENOD) +C + integer a, b, i, j + double precision BBC(3,6) +C + do a = 1, NENOD +C + do j = 1, 6 + BBC(1,j) = Shpf(a,1)*D(1,j) + & + Shpf(a,2)*D(4,j) + & + Shpf(a,3)*D(6,j) + BBC(2,j) = Shpf(a,1)*D(4,j) + & + Shpf(a,2)*D(2,j) + & + Shpf(a,3)*D(5,j) + BBC(3,j) = Shpf(a,1)*D(6,j) + & + Shpf(a,2)*D(5,j) + & + Shpf(a,3)*D(3,j) + end do +C + do b = 1, NENOD +C + EKt(1,a,1,b) = EKt(1,a,1,b) + & + BBC(1,1) * Shpf(b,1) + & + BBC(1,4) * Shpf(b,2) + & + BBC(1,6) * Shpf(b,3) +C + EKt(1,a,2,b) = EKt(1,a,2,b) + & + BBC(1,4) * Shpf(b,1) + & + BBC(1,2) * Shpf(b,2) + & + BBC(1,5) * Shpf(b,3) +C + EKt(1,a,3,b) = EKt(1,a,3,b) + & + BBC(1,6) * Shpf(b,1) + & + BBC(1,5) * Shpf(b,2) + & + BBC(1,3) * Shpf(b,3) +C + EKt(2,a,1,b) = EKt(2,a,1,b) + & + BBC(2,1) * Shpf(b,1) + & + BBC(2,4) * Shpf(b,2) + & + BBC(2,6) * Shpf(b,3) +C + EKt(2,a,2,b) = EKt(2,a,2,b) + & + BBC(2,4) * Shpf(b,1) + & + BBC(2,2) * Shpf(b,2) + & + BBC(2,5) * Shpf(b,3) +C + EKt(2,a,3,b) = EKt(2,a,3,b) + & + BBC(2,6) * Shpf(b,1) + & + BBC(2,5) * Shpf(b,2) + & + BBC(2,3) * Shpf(b,3) +C + EKt(3,a,1,b) = EKt(3,a,1,b) + & + BBC(3,1) * Shpf(b,1) + & + BBC(3,4) * Shpf(b,2) + & + BBC(3,6) * Shpf(b,3) +C + EKt(3,a,2,b) = EKt(3,a,2,b) + & + BBC(3,4) * Shpf(b,1) + & + BBC(3,2) * Shpf(b,2) + & + BBC(3,5) * Shpf(b,3) +C + EKt(3,a,3,b) = EKt(3,a,3,b) + & + BBC(3,6) * Shpf(b,1) + & + BBC(3,5) * Shpf(b,2) + & + BBC(3,3) * Shpf(b,3) +C + end do +C + end do +C + end diff --git a/Apps/FiniteDefElasticity/accKMx.f b/Apps/FiniteDefElasticity/accKMx.f new file mode 100644 index 00000000..296dd405 --- /dev/null +++ b/Apps/FiniteDefElasticity/accKMx.f @@ -0,0 +1,153 @@ +C $Id: accKMx.f,v 1.2 2010-12-20 17:01:21 kmo Exp $ +C======================================================================= +C! +C! \file accKMx.f +C! +C! \date Dec 18 2010 +C! +C! \author Knut Morten Okstad / SINTEF +C! +C! \brief Material stiffness accumulation routines for B-bar methods. +C! +C======================================================================= +C + subroutine accKMx2D (NENOD,Shpf,Shpbar,D,EKt) +C + implicit none +C + integer NENOD + double precision Shpf(NENOD,2), Shpbar(NENOD,2), D(7,7) + double precision EKt(2,NENOD,2,NENOD) +C + integer a, b, i, j + double precision BBC(2,7) +C + do a = 1, NENOD +C + do j = 1, 7 + BBC(1,j) = Shpf(a,1)*D(1,j) + & + Shpf(a,2)*D(4,j) + & + Shpbar(a,1)*D(7,j) + BBC(2,j) = Shpf(a,1)*D(4,j) + & + Shpf(a,2)*D(2,j) + & + Shpbar(a,2)*D(7,j) + end do +C + do b = 1, NENOD +C + EKt(1,a,1,b) = EKt(1,a,1,b) + & + BBC(1,1) * Shpf(b,1) + & + BBC(1,4) * Shpf(b,2) + & + BBC(1,7) * Shpbar(b,1) +C + EKt(1,a,2,b) = EKt(1,a,2,b) + & + BBC(1,4) * Shpf(b,1) + & + BBC(1,2) * Shpf(b,2) + & + BBC(1,7) * Shpbar(b,2) +C + EKt(2,a,1,b) = EKt(2,a,1,b) + & + BBC(2,1) * Shpf(b,1) + & + BBC(2,4) * Shpf(b,2) + & + BBC(2,7) * Shpbar(b,1) +C + EKt(2,a,2,b) = EKt(2,a,2,b) + & + BBC(2,4) * Shpf(b,1) + & + BBC(2,2) * Shpf(b,2) + & + BBC(2,7) * Shpbar(b,2) +C + end do +C + end do +C + end +C +C + subroutine accKMx3D (NENOD,Shpf,Shpbar,D,EKt) +C + implicit none +C + integer NENOD + double precision Shpf(NENOD,3), Shpbar(NENOD,3), D(7,7) + double precision EKt(3,NENOD,3,NENOD) +C + integer a, b, i, j + double precision BBC(3,7) +C + do a = 1, NENOD +C + do j = 1, 7 + BBC(1,j) = Shpf(a,1)*D(1,j) + & + Shpf(a,2)*D(4,j) + & + Shpf(a,3)*D(6,j) + & + Shpbar(a,1)*D(7,j) + BBC(2,j) = Shpf(a,1)*D(4,j) + & + Shpf(a,2)*D(2,j) + & + Shpf(a,3)*D(5,j) + & + Shpbar(a,2)*D(7,j) + BBC(3,j) = Shpf(a,1)*D(6,j) + & + Shpf(a,2)*D(5,j) + & + Shpf(a,3)*D(3,j) + & + Shpbar(a,3)*D(7,j) + end do +C + do b = 1, NENOD +C + EKt(1,a,1,b) = EKt(1,a,1,b) + & + BBC(1,1) * Shpf(b,1) + & + BBC(1,4) * Shpf(b,2) + & + BBC(1,6) * Shpf(b,3) + & + BBC(1,7) * Shpbar(b,1) +C + EKt(1,a,2,b) = EKt(1,a,2,b) + & + BBC(1,4) * Shpf(b,1) + & + BBC(1,2) * Shpf(b,2) + & + BBC(1,5) * Shpf(b,3) + & + BBC(1,7) * Shpbar(b,2) +C + EKt(1,a,3,b) = EKt(1,a,3,b) + & + BBC(1,6) * Shpf(b,1) + & + BBC(1,5) * Shpf(b,2) + & + BBC(1,3) * Shpf(b,3) + & + BBC(1,7) * Shpbar(b,3) +C + EKt(2,a,1,b) = EKt(2,a,1,b) + & + BBC(2,1) * Shpf(b,1) + & + BBC(2,4) * Shpf(b,2) + & + BBC(2,6) * Shpf(b,3) + & + BBC(2,7) * Shpbar(b,1) +C + EKt(2,a,2,b) = EKt(2,a,2,b) + & + BBC(2,4) * Shpf(b,1) + & + BBC(2,2) * Shpf(b,2) + & + BBC(2,5) * Shpf(b,3) + & + BBC(2,7) * Shpbar(b,2) +C + EKt(2,a,3,b) = EKt(2,a,3,b) + & + BBC(2,6) * Shpf(b,1) + & + BBC(2,5) * Shpf(b,2) + & + BBC(2,3) * Shpf(b,3) + & + BBC(2,7) * Shpbar(b,3) +C + EKt(3,a,1,b) = EKt(3,a,1,b) + & + BBC(3,1) * Shpf(b,1) + & + BBC(3,4) * Shpf(b,2) + & + BBC(3,6) * Shpf(b,3) + & + BBC(3,7) * Shpbar(b,1) +C + EKt(3,a,2,b) = EKt(3,a,2,b) + & + BBC(3,4) * Shpf(b,1) + & + BBC(3,2) * Shpf(b,2) + & + BBC(3,5) * Shpf(b,3) + & + BBC(3,7) * Shpbar(b,2) +C + EKt(3,a,3,b) = EKt(3,a,3,b) + & + BBC(3,6) * Shpf(b,1) + & + BBC(3,5) * Shpf(b,2) + & + BBC(3,3) * Shpf(b,3) + & + BBC(3,7) * Shpbar(b,3) +C + end do +C + end do +C + end diff --git a/Apps/FiniteDefElasticity/main_NonLinEl.C b/Apps/FiniteDefElasticity/main_NonLinEl.C new file mode 100644 index 00000000..d176de88 --- /dev/null +++ b/Apps/FiniteDefElasticity/main_NonLinEl.C @@ -0,0 +1,313 @@ +// $Id: main_NonLinEl.C,v 1.6 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file main_NonLinEl.C +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Main program for the isogeometric nonlinear elasticity solver. +//! +//============================================================================== + +#include "NonLinSIM.h" +#include "SIMFiniteDefEl.h" +#include "LinAlgInit.h" +#include "Profiler.h" +#include "VTF.h" +#include +#include +#include +#include + + +/*! + \brief Main program for the isogeometric nonlinear elasticity solver. + + The input to the program is specified through the following + command-line arguments. The arguments may be given in arbitrary order. + + \arg \a input-file : Input file with model definition + \arg -dense : Use the dense LAPACK matrix equation solver + \arg -spr : Use the SPR direct equation solver + \arg -superlu : Use the sparse SuperLU equation solver + \arg -samg : Use the sparse algebraic multi-grid equation solver + \arg -petsc : Use equation solver from PETSc library + \arg -nGauss \a n : Number of Gauss points over a knot-span in each direction + \arg -vtf \a format : VTF-file format (0=ASCII, 1=BINARY) + \arg -nviz \a nviz : Number of visualization points over each knot-span + \arg -nu \a nu : Number of visualization points per knot-span in u-direction + \arg -nv \a nv : Number of visualization points per knot-span in v-direction + \arg -nw \a nw : Number of visualization points per knot-span in w-direction + \arg -skip2nd : Skip VTF-output of secondary solution fields + \arg -saveInc \a dtSave : Time increment between each result save to VTF + \arg -dumpInc \a dtDump [raw] : Time increment between each solution dump + \arg -ignore \a p1, \a p2, ... : Ignore these patches in the analysis + \arg -check : Data check only, read model and output to VTF (no solution) + \arg -checkRHS : Check that the patches are modelled in a right-hand system + \arg -fixDup : Resolve co-located nodes by merging them into a single node + \arg -2D : Use two-parametric simulation driver + \arg -Tensor : Use tensorial total Lagrangian formulation (slow...) + \arg -UL \a mver : Use updated Lagrangian formulation with nonlinear material + \arg -MX \a pord : Mixed formulation with internal discontinuous pressure + \arg -mixed : Mixed formulation with continuous pressure and volumetric change + \arg -lag : Use Lagrangian basis functions instead of splines/NURBS + \arg -spec : Use Spectral basis functions instead of splines/NURBS +*/ + +int main (int argc, char** argv) +{ + Profiler prof(argv[0]); + + SystemMatrix::Type solver = SystemMatrix::SPARSE; + int form = SIM::TOTAL_LAGRANGE; + int nGauss = 4; + int format = 0; + int n[3] = { 2, 2, 2 }; + std::vector ignoredPatches; + double dtSave = 0.0; + double dtDump = 0.0; + bool dumpWithID = true; + bool skip2nd = false; + bool checkRHS = false; + bool fixDup = false; + char twoD = false; + char* infile = 0; + + LinAlgInit linalg(argc,argv); + + std::vector options(2,-1); + for (int i = 1; i < argc; i++) + if (!strcmp(argv[i],"-dense")) + solver = SystemMatrix::DENSE; + else if (!strcmp(argv[i],"-spr")) + solver = SystemMatrix::SPR; + else if (!strncmp(argv[i],"-superlu",8)) + { + solver = SystemMatrix::SPARSE; + if (isdigit(argv[i][8])) + SIMbase::num_threads_SLU = atoi(argv[i]+8); + } + else if (!strcmp(argv[i],"-samg")) + solver = SystemMatrix::SAMG; + else if (!strcmp(argv[i],"-petsc")) + solver = SystemMatrix::PETSC; + else if (!strcmp(argv[i],"-nGauss") && i < argc-1) + nGauss = atoi(argv[++i]); + else if (!strcmp(argv[i],"-vtf") && i < argc-1) + format = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nviz") && i < argc-1) + n[0] = n[1] = n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nu") && i < argc-1) + n[0] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nv") && i < argc-1) + n[1] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nw") && i < argc-1) + n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-skip2nd")) + skip2nd = true; + else if (!strcmp(argv[i],"-saveInc") && i < argc-1) + dtSave = atof(argv[++i]); + else if (!strcmp(argv[i],"-dumpInc") && i < argc-1) + { + dtDump = atof(argv[++i]); + if (++i < argc && !strcmp(argv[i],"raw")) + dumpWithID = false; + else + --i; + } + else if (!strcmp(argv[i],"-ignore")) + while (i < argc-1 && isdigit(argv[i+1][0])) + { + char* endp = 0; + int endVal = 0; + ignoredPatches.push_back(strtol(argv[++i],&endp,10)); + if (endp && *endp == ':') + endVal = strtol(endp+1,&endp,10); + while (ignoredPatches.back() < endVal) + ignoredPatches.push_back(ignoredPatches.back()+1); + } + else if (!strcmp(argv[i],"-vox") && i < argc-1) + VTF::vecOffset[0] = atof(argv[++i]); + else if (!strcmp(argv[i],"-voy") && i < argc-1) + VTF::vecOffset[1] = atof(argv[++i]); + else if (!strcmp(argv[i],"-voz") && i < argc-1) + VTF::vecOffset[2] = atof(argv[++i]); + else if (!strcmp(argv[i],"-check")) + form += 100; + else if (!strcmp(argv[i],"-checkRHS")) + checkRHS = true; + else if (!strcmp(argv[i],"-fixDup")) + fixDup = true; + else if (!strncmp(argv[i],"-2D",3)) + twoD = strcmp(argv[i],"-2Dpstrain") ? 1 : 2; + else if (!strcmp(argv[i],"-UL")) + { + if (form < SIM::UPDATED_LAGRANGE) + form = SIM::UPDATED_LAGRANGE; + if (i < argc-1 && isdigit(argv[i+1][0])) + options[0] = atoi(argv[++i]); + } + else if (!strcmp(argv[i],"-MX")) + { + form = SIM::MIXED_QnPn1; + if (i < argc-1 && isdigit(argv[i+1][0])) + options[1] = atoi(argv[++i]); + else + options[1] = 0; + } + else if (!strcmp(argv[i],"-mixed")) + form = SIM::MIXED_QnQn1; + else if (!strcmp(argv[i],"-Tensor")) + form = SIM::NONLINEAR; + else if (!strcmp(argv[i],"-NeoHooke")) + form = SIM::NEOHOOKE; + else if (!strcmp(argv[i],"-NeoHookeIV")) + form = SIM::NEOHOOKE_IV; + else if (!strncmp(argv[i],"-lag",4)) + SIMbase::discretization = SIMbase::Lagrange; + else if (!strncmp(argv[i],"-spec",5)) + SIMbase::discretization = SIMbase::Spectral; + else if (!infile) + infile = argv[i]; + else + std::cerr <<" ** Unknown option ignored: "<< argv[i] << std::endl; + + if (!infile) + { + std::cout <<"usage: "<< argv[0] + <<" [-dense|-spr|-superlu[]|-samg|-petsc]\n " + <<" [-lag] [-spec] [-2D[pstrain]] [-UL []] [-MX [

]|-mixe" + <<"d] [-nGauss ]\n [-vtf ] [-nviz ]" + <<" [-nu ] [-nv ] [-nw ]\n " + <<" [-saveInc ] [-dumpInc [raw]]\n " + <<" [-ignore ...] [-fixDup] [-checkRHS] [-check]\n"; + return 0; + } + + if (linalg.myPid == 0) + std::cout <<"\n >>> Spline FEM Nonlinear Elasticity solver <<<" + <<"\n ==============================================\n" + <<"\nInput file: "<< infile + <<"\nEquation solver: "<< solver + <<"\nNumber of Gauss points: "<< nGauss + <<"\nVTF file format: "<< (format ? "BINARY":"ASCII") + <<"\nNumber of visualization points: " + << n[0] <<" "<< n[1]; + if (twoD) + n[2] = 1; + else if (linalg.myPid == 0) + std::cout <<" "<< n[2]; + + if (linalg.myPid == 0) + { + if (dtSave > 0.0) + std::cout <<"\nTime between each result save: "<< dtSave; + if (dtDump > 0.0) + std::cout <<"\nTime between each primary solution dump: "<< dtDump; + if (SIMbase::discretization == SIMbase::Lagrange) + std::cout <<"\nLagrangian basis functions are used"; + else if (SIMbase::discretization == SIMbase::Spectral) + std::cout <<"\nSpectral basis functions are used"; + if (fixDup) + std::cout <<"\nCo-located nodes will be merged"; + if (checkRHS) + std::cout <<"\nCheck that each patch has a right-hand coordinate system"; + if (!ignoredPatches.empty()) + { + std::cout <<"\nIgnored patches:"; + for (size_t i = 0; i < ignoredPatches.size(); i++) + std::cout <<" "<< ignoredPatches[i]; + } + std::cout << std::endl; + } + utl::profiler->start("Model input"); + + // Create the finite deformation elasticity model + SIMbase* model; + if (twoD) + model = new SIMFiniteDefEl2D(form%100,twoD==1,options); + else + model = new SIMFiniteDefEl3D(checkRHS,form%100,options); + + // Read in solver and model definitions + NonLinSIM simulator(model); + if (!simulator.read(infile)) + return 1; + + utl::profiler->stop("Model input"); + + // Preprocess the model and establish data structures for the algebraic system + model->printProblem(std::cout); + if (!model->preprocess(ignoredPatches,fixDup)) + return 2; + + // Save FE model to VTF file for visualization + if (!simulator.saveModel(infile,format,n)) + return 3; + + std::ostream* oss = 0; + if (dtDump > 0.0 && !dumpWithID) + { + // Write (refined?) model to g2-file + strcat(strtok(infile,"."),".g2"); + std::cout <<"\nWriting updated g2-file "<< infile << std::endl; + std::ofstream osg(infile); + model->dumpGeometry(osg); + // Open ASCII file for solution dump + strcat(strtok(infile,"."),".sol"); + oss = new std::ofstream(infile); + *oss <<"#NPoints="<< model->getNoNodes() <<"\n"; + } + + // Define the initial configuration + NonLinSIM::SolvePrm params; + simulator.init(params); + + const double epsT = 1.0e-6; + if (dtDump <= 0.0) dtDump = params.stopTime + 1.0; + double nextDump = params.time.t + dtDump; + double nextSave = params.time.t + dtSave; + + int iStep = 0; // Save initial state to VTF + if (params.multiSteps()) + if (!simulator.saveStep(-(++iStep),params.time.t,n,skip2nd)) + return 4; + + if (form >= 100) + return 0; // model check + + // Initialize the linear solver + model->initSystem(solver,1,1); + model->setAssociatedRHS(0,0); + model->setQuadratureRule(nGauss); + + // Invoke the time/load-step loop + while (simulator.advanceStep(params)) + { + // Solve the nonlinear FE problem at this load step + if (!simulator.solveStep(params,SIM::STATIC)) + return 5; + + // Dump primary solution for inspection or external processing + if (params.time.t + epsT*params.time.dt > nextDump) + { + if (dumpWithID) + simulator.dumpStep(params.step,params.time.t,std::cout); + else + simulator.dumpStep(params.step,params.time.t,*oss,false); + nextDump = params.time.t + dtDump; + } + + // Save solution variables to VTF for visualization + if (params.time.t + epsT*params.time.dt > nextSave) + if (simulator.saveStep(++iStep,params.time.t,n,skip2nd)) + nextSave = params.time.t + dtSave; + else + return 6; + } + + if (oss) delete oss; + return 0; +} diff --git a/Apps/FiniteDefElasticity/stiff_TL.f b/Apps/FiniteDefElasticity/stiff_TL.f new file mode 100644 index 00000000..c3ff79ae --- /dev/null +++ b/Apps/FiniteDefElasticity/stiff_TL.f @@ -0,0 +1,205 @@ +C $Id: stiff_TL.f,v 1.1 2010-12-18 20:03:54 kmo Exp $ +C======================================================================= +C! +C! \file stiff_TL.f +C! +C! \date Jun 8 2010 +C! +C! \author Knut Morten Okstad / SINTEF +C! +C! \brief Routines for Total Lagrangian material stiffness integration. +C! +C======================================================================= +C + subroutine stiff_TL2D (NENOD,detJW,dNdX,F,Cmat,EK) +C + implicit none +C + integer NENOD, a, b, i, j, k, l, m, n + double precision detJW, dNdX(NENOD,2), F(2,2), Cmat(3,3) + double precision EK(2,NENOD,2,NENOD), C(2,2,2,2), km +C + do 200 i = 1, 2 + do 200 j = i, 2 + if (i == j) then + a = i + else + a = 3 + end if + do 100 k = 1, 2 + do 100 l = k, 2 + if (k == l) then + b = k + else + b = 3 + end if + C(i,j,k,l) = Cmat(a,b) + C(j,i,k,l) = Cmat(a,b) + C(i,j,l,k) = Cmat(a,b) + C(j,i,l,k) = Cmat(a,b) + 100 continue + 200 continue +C + do 450 a = 1, NENOD + do 450 b = a, NENOD + do 400 m = 1, 2 + do 400 n = 1, 2 + km = 0.0D0 + do 350 i = 1, 2 + do 350 j = 1, 2 + do 300 k = 1, 2 + do 300 l = 1, 2 + km = km + dNdX(a,i)*F(m,j)*C(i,j,k,l)*F(n,k)*dNdX(b,l) + 300 continue + 350 continue + EK(m,a,n,b) = EK(m,a,n,b) + km*detJW + if (b > a) then + EK(n,b,m,a) = EK(n,b,m,a) + km*detJW + end if + 400 continue + 450 continue +C + end +C +C + subroutine stiff_TL3D (NENOD,detJW,dNdX,F,Cmat,EK) +C + implicit none +C + integer NENOD, a, b, i, j, k, l, m, n + double precision detJW, DNDX(NENOD,3), F(3,3), Cmat(6,6) + double precision EK(3,NENOD,3,NENOD), C(3,3,3,3), km +C + do 200 i = 1, 3 + do 200 j = i, 3 + if (i == j) then + a = i + else if (i == 1) then + a = j+2 + else + a = 6 + end if + do 100 k = 1, 3 + do 100 l = k, 3 + if (k == l) then + b = k + else if (k == 1) then + b = l+2 + else + b = 6 + end if + C(i,j,k,l) = Cmat(a,b) + C(j,i,k,l) = Cmat(a,b) + C(i,j,l,k) = Cmat(a,b) + C(j,i,l,k) = Cmat(a,b) + 100 continue + 200 continue +C + do 450 a = 1, NENOD + do 450 b = a, NENOD + do 400 m = 1, 3 + do 400 n = 1, 3 + km = 0.0D0 + do 350 i = 1, 3 + do 350 j = 1, 3 + do 300 k = 1, 3 + do 300 l = 1, 3 + km = km + dNdX(a,i)*F(m,j)*C(i,j,k,l)*F(n,k)*dNdX(b,l) + 300 continue + 350 continue + EK(m,a,n,b) = EK(m,a,n,b) + km*detJW + if (b > a) then + EK(n,b,m,a) = EK(n,b,m,a) + km*detJW + end if + 400 continue + 450 continue +C + end +C +C + subroutine stiff_TL2D_isoel (NENOD,detJW,dNdX,F,C1,C2,C3,EK) +C +C Use this subroutine in 2D, but only for isotrophic materials, +C where the fourth-order constitutive tensor is assumed to only +C contain three different values, C1, C2, C3 (and zeroes): +C +C C1 = C(1,1,1,1) = C(2,2,2,2) +C C2 = C(1,1,2,2) = C(2,2,1,1 +C C3 = C(1,2,1,2) = C(1,2,2,1) = C(2,1,1,2) = C(2,1,2,1) +C + implicit none +C + integer NENOD, a, b, m, n + double precision detJW, DNDX(NENOD,2), F(2,2), C1, C2, C3 + double precision EK(2,NENOD,2,NENOD), ek1, ek2, ek3 +C + do 350 a = 1, NENOD + do 350 b = a, NENOD + do 300 m = 1, 2 + do 300 n = 1, 2 + ek1 = 0.0D0 + ek2 = 0.0D0 + ek3 = 0.0D0 + ek1 = dNdX(a,1)*F(m,1)*F(n,1)*dNdX(b,1) + + + dNdX(a,2)*F(m,2)*F(n,2)*dNdX(b,2) + ek2 = dNdX(a,1)*F(m,1)*F(n,2)*dNdX(b,2) + + + dNdX(a,2)*F(m,2)*F(n,1)*dNdX(b,1) + ek3 = dNdX(a,1)*F(m,2)*(F(n,1)*dNdX(b,2) + + + F(n,2)*dNdX(b,1)) + + + dNdX(a,2)*F(m,1)*(F(n,2)*dNdX(b,1) + + + F(n,1)*dNdX(b,2)) + EK(m,a,n,b) = EK(m,a,n,b) + (ek1*C1+ek2*C2+ek3*C3)*detJW + if (b > a) then + EK(n,b,m,a) = EK(n,b,m,a) + (ek1*C1+ek2*C2+ek3*C3)*detJW + end if + 300 continue + 350 continue +C + end +C +C + subroutine stiff_TL3D_isoel (NENOD,detJW,dNdX,F,C1,C2,C3,EK) +C +C Use this subroutine in 3D, but only for isotrophic materials, +C where the fourth-order constitutive tensor is assumed to only +C contain three different values, C1, C2, C3 (and zeroes): +C +C C1 = C(1,1,1,1) = C(2,2,2,2) = C(3,3,3,3) +C C2 = C(1,1,2,2) = C(1,1,3,3) = C(2,2,3,3) +C = C(2,2,1,1) = C(3,3,1,1) = C(3,3,2,2) +C C3 = C(1,2,1,2) = C(1,2,2,1) = C(2,1,1,2) = C(2,1,2,1) +C = C(1,3,1,3) = C(1,3,3,1) = C(3,1,1,3) = C(3,1,3,1) +C = C(2,3,2,3) = C(2,3,3,2) = C(3,2,2,3) = C(3,2,3,2) +C + implicit none +C + integer NENOD, a, b, i, j, m, n + double precision detJW, DNDX(NENOD,3), F(3,3), C1, C2, C3 + double precision EK(3,NENOD,3,NENOD), ek1, ek2, ek3 +C + do 350 a = 1, NENOD + do 350 b = a, NENOD + do 300 m = 1, 3 + do 300 n = 1, 3 + ek1 = 0.0D0 + ek2 = 0.0D0 + ek3 = 0.0D0 + do 200 i = 1, 3 + ek1 = ek1 + dNdX(a,i)*F(m,i)*F(n,i)*dNdX(b,i) + do 100 j = i+1, 3 + ek2 = ek2 + dNdX(a,i)*F(m,i)*F(n,j)*dNdX(b,j) + + + dNdX(a,j)*F(m,j)*F(n,i)*dNdX(b,i) + ek3 = ek3 + dNdX(a,i)*F(m,j)*(F(n,i)*dNdX(b,j) + + + F(n,j)*dNdX(b,i)) + + + dNdX(a,j)*F(m,i)*(F(n,j)*dNdX(b,i) + + + F(n,i)*dNdX(b,j)) + 100 continue + 200 continue + EK(m,a,n,b) = EK(m,a,n,b) + (ek1*C1+ek2*C2+ek3*C3)*detJW + if (b > a) then + EK(n,b,m,a) = EK(n,b,m,a) + (ek1*C1+ek2*C2+ek3*C3)*detJW + end if + 300 continue + 350 continue +C + end diff --git a/Apps/Makefile b/Apps/Makefile new file mode 100644 index 00000000..1f78727b --- /dev/null +++ b/Apps/Makefile @@ -0,0 +1,76 @@ +# $Id: Makefile,v 1.13 2011-02-04 17:31:40 kmo Exp $ +#=============================================================================== +# +# File: Makefile +# +# Created: Jan 12 2010 +# +# Author: Knut Morten Okstad / SINTEF +# +# Description: +# Makefile for spline-based isogeometric finite element simulation applications. +# +#=============================================================================== + +# Comment in this line if PETSc and SLEPc is used +#include ${SLEPC_DIR}/conf/slepc_common_variables + +SRCS0 = SIMLinEl2D.C SIMLinEl3D.C \ + FiniteDefElasticity/NonlinearElasticity.C \ + FiniteDefElasticity/NonlinearElasticityTL.C +SRCS1 = main_LinEl3D.C $(SRCS0) +SRCS2 = main_LinElStep.C $(SRCS0) +OBJS1 = $(SRCS1:.C=.o) FiniteDefElasticity/stiff_TL.o +OBJS2 = $(SRCS2:.C=.o) FiniteDefElasticity/stiff_TL.o +APP1 = linel3D +APP2 = linelStep + +MYLIB = ../libSplineFEM.a +GOTLIB = -L../GoTools/lib -lGoTrivariate -lGoToolsCore +BLASLIB = -larpack -llapack -lblas +SLULIB = -lsuperlu +#SLULIB = -L../SuperLU_MT/lib -lsuperlu -lpthread +#SAMLIB = -L../SAM -lSPR -lASM -lEIG -lMAT -lMDR -lUTL +#SAMGLIB = -L../SAMG/samg -lamg_coo +# Comment in this line if PETSc and SLEPc is used +#PETSCOPT = -DHAS_PETSC $(CC_INCLUDES) +# Comment in this line if parallel PETSc and SLEPc are used +#PETSCOPT = -DHAS_PETSC -DPARALLEL_PETSC $(CC_INCLUDES) +VTFLIB = -L../GLviewExpressWriter/lib -lVTFExpressAPI +LIBS = $(MYLIB) $(GOTLIB) $(VTFLIB) \ + $(SAMLIB) $(SAMGLIB) $(SLULIB) $(SLEPC_LIB) $(BLASLIB) + +FC = gfortran +CXX = g++ + +#CDEBUG = -g -DINDEX_CHECK +CDEBUG = -O2 + +FFLAGS = -O3 -funroll-loops +CXXFLAGS = $(CDEBUG) -Dreal=double -DUSE_CBLAS \ + -I. -I../Integrands -I../SIM -I../ASM -I../LinAlg -I../Utility \ + -IFiniteDefElasticity $(PETSCOPT) + +all: $(APP1) $(APP2) + +$(APP1): $(OBJS1) $(MYLIB) Makefile + $(CXX) -o $@ $(OBJS1) $(LIBS) -lgfortran + +$(APP2): $(OBJS2) $(MYLIB) Makefile + $(CXX) -o $@ $(OBJS2) $(LIBS) -lgfortran + +../libSplineFEM.a: + cd ..; make + +clean: + rm -f $(OBJS1) $(OBJS2) $(OBJS3) + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile > /dev/null + mv Makefile.tmp Makefile + +depend: + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile > /dev/null + $(CXX) -M $(CXXFLAGS) *.C >> Makefile.tmp + mv Makefile Makefile.bak + mv Makefile.tmp Makefile + +# DO NOT DELETE diff --git a/Apps/SIMLinEl2D.C b/Apps/SIMLinEl2D.C new file mode 100644 index 00000000..85a157e9 --- /dev/null +++ b/Apps/SIMLinEl2D.C @@ -0,0 +1,293 @@ +// $Id: SIMLinEl2D.C,v 1.18 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file SIMLinEl2D.C +//! +//! \date Dec 04 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 2D NURBS-based linear elastic FEM analysis. +//! +//============================================================================== + +#include "SIMLinEl2D.h" +#include "LinearElasticity.h" +#include "NonlinearElasticity.h" +#include "AnalyticSolutions.h" +#include "Functions.h" +#include "Utilities.h" +#include "Vec3Oper.h" +#include "Property.h" +#include "Tensor.h" +#include + + +SIMLinEl2D::SIMLinEl2D (int form, bool planeStress) +{ + switch (form) + { + case SIM::LINEAR: + myProblem = new LinearElasticity(2,planeStress); + break; + case SIM::NONLINEAR: + myProblem = new NonlinearElasticity(2,planeStress); + break; + } + + asol = 0; +} + + +SIMLinEl2D::~SIMLinEl2D () +{ + if (asol) delete asol; +} + + +bool SIMLinEl2D::parse (char* keyWord, std::istream& is) +{ + char* cline = 0; + Elasticity* elp = dynamic_cast(myProblem); + if (!elp) return false; + + if (!strncasecmp(keyWord,"GRAVITY",7)) + { + double gx = atof(strtok(keyWord+7," ")); + double gy = atof(strtok(NULL," ")); + elp->setGravity(gx,gy,0.0); + if (myPid == 0) + std::cout <<"\nGravitation vector: " + << gx <<" "<< gy << std::endl; + } + + else if (!strncasecmp(keyWord,"ISOTROPHIC",10)) + { + int nmat = atoi(keyWord+10); + if (myPid == 0) + std::cout <<"\nNumber of isotrophic materials: "<< nmat << std::endl; + + for (int i = 0; i < nmat && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + double E = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + double rho = atof(strtok(NULL," ")); + if (myPid == 0) + std::cout <<"\tMaterial code "<< code <<": " + << E <<" "<< nu <<" "<< rho << std::endl; + if (code == 0 || i == 0) + elp->setMaterial(E,nu,rho); + for (unsigned int j = 0; j < myProps.size() && code > 0; j++) + if (myProps[j].pindx == (size_t)code && + myProps[j].pcode == Property::UNDEFINED) + { + myProps[j].pindx = mVec.size(); + myProps[j].pcode = Property::MATERIAL; + } + if (code > 0) + mVec.push_back(IsoMat(E,nu,rho)); + } + } + + else if (!strncasecmp(keyWord,"CONSTANT_PRESSURE",17)) + { + int npres = atoi(keyWord+17); + if (myPid == 0) + std::cout <<"\nNumber of pressures: "<< npres << std::endl; + + for (int i = 0; i < npres && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + int pdir = atoi(strtok(NULL," ")); + double p = atof(strtok(NULL," ")); + if (myPid == 0) + std::cout <<"\tPressure code "<< code <<" direction "<< pdir + <<": "<< p << std::endl; + + this->setPropertyType(code,Property::NEUMANN); + myTracs[code] = new PressureField(p,pdir); + } + } + + else if (!strncasecmp(keyWord,"ANASOL",6)) + { + cline = strtok(keyWord+6," "); + if (!strncasecmp(cline,"HOLE",4)) + { + double a = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + asol = new Hole(a,F0,nu); + std::cout <<"\nAnalytical solution: Hole a="<< a <<" F0="<< F0 + <<" nu="<< nu << std::endl; + } + else if (!strncasecmp(cline,"LSHAPE",6)) + { + double a = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + asol = new Lshape(a,F0,nu); + std::cout <<"\nAnalytical solution: Lshape a="<< a <<" F0="<< F0 + <<" nu="<< nu << std::endl; + } + else if (!strncasecmp(cline,"CANTS",5)) + { + double L = atof(strtok(NULL," ")); + double H = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + asol = new CanTS(L,H,F0); + std::cout <<"\nAnalytical solution: CanTS L="<< L <<" H="<< H + <<" F0="<< F0 << std::endl; + } + else if (!strncasecmp(cline,"CANTM",5)) + { + double H = atof(strtok(NULL," ")); + double M0 = atof(strtok(NULL," ")); + asol = new CanTM(H,M0); + std::cout <<"\nAnalytical solution: CanTM H="<< H + <<" M0="<< M0 << std::endl; + } + else if (!strncasecmp(cline,"CURVED",6)) + { + double a = atof(strtok(NULL," ")); + double b = atof(strtok(NULL," ")); + double u0 = atof(strtok(NULL," ")); + double E = atof(strtok(NULL," ")); + asol = new CurvedBeam(u0,a,b,E); + std::cout <<"\nAnalytical solution: Curved Beam a="<< a <<" b="<< b + <<" u0="<< u0 <<" E="<< E << std::endl; + } + else + std::cerr <<" ** SIMLinEl2D::parse: Unknown analytical solution " + << cline << std::endl; + + // Define the analytical boundary traction field + int code = (cline = strtok(NULL," ")) ? atoi(cline) : 0; + if (code > 0 && asol) + { + std::cout <<"Pressure code "<< code <<": Analytical traction"<< std::endl; + this->setPropertyType(code,Property::NEUMANN); + myTracs[code] = new TractionField(*asol); + } + } + + // The remaining keywords are retained for backward compatibility with the + // prototype version. They enable direct specification of properties onto + // the topological entities (blocks and faces) of the model. + + else if (!strncasecmp(keyWord,"PRESSURE",8)) + { + Property press; + press.pcode = Property::NEUMANN; + press.ldim = 1; + + int npres = atoi(keyWord+8); + std::cout <<"\nNumber of pressures: "<< npres << std::endl; + for (int i = 0; i < npres && (cline = utl::readLine(is)); i++) + { + press.pindx = 1+i; + press.patch = atoi(strtok(cline," ")); + + int pid = this->getLocalPatchIndex(press.patch); + if (pid < 0) return false; + if (pid < 1) continue; + + press.lindx = atoi(strtok(NULL," ")); + if (press.lindx < 1 || press.lindx > 4) + { + std::cerr <<" *** SIMLinEl2D::parse: Invalid edge index " + << (int)press.lindx << std::endl; + return false; + } + + if (asol) + { + std::cout <<"\tTraction on P"<< press.patch + <<" E"<< (int)press.lindx << std::endl; + myTracs[1+i] = new TractionField(*asol); + } + else + { + int pdir = atoi(strtok(NULL," ")); + double p = atof(strtok(NULL," ")); + std::cout <<"\tPressure on P"<< press.patch + <<" E"<< (int)press.lindx <<" direction "<< pdir <<": "; + if ((cline = strtok(NULL," "))) + myTracs[1+i] = new PressureField(utl::parseRealFunc(cline,p),pdir); + else + { + std::cout << p; + myTracs[1+i] = new PressureField(p,pdir); + } + std::cout << std::endl; + } + + press.patch = pid; + myProps.push_back(press); + } + } + + else if (!strncasecmp(keyWord,"MATERIAL",8)) + { + int nmat = atoi(keyWord+8); + std::cout <<"\nNumber of materials: "<< nmat << std::endl; + for (int i = 0; i < nmat && (cline = utl::readLine(is)); i++) + { + double E = atof(strtok(cline," ")); + double nu = atof(strtok(NULL," ")); + double rho = atof(strtok(NULL," ")); + while ((cline = strtok(NULL," "))) + if (!strncasecmp(cline,"ALL",3)) + { + std::cout <<"\tMaterial for all patches: " + << E <<" "<< nu <<" "<< rho << std::endl; + elp->setMaterial(E,nu,rho); + } + else + { + int patch = atoi(cline); + int pid = this->getLocalPatchIndex(patch); + if (pid < 0) return false; + if (pid < 1) continue; + + std::cout <<"\tMaterial for P"<< patch + <<": "<< E <<" "<< nu <<" "<< rho << std::endl; + myProps.push_back(Property(Property::MATERIAL,mVec.size(),pid,3)); + mVec.push_back(IsoMat(E,nu,rho)); + if (i == 0) + elp->setMaterial(E,nu,rho); + } + } + } + + else + return this->SIM2D::parse(keyWord,is); + + return true; +} + + +bool SIMLinEl2D::initMaterial (size_t propInd) +{ + Elasticity* elp = dynamic_cast(myProblem); + if (!elp) return false; + + if (propInd >= mVec.size()) propInd = mVec.size()-1; + + elp->setMaterial(mVec[propInd].E,mVec[propInd].nu,mVec[propInd].rho); + return true; +} + + +bool SIMLinEl2D::initNeumann (size_t propInd) +{ + TracFuncMap::const_iterator tit = myTracs.find(propInd); + if (tit == myTracs.end()) return false; + + Elasticity* elp = dynamic_cast(myProblem); + if (!elp) return false; + + elp->setTraction(tit->second); + return true; +} diff --git a/Apps/SIMLinEl2D.h b/Apps/SIMLinEl2D.h new file mode 100644 index 00000000..180422d2 --- /dev/null +++ b/Apps/SIMLinEl2D.h @@ -0,0 +1,74 @@ +// $Id: SIMLinEl2D.h,v 1.9 2010-12-18 16:23:52 kmo Exp $ +//============================================================================== +//! +//! \file SIMLinEl2D.h +//! +//! \date Feb 04 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 2D NURBS-based linear elastic FEM analysis. +//! +//============================================================================== + +#ifndef _SIM_LIN_EL_2D_H +#define _SIM_LIN_EL_2D_H + +#include "SIM2D.h" +#include "SIMenums.h" + + +/*! + \brief Driver class for 2D isogeometric FEM analysis of elasticity problems. + \details The class incapsulates data and methods for solving linear elasticity + problems using NURBS-based finite elements. It reimplements the parse method + of the parent class. +*/ + +class SIMLinEl2D : public SIM2D +{ + //! \brief Struct for storage of physical material property parameters. + struct IsoMat + { + double E; //!< Young's modulus + double nu; //!< Poisson's ratio + double rho; //!< Mass density + //! \brief Default constructor. + IsoMat() { E = nu = rho = 0.0; } + //! \brief Constructor initializing a material instance. + IsoMat(double Emod, double Poiss, double D) : E(Emod), nu(Poiss), rho(D) {} + }; + + typedef std::vector MaterialVec; //!< Vector of material data sets + +public: + //! \brief Default constructor. + //! \param[in] form Problem formulation option + //! \param[in] planeStress Plane stress/plane strain option + SIMLinEl2D(int form = SIM::LINEAR, bool planeStress = true); + //! \brief The destructor frees the dynamically allocated data. + virtual ~SIMLinEl2D(); + +protected: + //! \brief Parses a data section from the input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + + //! \brief Initializes material properties for integration of interior terms. + //! \param[in] propInd Physical property index + virtual bool initMaterial(size_t propInd); + + //! \brief Initializes for integration of Neumann terms for a given property. + //! \param[in] propInd Physical property index + virtual bool initNeumann(size_t propInd); + + //! \brief Returns the analytical solution function, if any. + virtual TensorFunc* getAnaSol() const { return asol; } + +private: + MaterialVec mVec; //!< Material data + TensorFunc* asol; //!< Analytical stress field +}; + +#endif diff --git a/Apps/SIMLinEl3D.C b/Apps/SIMLinEl3D.C new file mode 100644 index 00000000..3d38aa1b --- /dev/null +++ b/Apps/SIMLinEl3D.C @@ -0,0 +1,428 @@ +// $Id: SIMLinEl3D.C,v 1.28 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file SIMLinEl3D.C +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 3D NURBS-based linear elastic FEM analysis. +//! +//============================================================================== + +#include "SIMLinEl3D.h" +#include "LinearElasticity.h" +#include "NonlinearElasticity.h" +#include "AnalyticSolutions.h" +#include "Functions.h" +#include "Utilities.h" +#include "Vec3Oper.h" +#include "Property.h" +#include "Tensor.h" +#include + + +/*! + \brief Local coordinate system for a cylinder along global z-axis. +*/ + +class CylinderCS : public LocalSystem +{ +public: + //! \brief Constructor printing a message making user aware of its presense. + CylinderCS() + { + std::cout <<"\nLocal coordinate system: Cylindric"<< std::endl; + } + + //! \brief Computes the global-to-local transformation at the point \a X. + virtual const Tensor& getTmat(const Vec3& X) const + { + static Tensor T(3); + double r = hypot(X.x,X.y); + T(1,1) = X.x/r; + T(1,2) = X.y/r; + T(2,1) = -T(1,2); + T(2,2) = T(1,1); + T(3,3) = 1.0; + return T; + } +}; + + +#ifdef PRINT_CS +#include +#endif + +/*! + \brief Local coordinate system for a cylinder along global z-axis, + closed by a spherical cap. +*/ + +class CylinderSphereCS : public LocalSystem +{ +public: + //! \brief Constructor printing a message making user aware of its presense. + CylinderSphereCS(double H = 0.0) : h(H) + { + std::cout <<"\nLocal coordinate system: Cylindric with Spherical cap, h=" + << h << std::endl; +#ifdef PRINT_CS + sn.open("nodes.dat"); + se.open("elements.dat"); + s1.open("v1.dat"); + s2.open("v2.dat"); + s3.open("v3.dat"); + sn <<"\n*NODES 4\n"; + se <<"\n*ELEMENTS 4\n%NODES #4\n" + <<"%NO_ID\n%MAP_NODE_INDICES\n%PART_ID 4\n%POINTS\n"; + s1 <<"\n*RESULTS 31\n%NO_ID\n%DIMENSION 3\n%PER_NODE #4\n"; + s2 <<"\n*RESULTS 32\n%NO_ID\n%DIMENSION 3\n%PER_NODE #4\n"; + s3 <<"\n*RESULTS 33\n%NO_ID\n%DIMENSION 3\n%PER_NODE #4\n"; + } + + virtual ~CylinderSphereCS() + { + std::cout <<"Finalizing VTF-output of local coordinate systems"<< std::endl; + s1 <<"\n*GLVIEWVECTOR 2\n%NAME \"v1\"\n%STEP 1\n31\n"; + s2 <<"\n*GLVIEWVECTOR 3\n%NAME \"v2\"\n%STEP 1\n32\n"; + s3 <<"\n*GLVIEWVECTOR 4\n%NAME \"v3\"\n%STEP 1\n33\n"; +#endif + } + + //! \brief Computes the global-to-local transformation at the point \a X. + virtual const Tensor& getTmat(const Vec3& X) const + { + static Tensor T(3); +#ifdef PRINT_CS + sn << X <<'\n'; + static int iel = 0; + se << ++iel <<'\n'; +#endif + if (patch == 1) // Cylindric system {-z,theta,r} + { + T.zero(); + double r = hypot(X.x,X.y); + T(1,3) = -1.0; + T(2,1) = -X.y/r; + T(2,2) = X.x/r; + T(3,1) = T(2,2); + T(3,2) = -T(2,1); +#ifdef PRINT_CS + s1 <<"0 0 -1\n"; + s2 << T(2,1) <<" "<< T(2,2) <<" 0\n"; + s3 << T(3,1) <<" "<< T(3,2) <<" 0\n"; +#endif + } + else // Spherical system {phi,theta,r} + { + Vec3 v3(X.x,X.y,X.z-h); + v3 /= v3.length(); + double theta = atan2(X.y,X.x); + double phi = acos(v3.z); + Vec3 v1(cos(theta)*cos(phi),sin(theta)*cos(phi),-sin(phi)); + Vec3 v2(v3,v1); + for (int i = 1; i <= 3; i++) + { + T(1,i) = v1[i-1]; + T(2,i) = v2[i-1]; + T(3,i) = v3[i-1]; + } +#ifdef PRINT_CS + s1 << v1 <<'\n'; + s2 << v2 <<'\n'; + s3 << v3 <<'\n'; +#endif + } + return T; + } + +private: + double h; //!< Height above global origin of the centre of the sphere +#ifdef PRINT_CS + mutable std::ofstream sn; //!< VTF output stream for CS nodes + mutable std::ofstream se; //!< VTF output stream for CS point elements + mutable std::ofstream s1; //!< VTF output stream for vector v1 of local CS + mutable std::ofstream s2; //!< VTF output stream for vector v2 of local CS + mutable std::ofstream s3; //!< VTF output stream for vector v3 of local CS +#endif +}; + + +SIMLinEl3D::SIMLinEl3D (bool checkRHS, int form) : SIM3D(checkRHS) +{ + switch (form) + { + case SIM::LINEAR: + myProblem = new LinearElasticity(); + break; + case SIM::NONLINEAR: + myProblem = new NonlinearElasticity(); + break; + } + + asol = 0; +} + + +SIMLinEl3D::~SIMLinEl3D () +{ + if (asol) delete asol; +} + + +bool SIMLinEl3D::parse (char* keyWord, std::istream& is) +{ + char* cline = 0; + int nConstPress = 0; + int nLinearPress = 0; + Elasticity* elp = dynamic_cast(myProblem); + if (!elp) return false; + + if (!strncasecmp(keyWord,"GRAVITY",7)) + { + double gx = atof(strtok(keyWord+7," ")); + double gy = atof(strtok(NULL," ")); + double gz = atof(strtok(NULL," ")); + elp->setGravity(gx,gy,gz); + if (myPid == 0) + std::cout <<"\nGravitation vector: " + << gx <<" "<< gy <<" "<< gz << std::endl; + } + + else if (!strncasecmp(keyWord,"ISOTROPHIC",10)) + { + int nmat = atoi(keyWord+10); + if (myPid == 0) + std::cout <<"\nNumber of isotrophic materials: "<< nmat << std::endl; + + for (int i = 0; i < nmat && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + double E = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + double rho = atof(strtok(NULL," ")); + if (myPid == 0) + std::cout <<"\tMaterial code "<< code <<": " + << E <<" "<< nu <<" "<< rho << std::endl; + if (code == 0 || i == 0) + elp->setMaterial(E,nu,rho); + for (unsigned int j = 0; j < myProps.size() && code > 0; j++) + if (myProps[j].pindx == code && myProps[j].pcode == Property::UNDEFINED) + { + myProps[j].pindx = mVec.size(); + myProps[j].pcode = Property::MATERIAL; + } + if (code > 0) + mVec.push_back(IsoMat(E,nu,rho)); + } + } + + else if (!strncasecmp(keyWord,"CONSTANT_PRESSURE",17)) + nConstPress = atoi(keyWord+17); + else if (!strncasecmp(keyWord,"LINEAR_PRESSURE",15)) + nLinearPress = atoi(keyWord+15); + + else if (!strncasecmp(keyWord,"ANASOL",6)) + { + cline = strtok(keyWord+6," "); + if (!strncasecmp(cline,"HOLE",4)) + { + double a = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + asol = new Hole(a,F0,nu,true); + std::cout <<"\nAnalytical solution: Hole a="<< a <<" F0="<< F0 + <<" nu="<< nu << std::endl; + } + else if (!strncasecmp(cline,"LSHAPE",6)) + { + double a = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + asol = new Lshape(a,F0,nu,true); + std::cout <<"\nAnalytical solution: Lshape a="<< a <<" F0="<< F0 + <<" nu="<< nu << std::endl; + } + else if (!strncasecmp(cline,"CANTS",5)) + { + double L = atof(strtok(NULL," ")); + double H = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + asol = new CanTS(L,H,F0,true); + std::cout <<"\nAnalytical solution: CanTS L="<< L <<" H="<< H + <<" F0="<< F0 << std::endl; + } + else + std::cerr <<" ** SIMLinEl3D::parse: Unknown analytical solution " + << cline << std::endl; + + // Define the analytical boundary traction field + int code = (cline = strtok(NULL," ")) ? atoi(cline) : 0; + if (code > 0 && asol) + { + std::cout <<"Pressure code "<< code <<": Analytical traction"<< std::endl; + this->setPropertyType(code,Property::NEUMANN); + myTracs[code] = new TractionField(*asol); + } + } + + // The remaining keywords are retained for backward compatibility with the + // prototype version. They enable direct specification of properties onto + // the topological entities (blocks and faces) of the model. + + else if (!strncasecmp(keyWord,"PRESSURE",8)) + { + Property press; + press.pcode = Property::NEUMANN; + press.ldim = 2; + + int npres = atoi(keyWord+8); + std::cout <<"\nNumber of pressures: "<< npres << std::endl; + for (int i = 0; i < npres && (cline = utl::readLine(is)); i++) + { + press.pindx = 1+i; + press.patch = atoi(strtok(cline," ")); + + int pid = this->getLocalPatchIndex(press.patch); + if (pid < 0) return false; + if (pid < 1) continue; + + press.lindx = atoi(strtok(NULL," ")); + if (press.lindx < 1 || press.lindx > 6) + { + std::cerr <<" *** SIMLinEl3D::parse: Invalid face index " + << (int)press.lindx << std::endl; + return false; + } + + if (asol) + { + std::cout <<"\tTraction on P"<< press.patch + <<" F"<< (int)press.lindx << std::endl; + myTracs[1+i] = new TractionField(*asol); + } + else + { + int pdir = atoi(strtok(NULL," ")); + double p = atof(strtok(NULL," ")); + std::cout <<"\tPressure on P"<< press.patch + <<" F"<< (int)press.lindx <<" direction "<< pdir <<": "; + if (cline = strtok(NULL," ")) + myTracs[1+i] = new PressureField(utl::parseRealFunc(cline,p),pdir); + else + { + std::cout << p; + myTracs[1+i] = new PressureField(p,pdir); + } + std::cout << std::endl; + } + + press.patch = pid; + myProps.push_back(press); + } + } + + else if (!strncasecmp(keyWord,"MATERIAL",8)) + { + int nmat = atoi(keyWord+8); + std::cout <<"\nNumber of materials: "<< nmat << std::endl; + for (int i = 0; i < nmat && (cline = utl::readLine(is)); i++) + { + double E = atof(strtok(cline," ")); + double nu = atof(strtok(NULL," ")); + double rho = atof(strtok(NULL," ")); + while (cline = strtok(NULL," ")) + if (!strncasecmp(cline,"ALL",3)) + { + std::cout <<"\tMaterial for all patches: " + << E <<" "<< nu <<" "<< rho << std::endl; + elp->setMaterial(E,nu,rho); + } + else + { + int patch = atoi(cline); + int pid = this->getLocalPatchIndex(patch); + if (pid < 0) return false; + if (pid < 1) continue; + + std::cout <<"\tMaterial for P"<< patch + <<": "<< E <<" "<< nu <<" "<< rho << std::endl; + myProps.push_back(Property(Property::MATERIAL,mVec.size(),pid,3)); + mVec.push_back(IsoMat(E,nu,rho)); + if (i == 0) + elp->setMaterial(E,nu,rho); + } + } + } + + else if (!strncasecmp(keyWord,"LOCAL_SYSTEM",12)) + { + size_t i = 12; + while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + if (!strncasecmp(keyWord+i,"CYLINDRICZ",10)) + elp->setLocalSystem(new CylinderCS); + else if (!strncasecmp(keyWord+i,"CYLINDER+SPHERE",15)) + elp->setLocalSystem(new CylinderSphereCS(atof(keyWord+i+15))); + else + std::cerr <<" *** SIMLinEl3D::parse: Unsupported coordinate system: " + << keyWord+i << std::endl; + } + + else + return this->SIM3D::parse(keyWord,is); + + int npres = nConstPress + nLinearPress; + if (npres > 0) + { + if (myPid == 0) + std::cout <<"\nNumber of pressures: "<< npres << std::endl; + for (int i = 0; i < npres && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + int pdir = atoi(strtok(NULL," ")); + double p = atof(strtok(NULL," ")); + if (myPid == 0) + std::cout <<"\tPressure code "<< code <<" direction "<< pdir + <<": "<< p << std::endl; + + this->setPropertyType(code,Property::NEUMANN); + + if (nLinearPress) + { + RealFunc* pfl = new ConstTimeFunc(new LinearFunc(p)); + myTracs[code] = new PressureField(pfl,pdir); + } + else + myTracs[code] = new PressureField(p,pdir); + } + } + + return true; +} + + +bool SIMLinEl3D::initMaterial (size_t propInd) +{ + Elasticity* elp = dynamic_cast(myProblem); + if (!elp) return false; + + if (propInd >= mVec.size()) propInd = mVec.size()-1; + + elp->setMaterial(mVec[propInd].E,mVec[propInd].nu,mVec[propInd].rho); + return true; +} + + +bool SIMLinEl3D::initNeumann (size_t propInd) +{ + TracFuncMap::const_iterator tit = myTracs.find(propInd); + if (tit == myTracs.end()) return false; + + Elasticity* elp = dynamic_cast(myProblem); + if (!elp) return false; + + elp->setTraction(tit->second); + return true; +} diff --git a/Apps/SIMLinEl3D.h b/Apps/SIMLinEl3D.h new file mode 100644 index 00000000..6a1e4e8c --- /dev/null +++ b/Apps/SIMLinEl3D.h @@ -0,0 +1,74 @@ +// $Id: SIMLinEl3D.h,v 1.11 2010-12-18 16:23:52 kmo Exp $ +//============================================================================== +//! +//! \file SIMLinEl3D.h +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 3D NURBS-based linear elastic FEM analysis. +//! +//============================================================================== + +#ifndef _SIM_LIN_EL_3D_H +#define _SIM_LIN_EL_3D_H + +#include "SIM3D.h" +#include "SIMenums.h" + + +/*! + \brief Driver class for 3D isogeometric FEM analysis of elasticity problems. + \details The class incapsulates data and methods for solving linear elasticity + problems using NURBS-based finite elements. It reimplements the parse method + of the parent class. +*/ + +class SIMLinEl3D : public SIM3D +{ + //! \brief Struct for storage of physical material property parameters. + struct IsoMat + { + double E; //!< Young's modulus + double nu; //!< Poisson's ratio + double rho; //!< Mass density + //! \brief Default constructor. + IsoMat() { E = nu = rho = 0.0; } + //! \brief Constructor initializing a material instance. + IsoMat(double Emod, double Poiss, double D) : E(Emod), nu(Poiss), rho(D) {} + }; + + typedef std::vector MaterialVec; //!< Vector of material data sets + +public: + //! \brief Default constructor. + //! \param[in] checkRHS If \e true, ensure the model is in a right-hand system + //! \param[in] form Problem formulation option + SIMLinEl3D(bool checkRHS = false, int form = SIM::LINEAR); + //! \brief The destructor frees the dynamically allocated data. + virtual ~SIMLinEl3D(); + +protected: + //! \brief Parses a data section from the input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + + //! \brief Initializes material properties for integration of interior terms. + //! \param[in] propInd Physical property index + virtual bool initMaterial(size_t propInd); + + //! \brief Initializes for integration of Neumann terms for a given property. + //! \param[in] propInd Physical property index + virtual bool initNeumann(size_t propInd); + + //! \brief Returns the analytical solution function, if any. + virtual TensorFunc* getAnaSol() const { return asol; } + +private: + MaterialVec mVec; //!< Material data + TensorFunc* asol; //!< Analytical stress field +}; + +#endif diff --git a/Apps/main_LinEl3D.C b/Apps/main_LinEl3D.C new file mode 100644 index 00000000..ce01f96c --- /dev/null +++ b/Apps/main_LinEl3D.C @@ -0,0 +1,386 @@ +// $Id: main_LinEl3D.C,v 1.27 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file main_LinEl3D.C +//! +//! \date Jan 28 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Main program for the isogeometric linear elasticity solver. +//! +//============================================================================== + +#include "SIMLinEl3D.h" +#include "SIMLinEl2D.h" +#include "LinAlgInit.h" +#include "Profiler.h" +#include +#include +#include +#include + + +/*! + \brief Main program for the NURBS-based isogeometric linear elasticity solver. + + The input to the program is specified through the following + command-line arguments. The arguments may be given in arbitrary order. + + \arg \a input-file : Input file with model definition + \arg -dense : Use the dense LAPACK matrix equation solver + \arg -spr : Use the SPR direct equation solver + \arg -superlu : Use the sparse SuperLU equation solver + \arg -samg : Use the sparse algebraic multi-grid equation solver + \arg -petsc : Use equation solver from PETSc library + \arg -nGauss \a n : Number of Gauss points over a knot-span in each direction + \arg -vtf \a format : VTF-file format (-1=NONE, 0=ASCII, 1=BINARY) + \arg -nviz \a nviz : Number of visualization points over each knot-span + \arg -nu \a nu : Number of visualization points per knot-span in u-direction + \arg -nv \a nv : Number of visualization points per knot-span in v-direction + \arg -nw \a nw : Number of visualization points per knot-span in w-direction + \arg -dumpASC : Dump model and solution to ASCII files for external processing + \arg -ignore \a p1, \a p2, ... : Ignore these patches in the analysis + \arg -eig \a iop : Eigenproblem solver to use (1...6) + \arg -nev \a nev : Number of eigenvalues to compute + \arg -ncv \a ncv : Number of Arnoldi vectors to use in the eigenvalue analysis + \arg -shift \a shf : Shift value to use in the eigenproblem solver + \arg -free : Ignore all boundary conditions (use in free vibration analysis) + \arg -check : Data check only, read model and output to VTF (no solution) + \arg -checkRHS : Check that the patches are modelled in a right-hand system + \arg -vizRHS : Save the right-hand-side load vector on the VTF-file + \arg -fixDup : Resolve co-located nodes by merging them into a single node + \arg -2D : Use two-parametric simulation driver (plane stress) + \arg -2Dpstrain : Use two-parametric simulation driver (plane strain) + \arg -TL : Use the nonlinear total-lagrangian elasticity integrand + \arg -lag : Use Lagrangian basis functions instead of splines/NURBS + \arg -spec : Use Spectral basis functions instead of splines/NURBS +*/ + +int main (int argc, char** argv) +{ + Profiler prof(argv[0]); + utl::profiler->start("Initialization"); + + SIM::Formulation form = SIM::LINEAR; + SystemMatrix::Type solver = SystemMatrix::SPARSE; + int nGauss = 4; + int format = -1; + int n[3] = { 2, 2, 2 }; + std::vector ignoredPatches; + int iop = 0; + int nev = 10; + int ncv = 20; + double shf = 0.0; + bool checkRHS = false; + bool vizRHS = false; + bool fixDup = false; + bool dumpASCII = false; + char twoD = false; + char* infile = 0; + + LinAlgInit linalg(argc,argv); + + for (int i = 1; i < argc; i++) + if (!strcmp(argv[i],"-dense")) + solver = SystemMatrix::DENSE; + else if (!strcmp(argv[i],"-spr")) + solver = SystemMatrix::SPR; + else if (!strncmp(argv[i],"-superlu",8)) + { + solver = SystemMatrix::SPARSE; + if (isdigit(argv[i][8])) + SIMbase::num_threads_SLU = atoi(argv[i]+8); + } + else if (!strcmp(argv[i],"-samg")) + solver = SystemMatrix::SAMG; + else if (!strcmp(argv[i],"-petsc")) + solver = SystemMatrix::PETSC; + else if (!strcmp(argv[i],"-nGauss") && i < argc-1) + nGauss = atoi(argv[++i]); + else if (!strcmp(argv[i],"-vtf") && i < argc-1) + format = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nviz") && i < argc-1) + n[0] = n[1] = n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nu") && i < argc-1) + n[0] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nv") && i < argc-1) + n[1] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nw") && i < argc-1) + n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-dumpASC")) + dumpASCII = true; + else if (!strcmp(argv[i],"-ignore")) + while (i < argc-1 && isdigit(argv[i+1][0])) + { + char* endp = 0; + int endVal = 0; + ignoredPatches.push_back(strtol(argv[++i],&endp,10)); + if (endp && *endp == ':') + endVal = strtol(endp+1,&endp,10); + while (ignoredPatches.back() < endVal) + ignoredPatches.push_back(ignoredPatches.back()+1); + } + else if (!strcmp(argv[i],"-eig") && i < argc-1) + iop = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nev") && i < argc-1) + nev = atoi(argv[++i]); + else if (!strcmp(argv[i],"-ncv") && i < argc-1) + ncv = atoi(argv[++i]); + else if (!strcmp(argv[i],"-shift") && i < argc-1) + shf = atof(argv[++i]); + else if (!strcmp(argv[i],"-free")) + SIMbase::ignoreDirichlet = true; + else if (!strcmp(argv[i],"-check")) + iop = 100; + else if (!strcmp(argv[i],"-checkRHS")) + checkRHS = true; + else if (!strcmp(argv[i],"-vizRHS")) + vizRHS = true; + else if (!strcmp(argv[i],"-fixDup")) + fixDup = true; + else if (!strncmp(argv[i],"-2D",3)) + twoD = strcmp(argv[i],"-2Dpstrain") ? 1 : 2; + else if (!strcmp(argv[i],"-TL")) + form = SIM::NONLINEAR; + else if (!strncmp(argv[i],"-lag",4)) + SIMbase::discretization = SIMbase::Lagrange; + else if (!strncmp(argv[i],"-spec",5)) + SIMbase::discretization = SIMbase::Spectral; + else if (!infile) + infile = argv[i]; + else + std::cerr <<" ** Unknown option ignored: "<< argv[i] << std::endl; + + if (!infile) + { + std::cout <<"usage: "<< argv[0] + <<" [-dense|-spr|-superlu|-samg|-petsc]\n " + <<" [-free] [-lag] [-spec] [-2D[pstrain]] [-TL] [-nGauss ]\n" + <<" [-vtf ] [-nviz ]" + <<" [-nu ] [-nv ] [-nw ]\n" + <<" [-eig ] [-nev ] [-ncv ]\n" + <<" [-ignore ...] [-fixDup]" + <<" [-checkRHS] [-check] [-dumpASC]\n"; + return 0; + } + + if (twoD) n[2] = 1; + // Load vector visualization is not available when using additional viz-points + if (n[0] > 2 || n[1] > 2 || n[2] > 2) vizRHS = false; + + // Boundary conditions can be ignored only in generalized eigenvalue analysis + if (iop != 4 && iop != 6) SIMbase::ignoreDirichlet = false; + + if (linalg.myPid == 0) + { + std::cout <<"\n >>> Spline FEM Linear Elasticity solver <<<" + <<"\n ===========================================\n" + <<"\nInput file: "<< infile + <<"\nEquation solver: "<< solver + <<"\nNumber of Gauss points: "<< nGauss; + if (format >= 0) + { + std::cout <<"\nVTF file format: "<< (format ? "BINARY":"ASCII") + <<"\nNumber of visualization points: " + << n[0] <<" "<< n[1]; + if (!twoD) std::cout <<" "<< n[2]; + } + + if (iop > 0 && iop < 100) + std::cout <<"\nEigenproblem solver: "<< iop + <<"\nNumber of eigenvalues: "<< nev + <<"\nNumber of Arnoldi vectors: "<< ncv + <<"\nShift value: "<< shf; + if (form == SIM::NONLINEAR) + std::cout <<"\nGreen-Lagrange strains are used"; + if (SIMbase::discretization == SIMbase::Lagrange) + std::cout <<"\nLagrangian basis functions are used"; + else if (SIMbase::discretization == SIMbase::Spectral) + std::cout <<"\nSpectral basis functions are used"; + if (SIMbase::ignoreDirichlet) + std::cout <<"\nSpecified boundary conditions are ignored"; + if (fixDup) + std::cout <<"\nCo-located nodes will be merged"; + if (checkRHS) + std::cout <<"\nCheck that each patch has a right-hand coordinate system"; + if (!ignoredPatches.empty()) + { + std::cout <<"\nIgnored patches:"; + for (size_t i = 0; i < ignoredPatches.size(); i++) + std::cout <<" "<< ignoredPatches[i]; + } + std::cout << std::endl; + } + utl::profiler->stop("Initialization"); + utl::profiler->start("Model input"); + + // Read in model definitions and establish the FE data structures + SIMbase* model; + if (twoD) + model = new SIMLinEl2D(form,twoD==1); + else + model = new SIMLinEl3D(checkRHS,form); + + if (!model->read(infile)) + return 1; + + utl::profiler->stop("Model input"); + + if (linalg.myPid == 0) + model->printProblem(std::cout); + + if (!model->preprocess(ignoredPatches,fixDup)) + return 1; + + model->setQuadratureRule(nGauss); + + Matrix eNorm; + Vector gNorm, load; + Vectors displ(1); + std::vector modes; + std::vector::const_iterator it; + + switch (iop) { + case 0: + case 5: + // Static solution: Assemble [Km] and {R} + model->setMode(SIM::STATIC); + model->initSystem(solver,1,1); + model->setAssociatedRHS(0,0); + if (!model->assembleSystem()) + return 2; + else if (vizRHS) + model->extractLoadVec(load); + + // Solve the linear system of equations + if (!model->solveSystem(displ.front(),1)) + return 3; + + // Evaluate solution norms + model->setMode(SIM::RECOVERY); + if (!model->solutionNorms(displ,eNorm,gNorm)) + return 4; + + if (linalg.myPid == 0) + { + std::cout <<"Energy norm |u^h| = a(u^h,u^h)^0.5 : "<< gNorm(1); + std::cout <<"\nExternal energy ((f,u^h)+(t,u^h)^0.5 : "<< gNorm(2); + if (gNorm.size() > 2) + std::cout <<"\nExact norm |u| = a(u,u)^0.5 : "<< gNorm(3); + if (gNorm.size() > 3) + std::cout <<"\nExact error a(e,e)^0.5, e=u-u^h : "<< gNorm(4) + <<"\nExact relative error (%) : "<< gNorm(4)/gNorm(3)*100.0; + std::cout << std::endl; + } + + if (iop == 0) break; + + // Linearized buckling: Assemble [Km] and [Kg] + model->setMode(SIM::BUCKLING); + model->initSystem(solver,2,0); + if (!model->assembleSystem(displ)) + return 5; + + // Solve the generalized eigenvalue problem + if (!model->systemModes(modes,nev,ncv,iop,shf)) + return 6; + break; + + case 1: + case 2: + // Assemble and solve the regular eigenvalue problem + model->setMode(SIM::STIFF_ONLY); + model->initSystem(solver,1,0); + if (!model->assembleSystem()) + return 5; + + if (!model->systemModes(modes,nev,ncv,iop,shf)) + return 6; + break; + + case 100: + break; // Model check + + default: + // Free vibration: Assemble [Km] and [M] + model->setMode(SIM::VIBRATION); + model->initSystem(solver,2,0); + if (!model->assembleSystem()) + return 5; + + if (!model->systemModes(modes,nev,ncv,iop,shf)) + return 6; + } + + utl::profiler->start("Postprocessing"); + + if (format >= 0) + { + // Write VTF-file with model geometry + int iStep = 1, nBlock = 0; + if (!model->writeGlv(infile,n,format)) + return 7; + + // Write boundary tractions, if any + if (!model->writeGlvT(iStep,nBlock)) + return 8; + + // Write Dirichlet boundary conditions + if (!model->writeGlvBC(n,nBlock)) + return 8; + + // Write load vector to VTF-file + if (!model->writeGlvV(load,"Load vector",n,iStep,nBlock)) + return 9; + + // Write solution fields to VTF-file + if (!model->writeGlvS(displ.front(),n,iStep,nBlock)) + return 10; + + // Write eigenmodes + for (it = modes.begin(); it != modes.end(); it++) + if (!model->writeGlvM(*it, iop==3 || iop==4 || iop==6, n, nBlock)) + return 11; + + // Write element norms (when no additional visualization points are used) + if (n[0] == 2 && n[1] == 2 && n[2] <= 2) + if (!model->writeGlvN(eNorm,iStep,nBlock)) + return 12; + + model->closeGlv(); + } + + if (dumpASCII) + { + // Write (refined) model to g2-file + std::ofstream osg(strcat(strtok(infile,"."),".g2")); + std::cout <<"\nWriting updated g2-file "<< infile << std::endl; + model->dumpGeometry(osg); + if (!displ.front().empty()) + { + // Write solution (control point values) to ASCII files + std::ofstream osd(strcat(strtok(infile,"."),".dis")); + std::cout <<"\nWriting deformation to file "<< infile << std::endl; + model->dumpPrimSol(displ.front(),osd,false); + std::ofstream oss(strcat(strtok(infile,"."),".sol")); + std::cout <<"\nWriting solution to file "<< infile << std::endl; + model->dumpSolution(displ.front(),oss); + } + if (!modes.empty()) + { + // Write eigenvectors to ASCII files + std::ofstream ose(strcat(strtok(infile,"."),".eig")); + std::cout <<"\nWriting eigenvectors to file "<< infile << std::endl; + for (it = modes.begin(); it != modes.end(); it++) + { + ose <<"# Eigenvector_"<< it->eigNo <<" Eigenvalue="<< it->eigVal <<"\n"; + model->dumpPrimSol(it->eigVec,ose,false); + } + } + } + + utl::profiler->stop("Postprocessing"); + delete model; + return 0; +} diff --git a/Apps/main_LinElStep.C b/Apps/main_LinElStep.C new file mode 100644 index 00000000..52688a34 --- /dev/null +++ b/Apps/main_LinElStep.C @@ -0,0 +1,251 @@ +// $Id: main_LinElStep.C,v 1.7 2011-02-05 18:32:25 kmo Exp $ +//============================================================================== +//! +//! \file main_LinElStep.C +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Main program for the isogeometric linear elasticity solver. +//! +//============================================================================== + +#include "SIMLinEl3D.h" +#include "LinAlgInit.h" +#include "TimeDomain.h" +#include "Profiler.h" +#include +#include +#include + + +/*! + \brief Main program for the NURBS-based isogeometric linear elasticity solver. + + This program is basicly the same as main_LinEl3D.C, except that this one runs + in a loop over a specified number of load steps to compute the linear solution + for each step, when the load function is assumed to vary with time. + This version is for 3D patches only, and no eigenvalue solution is included. + + The input to the program is specified through the following + command-line arguments. The arguments may be given in arbitrary order. + + \arg \a input-file : Input file with model definition + \arg -dense : Use the dense LAPACK matrix equation solver + \arg -spr : Use the SPR direct equation solver + \arg -superlu : Use the sparse superLU equation solver + \arg -samg : Use the sparse algebraic multi-grid equation solver + \arg -petsc : Use equation solver from PETSc library + \arg -nGauss \a n : Number of Gauss points over a knot-span in each direction + \arg -vtf \a format : VTF-file format (0=ASCII, 1=BINARY) + \arg -nviz \a nviz : Number of visualization points over each knot-span + \arg -nu \a nu : Number of visualization points per knot-span in u-direction + \arg -nv \a nv : Number of visualization points per knot-span in v-direction + \arg -nw \a nw : Number of visualization points per knot-span in w-direction + \arg -ignore \a p1, \a p2, ... : Ignore these patches in the analysis + \arg -checkRHS : Check that the patches are modelled in a right-hand system + \arg -vizRHS : Save the right-hand-side load vector on the VTF-file + \arg -fixDup : Resolve co-located nodes by merging them into a single node + \arg -lag : Use Lagrangian basis functions instead of splines/NURBS + \arg -spec : Use Spectral basis functions instead of splines/NURBS + \arg -nstep \a n : Number of time steps + \arg -dt \a dt : Time increment size +*/ + +int main (int argc, char** argv) +{ + Profiler prof(argv[0]); + utl::profiler->start("Initialization"); + + SystemMatrix::Type solver = SystemMatrix::SPARSE; + int nGauss = 4; + int format = 0; + int n[3] = { 2, 2, 2 }; + std::vector ignoredPatches; + bool checkRHS = false; + bool vizRHS = false; + bool fixDup = false; + char* infile = 0; + TimeDomain time; + int nStep = 1; + + LinAlgInit linalg(argc,argv); + + for (int i = 1; i < argc; i++) + if (!strcmp(argv[i],"-dense")) + solver = SystemMatrix::DENSE; + else if (!strcmp(argv[i],"-spr")) + solver = SystemMatrix::SPR; + else if (!strcmp(argv[i],"-superlu")) + solver = SystemMatrix::SPARSE; + else if (!strcmp(argv[i],"-samg")) + solver = SystemMatrix::SAMG; + else if (!strcmp(argv[i],"-petsc")) + solver = SystemMatrix::PETSC; + else if (!strcmp(argv[i],"-nGauss") && i < argc-1) + nGauss = atoi(argv[++i]); + else if (!strcmp(argv[i],"-vtf") && i < argc-1) + format = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nviz") && i < argc-1) + n[0] = n[1] = n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nu") && i < argc-1) + n[0] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nv") && i < argc-1) + n[1] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nw") && i < argc-1) + n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-ignore")) + while (i < argc-1 && isdigit(argv[i+1][0])) + { + char* endp = 0; + int endVal = 0; + ignoredPatches.push_back(strtol(argv[++i],&endp,10)); + if (endp && *endp == ':') + endVal = strtol(endp+1,&endp,10); + while (ignoredPatches.back() < endVal) + ignoredPatches.push_back(ignoredPatches.back()+1); + } + else if (!strcmp(argv[i],"-checkRHS")) + checkRHS = true; + else if (!strcmp(argv[i],"-vizRHS")) + vizRHS = true; + else if (!strcmp(argv[i],"-fixDup")) + fixDup = true; + else if (!strncmp(argv[i],"-lag",4)) + SIMbase::discretization = SIMbase::Lagrange; + else if (!strncmp(argv[i],"-spec",5)) + SIMbase::discretization = SIMbase::Spectral; + else if (!strcmp(argv[i],"-nstep") && i < argc-1) + nStep = atoi(argv[++i]); + else if (!strcmp(argv[i],"-dt") && i < argc-1) + time.dt = atof(argv[++i]); + else if (!infile) + infile = argv[i]; + else + std::cerr <<" ** Unknown option ignored: "<< argv[i] << std::endl; + + if (!infile) + { + std::cout <<"usage: "<< argv[0] + <<" [-dense|-spr|-superlu|-samg|-petsc]\n" + <<" [-lag] [-spec] [-nGauss ] [-nstep ] [-dt

]\n" + <<" [-vtf ] [-nviz ]" + <<" [-nu ] [-nv ] [-nw ]\n" + <<" [-ignore ...] [-fixDup] [-checkRHS]\n"; + return 0; + } + + // Load vector visualization is not available when using additional viz-points + if (n[0] > 2 || n[1] > 2 || n[2] > 2) vizRHS = false; + + std::cout <<"\n >>> Spline FEM Linear Elasticity solver <<<" + <<"\n ===========================================\n" + <<"\nInput file: "<< infile + <<"\nEquation solver: "<< solver + <<"\nNumber of Gauss points: "<< nGauss + <<"\nVTF file format: "<< (format ? "BINARY":"ASCII") + <<"\nNumber of visualization points: " + << n[0] <<" "<< n[1] <<" "<< n[2]; + if (SIMbase::discretization == SIMbase::Lagrange) + std::cout <<"\nLagrangian basis functions are used"; + else if (SIMbase::discretization == SIMbase::Spectral) + std::cout <<"\nSpectral basis functions are used"; + if (fixDup) + std::cout <<"\nCo-located nodes will be merged"; + if (checkRHS) + std::cout <<"\nCheck that each patch has a right-hand coordinate system"; + if (!ignoredPatches.empty()) + { + std::cout <<"\nIgnored patches:"; + for (size_t i = 0; i < ignoredPatches.size(); i++) + std::cout <<" "<< ignoredPatches[i]; + } + std::cout << std::endl; + + utl::profiler->stop("Initialization"); + utl::profiler->start("Model input"); + + // Read in model definitions and establish the FE structures + SIMbase* model = new SIMLinEl3D(checkRHS); + if (!model->read(infile) || !model->preprocess(ignoredPatches,fixDup)) + return 1; + + model->setQuadratureRule(nGauss); + utl::profiler->stop("Model input"); + + // Write VTF-file with model geometry + int nBlock = 0; + if (!model->writeGlv(infile,n,format)) + return 2; + + // Write Dirichlet boundary conditions + if (!model->writeGlvBC(n,nBlock)) + return 3; + + Matrix eNorm; + Vector gNorm, displ, load; + + model->initSystem(solver,1,1); + model->setAssociatedRHS(0,0); + + for (int iStep = 1; iStep <= nStep; iStep++) + { + time.t += time.dt; + std::cout <<"\n\nStep "<< iStep <<", time = "<< time.t << std::endl; + model->setMode(SIM::STATIC); + + if (!model->assembleSystem(time)) + return 4; + else if (vizRHS) + model->extractLoadVec(load); + + // Solve the linear system of equations + if (!model->solveSystem(displ,1)) + return 5; + + // Evaluate solution norms + model->setMode(SIM::RECOVERY); + if (!model->solutionNorms(Vectors(1,displ),eNorm,gNorm)) + return 6; + std::cout <<"Energy norm |u^h| = a(u^h,u^h)^0.5 : "<< gNorm(1); + if (gNorm.size() > 1) + std::cout <<"\nExternal energy ((f,u^h)+(t,u^h)^0.5 : "<< gNorm(2); + if (gNorm.size() > 2) + std::cout <<"\nExact norm |u| = a(u,u)^0.5 : "<< gNorm(3); + if (gNorm.size() > 3) + std::cout <<"\nExact error a(e,e)^0.5, e=u-u^h : "<< gNorm(4) + <<"\nExact relative error (%) : "<< gNorm(4)/gNorm(3)*100.0; + std::cout << std::endl; + + utl::profiler->start("Postprocessing"); + + // Write boundary tractions, if any + if (!model->writeGlvT(iStep,nBlock)) + return 7; + + // Write load vector to VTF-file + if (!model->writeGlvV(load,"Load vector",n,iStep,nBlock)) + return 8; + + // Write solution fields to VTF-file + if (!model->writeGlvS(displ,n,iStep,nBlock)) + return 9; + + // Write element norms, if feasable + if (n[0] == 2 && n[1] == 2 && n[2] == 2) + if (!model->writeGlvN(eNorm,iStep,nBlock)) + return 10; + + // Write step information to VTF-file + if (!model->writeGlvStep(iStep,time.t)) + return 11; + + utl::profiler->stop("Postprocessing"); + } + + model->closeGlv(); + delete model; + + return 0; +} diff --git a/doc/SplineFEM.dox b/doc/SplineFEM.dox new file mode 100644 index 00000000..7b6e33f4 --- /dev/null +++ b/doc/SplineFEM.dox @@ -0,0 +1,45 @@ +// $Id: SplineFEM.dox,v 1.1 2010-01-12 15:35:06 kmo Exp $ +//============================================================================== +//! +//! \file SplineFEM.dox +//! +//! \date Jan 12 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief File containing the Main Page of the spline FEM documentation. +//! +//============================================================================== + +/*! \mainpage The ICADA Spline FEM analysis toolbox + +\author Knut Morten Okstad, Sintef ICT, Trondheim + +\section intro Introduction + +This is the source code documentation of the Spline FEM simulation toolbox +developed in the ICADA project at SINTEF ICT. + +\section overview Module overview + +The simulation toolbox is organized into a set of modules, +organized as class hierarchies implemented in the C++ language. +The top-level driver is organized in the class SIMbase and its sub-classes. +These classes have methods for reading model data from an input file, +to assemble the algebraic system of equations resulting from the finite element +discretization, and to solve that system. It also contains a method for writing +a VTF-file with results. +Problem-specific drivers and main programs are found in the sub-folder Apps. + +The core of the finite element implementation is contained in the class +ASMbase and its sub-classes, which have methods for evaluating the element +matrices involved and assembling them into the system matrices. +There is typically one ASMbase object for each spline patch in the model. + +The actual NURBS evaluation is performed through the GoTools library, which is +not part of the current package. In addition, this code depends on the ARPACK, +LAPACK and BLAS libraries, the VTFAPI library, and portions of the SAM library +(ASM and optionally SPR). Optionally, the SAMG algebraic multi-grid solver and +the SuperLU direct solver (public domain http://crd.lbl.gov/~xiaoye/SuperLU) +may be included. +*/ diff --git a/doc/class-hierarchies.pdf b/doc/class-hierarchies.pdf new file mode 100644 index 00000000..6d02890c Binary files /dev/null and b/doc/class-hierarchies.pdf differ diff --git a/doc/class-hierarchies.tex b/doc/class-hierarchies.tex new file mode 100644 index 00000000..4146467a --- /dev/null +++ b/doc/class-hierarchies.tex @@ -0,0 +1,409 @@ +\documentclass{article} +\usepackage{color} +\usepackage{/home/kmo/LaTeX/Sty/a4} +\input{/home/kmo/LaTeX/Macros/NameList} + +\title{ICADA: Class hierarchies of the {\bf SIM}ulation module} +\author{Knut Morten Okstad} + +\begin{document} +\maketitle + +\section{Introduction} + +This document intends to give a brief overview of the class hierarchies of the +simulation module of the isogeometric analysis toolbox, +developed for the ICADA project. +A prototype code for 3D linear elasticity problems has been developed in order +to gain experience in using splines or NURBS as basis functions in place of +standard Lagrange polynomials. +A framework for general FE analysis based on splines is then developed, +where the main point is that the user should be able to create his/her +own simulators of a PDE-governed problem, by creating only a few classes +representing the weak form of the mathematical problem at hand. + +\section{The main class hierarchies} + +The prototype code consists mainly of two classes (in addition to some utility +classes and linear algebra methods). +These are {\bf LinearEl} on the system level, +and {\bf VolumePatch} on the local (patch) level. +In the new version, these two classes are replaced by several class hierarchies +for increased flexibility, and with the ability for code reuse on a broader +range of applications. +The main hierarchies are the following: + +\begin{namelist}{\bf SystemMatrix} +\item[{\bf SIM}] (base class: {\tt SIMbase}) +Simulation drivers administrating an assembly of spline patches. +Solution algorithms for time stepping and path-following methods, etc., will +also be implemented withing this hierarchy. +\item[{\bf ASM}] (base class: {\tt ASMbase}) +Administers numerical integration and FE assembly over a patch. +Each patch has a pointer to a GoTools {\tt GeomObject} instance representing +the actual geometry and basis functions of that patch. +\item[{\bf Integrand}] (base class: {\tt Integrand}) +Represents the actual problem to be solved with physical property parameters +and methods for evaluating the integrand of the governing weak form problem. +An instance of this class is passed as argument to the integration methods +of the ASM-classes. +\item[{\bf Integral}] (base classes: {\tt LocalIntegral, GlobalIntegral}) +Represents the integrated quantities on element and system level, respectively. +An instance of {\tt GlobalIntegral} is passed as argument to the integration +methods of the ASM-classes, which then updates it with contributions from that +patch. +\item[{\bf SystemMatrix}] (base classes: {\tt SystemMatrix, SystemVector}) +Interface to different matrix and vector representations required by +the linear equation solvers to be used. +\end{namelist} +\clearpage + +\subsection{SIM} +\begin{center} +\fbox{\bf SIMbase} $\longleftarrow$ +\fbox{\bf SIM3D} $\longleftarrow$ +\fbox{\bf SIMLinEl3D} +\end{center} +The {\bf SIMbase} class replaces the {\bf LinearEl} class of the prototype. +Its main methods and data are indicated in the class definition below (refer to +the source code or the doxygen-generated documentation for complete definition): + +{\small\begin{verbatim} +class SIMbase +{ +public: + //! \brief Reads model data from the specified input file \a *fileName. + bool read(const char* fileName); + + //! \brief Administers assembly of the linear equation system. + //! \param[in] prevSol Previous primary solution vector in DOF-order + bool assembleSystem(const Vector* prevSol = 0); + + //! \brief Solves the assembled linear system of equations for a given load. + //! \param[out] solution Global primary solution vector + //! \param[in] printSol Print solution if its size is less than \a printSol + bool solveSystem(Vector& solution, int printSol = 0); + + //! \brief Integrates some solution norm quantities. + //! \details If an analytical solution is provided, norms of the exact + //! error in the solution are computed as well. + //! \param[in] psol Global primary solution vector + //! \param[out] eNorm Element-wise norm quantities + //! \param[out] gNorm Global norm quantities + bool solutionNorms(const Vector& psol, Matrix& eNorm, Vector& gNorm); + + //! \brief Performs a generalized eigenvalue analysis of the assembled system. + //! \param[in] iop Which eigensolver method to use + //! \param[in] nev Number of eigenvalues/vector (see ARPack documentation) + //! \param[in] ncv Number of Arnoldi vectors (see ARPack documentation) + //! \param[in] shift Eigenvalue shift + //! \param[out] solution Computed eigenvalues and associated eigenvectors + //! \param[in] iA Index of system matrix \b A in \a myEqSys->A + //! \param[in] iB Index of system matrix \b B in \a myEqSys->A + bool systemModes(std::vector& solution, + int nev, int ncv, int iop, double shift, + size_t iA = 0, size_t iB = 1); + +protected: + //! \brief Parses a data section from an input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is) = 0; + + // Model attributes + std::vector myModel; //!< The actual NURBS/spline model + PropertyVec myProps; //!< Physical property mapping + Integrand* myProblem; //!< Problem-specific data and methods + +private: + // Solver attributes + SAMpatch* mySam; //!< Data for FE assembly management + AlgEqSystem* myEqSys; //!< The linear equation system +}; +\end{verbatim}} +% +The {\bf SIM3D} class is a common base for drivers of 3D continuum problems, +whereas {\bf SIMLinEl3D} is a sample driver for 3D linear elasticity problems. +Similar drivers might be needed for 2D problems and shell models. +Basically, the subclasses only need to (re)implement the {\tt parse} method, +which parses the problem-specific data from an input file. + +\clearpage +\subsection{ASM} +\begin{center} +\fbox{\bf ASMbase} $\longleftarrow$ +\fbox{\bf ASMstruct} $\longleftarrow$ +\fbox{\bf ASMs3D} +\end{center} +The {\bf ASMbase} class replaces the {\bf VolumePatch} class of the prototype. +However, it is more generic and contains nothing specific to the actual problem +to be solved, nor does it pay attention to the spatial discretization method +(structured or unstructured), or the spatial dimensions of the problem. + +The subclass {\bf ASMstruct} is a common base for patches with structured +discretizations, that is, the ``nodal points'' are organized in a topological +pattern such that the local node number can be computed knowing only its index +along each parameter direction along with the number of nodes in each direction. + +Later, an accompanying class {\bf ASMunstruct} is to be made, +serving as a base for adaptive meshes based on T-splines and similar techniques. + +The sub-class {\bf ASMs3D} is for trivariate structured patches. +It has a pointer to an underlying {\bf SplineVolume} object from the +{\sl GoTools} library, containing all information needed to represent the +trivariate spline basis for the geometry and solution fields +Later, {\bf ASMs2D} and {\bf ASMs1D} for two-parametric and single-parametric +patches will be added. + +The computational core of the ASM-hierarchy is in the data and methods +reproduced in the class definition below (see source code or doxygen documentation +for complete class definition): + +{\small\begin{verbatim} +class ASMbase +{ +public: + // Methods for integration of finite element quantities. + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, + const LintegralVec& locInt = LintegralVec()) = 0; + + //! \brief Evaluates a boundary integral over a patch face. + //! \param integrand Object with problem-specific data and methods + //! \param[in] face Local index of the boundary face + //! \param glbInt The integrated quantity + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, short int face, + GlobalIntegral& glbInt, + const LintegralVec& locInt = LintegralVec()) = 0; +protected: + // Standard finite element data structures + unsigned char ndim; //!< Number of parametric dimensions (1, 2 or 3) + unsigned char nsd; //!< Number of space dimensions (ndim <= nsd <= 3) + + IntVec MLGE; //!< Matrix of Local to Global Element numbers + IntVec MLGN; //!< Matrix of Local to Global Node numbers + IntMat MNPC; //!< Matrix of Nodal Point Correspondence + BCVec BCode; //!< Vector of Boundary condition codes + MPCSet mpcs; //!< All multi-point constraints with slave in this patch +}; +\end{verbatim}} + +The two {\tt integrate} methods perform numerical integration over the interior +of the patch, and over one of its boundary faces, respectively. +The integrand and the resulting integrated quantity are provided as arguments +to the methods through virtual interface classes, such that the integration +methods themselves can be kept purely problem-independent. + +The {\tt integrate} methods are implemented in the {\bf ASMs3D} sub-class where +they can be optimized for 3D structured grids. +Other integration methods and/or unstructured grid representations can be +incorporated by deriving another subclasses of {\bf ASMbase} where {\tt integrate} +is re-implemented. + +\clearpage +\subsection{Integrand} +\begin{center} +\begin{minipage}{8cm} +\begin{tabbing} +\fbox{\bf Integrand} \= $\leftarrow$ \= +\fbox{\bf LinearElasticity} \+ \\ $\nwarrow$ \+ \\ +\fbox{\bf ElasticityNorm} \\ +\hskip1cm $\vdots$ +\end{tabbing} +\end{minipage} +\end{center} + +The purpose of the {\bf Integrand} class is to serve as a generic representation +of the mathematical problem to be solved. +It mainly contains virtual methods for evaluation of the weak from of the problem, +one method for interior terms, and another method for boundary terms. +It also has a method for evaluating secondary solution variables when the primary +solution is known. +All methods take FE quantities at current integration point as arguments. +The methods are invoked from the {\tt integrate} methods of the {\bf ASM}-classes. + +{\small\begin{verbatim} +class Integrand +{ +public: + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const { return false; } + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const { return false; } + + //! \brief Evaluates the secondary solution at current integration point. + //! \param[out] s The solution field values + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] MNPC Matrix of nodal point correspondence + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const { return false; } + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \param[in] asol Pointer to the analytical solution field (optional) + virtual Integrand* getNormIntegrand(TensorFunc* asol = 0) const { return 0; } + +protected: + Vector primsol; //!< Current primary solution vector for this patch +}; +\end{verbatim}} + +A sample class {\bf LinearElasticy} is provided, which can be used as a template +for other, similar problem classes, like Poisson problem, Navier-Stokes, etc. +{\bf ElasticityNorm} is an accompanying class, used for integrating solution norms +which depend on problem parameters. +It requires a reference to a {\bf LinearElasticity} object for access of problem +parameters, but by implementing the norm integrand as a separate {\bf Integrand} +class, it can be evaluated using the same methods in the {\bf ASM} and {\bf SIM} +hierarchies, as when assembling the finite element matrices. + +\clearpage +\subsection{LocalIntegral and GlobalIntegral} +\begin{center} +\begin{minipage}{7cm} +\begin{tabbing} +\fbox{\bf LocalIntegral} \= $\leftarrow$ \= +\fbox{\bf ElmMats} \+ \\ $\nwarrow$ \+ \\ +\fbox{\bf ElmNorm} +\end{tabbing} +\end{minipage} +\hskip1cm +\begin{minipage}{7cm} +\begin{tabbing} +\fbox{\bf GlobalIntegral} \= $\leftarrow$ \= +\fbox{\bf AlgEqSystem} \+ \\ $\nwarrow$ \+ \\ +\fbox{\bf GlbNorm} +\end{tabbing} +\end{minipage} +\end{center} + +The classes {\bf LocalIntegral} and {\bf GlobalIntegral} are generic interfaces +to integrated quantities over a single element, and over the global domain, +respectively. +We need these interfaces in order to make the {\tt integrate} implementations +of the {\bf ASM}-classes independent on the type of quantity that is integrated. +The {\bf LocalIntegral} class has no methods or data itself, whereas +{\bf GlobalIntegral} has one virtual method or assembling a local quantity into +its corresponding global quantity: + +{\small\begin{verbatim} +class LocalIntegral +{ +protected: + //! \brief The default constructor is protected to allow sub-classes only. + LocalIntegral() {} +public: + //! \brief Empty destructor. + virtual ~LocalIntegral() {} +}; + +class GlobalIntegral +{ +protected: + //! \brief The default constructor is protected to allow sub-classes only. + GlobalIntegral() {} + +public: + //! \brief Empty destructor. + virtual ~GlobalIntegral() {} + + //! \brief Adds a LocalIntegral object into a corresponding global object. + //! \param[in] elmObj The local integral object to add into \a *this. + //! \param[in] elmId Global number of the element associated with elmObj + virtual bool assemble(const LocalIntegral* elmObj, int elmId) = 0; +}; +\end{verbatim}} + +The {\bf ElmMats} sub-class contains just a set of element matrices and vectors, +and has an accompanying global equivalent in the {\bf AlgEqSystem} class. +These classes are used when assembling linear system of equations resulting from +the FE discretization. + +Similarly, the class {\bf ElmNorm} is just a vector of local norm quantities and +has its global equivalent in the {\bf GlbBNorm} class. +Their class definitions are particularly simple and are reproduced below: + +{\small\begin{verbatim} + +class ElmNorm : public LocalIntegral +{ +public: + //! \brief The constructor assigns the internal pointer. + ElmNorm(double* p) : ptr(p) {} + //! \brief Empty destructor. + virtual ~ElmNorm() {} + + //! \brief Indexing operator for assignment. + double& operator[](size_t i) { return ptr[i]; } + //! \brief Indexing operator for referencing. + const double& operator[](size_t i) const { return ptr[i]; } + +private: + double* ptr; //!< Pointer to the actual norm values +}; + + +class GlbNorm : public GlobalIntegral +{ +public: + //! \brief The constructor initializes a reference to the global norm vector. + GlbNorm(std::vector& vec) : myVals(vec) {} + //! \brief Empty destructor. + virtual ~GlbNorm() {} + + //! \brief Adds element norm quantities into the global norm object. + //! \param[in] elmObj Pointer to the element norms to add into \a *this + //! \param[in] elmId Global number of the element associated with \a *elmObj + virtual bool assemble(const LocalIntegral* elmObj, int elmId); + +private: + std::vector& myVals; //!< Reference to a vector of global norm values +}; +\end{verbatim}} + +\subsection{SystemMatrix (and SystemVector)} +\begin{center} +\begin{minipage}{7cm} +\begin{tabbing} +\fbox{\bf SystemMatrix} \= $\leftarrow$ \= +\fbox{\bf DenseMatrix} \+ \\ $\nwarrow$ \+ \\ +\fbox{\bf SparseMatrix} \\ \\ +\fbox{\bf SPRMatrix} +\end{tabbing} +\end{minipage} +\hskip1cm +\begin{minipage}{7cm} +\fbox{\bf SystemVector} +\end{minipage} +\end{center} + +The {\bf SystemMatrix} are not changed since the prototype version. +However, a new {\bf SystemVector} class is introduced, and used in place of +{\bf Vector} when it represents the right-hand-side vector of a global equation +system. +This class may then be a base class for later parallel version. +\end{document} diff --git a/doc/petsc.pdf b/doc/petsc.pdf new file mode 100644 index 00000000..3945d7fb Binary files /dev/null and b/doc/petsc.pdf differ diff --git a/doc/petsc.tex b/doc/petsc.tex new file mode 100644 index 00000000..db55c21f --- /dev/null +++ b/doc/petsc.tex @@ -0,0 +1,198 @@ +\documentclass[a4wide,11pt]{article} +\usepackage{a4wide} +\usepackage{latexsym} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{bm} +\usepackage{epsfig,graphics,color} + +\title{Use of PETSc and SLEPc in the SplineFEM library} +\author{Runar Holdahl, SINTEF ICT, Applied Mathematics} +\date{\today} + +\begin{document} + +\maketitle + +\section{Installation of PETSc} +The lastest version of PETSc can be downloaded from the webpage +\textsf{http://www.mcs.anl.gov/petsc/petsc-as/}. When writing +this note the tarball has the name \textsf{petsc-3.1-p5.tar.gz}. +Save the file to the directory where you want to install PETSc. +Then unpack the tarball by writing +%---------- +\begin{verbatim} +> tar xvfz petsc-3.1-p5.tar.gz +\end{verbatim} +%---------- +This should produce a directory named \textsf{petsc-3.1-p5}. Before +the library can be compiled the environment variable \textsf{PETSC\_DIR} +must be set to the path of the PETSc home directory. For instance, if the +library was unpacked at \$HOME/libs/PETSc, the variable can be set by +writing +%---------- +\begin{verbatim} +> export PETSC_DIR=$HOME/libs/PETSc/petsc-3.1-p5 +\end{verbatim} +%---------- +in the terminal window. Alternatively, the path can be set in the +\textsf{.bashrc} file under the home directory to define the variable +when a new terminal window is opened on the machine. + +Now the PETSc library can be compiled from \textsf{\$PETSC\_DIR} by +first calling the configuration script to generate the \textsf{Makefile}. +In my installation I have used the following configuration options +%---------- +\begin{verbatim} +> ./config/configure.py --with-cc=gcc --with-fc=gfortran +--download-f-blas-lapack --download-mpich --with-clanguage=c++ +--download-ml --download-superlu --download-superlu_dist +--download-parmetis --with-shared=0 +\end{verbatim} +%---------- +If you already have a version of some of the library on your machine +the \textsf{download} command can be replaced by the \textsf{with} +command specifying the path of the installed library. See the PETSc +homepage for more information on options for the configuration script. +To run in parallel MPI must be installed on the machine. Moreover, the +use of the distributed SuperLU solver requires that ParMetis is installed. +Trilinos/ML is not supported by the linear solver interface of the +SplineFEM library, but will be included at a later stage to give access +to algebraic multigrid preconditioners. + +After the configuration script has been runned, the code can be compiled +and tested by +%---------- +\begin{verbatim} +> make all test +\end{verbatim} +%---------- + +\section{Installation of SLEPc} +SLEPc is a library which is built on top of PETSc for computing eigenvalues +and eigenvalues. It can be downloaded from the webpage +\textsf{http://www.grycap.upv.es/slepc/}. At the time of writing the tarball +is named \textsf{slepc-3.1-p4.tgz}. Unpack the library by calling +%---------- +\begin{verbatim} +> tar xvfz slepc-3.1-p4.tgz +\end{verbatim} +%---------- +To compile the library PETSc must be installed and the environment variables +\textsf{PETSC\_DIR} and \textsf{SLEPC\_DIR} musted be set. For example, +if SLEPc is installed at the directory \textsf{\$HOME/libs/SLEPc/slepc-3.1-p4} +write +%---------- +\begin{verbatim} +> export SLEPC_DIR=$HOME/libs/SLEpc/slepc-3.1-p4 +\end{verbatim} +%---------- +From the \textsf{\$SLEPC\_DIR} directory the library can be compiled +by calling +%---------- +\begin{verbatim} +> ./configure +> make +\end{verbatim} +%---------- + +\section{Compilation of SplineFEM with PETSc/SLEPc} +To compile the SplineFEM library and application code with +PETSc and SLEPc the Makefiles in the \textsf{SplineFEM/src} +and \textsf{SplineFEM/src/Apps} directories must be modified. +Uncomment the line +%---------- +\begin{verbatim} +#include ${SLEPC_DIR}/conf/slepc_common_variables +\end{verbatim} +%---------- +to include the necessary variables from PETSc/SLEPc. Then uncomment +one of the \textsf{PETSCOPT} lines depending on whether you want to +run the code sequential or in parallel. Then compile the SplineFEM +library and application code as usual. + +\section{Input files when running with PETSc} +The input files must be modified slightly to run with PETSc. The +linear solver parameters are defined in the \textsf{LINEARSOLVER} +block which has the following parameters and default values +%---------- +\begin{verbatim} +LINEARSOLVER 8 +type = gmres # Krylov solver +pc = ilu # Preconditioner +package = petsc # Software package used for linear solver +levels = 0 # Number of levels for ilu +atol = 1.0e-6 # Absolute tolerance for residual +rtol = 1.0e-6 # Relative tolerance for residual +dtol = 1.0e-6 # Divergence tolerance +maxits = 1000 # Maximal number of iterations +\end{verbatim} +%---------- +If some parameters are not specified in the input file, the default +value is used. Note that not all solvers and preconditioners are +available both for serial and parallel computations. For instance, the +\textsf{ilu} preconditioner is not available in parallel. See the +PETSc documentation for legal choices. To use the distributed SuperLU +solver the following input is given +%---------- +\begin{verbatim} +LINEARSOLVER 3 +type = preonly +pc = lu +package = superlu_dist +\end{verbatim} +%---------- +In parallel computations the patches must be specified by the +\textsf{PATCHFILE} command and a global numbering of the unknowns +must be specfied by the \textsf{NODEFILE} command. A special +numbering scheme is required by PETSc, and can be generated +from the patchfile using the \textsf{gpm\_getGNO} program with +the option \textsf{-petsc}. Parallelization using PETSc is only +available for multi-patch cases and the user must specify the +distribution of patches to each process in the \textsf{PARTITIONING} +block at the beginning of the input file. For instance, the input +%---------- +\begin{verbatim} +PARTITIONING 2 +# proc first last + 0 1 2 + 1 3 3 +\end{verbatim} +%---------- +specifies a case with 3 patches which is run on 2 processes. Process 0 +is assigned patches 1 and 2, while process 1 gets patch 3. Consecutive +patch numbers must be assigned to the processes because of the special +numbering scheme used by PETSc. + +\section{Running application code using PETSc} +To run application code compiled in serial mode, it is sufficient +to give the option \textsf{-petsc} to the executable. For example, +running \textsf{linel3D} with input file \textsf{CanTS-p1.inp} using +PETSc +%---------- +\begin{verbatim} +> ./linel3D -petsc CanTS-p1.inp +\end{verbatim} +%---------- +To run in parallel MPI must be called with the correct number of +processes. If the input file has at least two patches, the program +can be run in parallel by +%---------- +\begin{verbatim} +> mpiexec -np 2 ./linel3D -petsc CanTS-p1.inp +\end{verbatim} +%---------- +For parallel simulations the output will be written to individual +files for each process. For instance, the case given above will produce +the output files \textsf{CanTS-p1\_p0000.vtf} and \textsf{CanTS-p1\_p0001.vtf}. +The \textsf{glue} program can then be used to merge the different VTF-files +in a pre-processing step. The command +%---------- +\begin{verbatim} +> ./glue -FileBaseIn CanTS-p1 -NoFileIn 2 -format ASCII +\end{verbatim} +%---------- +will generate a file named \textsf{CanTS-p1.vtf} containing the +results for all patches. + +\end{document} diff --git a/doc/sim-input.pdf b/doc/sim-input.pdf new file mode 100644 index 00000000..b99fd8ad Binary files /dev/null and b/doc/sim-input.pdf differ diff --git a/doc/sim-input.tex b/doc/sim-input.tex new file mode 100644 index 00000000..fc273e7c --- /dev/null +++ b/doc/sim-input.tex @@ -0,0 +1,253 @@ +% $Id: sim-input.tex,v 1.3 2010-08-18 11:23:00 kmo Exp $ + +\documentclass{article} +\usepackage{color} +\usepackage{/home/kmo/LaTeX/Sty/a4} +\input{/home/kmo/LaTeX/Macros/NameList} + +\title{{\large ICADA - Note 2010-001:}\hfill\mbox{}\\[2mm] +Specification of spline patch topology to SIM} +\author{Knut Morten Okstad and Trond Kvamsdal} + +\def\cube{ +\begin{picture}(95,85) +\thicklines +\put(15,15){\line(1,0){40}} +\put(15,50){\line(1,0){40}} +\put(15,15){\line(0,1){35}} +\put(55,15){\line(0,1){35}} +\put(15,50){\line(1,1){20}} +\put(55,50){\line(1,1){20}} +\put(55,15){\line(1,1){20}} +\put(35,70){\line(1,0){40}} +\put(75,35){\line(0,1){35}} +\put(15,15){\line(1,1){20}} +\put(35,35){\line(1,0){40}} +\put(35,35){\line(0,1){35}} +\thinlines +\put(10,10){\vector(-1,-1){7}} +\put(80,35){\vector(1,0){10}} +\put(35,75){\vector(0,1){10}} +\put( 0, 0){\mbox{I}} +\put(91,33){\mbox{J}} +\put(36,82){\mbox{K}} +\end{picture}} + +\def\nodes{ +\begin{picture}(60,55) +\put(20,20){\circle*{2}} +\put( 0, 0){\circle*{2}} +\put(60,20){\circle*{2}} +\put(40, 0){\circle*{2}} +\put(20,55){\circle*{2}} +\put( 0,35){\circle*{2}} +\put(60,55){\circle*{2}} +\put(40,35){\circle*{2}} +\end{picture}} + +\def\edges{ +\begin{picture}(60,55) +\put(10,10){\circle*{2}} +\put(50,10){\circle*{2}} +\put(10,45){\circle*{2}} +\put(50,45){\circle*{2}} +\put(35,20){\circle*{2}} +\put(20, 0){\circle*{2}} +\put(40,55){\circle*{2}} +\put(25,35){\circle*{2}} +\put(20,40){\circle*{2}} +\put( 0,20){\circle*{2}} +\put(60,40){\circle*{2}} +\put(40,15){\circle*{2}} +\end{picture}} + +\def\faces{ +\begin{picture}(60,55) +\put(40,40){\circle{5}} +\put(20,15){\circle{5}} +\put(10,25){\circle{5}} +\put(50,25){\circle{5}} +\put(30,10){\circle{5}} +\put(30,45){\circle{5}} +\end{picture}} + +\begin{document} +\maketitle + +\section{Assumptions} + +We only allow for completely matching blocks for now. +That is, two adjacent blocks are topologically connected +with a common surface if, and only if, all control points (nodes) +defining the geometry of the surface are identical for the two blocks. +Otherwise, they are assumed not connected (we have a ``crack''). + +\section{Input description, topology} + +The simulation module must generate a unique global node number for all +control points in the model. +The input to this process should be as compact as possible, but should contain +sufficient information such that the node number generation can be carried out +locally within each patch in arbitrary order or in parallel. +Only topology information is included, all geometry data goes via the g2-files +from GoTools. + +\begin{figure}[htb] +\begin{center} +\setlength{\unitlength}{4pt} +\begin{picture}(95,85) +\put( 0, 0){\cube} +\put(15,15){\nodes} +\put(31,35){\bf 1} +\put(11,14){\bf 2} +\put(78,35){\bf 3} +\put(58,13){\bf 4} +\put(31,70){\bf 5} +\put(11,49){\bf 6} +\put(78,69){\bf 7} +\put(58,48){\bf 8} +% Mark the nodes +\put(32,31){{\Large\tt *} icnod1} +\put(72,31){{\Large\tt *} icnod2} +\put(24,66){icnod3 {\Large\tt *}} +\put(64,66){icnod4 {\Large\tt *}} +\put(39,34){\Large\tt *}\put(41,36){icnod5} +\put(19,14){\Large\tt *}\put(21,16){icnod6} +\put(39,69){\Large\tt *}\put(41,71){icnod7} +\put(19,49){\Large\tt *}\put(21,51){icnod8} +\put(35,40){{\Large\tt *} icnod9} +\put(5.8,20){icnod10 {\Large\tt *}} +\put(75,40){{\Large\tt *} icnod11} +\put(45.8,20){icnod12 {\Large\tt *}} +\end{picture} +\end{center} +\caption{Local vertex numbering convention $(\bullet)$ +and the first node along each edge $(\star)$.} +\label{fig:vertices} +\end{figure} + +\clearpage +For each patch, the input is as follows (see Figures~\ref{fig:vertices}, +\ref{fig:edges} and~\ref{fig:faces} for the interpretation of the variables +ICNOD$i$, ISNOD$i$ and IINOD$i$): + +\vskip2mm\hskip\parindent +\fbox{% +\begin{minipage}{8cm} +\begin{tabbing} XXXXXX \= XXXXXX \= XXX \= XXXXXX \= \kill +IBLOCK \\ +IBNOD1 \> IBNOD2 \> ... \> IBNOD8 \\ +ICNOD1 \> INCR1 \\ +ICNOD2 \> INCR2 \\ +... \\ +ICNOD12 \> INCR12 \\ +ISNOD1 \> INCI1 \> INCJ1 \\ +ISNOD2 \> INCI2 \> INCJ2 \\ +... \\ +ISNOD6 \> INCI6 \> INCJ6 \\ +IINOD1 +\end{tabbing} +\end{minipage}} + +\begin{figure}[htb] +\begin{center} +\setlength{\unitlength}{3.8pt} +\begin{picture}(95,85) +\put( 0, 0){\cube} +\put(15,15){\edges} +\put(25,28){\bf 1} +\put(69,23){\bf 2} +\put(25,63){\bf 3} +\put(69,58){\bf 4} +\put(50,37){\bf 5} +\put(37,17){\bf 6} +\put(57,73){\bf 7} +\put(41,52){\bf 8} +\put(32,54){\bf 9} +\put(10,34){\bf 10} +\put(79,54){\bf 11} +\put(59,29){\bf 12} +% Mark the nodes +\put(38,37){{\Large\tt *} isnod1} +\put(17,17){\colorbox{white}{\Large\tt *}isnod2} +\put(24,35){isnod3 {\Large\tt *}} +\put(70.6,34){\colorbox{white}{\Large\tt *}}\put(64,36){isnod4} +\put(36,31){{\Large\tt *} isnod5} +\put(35,66){\colorbox{white}{\Large\tt *}isnod6} +\end{picture} +\end{center} +\caption{Local edge numbering convention $(\bullet)$ +and the first node on each surface $(\star)$.} +\label{fig:edges} +\end{figure} + +\begin{namelist}{IBLOCK} +\item[IBLOCK] Spline patch index +\item[IBNOD$i$] Global node number of vertex $i$ +\item[ICNOD$i$] Global node number of second point along edge $i$ +\item[INCR$i$] Increment in global numbering along the edge ($\pm1$) +\item[ISNOD$i$] Global node number of first interior point on face $i$ +\item[INCI$i$] Increment in global numbering in local $I$-direction on the face ($\pm1$) +\item[INCJ$i$] Increment in global numbering in local $J$-direction on the face ($\pm1$) +\item[IINOD1] Global node number of the first interior point of the patch +\end{namelist} + +The local $I$ and $J$ directions for a face are defined as the two remaining +directions when removing the index defining the normal direction of that face +from the $I-J-K$ triplet. +That is, for local faces 1 and 2, the local $I-J$ directions correspond the +``global'' $J-K$ directions (depicted in Figure~\ref{fig:faces}). +For local faces 3 and 4, the local $I-J$ directions correspond to the ``global'' +$I-K$ directions, respectively, whereas for local faces 5 and 6 they coincide +with the global $I-J$ directions. + +\begin{figure}[htb] +\begin{center} +\setlength{\unitlength}{3.8pt} +\begin{picture}(95,85) +\put(0,0){\cube} +\put(15,15){\faces} +\put(55,54){\bf 1} +\put(35,29){\bf 2} +\put(25,39){\bf 3} +\put(65,39){\bf 4} +\put(45,24){\bf 5} +\put(45,59){\bf 6} +% Mark the nodes +\put(37.2,32.8){\colorbox{white}{\Large\tt *}} +\put(40,36){iinod1} +\end{picture} +\end{center} +\caption{Local face numbering convention $(\circ)$ +and the first interior node $(\star)$.} +\label{fig:faces} +\end{figure} + +\section{Properties and boundary conditions} + +All physical properties defined in the GPM-module are mapped onto the +spline model through a set of user-defined codes. +The actual interpretation of each code is defined within SIM itself, +via a separate input file. +The property codes are specified through the following syntax: + +\vskip2mm\hskip\parindent\fbox{PCODE~~~IBLOCK~~~LDIM~~~LINDEX} + +\begin{namelist}{IBLOCK} +\item[PCODE] Property or boundary condition code +(either a string or an integer value) +\item[IBLOCK] Spline patch index +\item[LDIM] Local entity dimension flag (0, 1, 2, or 3) +\item[LINDEX] Local entity index which is assigned the property +\begin{itemize} +\item Local vertex if LDIM = 0 +\item Local edge if LDIM = 1 +\item Local face if LDIM = 2 +\item Not referenced if LDIM = 3 +\end{itemize} +\end{namelist} + +The local ordering of the vertices, edges and faces follows the convention +defined in Figures~\ref{fig:vertices}, \ref{fig:edges} and~\ref{fig:faces}, +respectively. +\end{document} diff --git a/src/ASM/ASMbase.C b/src/ASM/ASMbase.C new file mode 100644 index 00000000..78fa24f6 --- /dev/null +++ b/src/ASM/ASMbase.C @@ -0,0 +1,503 @@ +// $Id: ASMbase.C,v 1.13 2010-12-29 18:02:10 kmo Exp $ +//============================================================================== +//! +//! \file ASMbase.C +//! +//! \date Sep 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for spline-based FE assembly drivers. +//! +//============================================================================== + +#include "ASMbase.h" +#include "MPC.h" +#include "Vec3.h" +#include "Utilities.h" +#include + + +bool ASMbase::fixHomogeneousDirichlet = true; + + +ASMbase::ASMbase (unsigned char n_p, unsigned char n_s, unsigned char n_f) +{ + nf = n_f; + nsd = n_s > 3 ? 3 : n_s; + ndim = n_p > nsd ? nsd : n_p; +} + + +void ASMbase::clear () +{ + // Don't erase the elements, but set them to have zero nodes + for (size_t i = 0; i < MNPC.size(); i++) MNPC[i].clear(); + + // Erase the nodes, boundary conditions and multi-point constraints + MLGN.clear(); + BCode.clear(); + mpcs.clear(); +} + + +size_t ASMbase::getNodeIndex (int globalNum) const +{ + IntVec::const_iterator it = find(MLGN.begin(),MLGN.end(),globalNum); + if (it == MLGN.end()) return 0; + + return 1 + (it-MLGN.begin()); +} + + +int ASMbase::getNodeID (size_t inod) const +{ + if (inod < 1 || inod > MLGN.size()) + return 0; + + return MLGN[inod-1]; +} + + +int ASMbase::getElmID (size_t iel) const +{ + if (iel < 1 || iel > MLGE.size()) + return 0; + + return MLGE[iel-1]; +} + + +size_t ASMbase::getNoElms (bool includeZeroVolumeElms) const +{ + if (includeZeroVolumeElms) return MLGE.size(); + + size_t nel = 0; + for (size_t i = 0; i < MLGE.size(); i++) + if (MLGE[i] > 0) nel++; + + return nel; +} + + +void ASMbase::printNodes (std::ostream& os, const char* heading) const +{ + Matrix X; + this->getNodalCoordinates(X); + if (heading) os << heading; + for (size_t inod = 1; inod <= X.cols(); inod++) + { + os <<'\n'<< MLGN[inod-1] <<':'; + for (size_t i = 1; i <= X.rows(); i++) + os <<' '<< X(i,inod); + } + os << std::endl; +} + + +/*! + \brief An unary function that checks whether a DOF object matches the fixed + status of a BC object. +*/ + +class fixed : public std::unary_function +{ + const MPC::DOF& myDof; //!< The DOF object to compare with + +public: + //! \brief Constructor initializing the myDof reference. + fixed(const MPC::DOF& slaveDof) : myDof(slaveDof) {} + //! \brief Returns \e true if \a myDof has the same fixed status as \a bc. + bool operator()(const ASMbase::BC& bc) + { + if (bc.node == myDof.node) + switch (myDof.dof) + { + case 1: return bc.CX == 0; + case 2: return bc.CY == 0; + case 3: return bc.CZ == 0; + } + return false; + } +}; + + +bool ASMbase::addMPC (MPC* mpc, int code) +{ + if (!mpc) return true; + + // Silently ignore MPC's on dofs that already are marked as FIXED + bool retVal = true; + if (find_if(BCode.begin(),BCode.end(),fixed(mpc->getSlave())) == BCode.end()) + if (mpcs.insert(mpc).second) + { +#if SP_DEBUG > 1 + std::cout <<"Added constraint: "<< *mpc; +#endif + if (code > 0) dCode[mpc] = code; + return retVal; + } + else + { +#ifdef SP_DEBUG + std::cout <<"Ignored constraint (duplicated slave): "<< *mpc; +#endif + retVal = false; // This dof is already a slave in another MPC + } + + delete mpc; + return retVal; +} + + +bool ASMbase::addSPC (int node, int dir, int code) +{ + if (dir < 1 || dir > nsd) return true; + return this->addMPC(new MPC(node,dir), code); +} + + +bool ASMbase::addPeriodicity (size_t master, size_t slave, int dir) +{ + if (dir < 1 || dir > nsd) return true; + + int slaveNode = this->getNodeID(slave); + int masterNode = this->getNodeID(master); + if (slaveNode < 1 || masterNode < 1) + { + std::cerr <<" *** ASMbase::addPeriodicity: Invalid node indices " + << master <<", "<< slave << std::endl; + return false; + } + + MPC* mpc = new MPC(slaveNode,dir); + mpc->addMaster(masterNode,dir); + if (this->addMPC(mpc)) + return true; + + // Try to swap master and slave + mpc = new MPC(masterNode,dir); + mpc->addMaster(slaveNode,dir); + if (this->addMPC(mpc)) + return true; + + std::cerr <<" *** ASMbase::addPeriodicity: Failed to connect nodes " + << masterNode <<" and "<< slaveNode <<" in direction " + << dir << std::endl; + return false; +} + + +void ASMbase::makePeriodic (size_t master, size_t slave, int dirs) +{ + switch (dirs) + { + case 1: + case 2: + case 3: + this->addPeriodicity(master,slave,dirs); + break; + case 12: + case 21: + for (int dir = 1; dir <= 2; dir++) + this->addPeriodicity(master,slave,dir); + break; + case 13: + case 31: + for (int dir = 1; dir <= 3; dir += 2) + this->addPeriodicity(master,slave,dir); + break; + case 23: + case 32: + for (int dir = 2; dir <= 3; dir++) + this->addPeriodicity(master,slave,dir); + break; + default: + for (int dir = 1; dir <= 3; dir++) + this->addPeriodicity(master,slave,dir); + } +} + + +void ASMbase::prescribe (size_t inod, int dirs, int code) +{ + if (code == 0 && fixHomogeneousDirichlet) + return this->fix(inod,dirs); + + int node = this->getNodeID(inod); + if (node < 1) return; + + switch (dirs) + { + case 1: + case 2: + case 3: + this->addSPC(node,dirs,code); + break; + case 12: + case 21: + for (int dir = 1; dir <= 2; dir++) + this->addSPC(node,dir,code); + break; + case 13: + case 31: + for (int dir = 1; dir <= 3; dir += 2) + this->addSPC(node,dir,code); + break; + case 23: + case 32: + for (int dir = 2; dir <= 3; dir++) + this->addSPC(node,dir,code); + break; + default: + for (int dir = 1; dir <= 3; dir++) + this->addSPC(node,dir,code); + } +} + + +void ASMbase::fix (size_t inod, int dirs) +{ + int node = this->getNodeID(inod); + if (node < 1) return; + + switch (dirs) + { + case 1: + BCode.push_back(BC(node,0,1,1)); + break; + case 2: + BCode.push_back(BC(node,1,0,1)); + break; + case 3: + BCode.push_back(BC(node,1,1,0)); + break; + case 12: + case 21: + BCode.push_back(BC(node,0,0,1)); + break; + case 13: + case 31: + BCode.push_back(BC(node,0,1,0)); + break; + case 23: + case 32: + BCode.push_back(BC(node,1,0,0)); + break; + default: + BCode.push_back(BC(node,0,0,0)); + } + +#if SP_DEBUG > 1 + std::cout <<"\tFixed node: "<< node <<" "<< dirs << std::endl; +#endif +} + + +void ASMbase::resolveMPCchains (const ASMVec& model) +{ + MPCSet allMPCs; + for (size_t i = 0; i < model.size(); i++) + allMPCs.insert(model[i]->begin_MPC(),model[i]->end_MPC()); + + int nresolved = 0; + for (MPCSet::iterator cit = allMPCs.begin(); cit != allMPCs.end(); cit++) + if (ASMbase::resolveMPCchain(allMPCs,*cit)) nresolved++; + + if (nresolved > 0) + std::cout <<"Resolved "<< nresolved <<" MPC chains."<< std::endl; +} + + +// Recursive method to resolve (possibly multi-level) chaining in multi-point +// constraint equations (MPCs). If a master dof in one MPC is specified as a +// slave by another MPC, it is replaced by the master(s) of that other equation. + +bool ASMbase::resolveMPCchain (const MPCSet& allMPCs, MPC* mpc) +{ + bool resolved = false; + for (size_t i = 0; i < mpc->getNoMaster();) + { + MPC master(mpc->getMaster(i).node,mpc->getMaster(i).dof); + MPCSet::iterator cit = allMPCs.find(&master); + if (cit != allMPCs.end()) + { + // We have a master dof which is a slave in another constraint equation. + // Invoke resolveMPCchain recursively to ensure that all master dofs + // of that equation are not slaves themselves. + ASMbase::resolveMPCchain(allMPCs,*cit); + + // Remove current master specification + double coeff = mpc->getMaster(i).coeff; + mpc->removeMaster(i); + + // Add constant offset from the other equation + mpc->addOffset(coeff*(*cit)->getSlave().coeff); + + // Add masters from the other equations + for (size_t j = 0; j < (*cit)->getNoMaster(); j++) + mpc->addMaster((*cit)->getMaster(j).node, + (*cit)->getMaster(j).dof, + (*cit)->getMaster(j).coeff*coeff); + resolved = true; + } + else + i++; + } + +#if SP_DEBUG > 1 + if (resolved) std::cout <<"Resolved constraint: "<< *mpc; +#endif + return resolved; +} + + +bool ASMbase::updateDirichlet (const std::map& func, double time) +{ + std::map::const_iterator fit; + for (MPCMap::iterator cit = dCode.begin(); cit != dCode.end(); cit++) + if ((fit = func.find(cit->second)) == func.end()) + { + std::cerr <<" *** ASMbase::updateDirichlet: Code "<< cit->second + <<" is not associated with a scalar function"<< std::endl; + return false; + } + else + { + size_t inod = this->getNodeIndex(cit->first->getSlave().node); + if (inod < 1) return false; + + RealFunc& g = *fit->second; + Vec4 X(this->getCoord(inod),time); + cit->first->setSlaveCoeff(g(X)); + } + + return true; +} + + +void ASMbase::collapseNodes (int& node1, int& node2) +{ + if (node1 > node2) + node1 = node2; + else if (node2 > node1) + node2 = node1; +} + + +bool ASMbase::mergeNodes (size_t inod, int globalNum) +{ + if (inod < 1 || inod > MLGN.size()) + return false; + else if (MLGN[--inod] <= globalNum) + return false; + + MLGN[inod] = globalNum; + return true; +} + + +int ASMbase::renumberNodes (const ASMVec& model, IntVec* l2gn) +{ + int nnod = 0; + int renum = 0; + size_t i, j; + std::map old2new; + for (i = 0; i < model.size(); i++) + for (j = 0; j < model[i]->MLGN.size(); j++) + if (utl::renumber(model[i]->MLGN[j],nnod,old2new)) + renum++; + + if (renum > 0) + { + for (i = 0; i < model.size(); i++) + model[i]->renumberNodes(old2new,false); + std::cout <<"\nRenumbered "<< renum <<" nodes"<< std::endl; + } + + if (l2gn) + { + l2gn->resize(old2new.size(),0); + std::map::const_iterator it; + for (it = old2new.begin(); it != old2new.end(); it++) + (*l2gn)[it->second-1] = it->first; + } + + return nnod; +} + + +bool ASMbase::renumberNodes (const std::map& old2new, bool silent) +{ +#ifdef SP_DEBUG + bool printInvalidNodes = true; +#else + bool printInvalidNodes = false; +#endif + + int invalid = 0; + for (size_t j = 0; j < BCode.size(); j++) + if (!utl::renumber(BCode[j].node,old2new,printInvalidNodes)) + invalid++; + + for (MPCSet::iterator mit = mpcs.begin(); mit != mpcs.end(); mit++) + invalid += (*mit)->renumberNodes(old2new,printInvalidNodes); + + if (invalid == 0 || silent) return true; + + std::cerr <<" *** "<< invalid <<" invalid nodes found while renumbering\n"; + return false; +} + + +void ASMbase::extractElmRes (const Matrix& globRes, Matrix& elmRes) const +{ + elmRes.resize(globRes.rows(),MLGE.size(),true); + + size_t i, iel, ivel = 0; + for (iel = 0; iel < MLGE.size(); iel++) + if (MLGE[iel] > 0) + for (++ivel, i = 1; i <= globRes.rows(); i++) + elmRes(i,ivel) = globRes(i,MLGE[iel]); + + elmRes.resize(globRes.rows(),ivel); +} + + +void ASMbase::extractNodeVec (const Vector& globRes, Vector& nodeVec, + unsigned char nndof) const +{ + if (nndof == 0) nndof = nf; + + nodeVec.resize(nndof*MLGN.size()); + for (size_t i = 0; i < MLGN.size(); i++) + { + int n = MLGN[i]-1; + for (unsigned char j = 0; j < nndof; j++) + nodeVec[nndof*i+j] = globRes[nndof*n+j]; + } +} + + +bool ASMbase::tesselate (ElementBlock&, const int*) const +{ + std::cerr <<" *** ASMBase::tesselate: Must be implemented in sub-class." + << std::endl; + return false; +} + + +bool ASMbase::evalSolution (Matrix&, const Vector&, const int*) const +{ + std::cerr <<" *** ASMBase::evalSolution: Must be implemented in sub-class." + << std::endl; + return false; +} + + +bool ASMbase::evalSolution (Matrix&, const Integrand&, const int*) const +{ + std::cerr <<" *** ASMBase::evalSolution: Must be implemented in sub-class." + << std::endl; + return false; +} diff --git a/src/ASM/ASMbase.h b/src/ASM/ASMbase.h new file mode 100644 index 00000000..31e84e25 --- /dev/null +++ b/src/ASM/ASMbase.h @@ -0,0 +1,328 @@ +// $Id: ASMbase.h,v 1.19 2011-01-28 13:34:28 kmo Exp $ +//============================================================================== +//! +//! \file ASMbase.h +//! +//! \date Sep 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for spline-based FE assembly drivers. +//! +//============================================================================== + +#ifndef _ASM_BASE_H +#define _ASM_BASE_H + +#include "MatVec.h" +#include "MPCLess.h" +#include "Function.h" +#include +#include +#include + +typedef std::vector IntVec; //!< General integer vector +typedef std::vector IntMat; //!< General 2D integer matrix +typedef std::set MPCSet; //!< Sorted set of MPC equations +typedef std::map MPCMap; //!< MPC to function code mapping +typedef MPCSet::const_iterator MPCIter; //!< Iterator over an MPC equation set + +struct TimeDomain; +struct ElementBlock; +class GlobalIntegral; +class LocalIntegral; +class Integrand; +class ASMbase; +class Vec3; + +typedef std::vector LintegralVec; //!< Local integral container +typedef std::vector ASMVec; //!< Spline patch container + + +/*! + \brief Base class for spline-based finite element assembly drivers. + + \details This class incapsulates the data and methods needed for assembling + the algebraic equation system resulting from a finite element discretization + of a set of partial differential equations using splines as basis functions. + + The class does not contain any problem-specific data or methods. + The methods that need access to problem information are provided that through + Integrand objects that are passed as arguments to those methods. +*/ + +class ASMbase +{ +public: + //! \brief Struct for boundary condition codes. + struct BC + { + int node; //!< Global node number of the constrained node + char CX; //!< Boundary condition code for X-translation + char CY; //!< Boundary condition code for Y-translation + char CZ; //!< Boundary condition code for Z-translation + //! \brief Constructor initializing a BC instance. + BC(int n, char x, char y, char z) : node(n), CX(x), CY(y), CZ(z) {} + }; + + typedef std::vector BCVec; //!< Nodal boundary condition container + +protected: + //! \brief The constructor sets the number of space dimensions and fields. + //! \param[in] n_p Number of parameter dimensions + //! \param[in] n_s Number of spatial dimensions + //! \param[in] n_f Number of primary solution fields + ASMbase(unsigned char n_p, unsigned char n_s, unsigned char n_f); + +public: + //! \brief Empty destructor. + virtual ~ASMbase() {} + + //! \brief Checks if this patch is empty. + virtual bool empty() const = 0; + + //! \brief Generates the finite element topology data for this patch. + virtual bool generateFEMTopology() = 0; + + //! \brief Clears the contents of this patch, making it empty. + virtual void clear(); + + //! \brief Writes the geometry of the patch to the given stream. + virtual bool write(std::ostream&) const { return false; } + + + // Service methods for query of various model data + // =============================================== + + //! \brief Returns the number of spatial dimensions. + unsigned char getNoSpaceDim() const { return nsd; } + //! \brief Returns the number of parameter dimensions. + unsigned char getNoParamDim() const { return ndim; } + //! \brief Returns the number of solution fields. + virtual unsigned char getNoFields(int = 0) const { return nf; } + + //! \brief Returns local 1-based index of the node with given global number. + //! \details If the given node number is not present, 0 is returned. + //! \param[in] globalNum Global node number + size_t getNodeIndex(int globalNum) const; + //! \brief Returns the global node number for the given node. + //! \param[in] inod 1-based node index local to current patch + int getNodeID(size_t inod) const; + //! \brief Returns the global element number for the given element + //! \param[in] iel 1-based element index local to current patch + int getElmID(size_t iel) const; + //! \brief Returns the number of DOFs per node. + virtual unsigned char getNodalDOFs(size_t) const { return nf; } + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const = 0; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X nsd\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const = 0; + //! \brief Prints out the nodal coordinates of this patch to the given stream. + void printNodes(std::ostream& os, const char* heading = 0) const; + + //! \brief Returns the total number of nodes in this patch. + size_t getNoNodes() const { return MLGN.size(); } + //! \brief Returns the total number of elements in this patch. + size_t getNoElms(bool includeZeroVolumeElms = false) const; + //! \brief Returns the total number of MPC equations in this patch. + size_t getNoMPCs() const { return mpcs.size(); } + + //! \brief Returns the beginning of the BC array. + BCVec::const_iterator begin_BC() const { return BCode.begin(); } + //! \brief Returns the end of the BC array. + BCVec::const_iterator end_BC() const { return BCode.end(); } + + //! \brief Returns the beginning of the MNPC array. + IntMat::const_iterator begin_elm() const { return MNPC.begin(); } + //! \brief Returns the end of the MNPC array. + IntMat::const_iterator end_elm() const { return MNPC.end(); } + + //! \brief Returns the beginning of the MPC set. + MPCIter begin_MPC() const { return mpcs.begin(); } + //! \brief Returns the end of the MPC set. + MPCIter end_MPC() const { return mpcs.end(); } + + + // Various preprocessing methods + // ============================= + + //! \brief Merges a given node in this patch with a given global node. + //! \param[in] inod 1-based node index local to current patch + //! \param[in] globalNum Global number of the node to merge \a node with + bool mergeNodes(size_t inod, int globalNum); + + //! \brief Renumbers the global node numbers referred by this patch. + //! \details The node numbers referred by boundary condition and + //! multi-point constraint objects in the patch are renumbered. + //! The nodes themselves are assumed already to be up to date. + //! \param[in] old2new Old-to-new node number mapping + //! \param[in] silent If \e false, give error on missing node in \a old2new + bool renumberNodes(const std::map& old2new, bool silent); + + //! \brief Renumbers all global node numbers in the entire model. + //! \param[in] model All spline patches in the model + //! \param[out] l2gn Local-to-global node numbers (optional) + //! \return The number of unique nodes in the model + //! + //! \details After the renumbering, the global node numbers are in the range + //! [1,\a nnod ], where \a nnod is the number of unique nodes in the model. + static int renumberNodes(const ASMVec& model, IntVec* l2gn = 0); + + //! \brief Resolves (possibly multi-level) chaining in MPC equations. + //! \param[in] model All spline patches in the model + //! + //! \details If a master DOF in one MPC is specified as slave by another MPC, + //! it is replaced by the master(s) of that other equation. + //! Since an MPC-equation may couple nodes belonging to different patches, + //! this method must have all patches in the model available. + static void resolveMPCchains(const ASMVec& model); + + //! \brief Updates the time-dependent in-homogeneous Dirichlet coefficients. + //! \param[in] func Scalar property fields + //! \param[in] time Current time + bool updateDirichlet(const std::map& func, double time = 0.0); + + //! \brief Initializes the patch level MADOF array for mixed problems. + virtual void initMADOF(const int*) {} + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()) = 0; + + //! \brief Evaluates a boundary integral over a patch face/edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary face/edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()) = 0; + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lEdge Local index of the patch edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + virtual bool integrateEdge(Integrand& integrand, int lEdge, + GlobalIntegral& glbInt, + const TimeDomain& time) { return false; } + + + // Post-processing methods + // ======================= + + //! \brief Creates a standard FE model of this patch for visualization. + //! \param[out] grid The generated finite element grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector local to current patch + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + //! + //! \details The secondary solution is derived from the primary solution, + //! which is assumed to be stored in the \a integrand for current patch. + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + + + // Methods for result extraction + // ============================= + + //! \brief Extracts element results for this patch from a global vector. + //! \param[in] globRes Global matrix of element results + //! \param[out] elmRes Element results for this patch + void extractElmRes(const Matrix& globRes, Matrix& elmRes) const; + + //! \brief Extracts nodal results for this patch from the global vector. + //! \param[in] globVec Global solution vector in DOF-order + //! \param[out] nodeVec Nodal result vector for this patch + //! \param[in] nndof Number of DOFs per node (the default is \a nf) + virtual void extractNodeVec(const Vector& globVec, Vector& nodeVec, + unsigned char nndof = 0) const; + +protected: + + // Internal methods for preprocessing of boundary conditions + // ========================================================= + + //! \brief Adds a general multi-point-constraint equation to this patch. + //! \param[in] mpc Pointer to an MPC-object + //! \param[in] code Identifier for inhomogeneous Dirichlet condition field + bool addMPC(MPC* mpc, int code = 0); + //! \brief Creates and adds a single-point constraint to this patch. + //! \param[in] node Global node number of the node to constrain + //! \param[in] dir Which local DOF to constrain (1, 2, 3) + //! \param[in] code Identifier for inhomogeneous Dirichlet condition field + bool addSPC(int node, int dir, int code); + //! \brief Creates and adds a periodicity constraint to this patch. + //! \param[in] master 1-based local index of the master node + //! \param[in] slave 1-based local index of the slave node to constrain + //! \param[in] dir Which local DOF to constrain (1, 2, 3) + bool addPeriodicity(size_t master, size_t slave, int dir); + //! \brief Creates periodicity constraints between two nodes in this patch. + //! \param[in] master 1-based local index of the master node + //! \param[in] slave 1-based local index of the slave node to constrain + //! \param[in] dirs Which local DOFs to constrain (1, 2, 3, 12, 23, 123) + void makePeriodic(size_t master, size_t slave, int dirs = 123); + //! \brief Constrains DOFs in the given node to the given value. + //! \param[in] inod 1-based node index local to current patch + //! \param[in] dirs Which local DOFs to constrain (1, 2, 3, 12, 23, 123) + //! \param[in] code Identifier for inhomogeneous Dirichlet condition field + void prescribe(size_t inod, int dirs, int code); + //! \brief Constrains DOFs in the given node to zero. + //! \param[in] inod 1-based node index local to current patch + //! \param[in] dirs Which local DOFs to constrain (1, 2, 3, 12, 23, 123) + void fix(size_t inod, int dirs = 123); + + //! \brief Recursive method used by \a resolveMPCchains. + //! \param[in] allMPCs All multi-point constraint equations in the model + //! \param mpc Pointer to the multi-point constraint equation to resolve + static bool resolveMPCchain(const MPCSet& allMPCs, MPC* mpc); + + //! \brief Collapses the given two nodes into one. + //! \details The global node number of the node with the highest number + //! is changed into the number of the other node. + static void collapseNodes(int& node1, int& node2); + +public: + static bool fixHomogeneousDirichlet; //!< If \e true, pre-eliminate fixed DOFs + +protected: + // Standard finite element data structures + unsigned char ndim; //!< Number of parametric dimensions (1, 2 or 3) + unsigned char nsd; //!< Number of space dimensions (ndim <= nsd <= 3) + unsigned char nf; //!< Number of primary solution fields (1 or larger) + + IntVec MLGE; //!< Matrix of Local to Global Element numbers + IntVec MLGN; //!< Matrix of Local to Global Node numbers + IntMat MNPC; //!< Matrix of Nodal Point Correspondance + BCVec BCode; //!< Vector of Boundary condition codes + MPCSet mpcs; //!< All multi-point constraints with slave in this patch + MPCMap dCode; //!< Inhomogeneous Dirichlet condition codes for the MPCs +}; + +#endif diff --git a/src/ASM/ASMmxBase.C b/src/ASM/ASMmxBase.C new file mode 100644 index 00000000..9f914a1c --- /dev/null +++ b/src/ASM/ASMmxBase.C @@ -0,0 +1,53 @@ +// $Id: ASMmxBase.C,v 1.1 2010-12-29 18:41:38 kmo Exp $ +//============================================================================== +//! +//! \file ASMmxBase.C +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for spline-based mixed finite element assembly drivers. +//! +//============================================================================== + +#include "ASMmxBase.h" + + +ASMmxBase::ASMmxBase (unsigned char n_f1, unsigned char n_f2, bool geo1) +{ + nf1 = n_f1; + nf2 = n_f2; + + geoUsesBasis1 = geo1; +} + + +void ASMmxBase::init (const std::vector& MLGN, const int* sysMadof) +{ + MADOF.resize(MLGN.size()); + for (size_t i = 0; i < MADOF.size(); i++) + MADOF[i] = sysMadof[MLGN[i]-1]-1; +} + + +void ASMmxBase::extrNodeVec (const Vector& globRes, Vector& nodeVec) const +{ + nodeVec.resize(nf1*nb1 + nf2*nb2); + + size_t i, j; + int idof, ldof = 0; + for (i = 0; i < nb1; i++) + { + idof = MADOF[i]; + for (j = 0; j < nf1; j++, ldof++) + nodeVec[ldof] = globRes[idof++]; + } + + for (i = nb1; i < nb1+nb2; i++) + { + idof = MADOF[i]; + for (j = 0; j < nf2; j++, ldof++) + nodeVec[ldof] = globRes[idof++]; + } +} diff --git a/src/ASM/ASMmxBase.h b/src/ASM/ASMmxBase.h new file mode 100644 index 00000000..029e140e --- /dev/null +++ b/src/ASM/ASMmxBase.h @@ -0,0 +1,56 @@ +// $Id: ASMmxBase.h,v 1.1 2010-12-29 18:41:38 kmo Exp $ +//============================================================================== +//! +//! \file ASMmxBase.h +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for spline-based mixed finite element assembly drivers. +//! +//============================================================================== + +#ifndef _ASM_MX_BASE_H +#define _ASM_MX_BASE_H + +#include "MatVec.h" + + +/*! + \brief Base class for spline-based mixed finite element assembly drivers. +*/ + +class ASMmxBase +{ +protected: + //! \brief The constructor sets the number of field variables. + //! \param[in] n_f1 Number of nodal variables in field 1 + //! \param[in] n_f2 Number of nodal variables in field 2 + //! \param[in] geo1 If \e true, the geometry is represented by the first basis + ASMmxBase(unsigned char n_f1, unsigned char n_f2, bool geo1 = true); + + //! \brief Initializes the patch level MADOF array. + //! \param[in] MLGN Matrix of local-to-global node numbers + //! \param[out] sysMadof System-level matrix of accumulated DOFs + void init(const std::vector& MLGN, const int* sysMadof); + + //! \brief Extracts nodal results for this patch from the global vector. + //! \param[in] globVec Global solution vector in DOF-order + //! \param[out] nodeVec Nodal result vector for this patch + void extrNodeVec(const Vector& globVec, Vector& nodeVec) const; + +private: + std::vector MADOF; //!< Matrix of accumulated DOFs for this patch + +protected: + size_t nb1; //!< Number of basis functions in first basis + size_t nb2; //!< Number of basis functions in second basis + + unsigned char nf1; //!< Number of solution fields using first basis + unsigned char nf2; //!< Number of solution fields using second basis + + bool geoUsesBasis1; //!< If \e true, the first basis represents the geometry +}; + +#endif diff --git a/src/ASM/ASMs1D.C b/src/ASM/ASMs1D.C new file mode 100644 index 00000000..35881185 --- /dev/null +++ b/src/ASM/ASMs1D.C @@ -0,0 +1,776 @@ +// $Id: ASMs1D.C,v 1.8 2011-01-05 12:49:41 kmo Exp $ +//============================================================================== +//! +//! \file ASMs1D.C +//! +//! \date Apr 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 1D spline FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineCurve.h" +#include "GoTools/geometry/ObjectHeader.h" + +#include "ASMs1D.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "ElementBlock.h" +#include "Utilities.h" +#include "Vec3Oper.h" +#include +#include + +typedef std::vector DoubleVec; //!< 1D double array +typedef DoubleVec::const_iterator DoubleIter; //!< Iterator over DoubleVec + + +ASMs1D::ASMs1D (const char* fileName, unsigned char n_s, unsigned char n_f) + : ASMstruct(1,n_s,n_f) +{ + curv = 0; + + std::cout <<"\nReading patch file "<< fileName << std::endl; + std::ifstream is(fileName); + if (!is.good()) + std::cerr <<" *** ASMs1D: Failure opening patch file"<< std::endl; + else + this->read(is); + + geo = curv; +} + + +ASMs1D::ASMs1D (std::istream& is, unsigned char n_s, unsigned char n_f) + : ASMstruct(1,n_s,n_f) +{ + curv = 0; + + this->read(is); + + geo = curv; +} + + +ASMs1D::ASMs1D (unsigned char n_s, unsigned char n_f) + : ASMstruct(1,n_s,n_f) +{ + curv = 0; +} + + +bool ASMs1D::read (std::istream& is) +{ + if (curv) delete curv; + + Go::ObjectHeader head; + curv = new Go::SplineCurve; + is >> head >> *curv; + + // Eat white-space characters to see if there is more data to read + char c; + while (is.get(c)) + if (!isspace(c)) + { + is.putback(c); + break; + } + + if (!is.good() && !is.eof()) + { + std::cerr <<" *** ASMs1D::read: Failure reading spline data"<< std::endl; + delete curv; + curv = 0; + return false; + } + else if (curv->dimension() < 1) + { + std::cerr <<" *** ASMs1D::read: Invalid spline curve patch, dim=" + << curv->dimension() << std::endl; + return false; + } + else if (curv->dimension() < nsd) + { + std::cout <<" ** ASMs1D::read: The dimension of this curve patch " + << curv->dimension() <<" is less than nsd="<< nsd + <<".\n Resetting nsd to "<< curv->dimension() + <<" for this patch."<< std::endl; + nsd = curv->dimension(); + } + + return true; +} + + +bool ASMs1D::write (std::ostream& os) const +{ + if (!curv) return false; + + os <<"100 1 0 0\n"; + os << *curv; + + return os.good(); +} + + +void ASMs1D::clear () +{ + // Erase spline data + if (curv) delete curv; + curv = 0; + geo = 0; + + // Erase the FE data + ASMbase::clear(); +} + + + +bool ASMs1D::refine (const RealArray& xi) +{ + if (!curv || xi.empty()) return false; + if (xi.front() < 0.0 || xi.back() > 1.0) return false; + + DoubleVec extraKnots; + DoubleIter uit = curv->basis().begin(); + double ucurr, uprev = *(uit++); + while (uit != curv->basis().end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (size_t i = 0; i < xi.size(); i++) + if (i > 0 && xi[i] < xi[i-1]) + return false; + else + extraKnots.push_back(ucurr*xi[i] + uprev*(1.0-xi[i])); + + uprev = ucurr; + } + + curv->insertKnot(extraKnots); + return true; +} + + +bool ASMs1D::uniformRefine (int nInsert) +{ + if (!curv || nInsert < 1) return false; + + DoubleVec extraKnots; + DoubleIter uit = curv->basis().begin(); + double ucurr, uprev = *(uit++); + while (uit != curv->basis().end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nInsert; i++) + { + double xi = (double)(i+1)/(double)(nInsert+1); + extraKnots.push_back(ucurr*xi + uprev*(1.0-xi)); + } + uprev = ucurr; + } + + curv->insertKnot(extraKnots); + return true; +} + + +bool ASMs1D::raiseOrder (int ru) +{ + if (!curv) return false; + + curv->raiseOrder(ru); + return true; +} + + +bool ASMs1D::generateFEMTopology () +{ + if (!curv) return false; + + const int n1 = curv->numCoefs(); + if (!MLGN.empty()) + { + if (MLGN.size() == n1) return true; + std::cerr <<" *** ASMs1D::generateFEMTopology: Inconsistency between the" + <<" number of FE nodes "<< MLGN.size() + <<"\n and the number of spline coefficients "<< n1 + <<" in the patch."<< std::endl; + return false; + } + + const int p1 = curv->order(); +#ifdef SP_DEBUG + std::cout <<"numCoefs: "<< n1; + std::cout <<"\norder: "<< p1; + std::cout <<"\ndu:"; + for (int i = 0; i < n1; i++) + std::cout <<' '<< this->getKnotSpan(i); + std::cout << std::endl; +#endif + // Consistency checks, just to be fool-proof + if (n1 < 2) return false; + if (p1 < 1) return false; + if (p1 > n1) return false; + + MLGE.resize(n1-p1+1,0); + MLGN.resize(n1); + MNPC.resize(MLGE.size()); + + int iel = 0; + int inod = 0; + for (int i1 = 1; i1 <= n1; i1++) + { + if (i1 >= p1) + { + if (this->getKnotSpan(i1-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1,0); + + int lnod = 0; + for (int j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - j1; + } + + iel++; + } + MLGN[inod++] = ++gNod; // global node number over all patches + } + +#ifdef SP_DEBUG + std::cout <<"NEL = "<< iel <<" NNOD = "<< inod << std::endl; +#endif + return true; +} + + +bool ASMs1D::connectPatch (int vertex, ASMs1D& neighbor, int nvertex) +{ + // Set up the slave node number for this curve patch + + int n1 = this->getSize(); + int node = 1; + + switch (vertex) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + break; + + default: + std::cerr <<" *** ASMs1D::connectPatch: Invalid slave vertex " + << vertex << std::endl; + return false; + } + + // Set up the master node number for the neighboring patch + + n1 = neighbor.getSize(); + int nnode = 1; + + switch (nvertex) + { + case 2: // Positive I-direction + nnode = n1; + case 1: // Negative I-direction + break; + + default: + std::cerr <<" *** ASMs1D::connectPatch: Invalid master vertex " + << nvertex << std::endl; + return false; + } + + const double xtol = 1.0e-4; + if (!neighbor.getCoord(nnode).equal(this->getCoord(node),xtol)) + { + std::cerr <<" *** ASMs1D::connectPatch: Non-matching nodes " + << nnode <<": "<< neighbor.getCoord(nnode) + <<"\n and " + << node <<": "<< this->getCoord(node) << std::endl; + return false; + } + else + ASMbase::collapseNodes(neighbor.MLGN[nnode-1],MLGN[node-1]); + + return true; +} + + +void ASMs1D::closeEnds () +{ + int n1 = this->getSize(); + this->makePeriodic(1,n1); +} + + +void ASMs1D::constrainNode (double xi, int dof, int code) +{ + if (xi < 0.0 || xi > 1.0) return; + + int n1 = this->getSize(); + + int node = 1; + if (xi > 0.0) node += int(0.5+(n1-1)*xi); + + this->prescribe(node,dof,code); +} + + +#define DERR -999.99 + +double ASMs1D::getParametricLength (int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs1D::getParametricLength: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + if (MNPC[iel-1].empty()) + return 0.0; + + int inod1 = MNPC[iel-1].back(); +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= MLGN.size()) + { + std::cerr <<" *** ASMs1D::getParametricLength: Node index "<< inod1 + <<" out of range [0,"<< MLGN.size() <<">."<< std::endl; + return DERR; + } +#endif + + return this->getKnotSpan(inod1); +} + + +double ASMs1D::getKnotSpan (int i) const +{ + if (i < 0 || i >= curv->numCoefs() + curv->order() - 1) + return 0.0; + + DoubleIter uit = curv->basis().begin() + i; + return *(uit+1) - *uit; +} + + +bool ASMs1D::getElementCoordinates (Matrix& X, int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs1D::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } +#endif + + const IntVec& mnpc = MNPC[iel-1]; + X.resize(nsd,mnpc.size()); + + DoubleIter cit = curv->coefs_begin(); + for (size_t n = 0; n < mnpc.size(); n++) + { + int ip = mnpc[n]*curv->dimension(); + if (ip < 0) return false; + + for (size_t i = 0; i < nsd; i++) + X(i+1,n+1) = *(cit+(ip+i)); + } + +#if SP_DEBUG > 2 + std::cout <<"\nCoordinates for element "<< iel << X << std::endl; +#endif + return true; +} + + +void ASMs1D::getNodalCoordinates (Matrix& X) const +{ + const int n1 = curv->numCoefs(); + + X.resize(nsd,n1); + + DoubleIter cit = curv->coefs_begin(); + for (int inod = 0; inod < n1; inod++) + { + int ip = inod*curv->dimension(); + for (size_t i = 0; i < nsd; i++) + X(i+1,inod+1) = *(cit+(ip+i)); + } +} + + +Vec3 ASMs1D::getCoord (size_t inod) const +{ + Vec3 X; + int ip = (inod-1)*curv->dimension(); + if (ip < 0) return X; + + DoubleIter cit = curv->coefs_begin() + ip; + for (size_t i = 0; i < nsd; i++, cit++) + X[i] = *cit; + + return X; +} + + +int ASMs1D::getSize () const +{ + if (!curv) return 0; + + return curv->numCoefs(); +} + + +void ASMs1D::extractBasis (double u, Vector& N, Matrix& dNdu) const +{ + int p1 = curv->order(); + + DoubleVec bas(p1*2); + curv->basis().computeBasisValues(u,&bas.front(),1); + + N.resize(p1); + dNdu.resize(p1,1); + for (int i = 1; i <= p1; i++) + { + N(i) = bas[2*i-2]; + dNdu(i,1) = bas[2*i-1]; + } +} + + +bool ASMs1D::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!curv) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + + const int p1 = curv->order(); + const int n1 = curv->numCoefs(); + + Vector gpar; + int pm1 = p1 - 1; + DoubleIter uit = curv->basis().begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = n1 - pm1; + gpar.reserve(nGauss*nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 0; i < nGauss; i++) + gpar.push_back(0.5*((ucurr-uprev)*xg[i] + ucurr+uprev)); + uprev = ucurr; + } + + Vector N; + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + int iel = 1; + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-length element + + // Check that the current element has nonzero length + double dL = this->getParametricLength(iel); + if (dL < 0.0) return false; // topology error (probably logic error) + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element matrices + if (!integrand.initElement(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in current element ----------- + + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = 0.5*dL*wg[i]; + + // Compute basis functions and derivatives + this->extractBasis(gpar((iel-1)*nGauss+i+1),N,dNdu); + + // Compute derivatives in terms of physical co-ordinates + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs1D::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!curv) return true; // silently ignore empty patches + + // Integration of boundary point + + int iel; + double param; + switch (lIndex) + { + case 1: + iel = 1; + param = curv->startparam(); + break; + + case 2: + iel = this->getNoElms(); + param = curv->endparam(); + break; + + default: + return false; + } + + if (MLGE[iel-1] < 1) true; // zero-length element + + // Set up control point coordinates for current element + Matrix Xnod; + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element matrices + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + // Evaluate basis functions and corresponding derivatives + Vector N; + Matrix dNdu; + this->extractBasis(param,N,dNdu); + + // Cartesian coordinates of current integration point + Vec4 X(Xnod*N,time.t); + + // Compute basis function derivatives + Matrix Jac, dNdX; + utl::Jacobian(Jac,dNdX,Xnod,dNdu); + + // Set up the normal vector + Vec3 normal; + if (lIndex == 1) + normal.x = -copysign(1.0,Jac(1,1)); + else + normal.x = copysign(1.0,Jac(1,1)); + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,1.0,N,dNdX,X,normal)) + return false; + + // Assembly of global system integral + return glInt.assemble(elmInt,MLGE[iel-1]); +} + + +bool ASMs1D::getGridParameters (RealArray& prm, int nSegPerSpan) const +{ + if (!curv) return false; + + if (nSegPerSpan < 1) + { + std::cerr <<" *** ASMs1D::getGridParameters: Too few knot-span points " + << nSegPerSpan+1 << std::endl; + return false; + } + + DoubleIter uit = curv->basis().begin(); + double ucurr, uprev = *(uit++); + while (uit != curv->basis().end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nSegPerSpan; i++) + { + double xg = (double)(2*i-nSegPerSpan)/(double)nSegPerSpan; + prm.push_back(0.5*(ucurr*(1.0+xg) + uprev*(1.0-xg))); + } + uprev = ucurr; + } + + prm.push_back(curv->basis().endparam()); + return true; +} + + +bool ASMs1D::tesselate (ElementBlock& grid, const int* npe) const +{ + // Compute parameter values of the nodal points + DoubleVec gpar; + if (!this->getGridParameters(gpar,npe[0]-1)) + return false; + + // Evaluate the spline curve at all points + size_t nx = gpar.size(); + DoubleVec XYZ(3*nx,0.0); + + // Establish the block grid coordinates + size_t i, j, l; + for (i = l = 0; i < nx; i++, l += 3) + { + Go::Point pt; + curv->point(pt,gpar[i]); + for (j = 0; j < pt.size(); j++) + XYZ[l+j] = pt[j]; + } + + grid.resize(nx); + for (i = j = 0; i < grid.getNoNodes(); i++, j += 3) + grid.setCoor(i,XYZ[j],XYZ[j+1],XYZ[j+2]); + + // Establish the block grid topology + int n[2], ip = 0; + n[0] = 0; + n[1] = n[0] + 1; + + for (i = 1; i < nx; i++) + for (l = 0; l < 2; l++) + grid.setNode(ip++,n[l]++); + + return true; +} + + +/*! + \brief Auxilliary function for computation of GoTools basis function indices. +*/ + +static void scatterInd (int p1, int start, IntVec& index) +{ + index.resize(p1); + for (int i1 = 1; i1 <= p1; i1++) + index[i1-1] = start-p1+i1; +} + + +bool ASMs1D::evalSolution (Matrix& sField, const Vector& locSol, + const int* npe) const +{ + // Compute parameter values of the result sampling points + DoubleVec gpar; + if (!this->getGridParameters(gpar,npe[0]-1)) + return false; + + const int p1 = curv->order(); + size_t nComp = locSol.size() / this->getNoNodes(); + if (nComp*this->getNoNodes() != locSol.size()) + return false; + + Matrix Xtmp; + Vector Ytmp, basis(p1); + + // Evaluate the primary solution field at each point + size_t nPoints = gpar.size(); + sField.resize(nComp,nPoints); + for (size_t i = 0; i < nPoints; i++) + { + curv->basis().computeBasisValues(gpar[i],&basis.front()); + + IntVec ip; + scatterInd(p1,curv->basis().lastKnotInterval(),ip); + + utl::gather(ip,nComp,locSol,Xtmp); + Xtmp.multiply(basis,Ytmp); + sField.fillColumn(i+1,Ytmp); + } + + return true; +} + + +bool ASMs1D::evalSolution (Matrix& sField, const Integrand& integrand, + const int* npe) const +{ + sField.resize(0,0); + + // Compute parameter values of the result sampling points + DoubleVec gpar; + if (!this->getGridParameters(gpar,npe[0]-1)) + return false; + + const int p1 = curv->order(); + + // Fetch nodal (control point) coordinates + Matrix Xnod, Xtmp; + this->getNodalCoordinates(Xnod); + + Vector N(p1), solPt; + Matrix dNdu, dNdX, Jac; + + // Evaluate the secondary solution field at each point + size_t nPoints = gpar.size(); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch basis function derivatives at current integration point + this->extractBasis(gpar[i],N,dNdu); + + // Fetch indices of the non-zero basis functions at this point + IntVec ip; + scatterInd(p1,curv->basis().lastKnotInterval(),ip); + + // Fetch associated control point coordinates + utl::gather(ip,nsd,Xnod,Xtmp); + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xtmp,dNdu) == 0.0) // Jac = (Xtmp * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xtmp*N,ip)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + sField.fillColumn(1+i,solPt); + } + + return true; +} diff --git a/src/ASM/ASMs1D.h b/src/ASM/ASMs1D.h new file mode 100644 index 00000000..dc9dedf2 --- /dev/null +++ b/src/ASM/ASMs1D.h @@ -0,0 +1,186 @@ +// $Id: ASMs1D.h,v 1.4 2011-01-05 12:49:41 kmo Exp $ +//============================================================================== +//! +//! \file ASMs1D.h +//! +//! \date Apr 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 1D spline FE models. +//! +//============================================================================== + +#ifndef _ASM_S1D_H +#define _ASM_S1D_H + +#include "ASMstruct.h" + +namespace Go { + class SplineCurve; +} + + +/*! + \brief Driver for assembly of structured 1D spline FE models. + \details This class contains methods common for structured 1D spline patches. +*/ + +class ASMs1D : public ASMstruct +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs1D(const char* fileName, unsigned char n_s = 1, unsigned char n_f = 1); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs1D(std::istream& is, unsigned char n_s = 1, unsigned char n_f = 1); + //! \brief Default constructor creating an empty patch. + ASMs1D(unsigned char n_s = 1, unsigned char n_f = 1); + //! \brief Empty destructor. + virtual ~ASMs1D() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! and the global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + //! \brief Creates an instance by reading the given input stream, \a is. + bool read(std::istream& is); + //! \brief Writes the geometry of the SplineCurve object to given stream. + bool write(std::ostream& os) const; + + //! \brief Refine the parametrization by inserting extra knots. + //! \param[in] xi Relative positions of added knots in each existing knot span + bool refine(const RealArray& xi); + //! \brief Refine the parametrization by inserting extra knots uniformly. + //! \param[in] nInsert Number of extra knots to insert in each knot-span + bool uniformRefine(int nInsert); + //! \brief Raise the order of the SplineCurve object for this patch. + //! \param[in] ru Number of times to raise the order + bool raiseOrder(int ru); + + + // Various methods for preprocessing of boundary conditions + // ======================================================== + + //! \brief Makes the two end vertices of the curve periodic. + void closeEnds(); + + //! \brief Constrains a node identified by a relative parameter value. + //! \param[in] xi Parameter value along the curve + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] code Inhomogeneous dirichlet condition code + //! + //! \details The parameter value has to be in the domain [0.0,1.0], where + //! 0.0 means the beginning of the domain and 1.0 means the end. For values + //! in between, the actual index is taken as the integer value closest to + //! \a r*n, where \a r denotes the given relative parameter value, + //! and \a n is the number of nodes along that parameter direction. + void constrainNode(double xi, int dof = 123, int code = 0); + + //! \brief Connects matching nodes on two adjacent vertices. + //! \param[in] vertex Local vertex index of this patch, in range [1,2] + //! \param neighbor The neighbor patch + //! \param[in] nvertex Local vertex index of neighbor patch, in range [1,2] + bool connectPatch(int vertex, ASMs1D& neighbor, int nvertex); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch end. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the end point + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Creates a line element model of this patch for visualization. + //! \param[out] grid The generated line grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Calculates parameter values for the visualization nodal points. + //! \param[out] prm Parameter values for all points + //! \param[in] nSegSpan Number of visualization segments over each knot-span + virtual bool getGridParameters(RealArray& prm, int nSegSpan) const; + + //! \brief Returns the length in the parameter space for an element. + //! \param[in] iel Element index + double getParametricLength(int iel) const; + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const; + + //! \brief Returns the number of nodal points in the patch. + virtual int getSize() const; + +private: + //! \brief Establishes vectors with basis functions and 1st derivatives. + //! \param[in] u Parameter value of current integration point + //! \param[out] N Basis function values + //! \param[out] dNdu First derivatives of basis functions + void extractBasis(double u, Vector& N, Matrix& dNdu) const; + + //! \brief Returns the parametric length on the \a i'th knot-span + double getKnotSpan(int i) const; + +protected: + Go::SplineCurve* curv; //!< Pointer to the actual spline curve object +}; + +#endif diff --git a/src/ASM/ASMs1DLag.C b/src/ASM/ASMs1DLag.C new file mode 100644 index 00000000..958b1881 --- /dev/null +++ b/src/ASM/ASMs1DLag.C @@ -0,0 +1,362 @@ +// $Id: ASMs1DLag.C,v 1.5 2010-12-29 18:39:37 kmo Exp $ +//============================================================================== +//! +//! \file ASMs1DLag.C +//! +//! \date Apr 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 1D Lagrange FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineCurve.h" + +#include "ASMs1DLag.h" +#include "Lagrange.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "ElementBlock.h" +#include "Utilities.h" +#include "Vec3Oper.h" + + +ASMs1DLag::ASMs1DLag (const char* fileName, + unsigned char n_s, unsigned char n_f) + : ASMs1D(fileName,n_s,n_f) +{ + nx = 0; +} + + +ASMs1DLag::ASMs1DLag (std::istream& is, unsigned char n_s, unsigned char n_f) + : ASMs1D(is,n_s,n_f) +{ + nx = 0; +} + + +void ASMs1DLag::clear () +{ + coord.clear(); + nx = 0; + ASMs1D::clear(); +} + + +bool ASMs1DLag::generateFEMTopology () +{ + if (!curv) return false; + + // Order of the basis + const int p1 = curv->order(); + + // Evaluate the parametric values + RealArray gpar; + if (!this->getGridParameters(gpar,p1-1)) return false; + + // Number of nodes + nx = gpar.size(); + + if (!coord.empty()) + return coord.size() == nx; + + // Number of elements + const int nel = (nx-1)/(p1-1); + + MLGN.resize(nx); + coord.resize(nx); + + // Evaluate the nodal coordinates + for (int i = 0; i < nx; i++) + { + Go::Point pt; + curv->point(pt,gpar[i]); + for (int k = 0; k < pt.size(); k++) + coord[i][k] = pt[k]; + MLGN[i] = ++gNod; + } + + // Connectivety array: local --> global node relation + MLGE.resize(nel); + MNPC.resize(nel); + + int a, iel; + for (iel = 0; iel < nel; iel++) + { + MLGE[iel] = ++gEl; + // Element array + MNPC[iel].resize(p1); + // First node in current element + int first = (p1-1)*iel; + + for (a = 0; a < p1; a++) + MNPC[iel][a] = first + a; + } + + return true; +} + + +bool ASMs1DLag::getElementCoordinates (Matrix& X, int iel) const +{ + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs1DLag::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } + + const IntVec& ien = MNPC[iel-1]; + X.resize(nsd,ien.size()); + + for (size_t i = 0; i < ien.size(); i++) + X.fillColumn(i+1,coord[ien[i]].ptr()); + + return true; +} + + +void ASMs1DLag::getNodalCoordinates (Matrix& X) const +{ + X.resize(nsd,coord.size()); + + for (size_t inod = 0; inod < coord.size(); inod++) + X.fillColumn(inod+1,coord[inod].ptr()); +} + + +Vec3 ASMs1DLag::getCoord (size_t inod) const +{ + if (inod < 1 || inod > coord.size()) return Vec3(); + + return coord[inod-1]; +} + + +bool ASMs1DLag::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!curv) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Order of basis (order = degree + 1) + const int p1 = curv->order(); + + Vector N(p1); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction ---------- + + for (int i = 0; i < nGauss; i++) + { + // Compute basis function derivatives at current integration point + if (!Lagrange::computeBasis(N,dNdu,p1,xg[i])) + return false; + + // Compute Jacobian inverse of coordinate mapping and derivatives + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*wg[i],N,dNdX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs1DLag::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!curv) return true; // silently ignore empty patches + + // Number of elements + const int p1 = curv->order(); + const int nel = (nx-1)/(p1-1); + + // Integration of boundary point + + int iel; + double x; + switch (lIndex) + { + case 1: + iel = 1; + x = -1.0; + break; + + case 2: + iel = nel; + x = 1.0; + } + + // Set up control point coordinates for current element + Matrix Xnod; + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + // Evaluate basis functions and corresponding derivatives + Vector N; + Matrix dNdu; + if (!Lagrange::computeBasis(N,dNdu,p1,x)) return false; + + // Cartesian coordinates of current integration point + Vec4 X(Xnod*N,time.t); + + // Compute basis function derivatives + Matrix Jac, dNdX; + utl::Jacobian(Jac,dNdX,Xnod,dNdu); + + // Set up the normal vector + Vec3 normal; + if (lIndex == 1) + normal.x = -copysign(1.0,Jac(1,1)); + else + normal.x = copysign(1.0,Jac(1,1)); + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,1.0,N,dNdX,X,normal)) + return false; + + // Assembly of global system integral + return glInt.assemble(elmInt,MLGE[iel-1]); +} + + +bool ASMs1DLag::tesselate (ElementBlock& grid, const int*) const +{ + int i, l; + + grid.resize(nx); + for (i = 0; i < grid.getNoNodes(); i++) + grid.setCoor(i,coord[i].x,coord[i].y,coord[i].z); + + // Establish the block grid topology + int n[2], ip = 0; + n[0] = 0; + n[1] = n[0] + 1; + + for (i = 1; i < nx; i++) + for (l = 0; l < 2; l++) + grid.setNode(ip++,n[l]++); + + return true; +} + + +bool ASMs1DLag::evalSolution (Matrix& sField, const Vector& locSol, + const int*) const +{ + size_t nPoints = coord.size(); + size_t nComp = locSol.size() / nPoints; + if (nComp*nPoints != locSol.size()) + return false; + + size_t i, n, ip = 0; + sField.resize(nComp,nPoints); + for (n = 1; n <= nPoints; n++) + for (i = 1; i <= nComp; i++) + sField(i,n) = locSol(++ip); + + return true; +} + + +bool ASMs1DLag::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + const int p1 = curv->order(); + double incx = 2.0/double(p1-1); + + size_t nPoints = coord.size(); + IntVec check(nPoints,0); + + Vector N(p1), solPt; + std::vector globSolPt(nPoints); + Matrix dNdu, dNdX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + const IntVec& mnpc = MNPC[iel-1]; + this->getElementCoordinates(Xnod,iel); + + for (int loc = 0; loc < p1; loc++) + { + double xi = -1.0 + loc*incx; + if (!Lagrange::computeBasis(N,dNdu,p1,xi)) + return false; + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xnod,dNdu) == 0.0) // Jac = (Xnod * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xnod*N,mnpc)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc[loc]] == 1) + globSolPt[mnpc[loc]] = solPt; + else + globSolPt[mnpc[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs1DLag.h b/src/ASM/ASMs1DLag.h new file mode 100644 index 00000000..a090b138 --- /dev/null +++ b/src/ASM/ASMs1DLag.h @@ -0,0 +1,130 @@ +// $Id: ASMs1DLag.h,v 1.2 2010-09-07 07:51:46 kmo Exp $ +//============================================================================== +//! +//! \file ASMs1DLag.h +//! +//! \date Apr 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 1D Lagrange FE models. +//! +//============================================================================== + +#ifndef _ASM_S1D_LAG_H +#define _ASM_S1D_LAG_H + +#include "ASMs1D.h" +#include "Vec3.h" + + +/*! + \brief Driver for assembly of structured 2D Lagrange FE models. + \details This class contains methods for structured 2D Lagrange patches. +*/ + +class ASMs1DLag : public ASMs1D +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs1DLag(const char* fileName, unsigned char n_s = 1, unsigned char n_f = 1); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs1DLag(std::istream& is, unsigned char n_s = 1, unsigned char n_f = 1); + //! \brief Default constructor creating an empty patch. + ASMs1DLag(unsigned char n_s = 1, unsigned char n_f = 1) : ASMs1D(n_s,n_f) {} + //! \brief Empty destructor. + virtual ~ASMs1DLag() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! and the global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch end. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the end point + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Creates a quad element model of this patch for visualization. + //! \param[out] grid The generated quadrilateral grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \details The number of visualization points is the same as the order of + //! the Lagrange elements by default. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \details The number of visualization points is the same as the order of + //! the Lagrange elements by default. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const; + + //! \brief Returns the number of nodal points in the patch. + virtual int getSize() const { return nx; } + +private: + //! \brief The number of nodes in each direction for the patch + size_t nx; //!< Number of nodes in first parameter direction + std::vector coord; //!< Nodal coordinates +}; + +#endif diff --git a/src/ASM/ASMs1DSpec.C b/src/ASM/ASMs1DSpec.C new file mode 100644 index 00000000..5c81dbf5 --- /dev/null +++ b/src/ASM/ASMs1DSpec.C @@ -0,0 +1,212 @@ +// $Id: ASMs1DSpec.C,v 1.3 2010-12-29 18:36:06 kmo Exp $ +//============================================================================== +//! +//! \file ASMs1DSpec.C +//! +//! \date Mar 22 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 1D Spectral FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineCurve.h" + +#include "ASMs1DSpec.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "Vec3Oper.h" +#include "Legendre.h" +#include "Lagrange.h" + + +bool ASMs1DSpec::getGridParameters (RealArray& prm, int nSegPerSpan) const +{ + if (!curv) return false; + + // Evaluate the Gauss-Lobatto-Legendre points + Vector dummy, xGLL; + if (!Legendre::GLL(dummy,xGLL,curv->order())) return false; + + if (xGLL.size() != nSegPerSpan+1) + { + nSegPerSpan = xGLL.size() - 1; + std::cout <<"Spectral elements: Number of nodes per knot-span reset to " + << nSegPerSpan <<" (GLL points)"<< std::endl; + } + + std::vector::const_iterator uit = curv->basis().begin(); + double ucurr, uprev = *(uit++); + while (uit != curv->basis().end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 1; i <= nSegPerSpan; i++) + prm.push_back(0.5*(ucurr-uprev)*(1.0+xGLL(i)) + uprev); + uprev = ucurr; + } + + prm.push_back(curv->basis().endparam()); + return true; +} + + +bool ASMs1DSpec::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!curv) return true; // silently ignore empty patches + + // Order of basis (order = degree + 1) + const int p1 = curv->order(); + const int n1 = nGauss < 1 ? p1 : nGauss; + + // Evaluate integration points and weights + + Vector wg1, xg1, points1; + if (!Legendre::GLL(wg1,points1,p1)) + return false; + + Matrix D1; + if (nGauss < 1) + { + // We are using the nodal points themselves as integration points + if (!Legendre::basisDerivatives(n1,D1)) + return false; + } + else + // Using Gauss-Legendre scheme with nGauss points + if (!Legendre::GL(wg1,xg1,n1)) + return false; + + Vector N(p1); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over integration points ---------------------------- + + for (int i = 0; i < n1; i++) + { + // Weight of current integration point + double weight = wg1[i]; + + // Compute basis function derivatives at current integration point + if (nGauss < 1) + { + N.fill(0.0); + N(i+1) = 1.0; + dNdu.fillColumn(1,D1.getRow(i+1)); + } + else + if (!Lagrange::computeBasis(N,dNdu,points1,xg1[i])) + return false; + + // Compute Jacobian inverse of coordinate mapping and derivatives + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + + // Cartesian coordinates of current integration point + X = Xnod*N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs1DSpec::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!curv) return true; // silently ignore empty patches + + + return false; // not implemented +} + + +bool ASMs1DSpec::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + if (!curv) return false; + + const int p1 = curv->order(); + + Matrix D1; + if (!Legendre::basisDerivatives(p1,D1)) + return false; + + size_t nPoints = this->getNoNodes(); + IntVec check(nPoints,0); + + Vector N(p1), solPt; + std::vector globSolPt; + globSolPt.resize(nPoints); + Matrix dNdu(p1,1), dNdX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + const IntVec& mnpc = MNPC[iel-1]; + this->getElementCoordinates(Xnod,iel); + + for (int i = 0; i < p1; i++) + { + N.fill(0.0); + N(i+1) = 1.0; + dNdu.fillColumn(1,D1.getRow(i+1)); + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xnod,dNdu) == 0.0) // Jac = (Xnod * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xnod.getColumn(i+1),mnpc)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc[i]] == 1) + globSolPt[mnpc[i]] = solPt; + else + globSolPt[mnpc[i]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs1DSpec.h b/src/ASM/ASMs1DSpec.h new file mode 100644 index 00000000..dd5ffe59 --- /dev/null +++ b/src/ASM/ASMs1DSpec.h @@ -0,0 +1,91 @@ +// $Id: ASMs1DSpec.h,v 1.2 2010-10-05 07:25:25 kmo Exp $ +//============================================================================== +//! +//! \file ASMs1DSpec.h +//! +//! \date Apr 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 1D Spectral FE models. +//! +//============================================================================== + +#ifndef _ASM_S1D_SPEC_H +#define _ASM_S1D_SPEC_H + +#include "ASMs1DLag.h" + + +/*! + \brief Driver for assembly of structured 1D Spectral FE models. + \details This class contains methods for structured 1D Spectral patches. +*/ + +class ASMs1DSpec : public ASMs1DLag +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs1DSpec(const char* fileName, unsigned char n_s = 1, unsigned char n_f = 1) + : ASMs1DLag(fileName,n_s,n_f) {} + //! \brief Constructor creating an instance by reading the given input stream. + ASMs1DSpec(std::istream& is, unsigned char n_s = 1, unsigned char n_f = 1) + : ASMs1DLag(is,n_s,n_f) {} + //! \brief Default constructor creating an empty patch. + ASMs1DSpec(unsigned char n_s = 1, unsigned char n_f = 1) + : ASMs1DLag(n_s,n_f) {} + //! \brief Empty destructor. + virtual ~ASMs1DSpec() {} + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch end. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the end point + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Calculates parameter values for all visualization nodal points. + //! \param[out] prm Parameter values for all points + //! \param[in] nSegSpan Number of visualization segments over each knot-span + //! + //! This method is reimplemented to return the parameter values of the + //! Gauss-Lobatto-Legendre points, which define the FE nodes when using + //! spectral elements. This method is therefore also used when creating the + //! FE model (the visualization nodes and the FE nodes are identical). + virtual bool getGridParameters(RealArray& prm, int nSegSpan) const; +}; + +#endif diff --git a/src/ASM/ASMs2D.C b/src/ASM/ASMs2D.C new file mode 100644 index 00000000..409c886c --- /dev/null +++ b/src/ASM/ASMs2D.C @@ -0,0 +1,1302 @@ +// $Id: ASMs2D.C,v 1.22 2011-01-27 17:04:52 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2D.C +//! +//! \date Jan 19 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 2D spline FE models. +//! +//============================================================================== + +#include "GoTools/geometry/ObjectHeader.h" +#include "GoTools/geometry/SplineSurface.h" +#include "GoTools/geometry/SurfaceInterpolator.h" + +#include "ASMs2D.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "ElementBlock.h" +#include "Utilities.h" +#include "Profiler.h" +#include "Vec3Oper.h" +#include +#include + +typedef Go::SplineSurface::Dvector DoubleVec; //!< 1D double array +typedef DoubleVec::const_iterator DoubleIter; //!< Iterator over DoubleVec + + +ASMs2D::ASMs2D (const char* fileName, unsigned char n_s, unsigned char n_f) + : ASMstruct(2,n_s,n_f) +{ + surf = 0; + + std::cout <<"\nReading patch file "<< fileName << std::endl; + std::ifstream is(fileName); + if (!is.good()) + std::cerr <<" *** ASMs2D: Failure opening patch file"<< std::endl; + else + this->read(is); + + geo = surf; +} + + +ASMs2D::ASMs2D (std::istream& is, unsigned char n_s, unsigned char n_f) + : ASMstruct(2,n_s,n_f) +{ + surf = 0; + + this->read(is); + + geo = surf; +} + + +ASMs2D::ASMs2D (unsigned char n_s, unsigned char n_f) + : ASMstruct(2,n_s,n_f) +{ + surf = 0; +} + + +bool ASMs2D::read (std::istream& is) +{ + if (surf) delete surf; + + Go::ObjectHeader head; + surf = new Go::SplineSurface; + is >> head >> *surf; + + // Eat white-space characters to see if there is more data to read + char c; + while (is.get(c)) + if (!isspace(c)) + { + is.putback(c); + break; + } + + if (!is.good() && !is.eof()) + { + std::cerr <<" *** ASMs2D::read: Failure reading spline data"<< std::endl; + delete surf; + surf = 0; + return false; + } + else if (surf->dimension() < 2) + { + std::cerr <<" *** ASMs2D::read: Invalid spline surface patch, dim=" + << surf->dimension() << std::endl; + return false; + } + else if (surf->dimension() < nsd) + { + std::cout <<" ** ASMs2D::read: The dimension of this surface patch " + << surf->dimension() <<" is less than nsd="<< nsd + <<".\n Resetting nsd to "<< surf->dimension() + <<" for this patch."<< std::endl; + nsd = surf->dimension(); + } + + return true; +} + + +bool ASMs2D::write (std::ostream& os) const +{ + if (!surf) return false; + + os <<"200 1 0 0\n"; + os << *surf; + + return os.good(); +} + + +void ASMs2D::clear () +{ + // Erase spline data + if (surf) delete surf; + surf = 0; + geo = 0; + + // Erase the FE data + nodeInd.clear(); + ASMbase::clear(); +} + + +bool ASMs2D::refine (int dir, const RealArray& xi) +{ + if (!surf || dir < 0 || dir > 1 || xi.empty()) return false; + if (xi.front() < 0.0 || xi.back() > 1.0) return false; + + DoubleVec extraKnots; + DoubleIter uit = surf->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != surf->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (size_t i = 0; i < xi.size(); i++) + if (i > 0 && xi[i] < xi[i-1]) + return false; + else + extraKnots.push_back(ucurr*xi[i] + uprev*(1.0-xi[i])); + + uprev = ucurr; + } + + if (dir == 0) + surf->insertKnot_u(extraKnots); + else + surf->insertKnot_v(extraKnots); + + return true; +} + + +bool ASMs2D::uniformRefine (int dir, int nInsert) +{ + if (!surf || dir < 0 || dir > 1 || nInsert < 1) return false; + + DoubleVec extraKnots; + DoubleIter uit = surf->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != surf->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nInsert; i++) + { + double xi = (double)(i+1)/(double)(nInsert+1); + extraKnots.push_back(ucurr*xi + uprev*(1.0-xi)); + } + uprev = ucurr; + } + + if (dir == 0) + surf->insertKnot_u(extraKnots); + else + surf->insertKnot_v(extraKnots); + + return true; +} + + +bool ASMs2D::raiseOrder (int ru, int rv) +{ + if (!surf) return false; + + surf->raiseOrder(ru,rv); + return true; +} + + +bool ASMs2D::generateFEMTopology () +{ + if (!surf) return false; + + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + if (!nodeInd.empty()) + { + if (nodeInd.size() == n1*n2) return true; + std::cerr <<" *** ASMs2D::generateFEMTopology: Inconsistency between the" + <<" number of FE nodes "<< nodeInd.size() + <<"\n and the number of spline coefficients "<< n1*n2 + <<" in the patch."<< std::endl; + return false; + } + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); +#ifdef SP_DEBUG + std::cout <<"numCoefs: "<< n1 <<" "<< n2; + std::cout <<"\norder: "<< p1 <<" "<< p2; + std::cout <<"\ndu:"; + for (int i = 0; i < n1; i++) + std::cout <<' '<< surf->knotSpan(0,i); + std::cout <<"\ndv:"; + for (int j = 0; j < n2; j++) + std::cout <<' '<< surf->knotSpan(1,j); + std::cout << std::endl; +#endif + // Consistency checks, just to be fool-proof + if (n1 < 2 || n2 < 2) return false; + if (p1 < 1 || p1 < 1) return false; + if (p1 > n1 || p2 > n2) return false; + + MLGE.resize((n1-p1+1)*(n2-p2+1),0); + MLGN.resize(n1*n2); + MNPC.resize(MLGE.size()); + nodeInd.resize(MLGN.size()); + + int iel = 0; + int inod = 0; + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++) + { + nodeInd[inod].I = i1-1; + nodeInd[inod].J = i2-1; + if (i1 >= p1 && i2 >= p2) + { + if (surf->knotSpan(0,i1-1) > 0.0) + if (surf->knotSpan(1,i2-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1*p2,0); + + int lnod = 0; + for (int j2 = p2-1; j2 >= 0; j2--) + for (int j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - n1*j2 - j1; + } + + iel++; + } + MLGN[inod++] = ++gNod; // global node number over all patches + } + +#ifdef SP_DEBUG + std::cout <<"NEL = "<< iel <<" NNOD = "<< inod << std::endl; +#endif + return true; +} + + +int ASMs2D::Edge::next () +{ + int ret = icnod; + icnod += incr; + + return ret; +} + + +int ASMs2D::BlockNodes::next () +{ + int ret = iinod; + iinod += inc[0]; + + if (++indxI >= nnodI-1) + { + indxI = 1; + iinod += inc[1] - inc[0]*(nnodI-2); + } + + return ret; +} + + +bool ASMs2D::assignNodeNumbers (BlockNodes& nodes, int basis) +{ + int n1, n2; + if (!this->getSize(n1,n2,basis)) + return false; + + int m1 = 0, m2 = 0; + if (basis > 0) + if (!this->getSize(m1,m2,3-basis)) + return false; + + if (MLGN.size() != n1*n2+m1*m2) return false; + + nodes.nnodI = n1; + + if (nodes.inc[0] == 0 || nodes.inc[1] == 0) + { + nodes.inc[0] = 1; + nodes.inc[1] = n1-2; + } + + int inod = basis > 1 ? m1*m2 : 0; + for (int j = 1; j <= n2; j++) + for (int i = 1; i <= n1; i++, inod++) + if (j == 1) + { + if (i == 1) + MLGN[inod] = nodes.ibnod[0]; + else if (i == n1) + MLGN[inod] = nodes.ibnod[1]; + else + MLGN[inod] = nodes.edges[0].next(); + } + else if (j == n2) + { + if (i == 1) + MLGN[inod] = nodes.ibnod[2]; + else if (i == n1) + MLGN[inod] = nodes.ibnod[3]; + else + MLGN[inod] = nodes.edges[1].next(); + } + else + { + if (i == 1) + MLGN[inod] = nodes.edges[2].next(); + else if (i == n1) + MLGN[inod] = nodes.edges[3].next(); + else + MLGN[inod] = nodes.next(); + } + +#if SP_DEBUG > 1 + if (basis > 0) std::cout <<"\nBasis "<< basis <<":"; + for (int i = inod-n1*n2; i < inod; i++) + std::cout <<"\nNode "<< i+1 <<"\t: "<< nodeInd[i].I <<" "<< nodeInd[i].J + <<"\tglobal no. "<< MLGN[i]; + std::cout << std::endl; +#endif + return true; +} + + +bool ASMs2D::connectPatch (int edge, ASMs2D& neighbor, int nedge, bool rev) +{ + // Set up the slave node numbers for this surface patch + + int n1, n2; + if (!this->getSize(n1,n2)) return false; + int node = 1, i1 = 0; + + switch (edge) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + i1 = n1; + n1 = n2; + break; + + case 4: // Positive J-direction + node = n1*(n2-1)+1; + case 3: // Negative J-direction + i1 = 1; + break; + + default: + std::cerr <<" *** ASMs2D::connectPatch: Invalid slave edge " + << edge << std::endl; + return false; + } + + int i; + IntVec slaveNodes(n1,0); + for (i = 0; i < n1; i++, node += i1) + slaveNodes[i] = node; + + // Set up the master node numbers for the neighboring surface patch + + if (!neighbor.getSize(n1,n2)) return false; + node = 1; i1 = 0; + + switch (nedge) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + i1 = n1; + n1 = n2; + break; + + case 4: // Positive J-direction + node = n1*(n2-1)+1; + case 3: // Negative J-direction + i1 = 1; + break; + + default: + std::cerr <<" *** ASMs2D::connectPatch: Invalid master edge " + << nedge << std::endl; + return false; + } + + if (n1 != slaveNodes.size()) + { + std::cerr <<" *** ASMs2D::connectPatch: Non-matching edges, sizes " + << n1 <<" and "<< slaveNodes.size() << std::endl; + return false; + } + + const double xtol = 1.0e-4; + for (i = 0; i < n1; i++, node += i1) + { + int k = rev ? n1-i-1 : i; + if (!neighbor.getCoord(node).equal(this->getCoord(slaveNodes[k]),xtol)) + { + std::cerr <<" *** ASMs2D::connectPatch: Non-matching nodes " + << node <<": "<< neighbor.getCoord(node) + <<"\n and " + << slaveNodes[k] <<": "<< this->getCoord(slaveNodes[k]) + << std::endl; + return false; + } + else + ASMbase::collapseNodes(neighbor.MLGN[node-1],MLGN[slaveNodes[k]-1]); + } + + return true; +} + + +void ASMs2D::closeEdges (int dir) +{ + int n1, n2, master = 1; + if (!this->getSize(n1,n2)) return; + + switch (dir) + { + case 1: // Edges are closed in I-direction + for (int i2 = 1; i2 <= n2; i2++, master += n1) + this->makePeriodic(master,master+n1-1); + break; + + case 2: // Edges are closed in J-direction + for (int i1 = 1; i1 <= n1; i1++, master++) + this->makePeriodic(master,master+n1*(n2-1)); + break; + } +} + + +void ASMs2D::constrainEdge (int dir, int dof, int code) +{ + int n1, n2, node = 1; + if (!this->getSize(n1,n2,1)) return; + + switch (dir) + { + case 1: // Right edge (positive I-direction) + node += n1-1; + case -1: // Left edge (negative I-direction) + for (int i2 = 1; i2 <= n2; i2++, node += n1) + this->prescribe(node,dof,code); + break; + + case 2: // Back edge (positive J-direction) + node += n1*(n2-1); + case -2: // Front edge (negative J-direction) + for (int i1 = 1; i1 <= n1; i1++, node++) + this->prescribe(node,dof,code); + break; + } +} + + +void ASMs2D::constrainCorner (int I, int J, int dof, int code) +{ + int n1, n2; + if (!this->getSize(n1,n2,1)) return; + + int node = 1; + if (I > 0) node += n1-1; + if (J > 0) node += n1*(n2-1); + + this->prescribe(node,dof,code); +} + + +void ASMs2D::constrainNode (double xi, double eta, int dof, int code) +{ + if (xi < 0.0 || xi > 1.0) return; + if (eta < 0.0 || eta > 1.0) return; + + int n1, n2; + if (!this->getSize(n1,n2,1)) return; + + int node = 1; + if (xi > 0.0) node += int(0.5+(n1-1)*xi); + if (eta > 0.0) node += n1*int(0.5+(n2-1)*eta); + + this->prescribe(node,dof,code); +} + + +#define DERR -999.99 + +double ASMs2D::getParametricArea (int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs2D::getParametricArea: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + if (MNPC[iel-1].empty()) + return 0.0; + + int inod1 = MNPC[iel-1].back(); +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= nodeInd.size()) + { + std::cerr <<" *** ASMs2D::getParametricArea: Node index "<< inod1 + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return DERR; + } +#endif + + double du = surf->knotSpan(0,nodeInd[inod1].I); + double dv = surf->knotSpan(1,nodeInd[inod1].J); + return du*dv; +} + + +double ASMs2D::getParametricLength (int iel, int dir) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs2D::getParametricLength: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + if (MNPC[iel-1].empty()) + return 0.0; + + int inod1 = MNPC[iel-1].back(); +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= nodeInd.size()) + { + std::cerr <<" *** ASMs2D::getParametricLength: Node index "<< inod1 + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return DERR; + } +#endif + + const int ni = nodeInd[inod1].I; + const int nj = nodeInd[inod1].J; + switch (dir) + { + case 1: return surf->knotSpan(1,nj); + case 2: return surf->knotSpan(0,ni); + } + + std::cerr <<" *** ASMs2D::getParametricLength: Invalid edge direction " + << dir << std::endl; + return DERR; +} + + +int ASMs2D::coeffInd (size_t inod) const +{ +#ifdef INDEX_CHECK + if (inod >= nodeInd.size()) + { + std::cerr <<" *** ASMs2D::coeffInd: Node index "<< inod + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return -1; + } +#endif + + const int ni = nodeInd[inod].I; + const int nj = nodeInd[inod].J; + return nj*surf->numCoefs_u() + ni; +} + + +bool ASMs2D::getElementCoordinates (Matrix& X, int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs2D::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } +#endif + + const IntVec& mnpc = MNPC[iel-1]; + X.resize(nsd,mnpc.size()); + + DoubleIter cit = surf->coefs_begin(); + for (size_t n = 0; n < mnpc.size(); n++) + { + int ip = this->coeffInd(mnpc[n])*surf->dimension(); + if (ip < 0) return false; + + for (size_t i = 0; i < nsd; i++) + X(i+1,n+1) = *(cit+(ip+i)); + } + +#if SP_DEBUG > 2 + std::cout <<"\nCoordinates for element "<< iel << X << std::endl; +#endif + return true; +} + + +void ASMs2D::getNodalCoordinates (Matrix& X) const +{ + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + X.resize(nsd,n1*n2); + + DoubleIter cit = surf->coefs_begin(); + size_t inod = 1; + for (int i2 = 0; i2 < n2; i2++) + for (int i1 = 0; i1 < n1; i1++, inod++) + { + int ip = (i2*n1 + i1)*surf->dimension(); + for (size_t i = 0; i < nsd; i++) + X(i+1,inod) = *(cit+(ip+i)); + } +} + + +Vec3 ASMs2D::getCoord (size_t inod) const +{ + Vec3 X; + int ip = this->coeffInd(inod-1)*surf->dimension(); + if (ip < 0) return X; + + DoubleIter cit = surf->coefs_begin() + ip; + for (size_t i = 0; i < nsd; i++, cit++) + X[i] = *cit; + + return X; +} + + +bool ASMs2D::getSize (int& n1, int& n2, int) const +{ + if (!surf) return false; + + n1 = surf->numCoefs_u(); + n2 = surf->numCoefs_v(); + return true; +} + + +/*! + \brief Computes the characteristic element length from nodal coordinates. +*/ + +static double getElmSize (int p1, int p2, const Matrix& X) +{ + int n = X.rows(); + int i, j, id1, id2; + double value, v1, h = 1.0e12; + + // Y-direction + for (i = 1; i <= p1; i++) + { + id1 = i; + id2 = id1 + (p2-1)*p1; + value = 0.0; + for (j = 1; j <= n; j++) + { + v1 = X(j,id2) - X(j,id1); + value += v1*v1; + } + if (value < h) h = value; + } + + // X-direction + for (j = 0; j < p2; j++) + { + id1 = j*p1 + 1; + id2 = id1 + p1 - 1; + value = 0.0; + for (i = 1; i <= n; i++) + { + v1 = X(i,id2) - X(i,id1); + value += v1*v1; + } + if (value < h) h = value; + } + + return sqrt(h); +} + + +void ASMs2D::extractBasis (const Go::BasisDerivsSf& spline, + Vector& N, Matrix& dNdu) +{ + dNdu.resize(N.size(),2); + + size_t jp, n = 1; + for (jp = 0; jp < N.size(); jp++, n++) + { + N (n) = spline.basisValues[jp]; + dNdu(n,1) = spline.basisDerivs_u[jp]; + dNdu(n,2) = spline.basisDerivs_v[jp]; + } +} + + +void ASMs2D::extractBasis (const Go::BasisDerivsSf2& spline, + Vector& N, Matrix& dNdu, Matrix3D& d2Ndu2) +{ + dNdu .resize(N.size(),2); + d2Ndu2.resize(N.size(),2,2); + + size_t jp, n = 1; + for (jp = 0; jp < N.size(); jp++, n++) + { + N (n) = spline.basisValues[jp]; + dNdu (n,1) = spline.basisDerivs_u[jp]; + dNdu (n,2) = spline.basisDerivs_v[jp]; + d2Ndu2(n,1,1) = spline.basisDerivs_uu[jp]; + d2Ndu2(n,1,2) = d2Ndu2(n,2,1) = spline.basisDerivs_uv[jp]; + d2Ndu2(n,2,2) = spline.basisDerivs_vv[jp]; + } +} + + +bool ASMs2D::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + PROFILE2("ASMs2D::integrate(I)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + int dir; + Matrix gpar[2]; + for (dir = 0; dir < 2; dir++) + { + int pm1 = (dir == 0 ? surf->order_u() : surf->order_v()) - 1; + DoubleIter uit = surf->basis(dir).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = (dir == 0 ? surf->numCoefs_u() : surf->numCoefs_v()) - pm1; + gpar[dir].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[dir](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline; + std::vector spline2; + if (integrand.getIntegrandType() == 2) + surf->computeBasisGrid(gpar[0],gpar[1],spline2); + else + surf->computeBasisGrid(gpar[0],gpar[1],spline); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + const int nel1 = n1 - p1 + 1; + + Vector N(p1*p2), Navg; + Matrix dNdu, dNdX, Xnod, Jac; + Matrix3D d2Ndu2, d2NdX2, Hess; + Vec4 X; + double h; + + + // === Assembly loop over all elements in the patch ========================== + + int iel = 1; + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-area element + + // Get element area in the parameter space + double dA = this->getParametricArea(iel); + if (dA < 0.0) return false; // topology error (probably logic error) + + // Set up control point (nodal) coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Compute characteristic element length, if needed + if (integrand.getIntegrandType() == 2) + h = getElmSize(p1,p2,Xnod); + + else if (integrand.getIntegrandType() == 3) + { + // --- Compute average value of basis functions over the element ------- + + Navg.resize(p1*p2,true); + double area = 0.0; + int ip = ((i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Fetch basis function derivatives at current integration point + extractBasis(spline[ip],N,dNdu); + + // Compute Jacobian determinant of coordinate mapping + // and multiply by weight of current integration point + double weight = 0.25*dA*wg[i]*wg[j]; + double detJxW = utl::Jacobian(Jac,dNdX,Xnod,dNdu,false)*weight; + + // Numerical quadrature + Navg += N*detJxW; + area += detJxW; + } + + // Divide by element area + Navg /= area; + } + + else if (integrand.getIntegrandType() == 4) + { + // Compute the element center + Go::Point X0; + double u0 = 0.5*(gpar[0](1,i1-p1+1) + gpar[0](nGauss,i1-p1+1)); + double v0 = 0.5*(gpar[1](1,i2-p2+1) + gpar[1](nGauss,i2-p2+1)); + surf->point(X0,u0,v0); + X.x = X0[0]; + X.y = X0[1]; + } + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1],X,nGauss*nGauss)) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction ---------- + + int ip = ((i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.25*dA*wg[i]*wg[j]; + + // Fetch basis function derivatives at current integration point + if (integrand.getIntegrandType() == 2) + extractBasis(spline2[ip],N,dNdu,d2Ndu2); + else + extractBasis(spline[ip],N,dNdu); + + // Compute Jacobian inverse of coordinate mapping and derivatives + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + if (detJ == 0.0) continue; // skip singular points + + // Compute Hessian of coordinate mapping and 2nd order derivatives + if (integrand.getIntegrandType() == 2) + if (!utl::Hessian(Hess,d2NdX2,Jac,Xnod,d2Ndu2,dNdu)) + return false; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + bool ok = false; + switch (integrand.getIntegrandType()) + { + case 2: + ok = integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,d2NdX2,X,h); + break; + case 3: + ok = integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,Navg,X); + break; + default: + ok = integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,X); + } + if (!ok) return false; + } + + // Finalize the element quantities + if (!integrand.finalizeElement(elmInt)) + return false; + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2D::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + PROFILE2("ASMs2D::integrate(B)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the edge normal {-2,-1, 1, 2} + const int edgeDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = abs(edgeDir); // Tangent direction normal to the patch edge + const int t2 = 3-abs(edgeDir); // Tangent direction along the patch edge + + // Compute parameter values of the Gauss points along the whole patch edge + Matrix gpar[2]; + for (short int d = 0; d < 2; d++) + if (-1-d == edgeDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = d == 0 ? surf->startparam_u() : surf->startparam_v(); + } + else if (1+d == edgeDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = d == 0 ? surf->endparam_u() : surf->endparam_v(); + } + else + { + int pm1 = (d == 0 ? surf->order_u() : surf->order_v()) - 1; + DoubleIter uit = surf->basis(d).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = (d == 0 ? surf->numCoefs_u() : surf->numCoefs_v()) - pm1; + gpar[d].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[d](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline; + surf->computeBasisGrid(gpar[0],gpar[1],spline); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + const int nel1 = n1 - p1 + 1; + + Vector N(p1*p2); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + Vec3 normal; + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-area element + + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (edgeDir) + { + case -1: if (i1 > p1) skipMe = true; break; + case 1: if (i1 < n1) skipMe = true; break; + case -2: if (i2 > p2) skipMe = true; break; + case 2: if (i2 < n2) skipMe = true; break; + } + if (skipMe) continue; + + // Get element edge length in the parameter space + double dS = this->getParametricLength(iel,abs(edgeDir)); + if (dS < 0.0) return false; // topology error (probably logic error) + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points along the edge ------------- + + int ip = (abs(edgeDir) == 1 ? i2-p2 : i1-p1)*nGauss; + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.5*dS*wg[i]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline[ip],N,dNdu); + + // Compute basis function derivatives and the edge normal + double dT = utl::Jacobian(Jac,normal,dNdX,Xnod,dNdu,t1,t2); + if (dT == 0.0) continue; // skip singular points + + if (edgeDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dT*weight,N,dNdX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2D::getGridParameters (RealArray& prm, int dir, int nSegPerSpan) const +{ + if (!surf) return false; + + if (nSegPerSpan < 1) + { + std::cerr <<" *** ASMs2D::getGridParameters: Too few knot-span points " + << nSegPerSpan+1 <<" in direction "<< dir << std::endl; + return false; + } + + DoubleIter uit = surf->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != surf->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nSegPerSpan; i++) + { + double xg = (double)(2*i-nSegPerSpan)/(double)nSegPerSpan; + prm.push_back(0.5*(ucurr*(1.0+xg) + uprev*(1.0-xg))); + } + uprev = ucurr; + } + + prm.push_back(surf->basis(dir).endparam()); + return true; +} + + +bool ASMs2D::getGrevilleParameters (RealArray& prm, int dir) const +{ + if (!surf) return false; + + const Go::BsplineBasis& basis = surf->basis(dir); + + prm.resize(basis.numCoefs()); + for (size_t i = 0; i < prm.size(); i++) + prm[i] = basis.grevilleParameter(i); + + return true; +} + + +bool ASMs2D::tesselate (ElementBlock& grid, const int* npe) const +{ + // Compute parameter values of the nodal points + DoubleVec gpar[2]; + for (int dir = 0; dir < 2; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the spline surface at all points + size_t nx = gpar[0].size(); + size_t ny = gpar[1].size(); + DoubleVec XYZ(surf->dimension()*nx*ny); + surf->gridEvaluator(XYZ,gpar[0],gpar[1]); + + // Establish the block grid coordinates + size_t i, j, l; + double X[3] = { 0.0, 0.0, 0.0 }; + grid.resize(nx,ny); + for (i = j = 0; i < grid.getNoNodes(); i++, j += surf->dimension()) + { + for (l = 0; l < nsd; l++) + X[l] = XYZ[j+l]; + grid.setCoor(i,X[0],X[1],X[2]); + } + + // Establish the block grid topology + int n[4], ip = 0; + for (j = 1, n[1] = 0; j < ny; j++) + { + n[0] = n[1]; + n[1] = n[0] + 1; + n[2] = n[1] + nx; + n[3] = n[1] + nx-1; + for (i = 1; i < nx; i++) + for (l = 0; l < 4; l++) + grid.setNode(ip++,n[l]++); + } + + return true; +} + + +void ASMs2D::scatterInd (int n1, int n2, int p1, int p2, + const int* start, IntVec& index) +{ + index.reserve(p1*p2); + int ip = ((start[1]-p2+1))*n1 + (start[0]-p1+1); + for (int i2 = 0; i2 < p2; i2++, ip += n1-p1) + for (int i1 = 0; i1 < p1; i1++, ip++) + index.push_back(ip); +} + + +bool ASMs2D::evalSolution (Matrix& sField, const Vector& locSol, + const int* npe) const +{ + // Compute parameter values of the result sampling points + DoubleVec gpar[2]; + for (int dir = 0; dir < 2; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the basis functions at all points + std::vector spline; + surf->computeBasisGrid(gpar[0],gpar[1],spline); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + size_t nComp = locSol.size() / this->getNoNodes(); + if (nComp*this->getNoNodes() != locSol.size()) + return false; + + Matrix Xtmp; + Vector Ytmp; + + // Evaluate the primary solution field at each point + size_t nPoints = spline.size(); + sField.resize(nComp,nPoints); + for (size_t i = 0; i < nPoints; i++) + { + IntVec ip; + scatterInd(n1,n2,p1,p2,spline[i].left_idx,ip); + + utl::gather(ip,nComp,locSol,Xtmp); + Xtmp.multiply(spline[i].basisValues,Ytmp); + sField.fillColumn(1+i,Ytmp); + } + + return true; +} + + +bool ASMs2D::evalSolution (Matrix& sField, const Integrand& integrand, + const int* npe) const +{ + sField.resize(0,0); + + // Compute parameter values of the result sampling points + DoubleVec gpar[2]; + bool retVal = true; + for (int dir = 0; dir < 2; dir++) + if (npe) + retVal = this->getGridParameters(gpar[dir],dir,npe[dir]-1); + else + retVal = this->getGrevilleParameters(gpar[dir],dir); + + // Evaluate the secondary solution at all sampling points + if (retVal) retVal = this->evalSolution(sField,integrand,gpar); + if (!retVal || npe) return retVal; + + // Project the results onto the spline basis to find control point + // values based on the result values evaluated at the Greville points. + // Note that we here implicitly assume the the number of Greville points + // equals the number of control points such that we don't have to resize + // the result array. Think that is always the case, but beware if trying + // other projection schemes later. + + Vector sValues = sField; + DoubleVec weights; + if (surf->rational()) + surf->getWeights(weights); + + Go::SplineSurface* s = + Go::SurfaceInterpolator::regularInterpolation(surf->basis(0), + surf->basis(1), + gpar[0], gpar[1], + sValues, sField.rows(), + surf->rational(), weights); + + sField.fill(&(*s->coefs_begin())); + delete s; + + return retVal; +} + + +bool ASMs2D::evalSolution (Matrix& sField, const Integrand& integrand, + const RealArray* gpar) const +{ + sField.resize(0,0); + + // Evaluate the basis functions and their derivatives at all points + std::vector spline; + surf->computeBasisGrid(gpar[0],gpar[1],spline); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + + // Fetch nodal (control point) coordinates + Matrix Xnod, Xtmp; + this->getNodalCoordinates(Xnod); + + Vector N(p1*p2), solPt; + Matrix dNdu, dNdX, Jac; + + // Evaluate the secondary solution field at each point + size_t nPoints = spline.size(); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch indices of the non-zero basis functions at this point + IntVec ip; + scatterInd(n1,n2,p1,p2,spline[i].left_idx,ip); + + // Fetch associated control point coordinates + utl::gather(ip,nsd,Xnod,Xtmp); + + // Fetch basis function derivatives at current integration point + extractBasis(spline[i],N,dNdu); + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xtmp,dNdu) == 0.0) // Jac = (Xtmp * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xtmp*N,ip)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + sField.fillColumn(1+i,solPt); + } + + return true; +} diff --git a/src/ASM/ASMs2D.h b/src/ASM/ASMs2D.h new file mode 100644 index 00000000..77e65e34 --- /dev/null +++ b/src/ASM/ASMs2D.h @@ -0,0 +1,289 @@ +// $Id: ASMs2D.h,v 1.11 2011-01-28 13:34:28 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2D.h +//! +//! \date Sep 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 2D spline FE models. +//! +//============================================================================== + +#ifndef _ASM_S2D_H +#define _ASM_S2D_H + +#include "ASMstruct.h" + +namespace Go { + class SplineSurface; + class BasisDerivsSf; + class BasisDerivsSf2; +} + + +/*! + \brief Driver for assembly of structured 2D spline FE models. + \details This class contains methods common for structured 2D spline patches. +*/ + +class ASMs2D : public ASMstruct +{ + //! \brief Struct for nodal point data. + struct IJ + { + int I; //!< Index in first parameter direction + int J; //!< Index in second parameter direction + }; + + //! \brief Struct for edge node definitions. + struct Edge + { + int icnod; //!< Global node number of first interior point along the edge + int incr; //!< Increment in the global numbering along the edge + + //! \brief Default constructor. + Edge() { icnod = incr = 0; } + //! \brief Returns \a icnod which then is incremented. + int next(); + }; + +public: + //! \brief Struct with data for definition of global node numbers of a patch. + struct BlockNodes + { + int ibnod[4]; //!< Vertex nodes + Edge edges[4]; //!< Edge nodes + int iinod; //!< Global node number of the first interior node + int inc[2]; //!< Increment in global node numbering in each direction + int nnodI; //!< Number of nodes in parameter direction I + int indxI; //!< Running node index in the local I-direction + + //! \brief Default constructor. + BlockNodes() { iinod = inc[0] = inc[1] = 0; indxI = 1; } + //! \brief Returns \a iinod which then is incremented. + int next(); + }; + + //! \brief Constructor creating an instance by reading the given file. + ASMs2D(const char* fileName, unsigned char n_s = 2, unsigned char n_f = 2); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs2D(std::istream& is, unsigned char n_s = 2, unsigned char n_f = 2); + //! \brief Default constructor creating an empty patch. + ASMs2D(unsigned char n_s = 2, unsigned char n_f = 2); + //! \brief Empty destructor. + virtual ~ASMs2D() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the node-to-IJ-index array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + //! \brief Creates an instance by reading the given input stream, \a is. + bool read(std::istream& is); + //! \brief Writes the geometry of the SplineSurface object to given stream. + bool write(std::ostream& os) const; + + //! \brief Assigns new global node numbers for all nodes of the patch. + //! \param nodes Object with global nodes numbers to assign to this patch + //! \param[in] basis Which basis to assign node numbers for in mixed methods + //! + //! \details The global node numbers generated by \a generateFEMTopology are + //! non-unique in the sense that a node that is shared by two (or more) + //! patches along a common interface has a different number in each patch. + //! This method therefore assigns a new global number to each node in the + //! patch. The data provided through the \a nodes argument is sufficient + //! to determine the unique global number under the assumption that they + //! are ordered in the sequence determined by the local orientation of the + //! patch and its edges. + bool assignNodeNumbers(BlockNodes& nodes, int basis = 0); + + //! \brief Refine the parametrization by inserting extra knots. + //! \param[in] dir Parameter direction to refine + //! \param[in] xi Relative positions of added knots in each existing knot span + bool refine(int dir, const RealArray& xi); + //! \brief Refine the parametrization by inserting extra knots uniformly. + //! \param[in] dir Parameter direction to refine + //! \param[in] nInsert Number of extra knots to insert in each knot-span + bool uniformRefine(int dir, int nInsert); + //! \brief Raise the order of the SplineSurface object for this patch. + //! \param[in] ru Number of times to raise the order in u-direction + //! \param[in] rv Number of times to raise the order in v-direction + bool raiseOrder(int ru, int rv); + + + // Various methods for preprocessing of boundary conditions + // ======================================================== + + //! \brief Makes two opposite boundary edges periodic. + //! \param[in] dir Parameter direction defining the periodic edges + void closeEdges(int dir); + + //! \brief Constrains all DOFs on a given boundary edge. + //! \param[in] dir Parameter direction defining the edge to constrain + //! \param[in] dof Which DOFs to constrain at each node on the edge + //! \param[in] code Inhomogeneous dirichlet condition code + void constrainEdge(int dir, int dof = 123, int code = 0); + + //! \brief Constrains a corner node identified by the two parameter indices. + //! \param[in] I Parameter index in u-direction + //! \param[in] J Parameter index in v-direction + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] code Inhomogeneous dirichlet condition code + //! + //! \details The sign of the two indices is used to define whether we want + //! the node at the beginning or the end of that parameter direction. + //! The magnitude of the indices are not used. + void constrainCorner(int I, int J, int dof = 123, int code = 0); + //! \brief Constrains a node identified by two relative parameter values. + //! \param[in] xi Parameter in u-direction + //! \param[in] eta Parameter in v-direction + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] code Inhomogeneous dirichlet condition code + //! + //! \details The parameter values have to be in the domain [0.0,1.0], where + //! 0.0 means the beginning of the domain and 1.0 means the end. For values + //! in between, the actual index is taken as the integer value closest to + //! \a r*n, where \a r denotes the given relative parameter value, + //! and \a n is the number of nodes along that parameter direction. + void constrainNode(double xi, double eta, int dof = 123, int code = 0); + + //! \brief Connects all matching nodes on two adjacent boundary edges. + //! \param[in] edge Local edge index of this patch, in range [1,4] + //! \param neighbor The neighbor patch + //! \param[in] nedge Local edge index of neighbor patch, in range [1,4] + //! \param[in] revers Indicates whether the two edges have opposite directions + bool connectPatch(int edge, ASMs2D& neighbor, int nedge, bool revers = false); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Creates a quad element model of this patch for visualization. + //! \param[out] grid The generated quadrilateral grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + //! + //! \details If \a npe is NULL, the solution is evaluated at the Greville + //! points and then projected onto the spline basis to obtain the control + //! point values, which then are returned through \a sField. + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Evaluates the secondary solution field at the given points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] gpar Parameter values of the result sampling points + bool evalSolution(Matrix& sField, const Integrand& integrand, + const RealArray* gpar) const; + + //! \brief Calculates parameter values for visualization nodal points. + //! \param[out] prm Parameter values in given direction for all points + //! \param[in] dir Parameter direction (0,1) + //! \param[in] nSegSpan Number of visualization segments over each knot-span + virtual bool getGridParameters(RealArray& prm, int dir, int nSegSpan) const; + + //! \brief Calculates parameter values for the Greville points. + //! \param[out] prm Parameter values in given direction for all points + //! \param[in] dir Parameter direction (0,1) + bool getGrevilleParameters(RealArray& prm, int dir) const; + + //! \brief Returns the area in the parameter space for an element. + //! \param[in] iel Element index + double getParametricArea(int iel) const; + //! \brief Returns boundary edge length in the parameter space for an element. + //! \param[in] iel Element index + //! \param[in] dir Local index of the boundary edge + double getParametricLength(int iel, int dir) const; + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const; + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[in] basis Which basis to return size parameters for (mixed methods) + virtual bool getSize(int& n1, int& n2, int basis = 0) const; + + //! \brief Establishes matrices with basis functions and 1st derivatives. + static void extractBasis(const Go::BasisDerivsSf& spline, + Vector& N, Matrix& dNdu); + //! \brief Establishes matrices with basis functions, 1st and 2nd derivatives. + static void extractBasis(const Go::BasisDerivsSf2& spline, + Vector& N, Matrix& dNdu, Matrix3D& d2Ndu2); + + //! \brief Auxilliary function for computation of basis function indices. + static void scatterInd(int n1, int n2, int p1, int p2, + const int* start, IntVec& index); + +private: + //! \brief Returns an index into the internal coefficient array for a node. + //! \param[in] inod 0-based node index local to current patch + int coeffInd(size_t inod) const; + +protected: + Go::SplineSurface* surf; //!< Pointer to the actual spline surface object + std::vector nodeInd; //!< IJ-pairs for the control points (nodes) +}; + +#endif diff --git a/src/ASM/ASMs2DLag.C b/src/ASM/ASMs2DLag.C new file mode 100644 index 00000000..378bcf80 --- /dev/null +++ b/src/ASM/ASMs2DLag.C @@ -0,0 +1,443 @@ +// $Id: ASMs2DLag.C,v 1.11 2011-01-08 14:00:16 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2DLag.C +//! +//! \date Mar 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 2D Lagrange FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineSurface.h" + +#include "ASMs2DLag.h" +#include "Lagrange.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "ElementBlock.h" +#include "Utilities.h" +#include "Vec3Oper.h" + + +ASMs2DLag::ASMs2DLag (const char* fileName, + unsigned char n_s, unsigned char n_f) + : ASMs2D(fileName,n_s,n_f) +{ + nx = ny = 0; +} + + +ASMs2DLag::ASMs2DLag (std::istream& is, unsigned char n_s, unsigned char n_f) + : ASMs2D(is,n_s,n_f) +{ + nx = ny = 0; +} + + +void ASMs2DLag::clear () +{ + coord.clear(); + nx = ny = 0; + ASMs2D::clear(); +} + + +bool ASMs2DLag::generateFEMTopology () +{ + if (!surf) return false; + + // Order of the basis in the two parametric directions (order = degree + 1) + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + + // Evaluate the parametric values + RealArray gpar[2]; + if (!this->getGridParameters(gpar[0],0,p1-1)) return false; + if (!this->getGridParameters(gpar[1],1,p2-1)) return false; + + // Number of nodes in each direction + nx = gpar[0].size(); + ny = gpar[1].size(); + + if (!coord.empty()) + return coord.size() == nx*ny; + + // Number of elements in each direction + const int nelx = (nx-1)/(p1-1); + const int nely = (ny-1)/(p2-1); + + // Evaluate the nodal coordinates in the physical space + RealArray XYZ(surf->dimension()*nx*ny); + surf->gridEvaluator(XYZ,gpar[0],gpar[1]); + + size_t i1, j1; + MLGN.resize(nx*ny); + coord.resize(nx*ny); + for (i1 = j1 = 0; i1 < coord.size(); i1++) + { + MLGN[i1] = ++gNod; + for (size_t d = 0; d < nsd; d++) + coord[i1][d] = XYZ[j1+d]; + j1 += surf->dimension(); + } + + // Number of elements in patch + const int nel = nelx*nely; + // Number of nodes per element + const int nen = p1*p2; + + // Connectivety array: local --> global node relation + MLGE.resize(nel); + MNPC.resize(nel); + + int i, j, a, b, iel = 0; + for (j = 0; j < nely; j++) + for (i = 0; i < nelx; i++, iel++) + { + MLGE[iel] = ++gEl; + MNPC[iel].resize(nen); + // First node in current element + int corner = (p2-1)*nx*j + (p1-1)*i; + + for (b = 0; b < p2; b++) + { + int facenod = b*p1; + MNPC[iel][facenod] = corner + b*nx; + for (a = 1; a < p1; a++) + MNPC[iel][facenod+a] = MNPC[iel][facenod] + a; + } + } + + return true; +} + + +bool ASMs2DLag::getElementCoordinates (Matrix& X, int iel) const +{ + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs2DLag::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } + + // Number of nodes per element + const size_t nen = surf->order_u()*surf->order_v(); + + X.resize(nsd,nen); + for (size_t i = 0; i < nen; i++) + X.fillColumn(i+1,coord[MNPC[iel-1][i]].ptr()); + + return true; +} + + +void ASMs2DLag::getNodalCoordinates (Matrix& X) const +{ + X.resize(nsd,coord.size()); + + for (size_t inod = 0; inod < coord.size(); inod++) + X.fillColumn(inod+1,coord[inod].ptr()); +} + + +Vec3 ASMs2DLag::getCoord (size_t inod) const +{ + if (inod < 1 || inod > coord.size()) return Vec3(); + + return coord[inod-1]; +} + + +bool ASMs2DLag::getSize (int& n1, int& n2, int) const +{ + n1 = nx; + n2 = ny; + + return true; +} + + +bool ASMs2DLag::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Order of basis in the two parametric directions (order = degree + 1) + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + + Vector N(p1*p2); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + if (integrand.getIntegrandType() == 4) + { + // Compute the element "center" (average of element node coordinates) + X = 0.0; + for (size_t i = 1; i <= nsd; i++) + for (size_t j = 1; j <= Xnod.cols(); j++) + X[i-1] += Xnod(i,j); + + X *= 1.0/(double)Xnod.cols(); + } + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1],X,nGauss*nGauss)) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction ---------- + + for (int j = 0; j < nGauss; j++) + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = wg[i]*wg[j]; + + // Compute basis function derivatives at current integration point + // using tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N,dNdu,p1,xg[i],p2,xg[j])) + return false; + + // Compute Jacobian inverse of coordinate mapping and derivatives + double dA = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + if (dA == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,dA*weight,N,dNdX,X)) + return false; + } + + // Finalize the element quantities + if (!integrand.finalizeElement(elmInt)) + return false; + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2DLag::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the edge normal {-2,-1, 1, 2} + const int edgeDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = abs(edgeDir); // tangent direction normal to the patch edge + const int t2 = 3-t1; // tangent direction along the patch edge + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + + // Number of elements in each direction + const int nelx = (nx-1)/(p1-1); + const int nely = (ny-1)/(p2-1); + + Vector N(p1*p2); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + Vec3 normal; + double xi[2]; + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (edgeDir) + { + case -1: if (i1 > 0) skipMe = true; break; + case 1: if (i1 < nelx-1) skipMe = true; break; + case -2: if (i2 > 0) skipMe = true; break; + case 2: if (i2 < nely-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points along the edge ------------- + + for (int i = 0; i < nGauss; i++) + { + // Gauss point coordinates along the edge + xi[t1-1] = edgeDir < 0 ? -1.0 : 1.0; + xi[t2-1] = xg[i]; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N,dNdu,p1,xi[0],p2,xi[1])) + return false; + + // Compute basis function derivatives and the edge normal + double dS = utl::Jacobian(Jac,normal,dNdX,Xnod,dNdu,t1,t2); + if (dS == 0.0) continue; // skip singular points + + if (edgeDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*wg[i],N,dNdX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2DLag::tesselate (ElementBlock& grid, const int* npe) const +{ + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + if (p1 != npe[0] || p2 != npe[1]) + { + int* newnpe = const_cast(npe); + std::cout <<"\nLagrange elements: The number of visualization points are " + << p1 <<" "<< p2 <<" by default\n"<< std::endl; + newnpe[0] = p1; + newnpe[1] = p2; + } + + return this->ASMs2D::tesselate(grid,npe); +} + + +bool ASMs2DLag::evalSolution (Matrix& sField, const Vector& locSol, + const int*) const +{ + size_t nPoints = coord.size(); + size_t nComp = locSol.size() / nPoints; + if (nComp*nPoints != locSol.size()) + return false; + + size_t i, n, ip = 0; + sField.resize(nComp,nPoints); + for (n = 1; n <= nPoints; n++) + for (i = 1; i <= nComp; i++) + sField(i,n) = locSol(++ip); + + return true; +} + + +bool ASMs2DLag::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + double incx = 2.0/double(p1-1); + double incy = 2.0/double(p2-1); + + size_t nPoints = coord.size(); + IntVec check(nPoints,0); + + Vector N(p1*p2), solPt; + std::vector globSolPt(nPoints); + Matrix dNdu, dNdX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + const IntVec& mnpc = MNPC[iel-1]; + this->getElementCoordinates(Xnod,iel); + + int i, j, loc = 0; + for (j = 0; j < p2; j++) + for (i = 0; i < p1; i++, loc++) + { + double xi = -1.0 + i*incx; + double eta = -1.0 + j*incy; + if (!Lagrange::computeBasis(N,dNdu,p1,xi,p2,eta)) + return false; + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xnod,dNdu) == 0.0) // Jac = (Xnod * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xnod*N,mnpc)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc[loc]] == 1) + globSolPt[mnpc[loc]] = solPt; + else + globSolPt[mnpc[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs2DLag.h b/src/ASM/ASMs2DLag.h new file mode 100644 index 00000000..0a94e449 --- /dev/null +++ b/src/ASM/ASMs2DLag.h @@ -0,0 +1,132 @@ +// $Id: ASMs2DLag.h,v 1.6 2010-12-29 18:41:38 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2DLag.h +//! +//! \date Mar 20 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 2D Lagrange FE models. +//! +//============================================================================== + +#ifndef _ASM_S2D_LAG_H +#define _ASM_S2D_LAG_H + +#include "ASMs2D.h" +#include "Vec3.h" + + +/*! + \brief Driver for assembly of structured 2D Lagrange FE models. + \details This class contains methods for structured 2D Lagrange patches. +*/ + +class ASMs2DLag : public ASMs2D +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs2DLag(const char* fileName, unsigned char n_s = 2, unsigned char n_f = 2); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs2DLag(std::istream& is, unsigned char n_s = 2, unsigned char n_f = 2); + //! \brief Default constructor creating an empty patch. + ASMs2DLag(unsigned char n_s = 2, unsigned char n_f = 2) : ASMs2D(n_s,n_f) {} + //! \brief Empty destructor. + virtual ~ASMs2DLag() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the nodal coordinate array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Creates a quad element model of this patch for visualization. + //! \param[out] grid The generated quadrilateral grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \details The number of visualization points is the same as the order of + //! the Lagrange elements by default. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \details The number of visualization points is the same as the order of + //! the Lagrange elements by default. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const; + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + virtual bool getSize(int& n1, int& n2, int = 0) const; + +private: + size_t nx; //!< Number of nodes in first parameter direction + size_t ny; //!< Number of nodes in second parameter direction + std::vector coord; //!< Nodal coordinates +}; + +#endif diff --git a/src/ASM/ASMs2DSpec.C b/src/ASM/ASMs2DSpec.C new file mode 100644 index 00000000..fef1edc3 --- /dev/null +++ b/src/ASM/ASMs2DSpec.C @@ -0,0 +1,326 @@ +// $Id: ASMs2DSpec.C,v 1.6 2010-12-29 18:36:06 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2DSpec.C +//! +//! \date Mar 22 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 2D Spectral FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineSurface.h" + +#include "ASMs2DSpec.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "Vec3Oper.h" +#include "Legendre.h" + + +bool ASMs2DSpec::getGridParameters (RealArray& prm, int dir, + int nSegPerSpan) const +{ + if (!surf) return false; + + // Evaluate the Gauss-Lobatto-Legendre points in this direction + Vector dummy, xGLL; + int p = dir == 0 ? surf->order_u() : surf->order_v(); + if (!Legendre::GLL(dummy,xGLL,p)) return false; + + if (xGLL.size() != nSegPerSpan+1) + { + nSegPerSpan = xGLL.size() - 1; + std::cout <<"Spectral elements: Number of nodes per knot-span in " + << char('u'+dir) <<"-directon reset to "<< nSegPerSpan + <<" (GLL points)"<< std::endl; + } + + std::vector::const_iterator uit = surf->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != surf->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 1; i <= nSegPerSpan; i++) + prm.push_back(0.5*(ucurr-uprev)*(1.0+xGLL(i)) + uprev); + uprev = ucurr; + } + + prm.push_back(surf->basis(dir).endparam()); + return true; +} + + +/*! + \brief Establishes matrices with basis functions and 1st derivatives. +*/ + +static void evalBasis (int i, int j, int p1, int p2, + const Matrix& der1, const Matrix& der2, + Vector& N, Matrix& dNdu) +{ + int a, b, n = 1; + for (b = 1; b <= p2; b++) + for (a = 1; a <= p1; a++, n++) + { + N (n) = (a == i && b == j) ? 1.0 : 0.0; + dNdu(n,1) = (b == j) ? der1(i,a) : 0.0; + dNdu(n,2) = (a == i) ? der2(j,b) : 0.0; + } +} + + +bool ASMs2DSpec::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + // Order of basis in the two parametric directions (order = degree + 1) + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + + // Evaluate integration points (= nodal points) and weights + Vector wg1,xg1,wg2,xg2; + if (!Legendre::GLL(wg1,xg1,p1)) return false; + if (!Legendre::GLL(wg2,xg2,p2)) return false; + + Matrix D1, D2; + if (!Legendre::basisDerivatives(p1,D1)) return false; + if (!Legendre::basisDerivatives(p2,D2)) return false; + + Vector N (p1*p2); + Matrix dNdu(p1*p2,2); + Matrix dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction ---------- + + int count = 1; + for (int j = 1; j <= p2; j++) + for (int i = 1; i <= p1; i++, count++) + { + // Weight of current integration point + double weight = wg1(i)*wg2(j); + + // Evaluate the basis functions and gradients using + // tensor product of one-dimensional Lagrange polynomials + evalBasis(i,j,p1,p2,D1,D2,N,dNdu); + + // Compute Jacobian inverse of coordinate mapping and derivatives + double dA = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + if (dA == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X.x = Xnod(1,count); + X.y = Xnod(2,count); + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,dA*weight,N,dNdX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2DSpec::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + // Find the parametric direction of the edge normal {-2,-1, 1, 2} + int edgeDir = lIndex = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = abs(edgeDir); // Tangent direction normal to the patch edge + const int t2 = 3-abs(edgeDir); // Tangent direction along the patch edge + + // Order of basis in the two parametric directions (order = degree + 1) + int p[2]; + p[0] = surf->order_u(); + p[1] = surf->order_v(); + + // Number of elements in each direction + int n1, n2; + this->getSize(n1,n2); + const int nelx = (n1-1)/(p[0]-1); + const int nely = (n2-1)/(p[1]-1); + + // Evaluate integration points and weights + + Vector wg[2],xg1,xg2; + if (!Legendre::GLL(wg[0],xg1,p[0])) return false; + if (!Legendre::GLL(wg[1],xg2,p[1])) return false; + + Matrix D1, D2; + if (!Legendre::basisDerivatives(p[0],D1)) return false; + if (!Legendre::basisDerivatives(p[1],D2)) return false; + + int nen = p[0]*p[1]; + + Vector N(nen); + Matrix dNdu(nen,2); + Matrix dNdX, Xnod, Jac; + Vec4 X; + Vec3 normal; + int xi[2]; + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (edgeDir) + { + case -1: if (i1 > 0) skipMe = true; break; + case 1: if (i1 < nelx-1) skipMe = true; break; + case -2: if (i2 > 0) skipMe = true; break; + case 2: if (i2 < nely-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points along the edge ------------- + + for (int i = 0; i < p[t2-1]; i++) + { + // Weight of current integration point + double weight = wg[t2-1][i]; + + // "Coordinates" along the edge + xi[t1-1] = edgeDir < 0 ? 1 : p[t1-1]; + xi[t2-1] = i+1; + + // Evaluate the basis functions and gradients using + // tensor product of one-dimensional Lagrange polynomials + evalBasis(xi[0],xi[1],p[0],p[1],D1,D2,N,dNdu); + + // Compute basis function derivatives and the edge normal + double dS = utl::Jacobian(Jac,normal,dNdX,Xnod,dNdu,t1,t2); + if (dS == 0.0) continue; // skip singular points + + if (edgeDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*weight,N,dNdX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2DSpec::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + + Vector wg1,xg1,wg2,xg2; + if (!Legendre::GLL(wg1,xg1,p1)) return false; + if (!Legendre::GLL(wg2,xg2,p2)) return false; + + Matrix D1, D2; + if (!Legendre::basisDerivatives(p1,D1)) return false; + if (!Legendre::basisDerivatives(p2,D2)) return false; + + size_t nPoints = this->getNoNodes(); + IntVec check(nPoints,0); + + Vector N(p1*p2), solPt; + std::vector globSolPt(nPoints); + Matrix dNdu(p1*p2,2), dNdX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + const IntVec& mnpc = MNPC[iel-1]; + this->getElementCoordinates(Xnod,iel); + + int i, j, loc = 0; + for (j = 0; j < p2; j++) + for (i = 0; i < p1; i++, loc++) + { + evalBasis(i+1,j+1,p1,p2,D1,D2,N,dNdu); + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xnod,dNdu) == 0.0) // Jac = (Xnod * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xnod.getColumn(loc+1),mnpc)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc[loc]] == 1) + globSolPt[mnpc[loc]] = solPt; + else + globSolPt[mnpc[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs2DSpec.h b/src/ASM/ASMs2DSpec.h new file mode 100644 index 00000000..fd4a5c1d --- /dev/null +++ b/src/ASM/ASMs2DSpec.h @@ -0,0 +1,92 @@ +// $Id: ASMs2DSpec.h,v 1.4 2010-10-05 07:25:25 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2DSpec.h +//! +//! \date Mar 22 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 2D Spectral FE models. +//! +//============================================================================== + +#ifndef _ASM_S2D_SPEC_H +#define _ASM_S2D_SPEC_H + +#include "ASMs2DLag.h" + + +/*! + \brief Driver for assembly of structured 2D Spectral FE models. + \details This class contains methods for structured 2D Spectral patches. +*/ + +class ASMs2DSpec : public ASMs2DLag +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs2DSpec(const char* fileName, unsigned char n_s = 2, unsigned char n_f = 2) + : ASMs2DLag(fileName,n_s,n_f) {} + //! \brief Constructor creating an instance by reading the given input stream. + ASMs2DSpec(std::istream& is, unsigned char n_s = 2, unsigned char n_f = 2) + : ASMs2DLag(is,n_s,n_f) {} + //! \brief Default constructor creating an empty patch. + ASMs2DSpec(unsigned char n_s = 2, unsigned char n_f = 2) + : ASMs2DLag(n_s,n_f) {} + //! \brief Empty destructor. + virtual ~ASMs2DSpec() {} + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Calculates parameter values for all visualization nodal points. + //! \param[out] prm Parameter values in given direction for all points + //! \param[in] dir Parameter direction (0,1) + //! \param[in] nSegSpan Number of visualization segments over each knot-span + //! + //! This method is reimplemented to return the parameter values of the + //! Gauss-Lobatto-Legendre points, which define the FE nodes when using + //! spectral elements. This method is therefore also used when creating the + //! FE model (the visualization nodes and the FE nodes are identical). + virtual bool getGridParameters(RealArray& prm, int dir, int nSegSpan) const; +}; + +#endif diff --git a/src/ASM/ASMs2Dmx.C b/src/ASM/ASMs2Dmx.C new file mode 100644 index 00000000..b505c74c --- /dev/null +++ b/src/ASM/ASMs2Dmx.C @@ -0,0 +1,782 @@ +// $Id: ASMs2Dmx.C,v 1.2 2010-12-30 15:02:02 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2Dmx.C +//! +//! \date Oct 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 2D spline mixed FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineSurface.h" + +#include "ASMs2Dmx.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "Utilities.h" +#include "Profiler.h" +#include "Vec3Oper.h" +#include "Vec3.h" + +typedef Go::SplineSurface::Dvector DoubleVec; //!< 1D double array +typedef DoubleVec::const_iterator DoubleIter; //!< Iterator over DoubleVec + + +ASMs2Dmx::ASMs2Dmx (const char* fileName, unsigned char n_s, + char n_f1, unsigned char n_f2) + : ASMs2D(fileName, n_s), ASMmxBase(n_f1 < 0 ? -n_f1 : n_f1, n_f2, n_f1 < 0) +{ + basis1 = basis2 = 0; + nf = nf1 + nf2; +} + + +ASMs2Dmx::ASMs2Dmx (std::istream& is, unsigned char n_s, + char n_f1, unsigned char n_f2) + : ASMs2D(is, n_s), ASMmxBase(n_f1 < 0 ? -n_f1 : n_f1, n_f2, n_f1 < 0) +{ + basis1 = basis2 = 0; + nf = nf1 + nf2; +} + + +ASMs2Dmx::ASMs2Dmx (unsigned char n_s, char n_f1, unsigned char n_f2) + : ASMs2D(n_s), ASMmxBase(n_f1 < 0 ? -n_f1 : n_f1, n_f2, n_f1 < 0) +{ + basis1 = basis2 = 0; + nf = nf1 + nf2; +} + + +void ASMs2Dmx::clear () +{ + // Erase the spline data + if (basis1) delete basis1; + if (basis2) delete basis2; + surf = basis1 = basis2 = 0; + + // Erase the FE data + ASMs2D::clear(); +} + + +unsigned char ASMs2Dmx::getNoFields (int basis) const +{ + switch (basis) + { + case 1: return nf1; + case 2: return nf2; + } + + return nf; +} + + +unsigned char ASMs2Dmx::getNodalDOFs (size_t inod) const +{ + return inod <= nb1 ? nf1 : nf2; +} + + +void ASMs2Dmx::initMADOF (const int* sysMadof) +{ + this->init(MLGN,sysMadof); +} + + +void ASMs2Dmx::extractNodeVec (const Vector& globRes, Vector& nodeVec, + unsigned char) const +{ + this->extrNodeVec(globRes,nodeVec); +} + + +bool ASMs2Dmx::generateFEMTopology () +{ + if (!surf) return false; + + if (!basis1 && !basis2) + { + // With mixed methods we need two separates spline spaces + basis1 = new Go::SplineSurface(*surf); + basis2 = surf; + + // Order-elevate basis1 such that it is of one degree higher than basis2 + basis1->raiseOrder(1,1); + + // Define which basis that should be used to represent the geometry + if (geoUsesBasis1) surf = basis1; + } + + const int n1 = basis1->numCoefs_u(); + const int n2 = basis1->numCoefs_v(); + const int m1 = basis2->numCoefs_u(); + const int m2 = basis2->numCoefs_v(); + if (!nodeInd.empty()) + { + if (nodeInd.size() == nb1 + nb2) return true; + std::cerr <<" *** ASMs2Dmx::generateFEMTopology: Inconsistency between the" + <<" number of FE nodes "<< nodeInd.size() + <<"\n and the number of spline coefficients "<< nb1 + nb2 + <<" in the patch."<< std::endl; + return false; + } + + nb1 = n1*n2; // Number of functions in first basis + nb2 = m1*m2; // Number of functions in second basis + + const int p1 = basis1->order_u(); + const int p2 = basis1->order_v(); + const int q1 = basis2->order_u(); + const int q2 = basis2->order_v(); + + int i1, i2, j1, j2; +#ifdef SP_DEBUG + std::cout <<"numCoefs: "<< n1 <<" "<< n2 <<", "<< m1 <<" "<< m2; + std::cout <<"\norder: "<< p1 <<" "<< p2 <<", "<< q1 <<" "<< q2; + std::cout <<"\ndu:"; + for (i1 = 0; i1 < n1; i1++) + std::cout <<' '<< basis1->knotSpan(0,i1); + for (i1 = 0; i1 < m1; i1++) + std::cout <<' '<< basis2->knotSpan(0,i1); + std::cout <<"\ndv:"; + for (i2 = 0; i2 < n2; i2++) + std::cout <<' '<< basis1->knotSpan(1,i2); + for (i2 = 0; i2 < m2; i2++) + std::cout <<' '<< basis2->knotSpan(1,i2); + std::cout << std::endl; +#endif + // Consistency checks, just to be fool-proof + if (m1 < 2 || m2 < 2) return false; + if (q1 < 1 || q2 < 1) return false; + if (p1 > n1 || p2 > n2) return false; + if (q1 > m1 || q2 > m2) return false; + + MLGE.resize((n1-p1+1)*(n2-p2+1),0); + MLGN.resize(nb1 + nb2); + MNPC.resize(MLGE.size()); + nodeInd.resize(MLGN.size()); + + size_t iel, inod = 0; + for (i2 = 0; i2 < n2; i2++) + for (i1 = 0; i1 < n1; i1++) + { + nodeInd[inod].I = i1; + nodeInd[inod].J = i2; + MLGN[inod++] = ++gNod; + } + + for (i2 = 0; i2 < m2; i2++) + for (i1 = 0; i1 < m1; i1++) + { + nodeInd[inod].I = i1; + nodeInd[inod].J = i2; + MLGN[inod++] = ++gNod; + } + + if (geoUsesBasis1) + { + // Create nodal connectivities for basis 1 + iel = inod = 0; + for (i2 = 1; i2 <= n2; i2++) + for (i1 = 1; i1 <= n1; i1++, inod++) + if (i1 >= p1 && i2 >= p2) + { + if (basis1->knotSpan(0,i1-1) > 0.0) + if (basis1->knotSpan(1,i2-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1*p2+q1*q2,0); + + int lnod = 0; + for (j2 = p2-1; j2 >= 0; j2--) + for (j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - n1*j2 - j1; + } + + iel++; + } + + // Create nodal connectivities for basis 2 + iel = 0; + for (i2 = 1; i2 <= m2; i2++) + for (i1 = 1; i1 <= m1; i1++, inod++) + if (i1 >= q1 && i2 >= q2) + if (basis2->knotSpan(0,i1-1) > 0.0) + if (basis2->knotSpan(1,i2-1) > 0.0) + { + while (iel < MNPC.size() && MNPC[iel].empty()) iel++; + + int lnod = p1*p2; + for (j2 = q2-1; j2 >= 0; j2--) + for (j1 = q1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - m1*j2 - j1; + + iel++; + } + } + else + { + // Create nodal connectivities for basis 2 + iel = 0; + inod = n1*n2; + for (i2 = 1; i2 <= m2; i2++) + for (i1 = 1; i1 <= m1; i1++, inod++) + if (i1 >= q1 && i2 >= q2) + { + if (basis2->knotSpan(0,i1-1) > 0.0) + if (basis2->knotSpan(1,i2-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1*p2+q1*q2,0); + + int lnod = p1*p2; + for (j2 = q2-1; j2 >= 0; j2--) + for (j1 = q1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - m1*j2 - j1; + } + + iel++; + } + + // Create nodal connectivities for basis 1 + iel = inod = 0; + for (i2 = 1; i2 <= n2; i2++) + for (i1 = 1; i1 <= n1; i1++, inod++) + if (i1 >= p1 && i2 >= p2) + if (basis1->knotSpan(0,i1-1) > 0.0) + if (basis1->knotSpan(1,i2-1) > 0.0) + { + while (iel < MNPC.size() && MNPC[iel].empty()) iel++; + + int lnod = 0; + for (j2 = p2-1; j2 >= 0; j2--) + for (j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - n1*j2 - j1; + + iel++; + } + } + +#ifdef SP_DEBUG + std::cout <<"NEL = "<< MLGE.size() <<" NNOD = "<< MLGN.size() << std::endl; +#endif + return true; +} + + +bool ASMs2Dmx::getElementCoordinates (Matrix& X, int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs2Dmx::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } +#endif + + size_t nenod = surf->order_u()*surf->order_v(); + size_t lnod0 = geoUsesBasis1 ? 0 : basis1->order_u()*basis1->order_v(); + + X.resize(nsd,nenod); + const IntVec& mnpc = MNPC[iel-1]; + + DoubleIter cit = surf->coefs_begin(); + for (size_t n = 0; n < nenod; n++) + { + int iI = nodeInd[mnpc[lnod0+n]].I; + int iJ = nodeInd[mnpc[lnod0+n]].J; + int ip = (iJ*surf->numCoefs_u() + iI)*surf->dimension(); + for (size_t i = 0; i < nsd; i++) + X(i+1,n+1) = *(cit+(ip+i)); + } + +#if SP_DEBUG > 2 + std::cout <<"\nCoordinates for element "<< iel << X << std::endl; +#endif + return true; +} + + +Vec3 ASMs2Dmx::getCoord (size_t inod) const +{ +#ifdef INDEX_CHECK + if (inod < 1 || inod > nodeInd.size()) + { + std::cerr <<" *** ASMs2Dmx::getCoord: Node index "<< inod + <<" out of range [1,"<< nodeInd.size() <<"]."<< std::endl; + return Vec3(); + } +#endif + + DoubleIter cit; + const int I = nodeInd[inod-1].I; + const int J = nodeInd[inod-1].J; + if (inod <= nb1) + cit = basis1->coefs_begin() + + (J*basis1->numCoefs_u()+I) * basis1->dimension(); + else + cit = basis2->coefs_begin() + + (J*basis2->numCoefs_u()+I) * basis2->dimension(); + + Vec3 X; + for (size_t i = 0; i < nsd; i++, cit++) + X[i] = *cit; + + return X; +} + + +bool ASMs2Dmx::getSize (int& n1, int& n2, int basis) const +{ + switch (basis) + { + case 1: + if (!basis1) return false; + n1 = basis1->numCoefs_u(); + n2 = basis1->numCoefs_v(); + return true; + + case 2: + if (!basis2) return false; + n1 = basis2->numCoefs_u(); + n2 = basis2->numCoefs_v(); + return true; + } + + return this->ASMs2D::getSize(n1,n2); +} + + +bool ASMs2Dmx::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + if (!basis1 || !basis2) return false; + + PROFILE2("ASMs2Dmx::integrate(I)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + int dir; + Matrix gpar[2]; + for (dir = 0; dir < 2; dir++) + { + int pm1 = (dir == 0 ? surf->order_u() : surf->order_v()) - 1; + DoubleIter uit = surf->basis(dir).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = (dir == 0 ? surf->numCoefs_u() : surf->numCoefs_v()) - pm1; + gpar[dir].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[dir](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],spline2); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + const int nel1 = n1 - p1 + 1; + + Vector N1(basis1->order_u()*basis1->order_v()); + Vector N2(basis2->order_u()*basis2->order_v()); + Matrix dN1du, dN1dX, dN2du, dN2dX, Xnod, Jac; + Vec4 X; + double detJ; + + + // === Assembly loop over all elements in the patch ========================== + + int iel = 1; + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-area element + + // Get element area in the parameter space + double dA = this->getParametricArea(iel); + if (dA < 0.0) return false; // topology error (probably logic error) + + // Set up control point (nodal) coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElement(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction ---------- + + int ip = ((i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.25*dA*wg[i]*wg[j]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline1[ip],N1,dN1du); + extractBasis(spline2[ip],N2,dN2du); + + // Compute Jacobian inverse of the coordinate mapping and + // basis function derivatives w.r.t. Cartesian coordinates + if (geoUsesBasis1) + { + detJ = utl::Jacobian(Jac,dN1dX,Xnod,dN1du); + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + } + else + { + detJ = utl::Jacobian(Jac,dN2dX,Xnod,dN2du); + dN1dX.multiply(dN1du,Jac); // dN1dX = dN1du * J^-1 + } + if (detJ == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * (geoUsesBasis1 ? N1 : N2); + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*weight,N1,N2,dN1dX,dN2dX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2Dmx::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + if (!basis1 || !basis2) return false; + + PROFILE2("ASMs2Dmx::integrate(B)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the edge normal {-2,-1, 1, 2} + const int edgeDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = abs(edgeDir); // Tangent direction normal to the patch edge + const int t2 = 3-abs(edgeDir); // Tangent direction along the patch edge + + // Compute parameter values of the Gauss points along the whole patch edge + Matrix gpar[2]; + for (short int d = 0; d < 2; d++) + if (-1-d == edgeDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = d == 0 ? surf->startparam_u() : surf->startparam_v(); + } + else if (1+d == edgeDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = d == 0 ? surf->endparam_u() : surf->endparam_v(); + } + else + { + int pm1 = (d == 0 ? surf->order_u() : surf->order_v()) - 1; + DoubleIter uit = surf->basis(d).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = (d == 0 ? surf->numCoefs_u() : surf->numCoefs_v()) - pm1; + gpar[d].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[d](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],spline2); + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int n1 = surf->numCoefs_u(); + const int n2 = surf->numCoefs_v(); + const int nel1 = n1 - p1 + 1; + + Vector N1(basis1->order_u()*basis1->order_v()); + Vector N2(basis2->order_u()*basis2->order_v()); + Matrix dN1du, dN1dX, dN2du, dN2dX, Xnod, Jac; + Vec4 X; + Vec3 normal; + double detJ; + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-area element + + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (edgeDir) + { + case -1: if (i1 > p1) skipMe = true; break; + case 1: if (i1 < n1) skipMe = true; break; + case -2: if (i2 > p2) skipMe = true; break; + case 2: if (i2 < n2) skipMe = true; break; + } + if (skipMe) continue; + + // Get element edge length in the parameter space + double dS = this->getParametricLength(iel,abs(edgeDir)); + if (dS < 0.0) return false; // topology error (probably logic error) + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElementBou(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points along the edge ------------- + + int ip = (abs(edgeDir) == 1 ? i2-p2 : i1-p1)*nGauss; + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.5*dS*wg[i]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline1[ip],N1,dN1du); + extractBasis(spline2[ip],N2,dN2du); + + // Compute Jacobian inverse of the coordinate mapping and + // basis function derivatives w.r.t. Cartesian coordinates + if (geoUsesBasis1) + { + detJ = utl::Jacobian(Jac,normal,dN1dX,Xnod,dN1du,t1,t2); + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + } + else + { + detJ = utl::Jacobian(Jac,normal,dN2dX,Xnod,dN2du,t1,t2); + dN1dX.multiply(dN1du,Jac); // dN1dX = dN1du * J^-1 + } + if (detJ == 0.0) continue; // skip singular points + + if (edgeDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * (geoUsesBasis1 ? N1 : N2); + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,detJ*weight, + N1,N2,dN1dX,dN2dX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2Dmx::evalSolution (Matrix& sField, const Vector& locSol, + const int* npe) const +{ + // Compute parameter values of the result sampling points + DoubleVec gpar[2]; + for (int dir = 0; dir < 2; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + if (!basis1 || !basis2) return false; + + // Evaluate the basis functions at all points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],spline2); + + const int p1 = basis1->order_u(); + const int p2 = basis1->order_v(); + const int n1 = basis1->numCoefs_u(); + const int n2 = basis1->numCoefs_v(); + + const int q1 = basis2->order_u(); + const int q2 = basis2->order_v(); + const int m1 = basis2->numCoefs_u(); + const int m2 = basis2->numCoefs_v(); + + size_t nc1 = nf1; + size_t nc2 = 0; + if (nc1*nb1 < locSol.size()) + nc2 = (locSol.size() - nc1*nb1)/nb2; + else + nc1 = locSol.size()/nb1; + + if (nc1*nb1 + nc2*nb2 != locSol.size()) + return false; + + Matrix Xtmp; + Vector Ytmp, Ztmp; + + // Evaluate the primary solution field at each point + size_t nPoints = spline1.size(); + sField.resize(nc1+nc2,nPoints); + for (size_t i = 0; i < nPoints; i++) + { + IntVec ip; + scatterInd(n1,n2,p1,p2,spline1[i].left_idx,ip); + + utl::gather(ip,nc1,locSol,Xtmp); + Xtmp.multiply(spline1[i].basisValues,Ytmp); + + if (nc2 > 0) + { + ip.clear(); + scatterInd(m1,m2,q1,q2,spline1[i].left_idx,ip); + + utl::gather(ip,nc2,locSol,Xtmp,nc1*nb1); + Xtmp.multiply(spline2[i].basisValues,Ztmp); + Ytmp.insert(Ytmp.end(),Ztmp.begin(),Ztmp.end()); + } + sField.fillColumn(1+i,Ytmp); + } + + return true; +} + + +bool ASMs2Dmx::evalSolution (Matrix& sField, const Integrand& integrand, + const int* npe) const +{ + sField.resize(0,0); + + // Compute parameter values of the result sampling points + DoubleVec gpar[2]; + for (int dir = 0; dir < 2; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + if (!basis1 || !basis2) return false; + + // Evaluate the basis functions and their derivatives at all points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],spline2); + + const int p1 = basis1->order_u(); + const int p2 = basis1->order_v(); + const int n1 = basis1->numCoefs_u(); + const int n2 = basis1->numCoefs_v(); + + const int q1 = basis2->order_u(); + const int q2 = basis2->order_v(); + const int m1 = basis2->numCoefs_u(); + const int m2 = basis2->numCoefs_v(); + + // Fetch nodal (control point) coordinates + Matrix Xnod, Xtmp; + this->getNodalCoordinates(Xnod); + + Vector N1(p1*p2), N2(q1*q2), solPt; + Matrix dN1du, dN1dX, dN2du, dN2dX, Jac; + Vec3 X; + + // Evaluate the secondary solution field at each point + size_t nPoints = spline1.size(); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch indices of the non-zero basis functions at this point + IntVec ip1, ip2; + scatterInd(n1,n2,p1,p2,spline1[i].left_idx,ip1); + scatterInd(m1,m2,q1,q2,spline2[i].left_idx,ip2); + + // Fetch associated control point coordinates + utl::gather(geoUsesBasis1 ? ip1 : ip2, nsd, Xnod, Xtmp); + + // Fetch basis function derivatives at current integration point + extractBasis(spline1[i],N1,dN1du); + extractBasis(spline2[i],N2,dN2du); + + // Compute Jacobian inverse of the coordinate mapping and + // basis function derivatives w.r.t. Cartesian coordinates + if (geoUsesBasis1) + if (utl::Jacobian(Jac,dN1dX,Xtmp,dN1du) == 0.0) // Jac = (Xtmp * dN1du)^-1 + continue; // skip singular points + else + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + else + if (utl::Jacobian(Jac,dN2dX,Xtmp,dN2du) == 0.0) // Jac = (Xtmp * dN2du)^-1 + continue; // skip singular points + else + dN1dX.multiply(dN1du,Jac); // dN1dX = dN1du * J^-1 + + // Cartesian coordinates of current integration point + X = Xtmp * (geoUsesBasis1 ? N1 : N2); + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N1,N2,dN1dX,dN2dX,X,ip1,ip2)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + sField.fillColumn(1+i,solPt); + } + + return true; +} diff --git a/src/ASM/ASMs2Dmx.h b/src/ASM/ASMs2Dmx.h new file mode 100644 index 00000000..06334ae9 --- /dev/null +++ b/src/ASM/ASMs2Dmx.h @@ -0,0 +1,140 @@ +// $Id: ASMs2Dmx.h,v 1.2 2010-12-30 15:02:02 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2Dmx.h +//! +//! \date Nov 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 2D spline mixed FE models. +//! +//============================================================================== + +#ifndef _ASM_S2D_MX_H +#define _ASM_S2D_MX_H + +#include "ASMs2D.h" +#include "ASMmxBase.h" + + +/*! + \brief Driver for assembly of structured 2D spline mixed FE models. + \details This class implements a two-field mixed formulation with splines as + basis functions. The first field is of one order higher than the second field, + and its basis is obtained by order-elevating the input spline object once. + By default, the geometry is represented by the second (lower order) basis, + however, by negating the \a n_f1 argument to the constructor, the first + basis is used instead. +*/ + +class ASMs2Dmx : public ASMs2D, private ASMmxBase +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs2Dmx(const char* fileName, unsigned char n_s = 2, + char n_f1 = 2, unsigned char n_f2 = 1); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs2Dmx(std::istream& is, unsigned char n_s = 2, + char n_f1 = 2, unsigned char n_f2 = 1); + //! \brief Default constructor creating an empty patch. + ASMs2Dmx(unsigned char n_s = 2, char n_f1 = 2, unsigned char n_f2 = 1); + //! \brief Empty destructor. + virtual ~ASMs2Dmx() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the node-to-IJ-index array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + //! \brief Returns the number of solution fields. + virtual unsigned char getNoFields(int basis = 0) const; + //! \brief Returns the number of DOFs per node. + //! \param[in] inod 1-based node index local to current patch + virtual unsigned char getNodalDOFs(size_t inod) const; + + //! \brief Initializes the patch level MADOF array for mixed problems. + virtual void initMADOF(const int* sysMadof); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + + //! \brief Extracts nodal results for this patch from the global vector. + //! \param[in] globVec Global solution vector in DOF-order + //! \param[out] nodeVec Nodal result vector for this patch + virtual void extractNodeVec(const Vector& globVec, Vector& nodeVec, + unsigned char = 0) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[in] basis Which basis to return size parameters for + virtual bool getSize(int& n1, int& n2, int basis = 0) const; + +private: + Go::SplineSurface* basis1; //!< Pointer to spline object for the first basis + Go::SplineSurface* basis2; //!< Pointer to spline object for the second basis +}; + +#endif diff --git a/src/ASM/ASMs2DmxLag.C b/src/ASM/ASMs2DmxLag.C new file mode 100644 index 00000000..6fee0ffd --- /dev/null +++ b/src/ASM/ASMs2DmxLag.C @@ -0,0 +1,450 @@ +// $Id: ASMs2DmxLag.C,v 1.2 2010-12-30 15:02:02 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2DmxLag.C +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 2D Lagrange mixed FE models. +//! +//============================================================================== + +#include "GoTools/geometry/SplineSurface.h" + +#include "ASMs2DmxLag.h" +#include "Lagrange.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "Utilities.h" +#include "Vec3Oper.h" + + +ASMs2DmxLag::ASMs2DmxLag (const char* fileName, unsigned char n_s, + unsigned char n_f1, unsigned char n_f2) + : ASMs2DLag(fileName,n_s), ASMmxBase(n_f1,n_f2) +{ + nx2 = ny2 = 0; + nf = nf1 + nf2; +} + + +ASMs2DmxLag::ASMs2DmxLag (std::istream& is, unsigned char n_s, + unsigned char n_f1, unsigned char n_f2) + : ASMs2DLag(is,n_s), ASMmxBase(n_f1,n_f2) +{ + nx2 = ny2 = 0; + nf = nf1 + nf2; +} + + +ASMs2DmxLag::ASMs2DmxLag (unsigned char n_s, + unsigned char n_f1, unsigned char n_f2) + : ASMs2DLag(n_s), ASMmxBase(n_f1,n_f2) +{ + nx2 = ny2 = 0; + nf = nf1 + nf2; +} + + +void ASMs2DmxLag::clear () +{ + nx2 = ny2 = 0; + ASMs2DLag::clear(); +} + + +unsigned char ASMs2DmxLag::getNoFields (int basis) const +{ + switch (basis) + { + case 1: return nf1; + case 2: return nf2; + } + + return nf; +} + + +unsigned char ASMs2DmxLag::getNodalDOFs (size_t inod) const +{ + return inod <= nb1 ? nf1 : nf2; +} + + +void ASMs2DmxLag::initMADOF (const int* sysMadof) +{ + this->init(MLGN,sysMadof); +} + + +void ASMs2DmxLag::extractNodeVec (const Vector& globRes, Vector& nodeVec, + unsigned char) const +{ + this->extrNodeVec(globRes,nodeVec); +} + + +bool ASMs2DmxLag::generateFEMTopology () +{ + // Generate/check FE data for the geometry/basis1 + bool haveFEdata = !MLGN.empty(); + bool basis1IsOK = this->ASMs2DLag::generateFEMTopology(); + if (haveFEdata || !basis1IsOK) return basis1IsOK; + + // Order of 2nd basis in the two parametric directions (order = degree + 1) + const int p1 = surf->order_u()-1; + const int p2 = surf->order_v()-1; + if (p1 < 2 || p2 < 2) + { + std::cerr <<" *** ASMs2DmxLag::generateFEMTopology: Too low order " + << p1 <<","<< p2 <<" for the second basis."<< std::endl; + return false; + } + + // Evaluate the parametric values + RealArray gpar[2]; + if (!this->getGridParameters(gpar[0],0,p1-1)) return false; + if (!this->getGridParameters(gpar[1],1,p2-1)) return false; + + // Number of nodes in each direction + nx2 = gpar[0].size(); + ny2 = gpar[1].size(); + + // Add nodes for second basis (coordinates are not needed) + nb1 = MLGN.size(); + nb2 = nx2*ny2; + MLGN.reserve(nb1+nb2); + int i, j, a, b, iel; + for (j = 0; j < ny2; j++) + for (i = 0; i < nx2; i++) + MLGN.push_back(++gNod); + + // Number of elements in each direction + const int nelx = (nx2-1)/(p1-1); + const int nely = (ny2-1)/(p2-1); + + // Add connectivety for second basis: local --> global node relation + for (j = iel = 0; j < nely; j++) + for (i = 0; i < nelx; i++, iel++) + { + size_t nen1 = MNPC[iel].size(); + MNPC[iel].resize(nen1+p1*p2); + + // First node in current element + int corner = nb1 + (p2-1)*nx2*j + (p1-1)*i; + + for (b = 0; b < p2; b++) + { + int facenod = nen1 + b*p1; + MNPC[iel][facenod] = corner + b*nx2; + for (a = 1; a < p1; a++) + MNPC[iel][facenod+a] = MNPC[iel][facenod] + a; + } + } + + return true; +} + + +bool ASMs2DmxLag::getSize (int& n1, int& n2, int basis) const +{ + if (basis != 2) + return this->ASMs2DLag::getSize(n1,n2,1); + + n1 = nx2; + n2 = ny2; + + return true; +} + + +bool ASMs2DmxLag::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Order of basis in the two parametric directions (order = degree + 1) + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + // The second basis is assumed one order lower in each direction + const int q1 = p1 - 1; + const int q2 = p2 - 1; + + Vector N1(p1*p2), N2(q1*q2); + Matrix dN1du, dN2du, dN1dX, dN2dX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElement(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction ---------- + + for (int j = 0; j < nGauss; j++) + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = wg[i]*wg[j]; + + // Compute basis function derivatives at current integration point + // using tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N1,dN1du,p1,xg[i],p2,xg[j])) + return false; + if (!Lagrange::computeBasis(N2,dN2du,q1,xg[i],q2,xg[j])) + return false; + + // Compute Jacobian inverse of coordinate mapping and derivatives + double dA = utl::Jacobian(Jac,dN1dX,Xnod,dN1du); + if (dA == 0.0) continue; // skip singular points + + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + + // Cartesian coordinates of current integration point + X = Xnod * N1; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,dA*weight,N1,N2,dN1dX,dN2dX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2DmxLag::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!surf) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the edge normal {-2,-1, 1, 2} + const int edgeDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = abs(edgeDir); // tangent direction normal to the patch edge + const int t2 = 3-t1; // tangent direction along the patch edge + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + // The second basis is assumed one order lower in each direction + const int q1 = p1 - 1; + const int q2 = p2 - 1; + + // Number of elements in each direction + const int nelx = (nx2-1)/(q1-1); + const int nely = (ny2-1)/(q2-1); + + Vector N1(p1*p2), N2(q1*q2); + Matrix dN1du, dN2du, dN1dX, dN2dX, Xnod, Jac; + Vec4 X; + Vec3 normal; + double xi[2]; + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (edgeDir) + { + case -1: if (i1 > 0) skipMe = true; break; + case 1: if (i1 < nelx-1) skipMe = true; break; + case -2: if (i2 > 0) skipMe = true; break; + case 2: if (i2 < nely-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElementBou(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points along the edge ------------- + + for (int i = 0; i < nGauss; i++) + { + // Gauss point coordinates along the edge + xi[t1-1] = edgeDir < 0 ? -1.0 : 1.0; + xi[t2-1] = xg[i]; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N1,dN1du,p1,xi[0],p2,xi[1])) + return false; + if (!Lagrange::computeBasis(N2,dN2du,q1,xi[0],q2,xi[1])) + return false; + + // Compute basis function derivatives and the edge normal + double dS = utl::Jacobian(Jac,normal,dN1dX,Xnod,dN1du,t1,t2); + if (dS == 0.0) continue; // skip singular points + + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + + if (edgeDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N1; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*wg[i], + N1,N2,dN1dX,dN2dX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs2DmxLag::evalSolution (Matrix& sField, const Vector& locSol, + const int*) const +{ + size_t nc1 = nf1; + size_t nc2 = 0; + if (nc1*nb1 < locSol.size()) + nc2 = (locSol.size() - nc1*nb1)/nb2; + else + nc1 = locSol.size()/nb1; + + if (nc1*nb1 + nc2*nb2 != locSol.size()) + return false; + + // TODO: Add evaluation second field at the nodes of the first field + size_t nPoints = nb1; + size_t nComp = nc1; + size_t i, n, ip = 0; + sField.resize(nComp,nPoints); + for (n = 1; n <= nPoints; n++) + for (i = 1; i <= nComp; i++) + sField(i,n) = locSol(++ip); + + return true; +} + + +bool ASMs2DmxLag::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + if (!surf) return false; + + const int p1 = surf->order_u(); + const int p2 = surf->order_v(); + const int q1 = p1 - 1; + const int q2 = p2 - 1; + double incx = 2.0/double(q1); + double incy = 2.0/double(q2); + + size_t nPoints = nb1; + IntVec check(nPoints,0); + + Vector N1(p1*p2), N2(q1*q2), solPt; + std::vector globSolPt(nPoints); + Matrix dN1du, dN2du, dN1dX, dN2dX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + IntVec::const_iterator f2start = MNPC[iel-1].begin() + p1*p2; + IntVec mnpc1(MNPC[iel-1].begin(),f2start); + IntVec mnpc2(f2start,MNPC[iel-1].end()); + + this->getElementCoordinates(Xnod,iel); + + int i, j, loc = 0; + for (j = 0; j < p2; j++) + for (i = 0; i < p1; i++, loc++) + { + double xi = -1.0 + i*incx; + double eta = -1.0 + j*incy; + if (!Lagrange::computeBasis(N1,dN1du,p1,xi,p2,eta)) + return false; + if (!Lagrange::computeBasis(N2,dN2du,q1,xi,q2,eta)) + return false; + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dN1dX,Xnod,dN1du) == 0.0) // Jac = (Xnod*dN1du)^-1 + continue; // skip singular points + else + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N1,N2,dN1dX,dN2dX,Xnod*N1,mnpc1,mnpc2)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc1[loc]] == 1) + globSolPt[mnpc1[loc]] = solPt; + else + globSolPt[mnpc1[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs2DmxLag.h b/src/ASM/ASMs2DmxLag.h new file mode 100644 index 00000000..86926278 --- /dev/null +++ b/src/ASM/ASMs2DmxLag.h @@ -0,0 +1,126 @@ +// $Id: ASMs2DmxLag.h,v 1.1 2010-12-29 18:41:38 kmo Exp $ +//============================================================================== +//! +//! \file ASMs2DmxLag.h +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 2D Lagrange mixed FE models. +//! +//============================================================================== + +#ifndef _ASM_S2D_MX_LAG_H +#define _ASM_S2D_MX_LAG_H + +#include "ASMs2DLag.h" +#include "ASMmxBase.h" + + +/*! + \brief Driver for assembly of structured 2D Lagrange mixed FE models. + \details This class implements a two-field mixed formulation with Lagrangian + basis functions. The geometry and the first field are of equal order and + is one order higher than the second field. +*/ + +class ASMs2DmxLag : public ASMs2DLag, private ASMmxBase +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs2DmxLag(const char* fileName, unsigned char n_s = 2, + unsigned char n_f1 = 2, unsigned char n_f2 = 1); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs2DmxLag(std::istream& is, unsigned char n_s = 2, + unsigned char n_f1 = 2, unsigned char n_f2 = 1); + //! \brief Default constructor creating an empty patch. + ASMs2DmxLag(unsigned char n_s = 2, + unsigned char n_f1 = 2, unsigned char n_f2 = 1); + //! \brief Empty destructor. + virtual ~ASMs2DmxLag() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the nodal coordinate array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the number of solution fields. + virtual unsigned char getNoFields(int basis = 0) const; + //! \brief Returns the number of DOFs per node. + //! \param[in] inod 1-based node index local to current patch + virtual unsigned char getNodalDOFs(size_t inod) const; + + //! \brief Initializes the patch level MADOF array for mixed problems. + virtual void initMADOF(const int* sysMadof); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int*) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int*) const; + + //! \brief Extracts nodal results for this patch from the global vector. + //! \param[in] globVec Global solution vector in DOF-order + //! \param[out] nodeVec Nodal result vector for this patch + virtual void extractNodeVec(const Vector& globVec, Vector& nodeVec, + unsigned char = 0) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[in] basis Which basis to return size parameters for + virtual bool getSize(int& n1, int& n2, int basis = 0) const; + +private: + size_t nx2; //!< Number of nodes in 1st parameter direction for second basis + size_t ny2; //!< Number of nodes in 2nd parameter direction for second basis +}; + +#endif diff --git a/src/ASM/ASMs3D.C b/src/ASM/ASMs3D.C new file mode 100644 index 00000000..002b1986 --- /dev/null +++ b/src/ASM/ASMs3D.C @@ -0,0 +1,1848 @@ +// $Id: ASMs3D.C,v 1.30 2011-01-27 17:04:52 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3D.C +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 3D spline FE models. +//! +//============================================================================== + +#include "GoTools/geometry/ObjectHeader.h" +#include "GoTools/trivariate/SplineVolume.h" +#include "GoTools/trivariate/VolumeInterpolator.h" + +#include "ASMs3D.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "ElementBlock.h" +#include "Utilities.h" +#include "Profiler.h" +#include "Vec3Oper.h" +#include +#include + +typedef Go::SplineVolume::Dvector DoubleVec; //!< 1D double array +typedef DoubleVec::const_iterator DoubleIter; //!< Iterator over DoubleVec + + +ASMs3D::ASMs3D (const char* fileName, bool checkRHS, unsigned char n_f) + : ASMstruct(3,3,n_f) +{ + svol = 0; + swapW = false; + + std::cout <<"\nReading patch file "<< fileName << std::endl; + std::ifstream is(fileName); + if (!is.good()) + std::cerr <<" *** ASMs3D: Failure opening patch file"<< std::endl; + else if (this->read(is) && checkRHS) + this->checkRightHandSystem(); + + geo = svol; +} + + +ASMs3D::ASMs3D (std::istream& is, bool checkRHS, unsigned char n_f) + : ASMstruct(3,3,n_f) +{ + svol = 0; + swapW = false; + + if (this->read(is) && checkRHS) + this->checkRightHandSystem(); + + geo = svol; +} + + +bool ASMs3D::read (std::istream& is) +{ + if (svol) delete svol; + + Go::ObjectHeader head; + svol = new Go::SplineVolume; + is >> head >> *svol; + + // Eat white-space characters to see if there is more data to read + char c; + while (is.get(c)) + if (!isspace(c)) + { + is.putback(c); + break; + } + + if (!is.good() && !is.eof()) + { + std::cerr <<" *** ASMs3D::read: Failure reading spline data"<< std::endl; + delete svol; + svol = 0; + return false; + } + else if (svol->dimension() < 3) + { + std::cerr <<" *** ASMs3D::read: Invalid spline volume patch, dim=" + << svol->dimension() << std::endl; + return false; + } + + return true; +} + + +bool ASMs3D::write (std::ostream& os) const +{ + if (!svol) return false; + + os <<"700 1 0 0\n"; + os << *svol; + + return os.good(); +} + + +void ASMs3D::clear () +{ + // Erase spline data + if (svol) delete svol; + svol = 0; + geo = 0; + + // Erase the FE data + nodeInd.clear(); + ASMbase::clear(); +} + + +bool ASMs3D::checkRightHandSystem () +{ + if (!svol) return false; + + // Evaluate the spline volume at its center + DoubleVec u(1,0.5*(svol->startparam(0) + svol->endparam(0))); + DoubleVec v(1,0.5*(svol->startparam(1) + svol->endparam(1))); + DoubleVec w(1,0.5*(svol->startparam(2) + svol->endparam(2))); + DoubleVec X(3), dXdu(3), dXdv(3), dXdw(3); + svol->gridEvaluator(u,v,w,X,dXdu,dXdv,dXdw); + + // Check that |J| = (dXdu x dXdv) * dXdw > 0.0 + if (Vec3(dXdu,dXdv) * dXdw > 0.0) return false; + + // This patch has a negative Jacobian determinant. Probably it is modelled + // in a left-hand-system. Swap the w-parameter direction to correct for this. + svol->reverseParameterDirection(2); + std::cout <<"\tSwapped."<< std::endl; + return swapW = true; +} + + +bool ASMs3D::refine (int dir, const RealArray& xi) +{ + if (!svol || dir < 0 || dir > 2 || xi.empty()) return false; + if (xi.front() < 0.0 || xi.back() > 1.0) return false; + + DoubleVec extraKnots; + DoubleIter uit = svol->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != svol->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (size_t i = 0; i < xi.size(); i++) + if (i > 0 && xi[i] < xi[i-1]) + return false; + else + extraKnots.push_back(ucurr*xi[i] + uprev*(1.0-xi[i])); + + uprev = ucurr; + } + + svol->insertKnot(dir,extraKnots); + return true; +} + + +bool ASMs3D::uniformRefine (int dir, int nInsert) +{ + if (!svol || dir < 0 || dir > 2 || nInsert < 1) return false; + + DoubleVec extraKnots; + DoubleIter uit = svol->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != svol->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nInsert; i++) + { + double xi = (double)(i+1)/(double)(nInsert+1); + extraKnots.push_back(ucurr*xi + uprev*(1.0-xi)); + } + uprev = ucurr; + } + + svol->insertKnot(dir,extraKnots); + return true; +} + + +bool ASMs3D::raiseOrder (int ru, int rv, int rw) +{ + if (!svol) return false; + + svol->raiseOrder(ru,rv,rw); + return true; +} + + +bool ASMs3D::generateFEMTopology () +{ + if (!svol) return false; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + if (!nodeInd.empty()) + { + if (nodeInd.size() == n1*n2*n3) return true; + std::cerr <<" *** ASMs3D::generateFEMTopology: Inconsistency between the" + <<" number of FE nodes "<< nodeInd.size() + <<"\n and the number of spline coefficients "<< n1*n2*n3 + <<" in the patch."<< std::endl; + return false; + } + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); +#ifdef SP_DEBUG + std::cout <<"numCoefs: "<< n1 <<" "<< n2 <<" "<< n3; + std::cout <<"\norder: "<< p1 <<" "<< p2 <<" "<< p3; + for (int d = 0; d < 3; d++) + { + std::cout <<"\nd"<< char('u'+d) <<':'; + for (int i = 0; i < svol->numCoefs(d); i++) + std::cout <<' '<< svol->knotSpan(d,i); + } + std::cout << std::endl; +#endif + // Consistency checks, just to be fool-proof + if (n1 < 2 || n2 < 2 || n3 < 2) return false; + if (p1 < 1 || p1 < 1 || p3 < 1) return false; + if (p1 > n1 || p2 > n2 || p3 > n3) return false; + + MLGE.resize((n1-p1+1)*(n2-p2+1)*(n3-p3+1),0); + MLGN.resize(n1*n2*n3); + MNPC.resize(MLGE.size()); + nodeInd.resize(MLGN.size()); + + int iel = 0; + int inod = 0; + for (int i3 = 1; i3 <= n3; i3++) + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++) + { + nodeInd[inod].I = i1-1; + nodeInd[inod].J = i2-1; + nodeInd[inod].K = i3-1; + if (i1 >= p1 && i2 >= p2 && i3 >= p3) + { + if (svol->knotSpan(0,i1-1) > 0.0) + if (svol->knotSpan(1,i2-1) > 0.0) + if (svol->knotSpan(2,i3-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1*p2*p3,0); + + int lnod = 0; + for (int j3 = p3-1; j3 >= 0; j3--) + for (int j2 = p2-1; j2 >= 0; j2--) + for (int j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - n1*n2*j3 - n1*j2 - j1; + } + + iel++; + } + MLGN[inod++] = ++gNod; // global node number over all patches + } + +#ifdef SP_DEBUG + std::cout <<"NEL = "<< iel <<" NNOD = "<< inod << std::endl; +#endif + return true; +} + + +int ASMs3D::Edge::next () +{ + int ret = icnod; + icnod += incr; + + return ret; +} + + +int ASMs3D::Face::next () +{ + int ret = isnod; + isnod += incrI; + + if (++indxI >= nnodI-1) + { + indxI = 1; + isnod += incrJ - incrI*(nnodI-2); + } + + return ret; +} + + +int ASMs3D::BlockNodes::next () +{ + int ret = iinod; + iinod += inc[0]; + + if (++indxI >= nnodI-1) + { + indxI = 1; + iinod += inc[1] - inc[0]*(nnodI-2); + if (++indxJ >= nnodJ-1) + { + indxJ = 1; + iinod += inc[2] - inc[1]*(nnodJ-2); + } + } + + return ret; +} + + +bool ASMs3D::assignNodeNumbers (BlockNodes& nodes, int basis) +{ + int n1, n2, n3; + if (!this->getSize(n1,n2,n3,basis)) + return false; + + int m1 = 0, m2 = 0, m3 = 0; + if (basis > 0) + if (!this->getSize(m1,m2,m3,3-basis)) + return false; + + if (MLGN.size() != n1*n2*n3+m1*m2*m3) return false; + + nodes.faces[0].nnodI = nodes.faces[1].nnodI = n2; + nodes.faces[2].nnodI = nodes.faces[3].nnodI = n1; + nodes.faces[4].nnodI = nodes.faces[5].nnodI = n1; + nodes.nnodI = n1; + nodes.nnodJ = n2; + + if (nodes.inc[0] == 0 || nodes.inc[1] == 0 || nodes.inc[2] == 0) + { + nodes.inc[0] = 1; + nodes.inc[1] = n1-2; + nodes.inc[2] = (n1-2)*(n2-2); + } + + int inod = basis > 1 ? m1*m2*m3 : 0; + for (int k = 1; k <= n3; k++) + for (int j = 1; j <= n2; j++) + for (int i = 1; i <= n1; i++, inod++) + if (k == 1) + { + if (j == 1) + { + if (i == 1) + MLGN[inod] = nodes.ibnod[0]; + else if (i == n1) + MLGN[inod] = nodes.ibnod[1]; + else + MLGN[inod] = nodes.edges[0].next(); + } + else if (j == n2) + { + if (i == 1) + MLGN[inod] = nodes.ibnod[2]; + else if (i == n1) + MLGN[inod] = nodes.ibnod[3]; + else + MLGN[inod] = nodes.edges[1].next(); + } + else + { + if (i == 1) + MLGN[inod] = nodes.edges[4].next(); + else if (i == n1) + MLGN[inod] = nodes.edges[5].next(); + else + MLGN[inod] = nodes.faces[4].next(); + } + } + else if (k == n3) + { + if (j == 1) + { + if (i == 1) + MLGN[inod] = nodes.ibnod[4]; + else if (i == n1) + MLGN[inod] = nodes.ibnod[5]; + else + MLGN[inod] = nodes.edges[2].next(); + } + else if (j == n2) + { + if (i == 1) + MLGN[inod] = nodes.ibnod[6]; + else if (i == n1) + MLGN[inod] = nodes.ibnod[7]; + else + MLGN[inod] = nodes.edges[3].next(); + } + else + { + if (i == 1) + MLGN[inod] = nodes.edges[6].next(); + else if (i == n1) + MLGN[inod] = nodes.edges[7].next(); + else + MLGN[inod] = nodes.faces[5].next(); + } + } + else + { + if (j == 1) + { + if (i == 1) + MLGN[inod] = nodes.edges[8].next(); + else if (i == n1) + MLGN[inod] = nodes.edges[9].next(); + else + MLGN[inod] = nodes.faces[2].next(); + } + else if (j == n2) + { + if (i == 1) + MLGN[inod] = nodes.edges[10].next(); + else if (i == n1) + MLGN[inod] = nodes.edges[11].next(); + else + MLGN[inod] = nodes.faces[3].next(); + } + else + { + if (i == 1) + MLGN[inod] = nodes.faces[0].next(); + else if (i == n1) + MLGN[inod] = nodes.faces[1].next(); + else + MLGN[inod] = nodes.next(); + } + } + +#if SP_DEBUG > 1 + if (basis > 0) std::cout <<"\nBasis "<< basis <<":"; + for (int i = inod-n1*n2*n3; i < inod; i++) + std::cout <<"\nNode "<< i+1 <<"\t: " + << nodeInd[i].I <<" "<< nodeInd[i].J <<" "<< nodeInd[i].K + <<"\tglobal no. "<< MLGN[i]; + std::cout << std::endl; +#endif + return true; +} + + +bool ASMs3D::connectPatch (int face, ASMs3D& neighbor, int nface, int norient) +{ + if (swapW) // Account for swapped parameter direction + if (face == 5) + face = 6; + else if (face == 6) + face = 5; + + if (neighbor.swapW) // Account for swapped parameter direction + if (nface == 5) + nface = 6; + else if (nface == 6) + nface = 5; + + // Set up the slave node numbers for this volume patch + + int n1, n2, n3; + if (!this->getSize(n1,n2,n3,1)) return false; + int node = 1, i1 = 0, i2 = 0; + + switch (face) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + i1 = n1; + n1 = n2; + n2 = n3; + break; + + case 4: // Positive J-direction + node = n1*(n2-1)+1; + case 3: // Negative J-direction + i2 = n1*(n2-1); + i1 = 1; + n2 = n3; + break; + + case 6: // Positive K-direction + node = n1*n2*(n3-1)+1; + case 5: // Negative K-direction + i1 = 1; + break; + + default: + std::cerr <<" *** ASMs3D::connectPatch: Invalid slave face " + << face << std::endl; + return false; + } + + int i, j; + IntMat slaveNodes(n1,IntVec(n2,0)); + for (j = 0; j < n2; j++, node += i2) + for (i = 0; i < n1; i++, node += i1) + slaveNodes[i][j] = node; + + // Set up the master node numbers for the neighboring volume patch + + if (!neighbor.getSize(n1,n2,n3,1)) return false; + node = 1; i1 = i2 = 0; + + switch (nface) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + i1 = n1; + n1 = n2; + n2 = n3; + break; + + case 4: // Positive J-direction + node = n1*(n2-1)+1; + case 3: // Negative J-direction + i2 = n1*(n2-1); + i1 = 1; + n2 = n3; + break; + + case 6: // Positive K-direction + node = n1*n2*(n3-1)+1; + case 5: // Negative K-direction + i1 = 1; + break; + + default: + std::cerr <<" *** ASMs3D::connectPatch: Invalid master face " + << nface << std::endl; + return false; + } + + bool matching = false; + if (norient < 0 || norient > 7) + { + std::cerr <<" *** ASMs3D::connectPatch: Orientation flag " + << norient <<" is out of range [0,7]"<< std::endl; + return false; + } + else if (norient < 4) + matching = (n1 == slaveNodes.size() && n2 == slaveNodes.front().size()); + else + matching = (n2 == slaveNodes.size() && n1 == slaveNodes.front().size()); + if (!matching) + { + std::cerr <<" *** ASMs3D::connectPatch: Non-matching faces, sizes " + << n1 <<","<< n2 <<" and " + << slaveNodes.size() <<","<< slaveNodes.front().size() + << std::endl; + return false; + } + + const double xtol = 1.0e-4; + for (j = 0; j < n2; j++, node += i2) + for (i = 0; i < n1; i++, node += i1) + { + int k = i, l = j; + switch (norient) + { + case 1: k = i ; l = n2-j-1; break; + case 2: k = n1-i-1; l = j ; break; + case 3: k = n1-i-1; l = n2-j-1; break; + case 4: k = j ; l = i ; break; + case 5: k = j ; l = n1-i-1; break; + case 6: k = n2-j-1; l = i ; break; + case 7: k = n2-j-1; l = n1-i-1; break; + default: k = i ; l = j ; + } + if (!neighbor.getCoord(node).equal(this->getCoord(slaveNodes[k][l]),xtol)) + { + std::cerr <<" *** ASMs3D::connectPatch: Non-matching nodes " + << node <<": "<< neighbor.getCoord(node) + <<"\n and " + << slaveNodes[k][l] <<": "<< this->getCoord(slaveNodes[k][l]) + << std::endl; + return false; + } + else + ASMbase::collapseNodes(neighbor.MLGN[node-1],MLGN[slaveNodes[k][l]-1]); + } + + return true; +} + + +void ASMs3D::closeFaces (int dir) +{ + int n1, n2, n3, master = 1; + if (!this->getSize(n1,n2,n3,1)) return; + + switch (dir) + { + case 1: // Faces are closed in I-direction + for (int i3 = 1; i3 <= n3; i3++) + for (int i2 = 1; i2 <= n2; i2++, master += n1) + this->makePeriodic(master,master+n1-1); + break; + + case 2: // Faces are closed in J-direction + for (int i3 = 1; i3 <= n3; i3++, master += n1*(n2-1)) + for (int i1 = 1; i1 <= n1; i1++, master++) + this->makePeriodic(master,master+n1*(n2-1)); + break; + + case 3: // Faces are closed in K-direction + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++, master++) + this->makePeriodic(master,master+n1*n2*(n3-1)); + break; + } +} + + +void ASMs3D::constrainFace (int dir, int dof, int code) +{ + int n1, n2, n3, node = 1; + if (!this->getSize(n1,n2,n3,1)) return; + + if (swapW) // Account for swapped parameter direction + if (dir == 3 || dir == -3) dir = -dir; + + switch (dir) + { + case 1: // Right face (positive I-direction) + node += n1-1; + case -1: // Left face (negative I-direction) + for (int i3 = 1; i3 <= n3; i3++) + for (int i2 = 1; i2 <= n2; i2++, node += n1) + this->prescribe(node,dof,code); + break; + + case 2: // Back face (positive J-direction) + node += n1*(n2-1); + case -2: // Front face (negative J-direction) + for (int i3 = 1; i3 <= n3; i3++, node += n1*(n2-1)) + for (int i1 = 1; i1 <= n1; i1++, node++) + this->prescribe(node,dof,code); + break; + + case 3: // Top face (positive K-direction) + node += n1*n2*(n3-1); + case -3: // Bottom face (negative K-direction) + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++, node++) + this->prescribe(node,dof,code); + break; + } +} + + +void ASMs3D::constrainEdge (int lEdge, int dof, int code) +{ + int n1, n2, n3, n, node = 1, inc = 1; + if (!this->getSize(n1,n2,n3,1)) return; + + if (swapW) // Account for swapped parameter direction + if (lEdge <= 8) + if ((lEdge-1)%4 < 2) + lEdge += 2; + else + lEdge -= 2; + + if (lEdge > 8) + { + inc = n1*n2; + n = n3; + } + else if (lEdge > 4) + { + inc = n1; + n = n2; + } + else + n = n1; + + switch (lEdge) + { + case 6: + case 10: + node = n1; + break; + case 2: + case 12: + node = n1*(n2-1) + 1; + break; + case 11: + node = n1*n2; + break; + case 3: + case 7: + node = n1*n2*(n3-1) + 1; + break; + case 8: + node = n1*(n2*(n3-1) + 1); + break; + case 4: + node = n1*(n2*n3-1) + 1; + break; + } + + for (int i = 0; i < n; i++, node += inc) + this->prescribe(node,dof,code); +} + + +void ASMs3D::constrainLine (int fdir, int ldir, double xi, int dof, int code) +{ + if (xi < 0.0 || xi > 1.0) return; + + int n1, n2, n3, node = 1; + if (!this->getSize(n1,n2,n3,1)) return; + + if (swapW) // Account for swapped parameter direction + if (fdir == 3 || fdir == -3) fdir = -fdir; + + switch (fdir) + { + case 1: // Right face (positive I-direction) + node += n1-1; + case -1: // Left face (negative I-direction) + if (ldir == 2) + { + // Line goes in J-direction + node += n1*n2*int(0.5+(n3-1)*(swapW ? 1.0-xi : xi)); + for (int i2 = 1; i2 <= n2; i2++, node += n1) + this->prescribe(node,dof,code); + } + else if (ldir == 3) + { + // Line goes in K-direction + node += n1*int(0.5+(n2-1)*xi); + for (int i3 = 1; i3 <= n3; i3++, node += n1*n2) + this->prescribe(node,dof,code); + } + break; + + case 2: // Back face (positive J-direction) + node += n1*(n2-1); + case -2: // Front face (negative J-direction) + if (ldir == 1) + { + // Line goes in I-direction + node += n1*n2*int(0.5+(n3-1)*(swapW ? 1.0-xi : xi)); + for (int i1 = 1; i1 <= n1; i1++, node++) + this->prescribe(node,dof,code); + } + else if (ldir == 3) + { + // Line goes in K-direction + node += int(0.5+(n1-1)*xi); + for (int i3 = 1; i3 <= n3; i3++, node += n1*n2) + this->prescribe(node,dof,code); + } + break; + + case 3: // Top face (positive K-direction) + node += n1*n2*(n3-1); + case -3: // Bottom face (negative K-direction) + if (ldir == 1) + { + // Line goes in I-direction + node += n1*int(0.5+(n2-1)*xi); + for (int i1 = 1; i1 <= n1; i1++, node++) + this->prescribe(node,dof,code); + } + else if (ldir == 2) + { + // Line goes in J-direction + node += int(0.5+(n1-1)*xi); + for (int i2 = 1; i2 <= n2; i2++, node += n1) + this->prescribe(node,dof,code); + } + break; + } +} + + +void ASMs3D::constrainCorner (int I, int J, int K, int dof, int code) +{ + int n1, n2, n3; + if (!this->getSize(n1,n2,n3,1)) return; + + if (swapW) // Account for swapped parameter direction + K = -K; + + int node = 1; + if (I > 0) node += n1-1; + if (J > 0) node += n1*(n2-1); + if (K > 0) node += n1*n2*(n3-1); + + this->prescribe(node,dof,code); +} + + +void ASMs3D::constrainNode (double xi, double eta, double zeta, + int dof, int code) +{ + if (xi < 0.0 || xi > 1.0) return; + if (eta < 0.0 || eta > 1.0) return; + if (zeta < 0.0 || zeta > 1.0) return; + + if (swapW) // Account for swapped parameter direction + zeta = 1.0-zeta; + + int n1, n2, n3; + if (!this->getSize(n1,n2,n3,1)) return; + + int node = 1; + if (xi > 0.0) node += int(0.5+(n1-1)*xi); + if (eta > 0.0) node += n1*int(0.5+(n2-1)*eta); + if (zeta > 0.0) node += n1*n2*int(0.5+(n3-1)*zeta); + + this->prescribe(node,dof,code); +} + + +#define DERR -999.99 + +double ASMs3D::getParametricVolume (int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs3D::getParametricVolume: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + if (MNPC[iel-1].empty()) + return 0.0; + + int inod1 = MNPC[iel-1].back(); +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= nodeInd.size()) + { + std::cerr <<" *** ASMs3D::getParametricVolume: Node index "<< inod1 + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return DERR; + } +#endif + + double du = svol->knotSpan(0,nodeInd[inod1].I); + double dv = svol->knotSpan(1,nodeInd[inod1].J); + double dw = svol->knotSpan(2,nodeInd[inod1].K); + return du*dv*dw; +} + + +double ASMs3D::getParametricArea (int iel, int dir) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs3D::getParametricArea: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + if (MNPC[iel-1].empty()) + return 0.0; + + int inod1 = MNPC[iel-1].back(); +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= nodeInd.size()) + { + std::cerr <<" *** ASMs3D::getParametricArea: Node index "<< inod1 + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return DERR; + } +#endif + + const int ni = nodeInd[inod1].I; + const int nj = nodeInd[inod1].J; + const int nk = nodeInd[inod1].K; + switch (dir) + { + case 1: return svol->knotSpan(1,nj)*svol->knotSpan(2,nk); + case 2: return svol->knotSpan(0,ni)*svol->knotSpan(2,nk); + case 3: return svol->knotSpan(0,ni)*svol->knotSpan(1,nj); + } + + std::cerr <<" *** ASMs3D::getParametricArea: Invalid face direction " + << dir << std::endl; + return DERR; +} + + +int ASMs3D::coeffInd (size_t inod) const +{ +#ifdef INDEX_CHECK + if (inod >= nodeInd.size()) + { + std::cerr <<" *** ASMs3D::coeffInd: Node index "<< inod + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return -1; + } +#endif + + const int ni = nodeInd[inod].I; + const int nj = nodeInd[inod].J; + const int nk = nodeInd[inod].K; + return (nk*svol->numCoefs(1) + nj)*svol->numCoefs(0) + ni; +} + + +bool ASMs3D::getElementCoordinates (Matrix& X, int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs3D::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } +#endif + + const IntVec& mnpc = MNPC[iel-1]; + X.resize(3,mnpc.size()); + + DoubleIter cit = svol->coefs_begin(); + for (size_t n = 0; n < mnpc.size(); n++) + { + int ip = this->coeffInd(mnpc[n])*svol->dimension(); + if (ip < 0) return false; + + for (size_t i = 0; i < 3; i++) + X(i+1,n+1) = *(cit+(ip+i)); + } + +#if SP_DEBUG > 2 + std::cout <<"\nCoordinates for element "<< iel << X << std::endl; +#endif + return true; +} + + +void ASMs3D::getNodalCoordinates (Matrix& X) const +{ + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + X.resize(3,n1*n2*n3); + + DoubleIter cit = svol->coefs_begin(); + size_t inod = 1; + for (int i3 = 0; i3 < n3; i3++) + for (int i2 = 0; i2 < n2; i2++) + for (int i1 = 0; i1 < n1; i1++, inod++) + { + int ip = ((i3*n2 + i2)*n1 + i1)*svol->dimension(); + for (size_t i = 0; i < 3; i++) + X(i+1,inod) = *(cit+(ip+i)); + } +} + + +Vec3 ASMs3D::getCoord (size_t inod) const +{ + if (inod == 0) return Vec3(); + int ip = this->coeffInd(inod-1)*svol->dimension(); + if (ip < 0) return Vec3(); + + DoubleIter cit = svol->coefs_begin() + ip; + return Vec3(*cit,*(cit+1),*(cit+2)); +} + + +bool ASMs3D::getSize (int& n1, int& n2, int& n3, int) const +{ + if (!svol) return false; + + n1 = svol->numCoefs(0); + n2 = svol->numCoefs(1); + n3 = svol->numCoefs(2); + return true; +} + + +/*! + \brief Computes the characteristic element length from nodal coordinates. +*/ + +static double getElmSize (int p1, int p2, int p3, const Matrix& X) +{ + int i, j, k, id1, id2; + double value, v1, h = 1.0e12; + + // Z-direction + for (i = 1; i <= p1; i++) + for (j = 0; j < p2; j++) + { + id1 = j*p1 + i; + id2 = id1 + (p3-1)*p2*p1; + value = 0.0; + for (k = 1; k <= 3; k++) + { + v1 = X(k,id2) - X(k,id1); + value += v1*v1; + } + if (value < h) h = value; + } + + // Y-direction + for (i = 1; i <= p1; i++) + for (k = 0; k < p2; k++) + { + id1 = k*p2*p1 + i; + id2 = id1 + (p2-1)*p1; + value = 0.0; + for (j = 1; j <= 3; j++) + { + v1 = X(j,id2) - X(j,id1); + value += v1*v1; + } + if (value < h) h = value; + } + + // X-direction + for (j = 0; j < p2; j++) + for (k = 0; k < p3; k++) + { + id1 = k*p1*p2 + j*p1 + 1; + id2 = id1 + p1 - 1; + value = 0.0; + for (i = 1; i <= 3; i++) + { + v1 = X(i,id2) - X(i,id1); + value += v1*v1; + } + if (value < h) h = value; + } + + return sqrt(h); +} + + +void ASMs3D::extractBasis (const Go::BasisDerivs& spline, + Vector& N, Matrix& dNdu) +{ + dNdu.resize(N.size(),3); + + size_t jp, n = 1; + for (jp = 0; jp < N.size(); jp++, n++) + { + N (n) = spline.basisValues[jp]; + dNdu(n,1) = spline.basisDerivs_u[jp]; + dNdu(n,2) = spline.basisDerivs_v[jp]; + dNdu(n,3) = spline.basisDerivs_w[jp]; + } +} + + +void ASMs3D::extractBasis (const Go::BasisDerivs2& spline, + Vector& N, Matrix& dNdu, Matrix3D& d2Ndu2) +{ + dNdu .resize(N.size(),3); + d2Ndu2.resize(N.size(),3,3); + + size_t jp, n = 1; + for (jp = 0; jp < N.size(); jp++, n++) + { + N (n) = spline.basisValues[jp]; + dNdu (n,1) = spline.basisDerivs_u[jp]; + dNdu (n,2) = spline.basisDerivs_v[jp]; + dNdu (n,3) = spline.basisDerivs_w[jp]; + d2Ndu2(n,1,1) = spline.basisDerivs_uu[jp]; + d2Ndu2(n,1,2) = d2Ndu2(n,2,1) = spline.basisDerivs_uv[jp]; + d2Ndu2(n,1,3) = d2Ndu2(n,3,1) = spline.basisDerivs_uw[jp]; + d2Ndu2(n,2,2) = spline.basisDerivs_vv[jp]; + d2Ndu2(n,2,3) = d2Ndu2(n,3,2) = spline.basisDerivs_vw[jp]; + d2Ndu2(n,3,3) = spline.basisDerivs_ww[jp]; + } +} + + +bool ASMs3D::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + PROFILE2("ASMs3D::integrate(I)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + int dir; + Matrix gpar[3]; + for (dir = 0; dir < 3; dir++) + { + int pm1 = svol->order(dir) - 1; + DoubleIter uit = svol->basis(dir).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(dir) - pm1; + gpar[dir].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[dir](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline; + std::vector spline2; + { + PROFILE2("Spline evaluation"); + if (integrand.getIntegrandType() == 2) + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline2); + else + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + } + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N(p1*p2*p3), Navg; + Matrix dNdu, dNdX, Xnod, Jac; + Matrix3D d2Ndu2, d2NdX2, Hess; + Vec4 X; + double h; + + + // === Assembly loop over all elements in the patch ========================== + + int iel = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-volume element + + // Get element volume in the parameter space + double dV = this->getParametricVolume(iel); + if (dV < 0.0) return false; // topology error (probably logic error) + + // Set up control point (nodal) coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Compute characteristic element length, if needed + if (integrand.getIntegrandType() == 2) + h = getElmSize(p1,p2,p3,Xnod); + + else if (integrand.getIntegrandType() == 3) + { + // --- Compute average value of basis functions over the element ----- + + Navg.resize(p1*p2*p3,true); + double vol = 0.0; + int ip = (((i3-p3)*nGauss*nel2 + i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int k = 0; k < nGauss; k++, ip += nGauss*(nel2-1)*nGauss*nel1) + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Fetch basis function derivatives at current integration point + extractBasis(spline[ip],N,dNdu); + + // Compute Jacobian determinant of coordinate mapping + // and multiply by weight of current integration point + double weight = 0.125*dV*wg[i]*wg[j]*wg[k]; + double detJxW = utl::Jacobian(Jac,dNdX,Xnod,dNdu,false)*weight; + + // Numerical quadrature + Navg += N*detJxW; + vol += detJxW; + } + + // Divide by element volume + Navg /= vol; + } + + else if (integrand.getIntegrandType() == 4) + { + // Compute the element center + Go::Point X0; + double u0 = 0.5*(gpar[0](1,i1-p1+1) + gpar[0](nGauss,i1-p1+1)); + double v0 = 0.5*(gpar[1](1,i2-p2+1) + gpar[1](nGauss,i2-p2+1)); + double w0 = 0.5*(gpar[2](1,i3-p3+1) + gpar[2](nGauss,i3-p3+1)); + svol->point(X0,u0,v0,w0); + X.x = X0[0]; + X.y = X0[1]; + X.z = X0[2]; + } + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1],X,nGauss*nGauss*nGauss)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + int ip = (((i3-p3)*nGauss*nel2 + i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int k = 0; k < nGauss; k++, ip += nGauss*(nel2-1)*nGauss*nel1) + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.125*dV*wg[i]*wg[j]*wg[k]; + + // Fetch basis function derivatives at current integration point + if (integrand.getIntegrandType() == 2) + extractBasis(spline2[ip],N,dNdu,d2Ndu2); + else + extractBasis(spline[ip],N,dNdu); + + // Compute Jacobian inverse of coordinate mapping and derivatives + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + if (detJ == 0.0) continue; // skip singular points + + // Compute Hessian of coordinate mapping and 2nd order derivatives + if (integrand.getIntegrandType() == 2) + if (!utl::Hessian(Hess,d2NdX2,Jac,Xnod,d2Ndu2,dNdu)) + return false; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + bool ok = false; + switch (integrand.getIntegrandType()) + { + case 2: + ok = integrand.evalInt(elmInt,time,detJ*weight, + N,dNdX,d2NdX2,X,h); + break; + case 3: + ok = integrand.evalInt(elmInt,time,detJ*weight, + N,dNdX,Navg,X); + break; + default: + ok = integrand.evalInt(elmInt,time,detJ*weight, + N,dNdX,X); + } + if (!ok) return false; + } + + // Finalize the element quantities + if (!integrand.finalizeElement(elmInt)) + return false; + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3D::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + PROFILE2("ASMs3D::integrate(B)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the face normal {-3,-2,-1, 1, 2, 3} + const int faceDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = 1 + abs(faceDir)%3; // first tangent direction + const int t2 = 1 + t1%3; // second tangent direction + + // Compute parameter values of the Gauss points over the whole patch face + Matrix gpar[3]; + for (int d = 0; d < 3; d++) + if (-1-d == faceDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = svol->startparam(d); + } + else if (1+d == faceDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = svol->endparam(d); + } + else + { + int pm1 = svol->order(d) - 1; + DoubleIter uit = svol->basis(d).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(d) - pm1; + gpar[d].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[d](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline; + { + PROFILE2("Spline evaluation"); + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + } + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N(p1*p2*p3); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + Vec3 normal; + + + // === Assembly loop over all elements on the patch face ===================== + + int iel = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-volume element + + // Skip elements that are not on current boundary face + bool skipMe = false; + switch (faceDir) + { + case -1: if (i1 > p1) skipMe = true; break; + case 1: if (i1 < n1) skipMe = true; break; + case -2: if (i2 > p2) skipMe = true; break; + case 2: if (i2 < n2) skipMe = true; break; + case -3: if (i3 > p3) skipMe = true; break; + case 3: if (i3 < n3) skipMe = true; break; + } + if (skipMe) continue; + + // Get element face area in the parameter space + double dA = this->getParametricArea(iel,abs(faceDir)); + if (dA < 0.0) return false; // topology error (probably logic error) + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Define some loop control variables depending on which face we are on + int nf1, j1, j2; + switch (abs(faceDir)) + { + case 1: nf1 = nel2; j2 = i3-p3; j1 = i2-p2; break; + case 2: nf1 = nel1; j2 = i3-p3; j1 = i1-p1; break; + case 3: nf1 = nel1; j2 = i2-p2; j1 = i1-p1; break; + } + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + int ip = (j2*nGauss*nf1 + j1)*nGauss; + for (int j = 0; j < nGauss; j++, ip += nGauss*(nf1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.25*dA*wg[i]*wg[j]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline[ip],N,dNdu); + + // Compute basis function derivatives and the face normal + double dS = utl::Jacobian(Jac,normal,dNdX,Xnod,dNdu,t1,t2); + if (dS == 0.0) continue; // skip singular points + + if (faceDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*weight,N,dNdX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3D::integrateEdge (Integrand& integrand, int lEdge, + GlobalIntegral& glInt, + const TimeDomain& time) +{ + if (!svol) return true; // silently ignore empty patches + + PROFILE2("ASMs3D::integrate(E)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points along the whole patch edge + Matrix gpar[3]; + for (int d = 0; d < 3; d++) + if (lEdge < d*4+1 || lEdge >= d*4+5) + { + gpar[d].resize(1,1); + if (lEdge%4 == 1) + gpar[d](1,1) = svol->startparam(d); + else if (lEdge%4 == 0) + gpar[d](1,1) = svol->endparam(d); + else if (lEdge == 6 || lEdge == 10) + gpar[d](1,1) = d == 0 ? svol->endparam(d) : svol->startparam(d); + else if (lEdge == 2 || lEdge == 11) + gpar[d](1,1) = d == 1 ? svol->endparam(d) : svol->startparam(d); + else if (lEdge == 3 || lEdge == 7) + gpar[d](1,1) = d == 2 ? svol->endparam(d) : svol->startparam(d); + } + else + { + int pm1 = svol->order(d) - 1; + DoubleIter uit = svol->basis(d).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(d) - pm1; + gpar[d].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[d](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline; + { + PROFILE2("Spline evaluation"); + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + } + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N(p1*p2*p3); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + Vec3 tangent; + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-volume element + + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (lEdge) + { + case 1: if (i2 > p2 || i3 > p3) skipMe = true; break; + case 2: if (i2 < n2 || i3 > p3) skipMe = true; break; + case 3: if (i2 > p2 || i3 < n3) skipMe = true; break; + case 4: if (i2 < n2 || i3 < n3) skipMe = true; break; + case 5: if (i1 > p1 || i3 > p3) skipMe = true; break; + case 6: if (i1 < n1 || i3 > p3) skipMe = true; break; + case 7: if (i1 > p1 || i3 < n3) skipMe = true; break; + case 8: if (i1 < n1 || i3 < n3) skipMe = true; break; + case 9: if (i1 > p1 || i2 > p2) skipMe = true; break; + case 10: if (i1 < n1 || i2 > p2) skipMe = true; break; + case 11: if (i1 > p1 || i2 < n2) skipMe = true; break; + case 12: if (i1 < n1 || i2 < n2) skipMe = true; break; + } + if (skipMe) continue; + + // Get element edge length in the parameter space + double dS = 0.0; + int ip = MNPC[iel-1].back(); +#ifdef INDEX_CHECK + if (ip < 0 || ip >= nodeInd.size()) return false; +#endif + if (lEdge < 5) + { + dS = svol->knotSpan(0,nodeInd[ip].I); + ip = (i1-p1)*nGauss; + } + else if (lEdge < 9) + { + dS = svol->knotSpan(1,nodeInd[ip].J); + ip = (i2-p2)*nGauss; + } + else if (lEdge < 13) + { + dS = svol->knotSpan(2,nodeInd[ip].K); + ip = (i3-p3)*nGauss; + } + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + + // --- Integration loop over all Gauss points along the edge ----------- + + LocalIntegral* elmInt = 0; + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.5*dS*wg[i]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline[ip],N,dNdu); + + // Compute basis function derivatives and the edge tangent + double dT = utl::Jacobian(Jac,tangent,dNdX,Xnod,dNdu,1+(lEdge-1)/4); + if (dT == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dT*weight,N,dNdX,X,tangent)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3D::getGridParameters (RealArray& prm, int dir, int nSegPerSpan) const +{ + if (!svol) return false; + + if (nSegPerSpan < 1) + { + std::cerr <<" *** ASMs3D::getGridParameters: Too few knot-span points " + << nSegPerSpan+1 <<" in direction "<< dir << std::endl; + return false; + } + + DoubleIter uit = svol->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != svol->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nSegPerSpan; i++) + { + double xg = (double)(2*i-nSegPerSpan)/(double)nSegPerSpan; + prm.push_back(0.5*(ucurr*(1.0+xg) + uprev*(1.0-xg))); + } + uprev = ucurr; + } + + prm.push_back(svol->basis(dir).endparam()); + return true; +} + + +bool ASMs3D::getGrevilleParameters (RealArray& prm, int dir) const +{ + if (!svol) return false; + + const Go::BsplineBasis& basis = svol->basis(dir); + + prm.resize(basis.numCoefs()); + for (size_t i = 0; i < prm.size(); i++) + prm[i] = basis.grevilleParameter(i); + + return true; +} + + +bool ASMs3D::tesselate (ElementBlock& grid, const int* npe) const +{ + // Compute parameter values of the nodal points + DoubleVec gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the spline volume at all points + size_t nx = gpar[0].size(); + size_t ny = gpar[1].size(); + size_t nz = gpar[2].size(); + DoubleVec XYZ(svol->dimension()*nx*ny*nz); + svol->gridEvaluator(gpar[0],gpar[1],gpar[2],XYZ); + + // Establish the block grid coordinates + size_t i, j, k, l; + grid.resize(nx,ny,nz); + for (i = j = 0; i < grid.getNoNodes(); i++, j += svol->dimension()) + grid.setCoor(i,XYZ[j],XYZ[j+1],XYZ[j+2]); + + // Establish the block grid topology + int n[8], ip = 0; + for (k = 1, n[2] = 0; k < nz; k++) + for (j = 1, n[1] = n[2]; j < ny; j++) + { + n[0] = n[1]; + n[1] = n[0] + 1; + n[2] = n[1] + nx; + n[3] = n[1] + nx-1; + n[4] = n[0] + nx*ny; + n[5] = n[4] + 1; + n[6] = n[5] + nx; + n[7] = n[5] + nx-1; + for (i = 1; i < nx; i++) + for (l = 0; l < 8; l++) + grid.setNode(ip++,n[l]++); + } + + return true; +} + + +void ASMs3D::scatterInd (int n1, int n2, int n3, int p1, int p2, int p3, + const int* start, IntVec& index) +{ + index.reserve(p1*p2*p3); + int ip = ((start[2]-p3+1)*n2 + (start[1]-p2+1))*n1 + (start[0]-p1+1); + for (int i3 = 0; i3 < p3; i3++, ip += n1*(n2-p2)) + for (int i2 = 0; i2 < p2; i2++, ip += n1-p1) + for (int i1 = 0; i1 < p1; i1++, ip++) + index.push_back(ip); +} + + +bool ASMs3D::evalSolution (Matrix& sField, const Vector& locSol, + const int* npe) const +{ + // Compute parameter values of the result sampling points + DoubleVec gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the basis functions at all points + std::vector spline; + { + PROFILE2("Spline evaluation"); + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + } + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + size_t nComp = locSol.size() / this->getNoNodes(); + if (nComp*this->getNoNodes() != locSol.size()) + return false; + + Matrix Xtmp; + Vector Ytmp; + + // Evaluate the primary solution field at each point + size_t nPoints = spline.size(); + sField.resize(nComp,nPoints); + for (size_t i = 0; i < nPoints; i++) + { + IntVec ip; + scatterInd(n1,n2,n3,p1,p2,p3,spline[i].left_idx,ip); + + utl::gather(ip,nComp,locSol,Xtmp); + Xtmp.multiply(spline[i].basisValues,Ytmp); + sField.fillColumn(1+i,Ytmp); + } + + return true; +} + + +bool ASMs3D::evalSolution (Matrix& sField, const Integrand& integrand, + const int* npe) const +{ + // Compute parameter values of the result sampling points + DoubleVec gpar[3]; + bool retVal = true; + for (int dir = 0; dir < 3 && retVal; dir++) + if (npe) + retVal = this->getGridParameters(gpar[dir],dir,npe[dir]-1); + else + retVal = this->getGrevilleParameters(gpar[dir],dir); + + // Evaluate the secondary solution at all sampling points + if (retVal) retVal = this->evalSolution(sField,integrand,gpar); + if (!retVal || npe) return retVal; + + // Project the results onto the spline basis to find control point + // values based on the result values evaluated at the Greville points. + // Note that we here implicitly assume the the number of Greville points + // equals the number of control points such that we don't have to resize + // the result array. Think that is always the case, but beware if trying + // other projection schemes later. + + Vector sValues = sField; + DoubleVec weights; + if (svol->rational()) + svol->getWeights(weights); + + Go::SplineVolume* s = + Go::VolumeInterpolator::regularInterpolation(svol->basis(0), + svol->basis(1), + svol->basis(2), + gpar[0], gpar[1], gpar[2], + sValues, sField.rows(), + svol->rational(), weights); + + sField.fill(&(*s->coefs_begin())); + delete s; + + return retVal; +} + + +bool ASMs3D::evalSolution (Matrix& sField, const Integrand& integrand, + const RealArray* gpar) const +{ + sField.resize(0,0); + + // Evaluate the basis functions and their derivatives at all points + std::vector spline; + { + PROFILE2("Spline evaluation"); + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + } + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + // Fetch nodal (control point) coordinates + Matrix Xnod, Xtmp; + this->getNodalCoordinates(Xnod); + + Vector N(p1*p2*p3), solPt; + Matrix dNdu, dNdX, Jac; + + // Evaluate the secondary solution field at each point + size_t nPoints = spline.size(); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch indices of the non-zero basis functions at this point + IntVec ip; + scatterInd(n1,n2,n3,p1,p2,p3,spline[i].left_idx,ip); + + // Fetch associated control point coordinates + utl::gather(ip,3,Xnod,Xtmp); + + // Fetch basis function derivatives at current integration point + extractBasis(spline[i],N,dNdu); + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xtmp,dNdu) == 0.0) // Jac = (Xtmp * dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xtmp*N,ip)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + sField.fillColumn(1+i,solPt); + } + + return true; +} diff --git a/src/ASM/ASMs3D.h b/src/ASM/ASMs3D.h new file mode 100644 index 00000000..c9461665 --- /dev/null +++ b/src/ASM/ASMs3D.h @@ -0,0 +1,357 @@ +// $Id: ASMs3D.h,v 1.21 2011-01-28 13:34:28 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3D.h +//! +//! \date Sep 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 3D spline FE models. +//! +//============================================================================== + +#ifndef _ASM_S3D_H +#define _ASM_S3D_H + +#include "ASMstruct.h" + +namespace Go { + class SplineVolume; + class BasisDerivs; + class BasisDerivs2; +} + + +/*! + \brief Driver for assembly of structured 3D spline FE models. + \details This class contains methods common for structured 3D spline patches. +*/ + +class ASMs3D : public ASMstruct +{ + //! \brief Struct for nodal point data. + struct IJK + { + int I; //!< Index in first parameter direction + int J; //!< Index in second parameter direction + int K; //!< Index in third parameter direction + }; + + //! \brief Struct for edge node definitions. + struct Edge + { + int icnod; //!< Global node number of first interior point along the edge + int incr; //!< Increment in the global numbering along the edge + + //! \brief Default constructor. + Edge() { icnod = incr = 0; } + //! \brief Returns \a icnod which then is incremented. + int next(); + }; + + //! \brief Struct for face node definitions. + struct Face + { + int isnod; //!< Global node number of the first interior point on the face + int incrI; //!< Increment in global numbering in the I-direction on the face + int incrJ; //!< Increment in global numbering in the J-direction on the face + int nnodI; //!< Number of nodes in the local I-direction on the face + int indxI; //!< Running node index in the local I-direction + + //! \brief Default constructor. + Face() { isnod = incrI = incrJ = nnodI = 0; indxI = 1; } + //! \brief Returns \a isnod which then is incremented. + int next(); + }; + +public: + //! \brief Struct with data for definition of global node numbers of a patch. + struct BlockNodes + { + int ibnod[8]; //!< Vertex nodes + Edge edges[12]; //!< Edge nodes + Face faces[6]; //!< Face nodes + int iinod; //!< Global node number of the first interior node + int inc[3]; //!< Increment in global node numbering in each direction + int nnodI; //!< Number of nodes in parameter direction I + int nnodJ; //!< Number of nodes in parameter direction J + int indxI; //!< Running node index in the local I-direction + int indxJ; //!< Running node index in the local J-direction + + //! \brief Default constructor. + BlockNodes() { iinod = inc[0] = inc[1] = inc[2] = 0; indxI = indxJ = 1; } + //! \brief Returns \a iinod which then is incremented. + int next(); + }; + + //! \brief Constructor creating an instance by reading the given file. + ASMs3D(const char* fileName, bool checkRHS = false, unsigned char n_f = 3); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs3D(std::istream& is, bool checkRHS = false, unsigned char n_f = 3); + //! \brief Default constructor creating an empty patch. + ASMs3D(unsigned char n_f = 3) : ASMstruct(3,3,n_f) { svol = 0; } + //! \brief Empty destructor. + virtual ~ASMs3D() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the node-to-IJK-index array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + //! \brief Creates an instance by reading the given input stream, \a is. + bool read(std::istream& is); + //! \brief Writes the geometry of the SplineVolume object to the given stream. + bool write(std::ostream& os) const; + + //! \brief Assigns new global node numbers for all nodes of the patch. + //! \param nodes Object with global nodes numbers to assign to this patch + //! \param[in] basis Which basis to assign node numbers for in mixed methods + //! + //! \details The global node numbers generated by \a generateFEMTopology are + //! non-unique in the sense that a node that is shared by two (or more) + //! patches along a common interface has a different number in each patch. + //! This method therefore assigns a new global number to each node in the + //! patch. The data provided through the \a nodes argument is sufficient + //! to determine the unique global number under the assumption that they + //! are ordered in the sequence determined by the local orientation of the + //! patch, its faces and edges. + bool assignNodeNumbers(BlockNodes& nodes, int basis = 0); + + //! \brief Check that the patch is modelled in a right-hand-side system. + //! \details If it isn't, the w-parameter direction is swapped. + bool checkRightHandSystem(); + + //! \brief Refine the parametrization by inserting extra knots. + //! \param[in] dir Parameter direction to refine + //! \param[in] xi Relative positions of added knots in each existing knot span + bool refine(int dir, const RealArray& xi); + //! \brief Refine the parametrization by inserting extra knots uniformly. + //! \param[in] dir Parameter direction to refine + //! \param[in] nInsert Number of extra knots to insert in each knot-span + bool uniformRefine(int dir, int nInsert); + //! \brief Raise the order of the SplineVolume object for this patch. + //! \param[in] ru Number of times to raise the order in u-direction + //! \param[in] rv Number of times to raise the order in v-direction + //! \param[in] rw Number of times to raise the order in w-direction + bool raiseOrder(int ru, int rv, int rw); + + + // Various methods for preprocessing of boundary conditions + // ======================================================== + + //! \brief Makes two opposite boundary faces periodic. + //! \param[in] dir Parameter direction defining the periodic faces + void closeFaces(int dir); + + //! \brief Constrains all DOFs on a given boundary face. + //! \param[in] dir Parameter direction defining the face to constrain + //! \param[in] dof Which DOFs to constrain at each node on the face + //! \param[in] code Inhomogeneous dirichlet condition code + void constrainFace(int dir, int dof = 123, int code = 0); + + //! \brief Constrains all DOFs on a given boundary edge. + //! \param[in] lEdge Local index [1,12] of the edge to constrain + //! \param[in] dof Which DOFs to constrain at each node on the edge + //! \param[in] code Inhomogeneous dirichlet condition code + void constrainEdge(int lEdge, int dof = 123, int code = 0); + + //! \brief Constrains all DOFs along a line on a given boundary face. + //! \param[in] fdir Parameter direction defining the face to constrain + //! \param[in] ldir Parameter direction defining the line to constrain + //! \param[in] xi Parameter value defining the line to constrain + //! \param[in] dof Which DOFs to constrain at each node along the line + //! \param[in] code Inhomogeneous dirichlet condition code + //! + //! \details The parameter \a xi has to be in the domain [0.0,1.0], where + //! 0.0 means the beginning of the domain and 1.0 means the end. The line to + //! constrain goes along the parameter direction \a ldir in the face with + //! with normal in parameter direction \a fdir, and positioned along the third + //! parameter direction as indicated by \a xi. The actual value of \a xi + //! is converted to the integer value closest to \a xi*n, where \a n is the + //! number of nodes (control points) in that parameter direction. + void constrainLine(int fdir, int ldir, double xi, + int dof = 123, int code = 0); + + //! \brief Constrains a corner node identified by the three parameter indices. + //! \param[in] I Parameter index in u-direction + //! \param[in] J Parameter index in v-direction + //! \param[in] K Parameter index in w-direction + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] code Inhomogeneous dirichlet condition code + //! + //! \details The sign of the three indices is used to define whether we want + //! the node at the beginning or the end of that parameter direction. + //! The magnitude of the indices are not used. + void constrainCorner(int I, int J, int K, + int dof = 123, int code = 0); + //! \brief Constrains a node identified by three relative parameter values. + //! \param[in] xi Parameter in u-direction + //! \param[in] eta Parameter in v-direction + //! \param[in] zeta Parameter in w-direction + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] code Inhomogeneous dirichlet condition code + //! + //! \details The parameter values have to be in the domain [0.0,1.0], where + //! 0.0 means the beginning of the domain and 1.0 means the end. For values + //! in between, the actual index is taken as the integer value closest to + //! \a r*n, where \a r denotes the given relative parameter value, + //! and \a n is the number of nodes along that parameter direction. + void constrainNode(double xi, double eta, double zeta, + int dof = 123, int code = 0); + + //! \brief Connects all matching nodes on two adjacent boundary faces. + //! \param[in] face Local face index of this patch, in range [1,6] + //! \param neighbor The neighbor patch + //! \param[in] nface Local face index of neighbor patch, in range [1,6] + //! \param[in] norient Relative face orientation flag (see below) + //! + //! \details The face orientation flag \a norient must be in range [0,7]. + //! When interpreted as a binary number, its 3 digits are decoded as follows: + //! - left digit = 1: The u and v parameters of the neighbor face are swapped + //! - middle digit = 1: Parameter \a u in neighbor patch face is reversed + //! - right digit = 1: Parameter \a v in neighbor patch face is reversed + bool connectPatch(int face, ASMs3D& neighbor, int nface, int norient = 0); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch face. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index [1,6] of the boundary face + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lEdge Local index [1,12] of the patch edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + virtual bool integrateEdge(Integrand& integrand, int lEdge, + GlobalIntegral& glbInt, const TimeDomain& time); + + + // Post-processing methods + // ======================= + + //! \brief Creates a hexahedron element model of this patch for visualization. + //! \param[out] grid The generated hexahedron grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + //! + //! \details If \a npe is NULL, the solution is evaluated at the Greville + //! points and then projected onto the spline basis to obtain the control + //! point values, which then are returned through \a sField. + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Evaluates the secondary solution field at the given points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] gpar Parameter values of the result sampling points + bool evalSolution(Matrix& sField, const Integrand& integrand, + const RealArray* gpar) const; + + //! \brief Calculates parameter values for visualization nodal points. + //! \param[out] prm Parameter values in given direction for all points + //! \param[in] dir Parameter direction (0,1,2) + //! \param[in] nSegSpan Number of visualization segments over each knot-span + virtual bool getGridParameters(RealArray& prm, int dir, int nSegSpan) const; + + //! \brief Calculates parameter values for the Greville points. + //! \param[out] prm Parameter values in given direction for all points + //! \param[in] dir Parameter direction (0,1,2) + bool getGrevilleParameters(RealArray& prm, int dir) const; + + //! \brief Returns the volume in the parameter space for an element. + //! \param[in] iel Element index + double getParametricVolume(int iel) const; + //! \brief Returns boundary face area in the parameter space for an element. + //! \param[in] iel Element index + //! \param[in] dir Local face index of the boundary face + double getParametricArea(int iel, int dir) const; + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const; + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[out] n3 Number of nodes in third (w) direction + //! \param[in] basis Which basis to return size parameters for (mixed methods) + virtual bool getSize(int& n1, int& n2, int& n3, int basis = 0) const; + + //! \brief Establishes matrices with basis functions and 1st derivatives. + static void extractBasis(const Go::BasisDerivs& spline, + Vector& N, Matrix& dNdu); + //! \brief Establishes matrices with basis functions, 1st and 2nd derivatives. + static void extractBasis(const Go::BasisDerivs2& spline, + Vector& N, Matrix& dNdu, Matrix3D& d2Ndu2); + + //! \brief Auxilliary function for computation of basis function indices. + static void scatterInd(int n1, int n2, int n3, int p1, int p2, int p3, + const int* start, IntVec& index); + +private: + //! \brief Returns an index into the internal coefficient array for a node. + //! \param[in] inod 0-based node index local to current patch + int coeffInd(size_t inod) const; + +protected: + Go::SplineVolume* svol; //!< Pointer to the actual spline volume object + bool swapW; //!< Has the w-parameter direction been swapped? + std::vector nodeInd; //!< IJK-triplets for the control points (nodes) +}; + +#endif diff --git a/src/ASM/ASMs3DLag.C b/src/ASM/ASMs3DLag.C new file mode 100644 index 00000000..9ebad39a --- /dev/null +++ b/src/ASM/ASMs3DLag.C @@ -0,0 +1,593 @@ +// $Id: ASMs3DLag.C,v 1.14 2011-01-08 14:00:16 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3DLag.C +//! +//! \date Feb 10 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 3D Lagrange FE models. +//! +//============================================================================== + +#include "GoTools/trivariate/SplineVolume.h" + +#include "ASMs3DLag.h" +#include "Lagrange.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "ElementBlock.h" +#include "Utilities.h" +#include "Vec3Oper.h" + + +ASMs3DLag::ASMs3DLag (const char* fName, bool checkRHS, unsigned char n_f) + : ASMs3D(fName,checkRHS,n_f) +{ + nx = ny = nz = 0; +} + + +ASMs3DLag::ASMs3DLag (std::istream& is, bool checkRHS, unsigned char n_f) + : ASMs3D(is,checkRHS,n_f) +{ + nx = ny = nz = 0; +} + + +void ASMs3DLag::clear () +{ + coord.clear(); + nx = ny = nz = 0; + ASMs3D::clear(); +} + + +bool ASMs3DLag::generateFEMTopology() +{ + if (!svol) return false; + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + // Evaluate the parametric values + RealArray gpar[3]; + if (!this->getGridParameters(gpar[0],0,p1-1)) return false; + if (!this->getGridParameters(gpar[1],1,p2-1)) return false; + if (!this->getGridParameters(gpar[2],2,p3-1)) return false; + + // Number of nodes in each direction + nx = gpar[0].size(); + ny = gpar[1].size(); + nz = gpar[2].size(); + + if (!coord.empty()) + return coord.size() == nx*ny*nz; + + // Number of elements in each direction + const int nelx = (nx-1)/(p1-1); + const int nely = (ny-1)/(p2-1); + const int nelz = (nz-1)/(p3-1); + + // Evaluate the nodal coordinates in the physical space + RealArray XYZ(svol->dimension()*nx*ny*nz); + svol->gridEvaluator(gpar[0],gpar[1],gpar[2],XYZ); + + size_t i1, j1; + MLGN.resize(nx*ny*nz); + coord.resize(nx*ny*nz); + for (i1 = j1 = 0; i1 < coord.size(); i1++) + { + MLGN[i1] = ++gNod; + coord[i1][0] = XYZ[j1++]; + coord[i1][1] = XYZ[j1++]; + coord[i1][2] = XYZ[j1++]; + } + + // Number of elements in patch + const int nel = nelx*nely*nelz; + // Number of nodes per element + const int nen = p1*p2*p3; + // Number of nodes in a xy-surface of an element + const int ct = p1*p2; + + // Connectivety array: local --> global node relation + MLGE.resize(nel); + MNPC.resize(nel); + + int i, j, k, a, b, c, iel = 0; + for (k = 0; k < nelz; k++) + for (j = 0; j < nely; j++) + for ( i = 0; i < nelx; i++, iel++) + { + MLGE[iel] = ++gEl; + MNPC[iel].resize(nen); + // First node in current element + int corner = (p3-1)*(nx*ny)*k + (p2-1)*nx*j + (p1-1)*i; + + for (c = 0; c < p3; c++) + { + int cornod = ct*c; + MNPC[iel][cornod] = corner + c*nx*ny; + for (b = 1; b < p2; b++) + { + int facenod = cornod + b*p1; + MNPC[iel][facenod] = MNPC[iel][cornod] + b*nx; + for (a = 1; a < p1; a++) + { + MNPC[iel][facenod+a] = MNPC[iel][facenod] + a; + MNPC[iel][cornod+a] = MNPC[iel][cornod] + a; + } + } + } + } + + return true; +} + + +bool ASMs3DLag::getElementCoordinates (Matrix& X, int iel) const +{ + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs3DLag::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } + + // Number of nodes per element + const size_t nen = svol->order(0)*svol->order(1)*svol->order(2); + + X.resize(3,nen); + for (size_t i = 0; i < nen; i++) + X.fillColumn(i+1,coord[MNPC[iel-1][i]].ptr()); + + return true; +} + + +void ASMs3DLag::getNodalCoordinates (Matrix& X) const +{ + X.resize(3,coord.size()); + + for (size_t inod = 0; inod < coord.size(); inod++) + X.fillColumn(inod+1,coord[inod].ptr()); +} + + +Vec3 ASMs3DLag::getCoord (size_t inod) const +{ + if (inod < 1 || inod > coord.size()) return Vec3(); + + return coord[inod-1]; +} + + +bool ASMs3DLag::getSize (int& n1, int& n2, int& n3, int) const +{ + n1 = nx; + n2 = ny; + n3 = nz; + + return true; +} + + +bool ASMs3DLag::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + Vector N(p1*p2*p3); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up nodal point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + if (integrand.getIntegrandType() == 4) + { + // Compute the element "center" (average of element node coordinates) + X = 0.0; + for (size_t i = 1; i <= 3; i++) + for (size_t j = 1; j <= Xnod.cols(); j++) + X[i-1] += Xnod(i,j); + + X *= 1.0/(double)Xnod.cols(); + } + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1],X,nGauss*nGauss*nGauss)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + for (int k = 0; k < nGauss; k++) + for (int j = 0; j < nGauss; j++) + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = wg[i]*wg[j]*wg[k]; + + // Compute basis function derivatives at current integration point + // using tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N,dNdu,p1,xg[i],p2,xg[j],p3,xg[k])) + return false; + + // Compute Jacobian inverse of coordinate mapping and derivatives + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + if (detJ == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,X)) + return false; + } + + // Finalize the element quantities + if (!integrand.finalizeElement(elmInt)) + return false; + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DLag::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the face normal {-3,-2,-1, 1, 2, 3} + const int faceDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t0 = abs(faceDir); // unsigned normal direction of the face + const int t1 = 1 + t0%3; // first tangent direction of the face + const int t2 = 1 + t1%3; // second tangent direction of the face + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + // Number of elements in each direction + const int nelx = (nx-1)/(p1-1); + const int nely = (ny-1)/(p2-1); + const int nelz = (nz-1)/(p3-1); + + Vector N(p1*p2*p3); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + Vec3 normal; + double xi[3]; + + + // === Assembly loop over all elements on the patch face ===================== + + int iel = 1; + for (int i3 = 0; i3 < nelz; i3++) + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary face + bool skipMe = false; + switch (faceDir) + { + case -1: if (i1 > 0) skipMe = true; break; + case 1: if (i1 < nelx-1) skipMe = true; break; + case -2: if (i2 > 0) skipMe = true; break; + case 2: if (i2 < nely-1) skipMe = true; break; + case -3: if (i3 > 0) skipMe = true; break; + case 3: if (i3 < nelz-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up nodal point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + for (int j = 0; j < nGauss; j++) + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = wg[i]*wg[j]; + + // Gauss point coordinates on the face + xi[t0-1] = faceDir < 0 ? -1.0 : 1.0; + xi[t1-1] = xg[i]; + xi[t2-1] = xg[j]; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N,dNdu,p1,xi[0],p2,xi[1],p3,xi[2])) + return false; + + // Compute basis function derivatives and the face normal + double dS = utl::Jacobian(Jac,normal,dNdX,Xnod,dNdu,t1,t2); + if (dS == 0.0) continue; // skip singular points + + if (faceDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*weight,N,dNdX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DLag::integrateEdge (Integrand& integrand, int lEdge, + GlobalIntegral& glInt, + const TimeDomain& time) +{ + if (!svol) return true; // silently ignore empty patches + + // Parametric direction of the edge {0, 1, 2} + const int lDir = (lEdge-1)/4; + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + // Number of elements in each direction + const int nelx = (nx-1)/(p1-1); + const int nely = (ny-1)/(p2-1); + const int nelz = (nz-1)/(p3-1); + + Vector N(p1*p2*p3); + Matrix dNdu, dNdX, Xnod, Jac; + Vec4 X; + Vec3 tangent; + double xi[3]; + + switch (lEdge) + { + case 1: xi[1] = -1.0; xi[2] = -1.0; break; + case 2: xi[1] = 1.0; xi[2] = -1.0; break; + case 3: xi[1] = -1.0; xi[2] = 1.0; break; + case 4: xi[1] = 1.0; xi[2] = 1.0; break; + case 5: xi[0] = -1.0; xi[2] = -1.0; break; + case 6: xi[0] = 1.0; xi[2] = -1.0; break; + case 7: xi[0] = -1.0; xi[2] = 1.0; break; + case 8: xi[0] = 1.0; xi[2] = 1.0; break; + case 9: xi[0] = -1.0; xi[1] = -1.0; break; + case 10: xi[0] = 1.0; xi[1] = -1.0; break; + case 11: xi[0] = -1.0; xi[1] = 1.0; break; + case 12: xi[0] = 1.0; xi[1] = 1.0; break; + } + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i3 = 0; i3 < nelz; i3++) + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (lEdge) + { + case 1: if (i2 > 0 || i3 > 0) skipMe = true; break; + case 2: if (i2 < nely-1 || i3 > 0) skipMe = true; break; + case 3: if (i2 > 0 || i3 < nelz-1) skipMe = true; break; + case 4: if (i2 < nely-1 || i3 < nelz-1) skipMe = true; break; + case 5: if (i1 > 0 || i3 > 0) skipMe = true; break; + case 6: if (i1 < nelx-1 || i3 > 0) skipMe = true; break; + case 7: if (i1 > 0 || i3 < nelz-1) skipMe = true; break; + case 8: if (i1 < nelx-1 || i3 < nelz-1) skipMe = true; break; + case 9: if (i1 > 0 || i2 > 0) skipMe = true; break; + case 10: if (i1 < nelx-1 || i2 > 0) skipMe = true; break; + case 11: if (i1 > 0 || i2 < nely-1) skipMe = true; break; + case 12: if (i1 < nelx-1 || i2 < nely-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up nodal point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + + // --- Integration loop over all Gauss points along the edge ----------- + + LocalIntegral* elmInt = 0; + for (int i = 0; i < nGauss; i++) + { + // Gauss point coordinates on the edge + xi[lDir] = xg[i]; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N,dNdu,p1,xi[0],p2,xi[1],p3,xi[2])) + return false; + + // Compute basis function derivatives and the edge tangent + double dS = utl::Jacobian(Jac,tangent,dNdX,Xnod,dNdu,1+lDir); + if (dS == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*wg[i],N,dNdX,X,tangent)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DLag::tesselate (ElementBlock& grid, const int* npe) const +{ + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + if (p1 != npe[0] || p2 != npe[1] || p2 != npe[2]) + { + int* newnpe = const_cast(npe); + std::cout <<"\nLagrange elements: The number of visualization points are " + << p1 <<" "<< p2 <<" "<< p3 <<" by default\n"<< std::endl; + newnpe[0] = p1; + newnpe[1] = p2; + newnpe[2] = p3; + } + + return this->ASMs3D::tesselate(grid,npe); +} + + +bool ASMs3DLag::evalSolution (Matrix& sField, const Vector& locSol, + const int*) const +{ + size_t nPoints = coord.size(); + size_t nComp = locSol.size() / nPoints; + if (nComp*nPoints != locSol.size()) + return false; + + size_t i, n, ip = 0; + sField.resize(nComp,nPoints); + for (n = 1; n <= nPoints; n++) + for (i = 1; i <= nComp; i++) + sField(i,n) = locSol(++ip); + + return true; +} + + +bool ASMs3DLag::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + double incx = 2.0/double(p1-1); + double incy = 2.0/double(p2-1); + double incz = 2.0/double(p3-1); + + size_t nPoints = coord.size(); + IntVec check(nPoints,0); + + Vector N(p1*p2*p3), solPt; + std::vector globSolPt(nPoints); + Matrix dNdu, dNdX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + const IntVec& mnpc = MNPC[iel-1]; + this->getElementCoordinates(Xnod,iel); + + int i, j, k, loc = 0; + for (k = 0; k < p3; k++) + for (j = 0; j < p2; j++) + for (i = 0; i < p1; i++, loc++) + { + double xi = -1.0 + i*incx; + double eta = -1.0 + j*incy; + double zeta = -1.0 + k*incz; + if (!Lagrange::computeBasis(N,dNdu,p1,xi,p2,eta,p3,zeta)) + return false; + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xnod,dNdu) == 0.0) // Jac = (Xnod*dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xnod*N,mnpc)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc[loc]] == 1) + globSolPt[mnpc[loc]] = solPt; + else + globSolPt[mnpc[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs3DLag.h b/src/ASM/ASMs3DLag.h new file mode 100644 index 00000000..4f51aa7f --- /dev/null +++ b/src/ASM/ASMs3DLag.h @@ -0,0 +1,142 @@ +// $Id: ASMs3DLag.h,v 1.7 2010-12-29 18:41:38 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3DLag.h +//! +//! \date Feb 10 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 3D Lagrange FE models. +//! +//============================================================================== + +#ifndef _ASM_S3D_LAG_H +#define _ASM_S3D_LAG_H + +#include "ASMs3D.h" +#include "Vec3.h" + + +/*! + \brief Driver for assembly of structured 3D Lagrange FE models. + \details This class contains methods for structured 3D Lagrange patches. +*/ + +class ASMs3DLag : public ASMs3D +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs3DLag(const char* fileName, bool checkRHS = false, unsigned char n_f = 3); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs3DLag(std::istream& is, bool checkRHS = false, unsigned char n_f = 3); + //! \brief Default constructor creating an empty patch. + ASMs3DLag(unsigned char n_f = 3) : ASMs3D(n_f) {} + //! \brief Empty destructor. + virtual ~ASMs3DLag() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the nodal coordinate array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch face. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index [1,6] of the boundary face + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lEdge Local index [1,12] of the patch edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + virtual bool integrateEdge(Integrand& integrand, int lEdge, + GlobalIntegral& glbInt, const TimeDomain& time); + + + // Post-processing methods + // ======================= + + //! \brief Creates a hexahedron element model of this patch for visualization. + //! \param[out] grid The generated hexahedron grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + virtual bool tesselate(ElementBlock& grid, const int* npe) const; + + //! \brief Evaluates the primary solution field at all visualization points. + //! \details The number of visualization points is the same as the order of + //! the Lagrange elements by default. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \details The number of visualization points is the same as the order of + //! the Lagrange elements by default. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + virtual void getNodalCoordinates(Matrix& X) const; + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[out] n3 Number of nodes in third (w) direction + virtual bool getSize(int& n1, int& n2, int& n3, int = 0) const; + +private: + size_t nx; //!< Number of nodes in first parameter direction + size_t ny; //!< Number of nodes in second parameter direction + size_t nz; //!< Number of nodes in third parameter direction + std::vector coord; //!< Nodal coordinates +}; + +#endif diff --git a/src/ASM/ASMs3DSpec.C b/src/ASM/ASMs3DSpec.C new file mode 100644 index 00000000..5d1693e7 --- /dev/null +++ b/src/ASM/ASMs3DSpec.C @@ -0,0 +1,474 @@ +// $Id: ASMs3DSpec.C,v 1.11 2010-12-29 18:36:06 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3DSpec.C +//! +//! \date Mar 22 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 3D Spectral FE models. +//! +//============================================================================== + +#include "GoTools/trivariate/SplineVolume.h" + +#include "ASMs3DSpec.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "Vec3Oper.h" +#include "Legendre.h" + + +bool ASMs3DSpec::getGridParameters (RealArray& prm, int dir, + int nSegPerSpan) const +{ + if (!svol) return false; + + // Evaluate the Gauss-Lobatto-Legendre points in this direction + Vector dummy, xGLL; + if (!Legendre::GLL(dummy,xGLL,svol->order(dir))) return false; + + if (xGLL.size() != nSegPerSpan+1) + { + nSegPerSpan = xGLL.size() - 1; + std::cout <<"Spectral elements: Number of nodes per knot-span in " + << char('u'+dir) <<"-directon reset to "<< nSegPerSpan + <<" (GLL points)"<< std::endl; + } + + std::vector::const_iterator uit = svol->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != svol->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 1; i <= nSegPerSpan; i++) + prm.push_back(0.5*(ucurr-uprev)*(1.0+xGLL(i)) + uprev); + uprev = ucurr; + } + + prm.push_back(svol->basis(dir).endparam()); + return true; +} + + +/*! + \brief Establishes matrices with basis functions and 1st derivatives. +*/ + +static void evalBasis (int i, int j, int k, int p1, int p2, int p3, + const Matrix& der1, const Matrix& der2, + const Matrix& der3, Vector& N, Matrix& dNdu) +{ + int a, b, c, n = 1; + for (c = 1; c <= p3; c++) + for (b = 1; b <= p2; b++) + for (a = 1; a <= p1; a++, n++) + { + N (n) = (a == i && b == j && c == k) ? 1.0 : 0.0; + dNdu(n,1) = (b == j && c == k) ? der1(i,a) : 0.0; + dNdu(n,2) = (a == i && c == k) ? der2(j,b) : 0.0; + dNdu(n,3) = (a == i && b == j) ? der3(k,c) : 0.0; + } +} + + +bool ASMs3DSpec::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + // Evaluate integration points (=nodal points) and weights + + Vector wg1,xg1,wg2,xg2,wg3,xg3; + if (!Legendre::GLL(wg1,xg1,p1)) return false; + if (!Legendre::GLL(wg2,xg2,p2)) return false; + if (!Legendre::GLL(wg3,xg3,p3)) return false; + + Matrix D1, D2, D3; + if (!Legendre::basisDerivatives(p1,D1)) return false; + if (!Legendre::basisDerivatives(p2,D2)) return false; + if (!Legendre::basisDerivatives(p3,D3)) return false; + + Vector N(p1*p2*p3); + Matrix dNdu(p1*p2*p3,3); + Matrix dNdX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up nodal point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElement(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + int count = 1; + for (int k = 1; k <= p3; k++) + for (int j = 1; j <= p2; j++) + for (int i = 1; i <= p1; i++, count++) + { + // Weight of current integration point + double weight = wg1(i)*wg2(j)*wg3(k); + + // Evaluate the basis functions and gradients using tensor product + // of the one-dimensional Lagrange polynomials + evalBasis(i,j,k,p1,p2,p3,D1,D2,D3,N,dNdu); + + // Compute Jacobian inverse of coordinate mapping and derivatives + double detJ = utl::Jacobian(Jac,dNdX,Xnod,dNdu); + if (detJ == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X.x = Xnod(1,count); + X.y = Xnod(2,count); + X.z = Xnod(3,count); + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*weight,N,dNdX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DSpec::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + // Find the parametric direction of the face normal {-3,-2,-1, 1, 2, 3} + const int faceDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t0 = abs(faceDir); // unsigned normal direction of the face + const int t1 = 1 + t0%3; // first tangent direction of the face + const int t2 = 1 + t1%3; // second tangent direction of the face + + // Order of basis in the three parametric directions (order = degree + 1) + int p[3]; + p[0] = svol->order(0); + p[1] = svol->order(1); + p[2] = svol->order(2); + + // Number of elements in each direction + int n1, n2, n3; + this->getSize(n1,n2,n3); + const int nelx = (n1-1)/(p[0]-1); + const int nely = (n2-1)/(p[1]-1); + const int nelz = (n3-1)/(p[2]-1); + + // Evaluate integration points (=nodal points) and weights + + Vector wg[3],xg1,xg2,xg3; + if (!Legendre::GLL(wg[0],xg1,p[0])) return false; + if (!Legendre::GLL(wg[1],xg2,p[1])) return false; + if (!Legendre::GLL(wg[2],xg3,p[2])) return false; + + Matrix D1, D2, D3; + if (!Legendre::basisDerivatives(p[0],D1)) return false; + if (!Legendre::basisDerivatives(p[1],D2)) return false; + if (!Legendre::basisDerivatives(p[2],D3)) return false; + + int nen = p[0]*p[1]*p[2]; + + Vector N(nen); + Matrix dNdu(nen,3); + Matrix dNdX, Xnod, Jac; + Vec4 X; + Vec3 normal; + int xi[3]; + + + // === Assembly loop over all elements on the patch face ===================== + + int iel = 1; + for (int i3 = 0; i3 < nelz; i3++) + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary face + bool skipMe = false; + switch (faceDir) + { + case -1: if (i1 > 0) skipMe = true; break; + case 1: if (i1 < nelx-1) skipMe = true; break; + case -2: if (i2 > 0) skipMe = true; break; + case 2: if (i2 < nely-1) skipMe = true; break; + case -3: if (i3 > 0) skipMe = true; break; + case 3: if (i3 < nelz-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up nodal point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + for (int j = 0; j < p[t2-1]; j++) + for (int i = 0; i < p[t1-1]; i++) + { + // Weight of current integration point + double weight = wg[t1-1][i]*wg[t2-1][j]; + + // "Coordinates" on the face + xi[t0-1] = faceDir < 0 ? 1 : p[t0-1]; + xi[t1-1] = i+1; + xi[t2-1] = j+1; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + evalBasis(xi[0],xi[1],xi[2],p[0],p[1],p[2],D1,D2,D3,N,dNdu); + + // Compute basis function derivatives and the face normal + double dS = utl::Jacobian(Jac,normal,dNdX,Xnod,dNdu,t1,t2); + if (dS == 0.0) continue; // skip singular points + + if (faceDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*weight,N,dNdX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DSpec::integrateEdge (Integrand& integrand, int lEdge, + GlobalIntegral& glInt, + const TimeDomain& time) +{ + if (!svol) return true; // silently ignore empty patches + + // Parametric direction of the edge {0, 1, 2} + const int lDir = (lEdge-1)/4; + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + const int pe = svol->order(lDir); + + // Number of elements in each direction + int n1, n2, n3; + this->getSize(n1,n2,n3); + const int nelx = (n1-1)/(p1-1); + const int nely = (n2-1)/(p2-1); + const int nelz = (n3-1)/(p3-1); + + // Evaluate integration points (=nodal points) and weights + + Vector wg[3],xg1,xg2,xg3; + if (!Legendre::GLL(wg[0],xg1,p1)) return false; + if (!Legendre::GLL(wg[1],xg2,p2)) return false; + if (!Legendre::GLL(wg[2],xg3,p3)) return false; + + Matrix D1, D2, D3; + if (!Legendre::basisDerivatives(p1,D1)) return false; + if (!Legendre::basisDerivatives(p2,D2)) return false; + if (!Legendre::basisDerivatives(p3,D3)) return false; + + const int nen = p1*p2*p3; + + Vector N(nen); + Matrix dNdu(nen,3); + Matrix dNdX, Xnod, Jac; + Vec4 X; + Vec3 tangent; + int xi[3]; + + switch (lEdge) + { + case 1: xi[1] = 1; xi[2] = 1; break; + case 2: xi[1] = p2; xi[2] = 1; break; + case 3: xi[1] = 1; xi[2] = p3; break; + case 4: xi[1] = p2; xi[2] = p3; break; + case 5: xi[0] = 1; xi[2] = 1; break; + case 6: xi[0] = p1; xi[2] = 1; break; + case 7: xi[0] = 1; xi[2] = p3; break; + case 8: xi[0] = p1; xi[2] = p3; break; + case 9: xi[0] = 1; xi[1] = 1; break; + case 10: xi[0] = p1; xi[1] = 1; break; + case 11: xi[0] = 1; xi[1] = p2; break; + case 12: xi[0] = p1; xi[1] = p2; break; + } + + + // === Assembly loop over all elements on the patch edge ===================== + + int iel = 1; + for (int i3 = 0; i3 < nelz; i3++) + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary edge + bool skipMe = false; + switch (lEdge) + { + case 1: if (i2 > 0 || i3 > 0) skipMe = true; break; + case 2: if (i2 < nely-1 || i3 > 0) skipMe = true; break; + case 3: if (i2 > 0 || i3 < nelz-1) skipMe = true; break; + case 4: if (i2 < nely-1 || i3 < nelz-1) skipMe = true; break; + case 5: if (i1 > 0 || i3 > 0) skipMe = true; break; + case 6: if (i1 < nelx-1 || i3 > 0) skipMe = true; break; + case 7: if (i1 > 0 || i3 < nelz-1) skipMe = true; break; + case 8: if (i1 < nelx-1 || i3 < nelz-1) skipMe = true; break; + case 9: if (i1 > 0 || i2 > 0) skipMe = true; break; + case 10: if (i1 < nelx-1 || i2 > 0) skipMe = true; break; + case 11: if (i1 > 0 || i2 < nely-1) skipMe = true; break; + case 12: if (i1 < nelx-1 || i2 < nely-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up nodal point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + if (!integrand.initElementBou(MNPC[iel-1])) return false; + + + // --- Integration loop over all Gauss points along the edge ----------- + + LocalIntegral* elmInt = 0; + for (int i = 0; i < pe; i++) + { + // "Coordinate" on the edge + xi[lDir] = i+1; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + evalBasis(xi[0],xi[1],xi[2],p1,p2,p3,D1,D2,D3,N,dNdu); + + // Compute basis function derivatives and the edge tangent + double dS = utl::Jacobian(Jac,tangent,dNdX,Xnod,dNdu,1+lDir); + if (dS == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * N; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*wg[lDir][i],N,dNdX,X,tangent)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DSpec::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + Vector wg1,xg1,wg2,xg2,wg3,xg3; + if (!Legendre::GLL(wg1,xg1,p1)) return false; + if (!Legendre::GLL(wg2,xg2,p2)) return false; + if (!Legendre::GLL(wg3,xg3,p3)) return false; + + Matrix D1, D2, D3; + if (!Legendre::basisDerivatives(p1,D1)) return false; + if (!Legendre::basisDerivatives(p2,D2)) return false; + if (!Legendre::basisDerivatives(p3,D3)) return false; + + size_t nPoints = this->getNoNodes(); + IntVec check(nPoints,0); + + Vector N(p1*p2*p3), solPt; + std::vector globSolPt(nPoints); + Matrix dNdu(p1*p2*p3,3), dNdX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + const IntVec& mnpc = MNPC[iel-1]; + this->getElementCoordinates(Xnod,iel); + + int i, j, k, loc = 0; + for (k = 0; k < p3; k++) + for (j = 0; j < p2; j++) + for (i = 0; i < p1; i++, loc++) + { + evalBasis(i+1,j+1,k+1,p1,p2,p3,D1,D2,D3,N,dNdu); + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dNdX,Xnod,dNdu) == 0.0) // Jac = (Xnod*dNdu)^-1 + continue; // skip singular points + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N,dNdX,Xnod.getColumn(loc+1),mnpc)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc[loc]] == 1) + globSolPt[mnpc[loc]] = solPt; + else + globSolPt[mnpc[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs3DSpec.h b/src/ASM/ASMs3DSpec.h new file mode 100644 index 00000000..9ea73c19 --- /dev/null +++ b/src/ASM/ASMs3DSpec.h @@ -0,0 +1,99 @@ +// $Id: ASMs3DSpec.h,v 1.6 2010-10-17 13:10:47 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3DSpec.h +//! +//! \date Mar 22 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Driver for assembly of structured 3D Spectral FE models. +//! +//============================================================================== + +#ifndef _ASM_S3D_SPEC_H +#define _ASM_S3D_SPEC_H + +#include "ASMs3DLag.h" + + +/*! + \brief Driver for assembly of structured 3D Spectral FE models. + \details This class contains methods for structured 3D Spectral patches. +*/ + +class ASMs3DSpec : public ASMs3DLag +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs3DSpec(const char* fileName, bool checkRHS = false, unsigned char n_f = 3) + : ASMs3DLag(fileName,checkRHS,n_f) {} + //! \brief Constructor creating an instance by reading the given input stream. + ASMs3DSpec(std::istream& is, bool checkRHS = false, unsigned char n_f = 3) + : ASMs3DLag(is,checkRHS,n_f) {} + //! \brief Default constructor creating an empty patch. + ASMs3DSpec(unsigned char n_f = 3) : ASMs3DLag(n_f) {} + //! \brief Empty destructor. + virtual ~ASMs3DSpec() {} + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch face. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index [1,6] of the boundary face + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lEdge Local index [1,12] of the patch edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + virtual bool integrateEdge(Integrand& integrand, int lEdge, + GlobalIntegral& glbInt, const TimeDomain& time); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Calculates parameter values for all visualization nodal points. + //! \param[out] prm Parameter values in given direction for all points + //! \param[in] dir Parameter direction (0,1,2) + //! \param[in] nSegSpan Number of visualization segments over each knot-span + //! + //! This method is reimplemented to return the parameter values of the + //! Gauss-Lobatto-Legendre points, which define the FE nodes when using + //! spectral elements. This method is therefore also used when creating the + //! FE model (the visualization nodes and the FE nodes are identical). + virtual bool getGridParameters(RealArray& prm, int dir, int nSegSpan) const; +}; + +#endif diff --git a/src/ASM/ASMs3Dmx.C b/src/ASM/ASMs3Dmx.C new file mode 100644 index 00000000..9c3d32d7 --- /dev/null +++ b/src/ASM/ASMs3Dmx.C @@ -0,0 +1,840 @@ +// $Id: ASMs3Dmx.C,v 1.2 2010-12-30 15:02:02 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3Dmx.C +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 3D spline mixed FE models. +//! +//============================================================================== + +#include "GoTools/trivariate/SplineVolume.h" + +#include "ASMs3Dmx.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "Utilities.h" +#include "Profiler.h" +#include "Vec3Oper.h" +#include "Vec3.h" + +typedef Go::SplineVolume::Dvector DoubleVec; //!< 1D double array +typedef DoubleVec::const_iterator DoubleIter; //!< Iterator over DoubleVec + + +ASMs3Dmx::ASMs3Dmx (const char* fileName, bool checkRHS, + char n_f1, unsigned char n_f2) + : ASMs3D(fileName,checkRHS), ASMmxBase(n_f1 < 0 ? -n_f1 : n_f1,n_f2, n_f1 < 0) +{ + basis1 = basis2 = 0; + nf = nf1 + nf2; +} + + +ASMs3Dmx::ASMs3Dmx (std::istream& is, bool checkRHS, + char n_f1, unsigned char n_f2) + : ASMs3D(is,checkRHS), ASMmxBase(n_f1 < 0 ? -n_f1 : n_f1, n_f2, n_f1 < 0) +{ + basis1 = basis2 = 0; + nf = nf1 + nf2; +} + + +ASMs3Dmx::ASMs3Dmx (bool checkRHS, char n_f1, unsigned char n_f2) + : ASMs3D(checkRHS), ASMmxBase(n_f1 < 0 ? -n_f1 : n_f1, n_f2, n_f1 < 0) +{ + basis1 = basis2 = 0; + nf = nf1 + nf2; +} + + +void ASMs3Dmx::clear () +{ + // Erase the spline data + if (basis1) delete basis1; + if (basis2) delete basis2; + svol = basis1 = basis2 = 0; + + // Erase the FE data + ASMs3D::clear(); +} + + +unsigned char ASMs3Dmx::getNoFields (int basis) const +{ + switch (basis) + { + case 1: return nf1; + case 2: return nf2; + } + + return nf; +} + + +unsigned char ASMs3Dmx::getNodalDOFs (size_t inod) const +{ + return inod <= nb1 ? nf1 : nf2; +} + + +void ASMs3Dmx::initMADOF (const int* sysMadof) +{ + this->init(MLGN,sysMadof); +} + + +void ASMs3Dmx::extractNodeVec (const Vector& globRes, Vector& nodeVec, + unsigned char) const +{ + this->extrNodeVec(globRes,nodeVec); +} + + +bool ASMs3Dmx::generateFEMTopology () +{ + if (!svol) return false; + + if (!basis1 && !basis2) + { + // With mixed methods we need two separates spline spaces + basis1 = new Go::SplineVolume(*svol); + basis2 = svol; + + // Order-elevate basis1 such that it is of one degree higher than basis2 + basis1->raiseOrder(1,1,1); + + // Define which basis that should be used to represent the geometry + if (geoUsesBasis1) svol = basis1; + } + + const int n1 = basis1->numCoefs(0); + const int n2 = basis1->numCoefs(1); + const int n3 = basis1->numCoefs(2); + const int m1 = basis2->numCoefs(0); + const int m2 = basis2->numCoefs(1); + const int m3 = basis2->numCoefs(2); + if (!nodeInd.empty()) + { + if (nodeInd.size() == nb1 + nb2) return true; + std::cerr <<" *** ASMs3Dmx::generateFEMTopology: Inconsistency between the" + <<" number of FE nodes "<< nodeInd.size() + <<"\n and the number of spline coefficients "<< nb1 + nb2 + <<" in the patch."<< std::endl; + return false; + } + + nb1 = n1*n2*n3; // Number of functions in first basis + nb2 = m1*m2*m3; // Number of functions in second basis + + const int p1 = basis1->order(0); + const int p2 = basis1->order(1); + const int p3 = basis1->order(2); + const int q1 = basis2->order(0); + const int q2 = basis2->order(1); + const int q3 = basis2->order(2); + +#ifdef SP_DEBUG + std::cout <<"numCoefs: "<< n1 <<" "<< n2 <<" "<< n3 + <<", "<< m1 <<" "<< m2 <<" "<< m3; + std::cout <<"\norder: "<< p1 <<" "<< p2 <<" "<< p3 + <<", "<< q1 <<" "<< q2 <<" "<< q3; + for (int d = 0; d < 3; d++) + { + std::cout <<"\nd"<< char('u'+d) <<':'; + for (int i = 0; i < basis1->numCoefs(d); i++) + std::cout <<' '<< basis1->knotSpan(d,i); + for (int j = 0; j < basis2->numCoefs(d); j++) + std::cout <<' '<< basis2->knotSpan(d,j); + } + std::cout << std::endl; +#endif + // Consistency checks, just to be fool-proof + if (m1 < 2 || m2 < 2 || m3 < 2) return false; + if (q1 < 1 || q2 < 1 || q3 < 1) return false; + if (p1 > n1 || p2 > n2 || p3 > n3) return false; + if (q1 > m1 || q2 > m2 || q3 > m3) return false; + + MLGE.resize((n1-p1+1)*(n2-p2+1)*(n3-p3+1),0); + MLGN.resize(nb1 + nb2); + MNPC.resize(MLGE.size()); + nodeInd.resize(MLGN.size()); + + int i1, i2, i3, j1, j2, j3; + size_t iel, inod = 0; + for (i3 = 0; i3 < n3; i3++) + for (i2 = 0; i2 < n2; i2++) + for (i1 = 0; i1 < n1; i1++) + { + nodeInd[inod].I = i1; + nodeInd[inod].J = i2; + nodeInd[inod].K = i3; + MLGN[inod++] = ++gNod; + } + + for (i3 = 0; i3 < m3; i3++) + for (i2 = 0; i2 < m2; i2++) + for (i1 = 0; i1 < m1; i1++) + { + nodeInd[inod].I = i1; + nodeInd[inod].J = i2; + nodeInd[inod].K = i3; + MLGN[inod++] = ++gNod; + } + + if (geoUsesBasis1) + { + // Create nodal connectivities for basis 1 + iel = inod = 0; + for (i3 = 1; i3 <= n3; i3++) + for (i2 = 1; i2 <= n2; i2++) + for (i1 = 1; i1 <= n1; i1++, inod++) + if (i1 >= p1 && i2 >= p2 && i3 >= p3) + { + if (basis1->knotSpan(0,i1-1) > 0.0) + if (basis1->knotSpan(1,i2-1) > 0.0) + if (basis1->knotSpan(2,i3-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1*p2*p3+q1*q2*q3,0); + + int lnod = 0; + for (j3 = p3-1; j3 >= 0; j3--) + for (j2 = p2-1; j2 >= 0; j2--) + for (j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - n1*n2*j3 - n1*j2 - j1; + } + + iel++; + } + + // Create nodal connectivities for basis 2 + iel = 0; + for (i3 = 1; i3 <= m3; i3++) + for (i2 = 1; i2 <= m2; i2++) + for (i1 = 1; i1 <= m1; i1++, inod++) + if (i1 >= q1 && i2 >= q2 && i3 >= q3) + if (basis2->knotSpan(0,i1-1) > 0.0) + if (basis2->knotSpan(1,i2-1) > 0.0) + if (basis2->knotSpan(2,i3-1) > 0.0) + { + while (iel < MNPC.size() && MNPC[iel].empty()) iel++; + + int lnod = p1*p2*p3; + for (j3 = q3-1; j3 >= 0; j3--) + for (j2 = q2-1; j2 >= 0; j2--) + for (j1 = q1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - m1*m2*j3 - m1*j2 - j1; + + iel++; + } + } + else + { + // Create nodal connectivities for basis 2 + iel = 0; + inod = n1*n2*n3; + for (i3 = 1; i3 <= m3; i3++) + for (i2 = 1; i2 <= m2; i2++) + for (i1 = 1; i1 <= m1; i1++, inod++) + if (i1 >= q1 && i2 >= q2 && i3 >= q3) + { + if (basis2->knotSpan(0,i1-1) > 0.0) + if (basis2->knotSpan(1,i2-1) > 0.0) + if (basis2->knotSpan(2,i3-1) > 0.0) + { + MLGE[iel] = ++gEl; // global element number over all patches + MNPC[iel].resize(p1*p2*p3+q1*q2*q3,0); + + int lnod = p1*p2*p3; + for (j3 = q3-1; j3 >= 0; j3--) + for (j2 = q2-1; j2 >= 0; j2--) + for (j1 = q1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - m1*m2*j3 - m1*j2 - j1; + } + + iel++; + } + + // Create nodal connectivities for basis 1 + iel = inod = 0; + for (i3 = 1; i3 <= n3; i3++) + for (i2 = 1; i2 <= n2; i2++) + for (i1 = 1; i1 <= n1; i1++, inod++) + if (i1 >= p1 && i2 >= p2 && i3 >= p3) + if (basis1->knotSpan(0,i1-1) > 0.0) + if (basis1->knotSpan(1,i2-1) > 0.0) + if (basis1->knotSpan(2,i3-1) > 0.0) + { + while (iel < MNPC.size() && MNPC[iel].empty()) iel++; + + int lnod = 0; + for (j3 = p3-1; j3 >= 0; j3--) + for (j2 = p2-1; j2 >= 0; j2--) + for (j1 = p1-1; j1 >= 0; j1--) + MNPC[iel][lnod++] = inod - n1*n2*j3 - n1*j2 - j1; + + iel++; + } + } + +#ifdef SP_DEBUG + std::cout <<"NEL = "<< MLGE.size() <<" NNOD = "<< MLGN.size() << std::endl; +#endif + return true; +} + + +bool ASMs3Dmx::getElementCoordinates (Matrix& X, int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** ASMs3Dmx::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } +#endif + + size_t nenod = svol->order(0)*svol->order(1)*svol->order(2); + size_t lnod0 = basis1->order(0)*basis1->order(1)*basis1->order(2); + if (geoUsesBasis1) lnod0 = 0; + + X.resize(3,nenod); + const IntVec& mnpc = MNPC[iel-1]; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int ndim = svol->dimension(); + DoubleIter cit = svol->coefs_begin(); + for (size_t n = 0; n < nenod; n++) + { + int iI = nodeInd[mnpc[lnod0+n]].I; + int iJ = nodeInd[mnpc[lnod0+n]].J; + int iK = nodeInd[mnpc[lnod0+n]].K; + int ip = ((iK*n2 + iJ)*n1 + iI)*ndim; + for (size_t i = 0; i < 3; i++) + X(i+1,n+1) = *(cit+(ip+i)); + } + +#if SP_DEBUG > 2 + std::cout <<"\nCoordinates for element "<< iel << X << std::endl; +#endif + return true; +} + + +Vec3 ASMs3Dmx::getCoord (size_t inod) const +{ +#ifdef INDEX_CHECK + if (inod < 1 || inod > nodeInd.size()) + { + std::cerr <<" *** ASMs3Dmx::getCoord: Node index "<< inod + <<" out of range [1,"<< nodeInd.size() <<"]."<< std::endl; + return Vec3(); + } +#endif + + DoubleIter cit; + const int I = nodeInd[inod-1].I; + const int J = nodeInd[inod-1].J; + const int K = nodeInd[inod-1].K; + if (inod <= nb1) + cit = basis1->coefs_begin() + + ((K*basis1->numCoefs(1)+J)*basis1->numCoefs(0)+I) * basis1->dimension(); + else + cit = basis2->coefs_begin() + + ((K*basis2->numCoefs(1)+J)*basis2->numCoefs(0)+I) * basis2->dimension(); + + return Vec3(*cit,*(cit+1),*(cit+2)); +} + + +bool ASMs3Dmx::getSize (int& n1, int& n2, int& n3, int basis) const +{ + switch (basis) + { + case 1: + if (!basis1) return false; + n1 = basis1->numCoefs(0); + n2 = basis1->numCoefs(1); + n3 = basis1->numCoefs(2); + return true; + + case 2: + if (!basis2) return false; + n1 = basis2->numCoefs(0); + n2 = basis2->numCoefs(1); + n3 = basis2->numCoefs(2); + return true; + } + + return this->ASMs3D::getSize(n1,n2,n3); +} + + +bool ASMs3Dmx::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + if (!basis1 || !basis2) return false; + + PROFILE2("ASMs3Dmx::integrate(I)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + int dir; + Matrix gpar[3]; + for (dir = 0; dir < 3; dir++) + { + int pm1 = svol->order(dir) - 1; + DoubleIter uit = svol->basis(dir).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(dir) - pm1; + gpar[dir].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[dir](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N1(basis1->order(0)*basis1->order(1)*basis1->order(2)); + Vector N2(basis2->order(0)*basis2->order(1)*basis2->order(2)); + Matrix dN1du, dN1dX, dN2du, dN2dX, Xnod, Jac; + Vec4 X; + double detJ; + + + // === Assembly loop over all elements in the patch ========================== + + int iel = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-volume element + + // Get element volume in the parameter space + double dV = this->getParametricVolume(iel); + if (dV < 0.0) return false; // topology error (probably logic error) + + // Set up control point (nodal) coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElement(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + int ip = (((i3-p3)*nGauss*nel2 + i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int k = 0; k < nGauss; k++, ip += nGauss*(nel2-1)*nGauss*nel1) + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.125*dV*wg[i]*wg[j]*wg[k]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline1[ip],N1,dN1du); + extractBasis(spline2[ip],N2,dN2du); + + // Compute Jacobian inverse of the coordinate mapping and + // basis function derivatives w.r.t. Cartesian coordinates + if (geoUsesBasis1) + { + detJ = utl::Jacobian(Jac,dN1dX,Xnod,dN1du); + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + } + else + { + detJ = utl::Jacobian(Jac,dN2dX,Xnod,dN2du); + dN1dX.multiply(dN1du,Jac); // dN1dX = dN1du * J^-1 + } + if (detJ == 0.0) continue; // skip singular points + + // Cartesian coordinates of current integration point + X = Xnod * (geoUsesBasis1 ? N1 : N2); + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,detJ*weight, + N1,N2,dN1dX,dN2dX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3Dmx::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + if (!basis1 || !basis2) return false; + + PROFILE2("ASMs3Dmx::integrate(B)"); + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the face normal {-3,-2,-1, 1, 2, 3} + const int faceDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t1 = 1 + abs(faceDir)%3; // first tangent direction + const int t2 = 1 + t1%3; // second tangent direction + + // Compute parameter values of the Gauss points along the whole patch edge + Matrix gpar[3]; + for (int d = 0; d < 3; d++) + if (-1-d == faceDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = svol->startparam(d); + } + else if (1+d == faceDir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = svol->endparam(d); + } + else + { + int pm1 = svol->order(d) - 1; + DoubleIter uit = svol->basis(d).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(d) - pm1; + gpar[d].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[d](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate basis function derivatives at all integration points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline2); + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N1(basis1->order(0)*basis1->order(1)*basis1->order(2)); + Vector N2(basis2->order(0)*basis2->order(1)*basis2->order(2)); + Matrix dN1du, dN1dX, dN2du, dN2dX, Xnod, Jac; + Vec4 X; + Vec3 normal; + double dS; + + + // === Assembly loop over all elements on the patch face ===================== + + int iel = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + if (MLGE[iel-1] < 1) continue; // zero-volume element + + // Skip elements that are not on current boundary face + bool skipMe = false; + switch (faceDir) + { + case -1: if (i1 > p1) skipMe = true; break; + case 1: if (i1 < n1) skipMe = true; break; + case -2: if (i2 > p2) skipMe = true; break; + case 2: if (i2 < n2) skipMe = true; break; + case -3: if (i3 > p3) skipMe = true; break; + case 3: if (i3 < n3) skipMe = true; break; + } + if (skipMe) continue; + + // Get element face area in the parameter space + double dA = this->getParametricArea(iel,abs(faceDir)); + if (dA < 0.0) return false; // topology error (probably logic error) + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElementBou(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Define some loop control variables depending on which face we are on + int nf1, j1, j2; + switch (abs(faceDir)) + { + case 1: nf1 = nel2; j2 = i3-p3; j1 = i2-p2; break; + case 2: nf1 = nel1; j2 = i3-p3; j1 = i1-p1; break; + case 3: nf1 = nel1; j2 = i2-p2; j1 = i1-p1; break; + } + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + int ip = (j2*nGauss*nf1 + j1)*nGauss; + for (int j = 0; j < nGauss; j++, ip += nGauss*(nf1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.25*dA*wg[i]*wg[j]; + + // Fetch basis function derivatives at current integration point + extractBasis(spline1[ip],N1,dN1du); + extractBasis(spline2[ip],N2,dN2du); + + // Compute Jacobian inverse of the coordinate mapping and + // basis function derivatives w.r.t. Cartesian coordinates + if (geoUsesBasis1) + { + dS = utl::Jacobian(Jac,normal,dN1dX,Xnod,dN1du,t1,t2); + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + } + else + { + dS = utl::Jacobian(Jac,normal,dN2dX,Xnod,dN2du,t1,t2); + dN1dX.multiply(dN1du,Jac); // dN1dX = dN1du * J^-1 + } + if (dS == 0.0) continue; // skip singular points + + if (faceDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * (geoUsesBasis1 ? N1 : N2); + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*weight, + N1,N2,dN1dX,dN2dX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3Dmx::evalSolution (Matrix& sField, const Vector& locSol, + const int* npe) const +{ + // Compute parameter values of the result sampling points + DoubleVec gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + if (!basis1 || !basis2) return false; + + // Evaluate the basis functions at all points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline2); + + const int p1 = basis1->order(0); + const int p2 = basis1->order(1); + const int p3 = basis1->order(2); + const int n1 = basis1->numCoefs(0); + const int n2 = basis1->numCoefs(1); + const int n3 = basis1->numCoefs(2); + + const int q1 = basis2->order(0); + const int q2 = basis2->order(1); + const int q3 = basis2->order(2); + const int m1 = basis2->numCoefs(0); + const int m2 = basis2->numCoefs(1); + const int m3 = basis2->numCoefs(2); + + size_t nc1 = nf1; + size_t nc2 = 0; + if (nc1*nb1 < locSol.size()) + nc2 = (locSol.size() - nc1*nb1)/nb2; + else + nc1 = locSol.size()/nb1; + + if (nc1*nb1 + nc2*nb2 != locSol.size()) + return false; + + Matrix Xtmp; + Vector Ytmp, Ztmp; + + // Evaluate the primary solution field at each point + size_t nPoints = spline1.size(); + sField.resize(nc1+nc2,nPoints); + for (size_t i = 0; i < nPoints; i++) + { + IntVec ip; + scatterInd(n1,n2,n3,p1,p2,p3,spline1[i].left_idx,ip); + + utl::gather(ip,nc1,locSol,Xtmp); + Xtmp.multiply(spline1[i].basisValues,Ytmp); + + if (nc2 > 0) + { + ip.clear(); + scatterInd(m1,m2,m3,q1,q2,q3,spline1[i].left_idx,ip); + + utl::gather(ip,nc2,locSol,Xtmp,nc1*nb1); + Xtmp.multiply(spline2[i].basisValues,Ztmp); + Ytmp.insert(Ytmp.end(),Ztmp.begin(),Ztmp.end()); + } + sField.fillColumn(1+i,Ytmp); + } + + return true; +} + + +bool ASMs3Dmx::evalSolution (Matrix& sField, const Integrand& integrand, + const int* npe) const +{ + sField.resize(0,0); + + // Compute parameter values of the result sampling points + DoubleVec gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + if (!basis1 || !basis2) return false; + + // Evaluate the basis functions and their derivatives at all points + std::vector spline1, spline2; + basis1->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline1); + basis2->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline2); + + const int p1 = basis1->order(0); + const int p2 = basis1->order(1); + const int p3 = basis1->order(2); + const int n1 = basis1->numCoefs(0); + const int n2 = basis1->numCoefs(1); + const int n3 = basis1->numCoefs(2); + + const int q1 = basis2->order(0); + const int q2 = basis2->order(1); + const int q3 = basis2->order(2); + const int m1 = basis2->numCoefs(0); + const int m2 = basis2->numCoefs(1); + const int m3 = basis2->numCoefs(2); + + // Fetch nodal (control point) coordinates + Matrix Xnod, Xtmp; + this->getNodalCoordinates(Xnod); + + Vector N1(p1*p2*p3), N2(q1*q2*q3), solPt; + Matrix dN1du, dN1dX, dN2du, dN2dX, Jac; + Vec3 X; + + // Evaluate the secondary solution field at each point + size_t nPoints = spline1.size(); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch indices of the non-zero basis functions at this point + IntVec ip1, ip2; + scatterInd(n1,n2,n3,p1,p2,p3,spline1[i].left_idx,ip1); + scatterInd(m1,m2,m3,q1,q2,q3,spline2[i].left_idx,ip2); + + // Fetch associated control point coordinates + utl::gather(geoUsesBasis1 ? ip1 : ip2, 3, Xnod, Xtmp); + + // Fetch basis function derivatives at current integration point + extractBasis(spline1[i],N1,dN1du); + extractBasis(spline2[i],N2,dN2du); + + // Compute Jacobian inverse of the coordinate mapping and + // basis function derivatives w.r.t. Cartesian coordinates + if (geoUsesBasis1) + if (utl::Jacobian(Jac,dN1dX,Xtmp,dN1du) == 0.0) // Jac = (Xtmp * dN1du)^-1 + continue; // skip singular points + else + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + else + if (utl::Jacobian(Jac,dN2dX,Xtmp,dN2du) == 0.0) // Jac = (Xtmp * dN2du)^-1 + continue; // skip singular points + else + dN1dX.multiply(dN1du,Jac); // dN1dX = dN1du * J^-1 + + // Cartesian coordinates of current integration point + X = Xtmp * (geoUsesBasis1 ? N1 : N2); + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N1,N2,dN1dX,dN2dX,X,ip1,ip2)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + sField.fillColumn(1+i,solPt); + } + + return true; +} diff --git a/src/ASM/ASMs3Dmx.h b/src/ASM/ASMs3Dmx.h new file mode 100644 index 00000000..1d454fd0 --- /dev/null +++ b/src/ASM/ASMs3Dmx.h @@ -0,0 +1,141 @@ +// $Id: ASMs3Dmx.h,v 1.2 2010-12-30 15:02:02 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3Dmx.h +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 3D spline mixed FE models. +//! +//============================================================================== + +#ifndef _ASM_S3D_MX_H +#define _ASM_S3D_MX_H + +#include "ASMs3D.h" +#include "ASMmxBase.h" + + +/*! + \brief Driver for assembly of structured 3D spline mixed FE models. + \details This class implements a two-field mixed formulation with splines as + basis functions. The first field is of one order higher than the second field, + and its basis is obtained by order-elevating the input spline object once. + By default, the geometry is represented by the second (lower order) basis, + however, by negating the \a n_f1 argument to the constructor, the first + basis is used instead. +*/ + +class ASMs3Dmx : public ASMs3D, private ASMmxBase +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs3Dmx(const char* fileName, bool checkRHS = false, + char n_f1 = 3, unsigned char n_f2 = 1); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs3Dmx(std::istream& is, bool checkRHS = false, + char n_f1 = 3, unsigned char n_f2 = 1); + //! \brief Default constructor creating an empty patch. + ASMs3Dmx(bool checkRHS = false, char n_f1 = 3, unsigned char n_f2 = 1); + //! \brief Empty destructor. + virtual ~ASMs3Dmx() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the node-to-IJ-index array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + virtual Vec3 getCoord(size_t inod) const; + + //! \brief Returns the number of solution fields. + virtual unsigned char getNoFields(int basis = 0) const; + //! \brief Returns the number of DOFs per node. + //! \param[in] inod 1-based node index local to current patch + virtual unsigned char getNodalDOFs(size_t inod) const; + + //! \brief Initializes the patch level MADOF array for mixed problems. + virtual void initMADOF(const int* sysMadof); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch edge. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index of the boundary edge + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int* npe) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + //! \param[in] npe Number of visualization nodes over each knot span + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int* npe) const; + + //! \brief Extracts nodal results for this patch from the global vector. + //! \param[in] globVec Global solution vector in DOF-order + //! \param[out] nodeVec Nodal result vector for this patch + virtual void extractNodeVec(const Vector& globVec, Vector& nodeVec, + unsigned char = 0) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + virtual bool getElementCoordinates(Matrix& X, int iel) const; + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[out] n3 Number of nodes in third (w) direction + //! \param[in] basis Which basis to return size parameters for + virtual bool getSize(int& n1, int& n2, int& n3, int basis = 0) const; + +private: + Go::SplineVolume* basis1; //!< Pointer to spline object for the first basis + Go::SplineVolume* basis2; //!< Pointer to spline object for the second basis +}; + +#endif diff --git a/src/ASM/ASMs3DmxLag.C b/src/ASM/ASMs3DmxLag.C new file mode 100644 index 00000000..08c9ae71 --- /dev/null +++ b/src/ASM/ASMs3DmxLag.C @@ -0,0 +1,485 @@ +// $Id: ASMs3DmxLag.C,v 1.2 2010-12-30 15:02:02 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3DmxLag.C +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 3D Lagrange mixed FE models. +//! +//============================================================================== + +#include "GoTools/trivariate/SplineVolume.h" + +#include "ASMs3DmxLag.h" +#include "Lagrange.h" +#include "TimeDomain.h" +#include "GlobalIntegral.h" +#include "IntegrandBase.h" +#include "CoordinateMapping.h" +#include "GaussQuadrature.h" +#include "Utilities.h" +#include "Vec3Oper.h" + + +ASMs3DmxLag::ASMs3DmxLag (const char* fileName, bool checkRHS, + unsigned char n_f1, unsigned char n_f2) + : ASMs3DLag(fileName,checkRHS), ASMmxBase(n_f1,n_f2) +{ + nx2 = ny2 = nz2 = 0; + nf = nf1 + nf2; +} + + +ASMs3DmxLag::ASMs3DmxLag (std::istream& is, bool checkRHS, + unsigned char n_f1, unsigned char n_f2) + : ASMs3DLag(is,checkRHS), ASMmxBase(n_f1,n_f2) +{ + nx2 = ny2 = nz2 = 0; + nf = nf1 + nf2; +} + + +ASMs3DmxLag::ASMs3DmxLag (bool checkRHS, + unsigned char n_f1, unsigned char n_f2) + : ASMs3DLag(checkRHS), ASMmxBase(n_f1,n_f2) +{ + nx2 = ny2 = nz2 = 0; + nf = nf1 + nf2; +} + + +void ASMs3DmxLag::clear () +{ + nx2 = ny2 = nz2 = 0; + ASMs3DLag::clear(); +} + + +unsigned char ASMs3DmxLag::getNoFields (int basis) const +{ + switch (basis) + { + case 1: return nf1; + case 2: return nf2; + } + + return nf; +} + + +unsigned char ASMs3DmxLag::getNodalDOFs (size_t inod) const +{ + return inod <= nb1 ? nf1 : nf2; +} + + +void ASMs3DmxLag::initMADOF (const int* sysMadof) +{ + this->init(MLGN,sysMadof); +} + + +void ASMs3DmxLag::extractNodeVec (const Vector& globRes, Vector& nodeVec, + unsigned char) const +{ + this->extrNodeVec(globRes,nodeVec); +} + + +bool ASMs3DmxLag::generateFEMTopology () +{ + // Generate/check FE data for the geometry/basis1 + bool haveFEdata = !MLGN.empty(); + bool basis1IsOK = this->ASMs3DLag::generateFEMTopology(); + if (haveFEdata || !basis1IsOK) return basis1IsOK; + + // Order of 2nd basis in the two parametric directions (order = degree + 1) + const int p1 = svol->order(0)-1; + const int p2 = svol->order(1)-1; + const int p3 = svol->order(2)-1; + if (p1 < 2 || p2 < 2 || p3 < 2) + { + std::cerr <<" *** ASMs3DmxLag::generateFEMTopology: Too low order "<< p1 + <<","<< p2 <<","<< p3 <<" for the second basis."<< std::endl; + return false; + } + + // Evaluate the parametric values + RealArray gpar[3]; + if (!this->getGridParameters(gpar[0],0,p1-1)) return false; + if (!this->getGridParameters(gpar[1],1,p2-1)) return false; + if (!this->getGridParameters(gpar[2],1,p3-1)) return false; + + // Number of nodes in each direction + nx2 = gpar[0].size(); + ny2 = gpar[1].size(); + nz2 = gpar[2].size(); + + // Add nodes for second basis (coordinates are not needed) + nb1 = MLGN.size(); + nb2 = nx2*ny2*nz2; + MLGN.reserve(nb1+nb2); + int i, j, k, a, b, c, iel; + for (k = 0; k < nz2; k++) + for (j = 0; j < ny2; j++) + for (i = 0; i < nx2; i++) + MLGN.push_back(++gNod); + + // Number of elements in each direction + const int nelx = (nx2-1)/(p1-1); + const int nely = (ny2-1)/(p2-1); + const int nelz = (nz2-1)/(p3-1); + + // Add connectivety for second basis: local --> global node relation + for (k = iel = 0; k < nelz; k++) + for (j = 0; j < nely; j++) + for (i = 0; i < nelx; i++, iel++) + { + size_t nen1 = MNPC[iel].size(); + MNPC[iel].resize(nen1+p1*p2*p3); + + // First node in current element + int corner = nb1 + (p3-1)*nx2*ny2*k + (p2-1)*nx2*j + (p1-1)*i; + + for (c = 0; c < p3; c++) + { + int cornod = nen1 + p1*p2*c; + MNPC[iel][cornod] = corner + nx2*ny2*c; + for (b = 1; b < p2; b++) + { + int facenod = cornod + p1*b; + MNPC[iel][facenod] = MNPC[iel][cornod] + nx2*b; + for (a = 1; a < p1; a++) + { + MNPC[iel][facenod+a] = MNPC[iel][facenod] + a; + MNPC[iel][cornod+a] = MNPC[iel][cornod] + a; + } + } + } + } + + return true; +} + + +bool ASMs3DmxLag::getSize (int& n1, int& n2, int& n3, int basis) const +{ + if (basis != 2) + return this->ASMs3DLag::getSize(n1,n2,n3,1); + + n1 = nx2; + n2 = ny2; + n3 = nz2; + + return true; +} + + +bool ASMs3DmxLag::integrate (Integrand& integrand, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Order of basis in the two parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + // The second basis is assumed one order lower in each direction + const int q1 = p1 - 1; + const int q2 = p2 - 1; + const int q3 = p3 - 1; + + Vector N1(p1*p2*p3), N2(q1*q2*q3); + Matrix dN1du, dN2du, dN1dX, dN2dX, Xnod, Jac; + Vec4 X; + + + // === Assembly loop over all elements in the patch ========================== + + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElement(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + for (int k = 0; k < nGauss; k++) + for (int j = 0; j < nGauss; j++) + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = wg[i]*wg[j]*wg[k]; + + // Compute basis function derivatives at current integration point + // using tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N1,dN1du,p1,xg[i],p2,xg[j],p3,xg[k])) + return false; + if (!Lagrange::computeBasis(N2,dN2du,q1,xg[i],q2,xg[j],q3,xg[k])) + return false; + + // Compute Jacobian inverse of coordinate mapping and derivatives + double dA = utl::Jacobian(Jac,dN1dX,Xnod,dN1du); + if (dA == 0.0) continue; // skip singular points + + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + + // Cartesian coordinates of current integration point + X = Xnod * N1; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalInt(elmInt,time,dA*weight,N1,N2,dN1dX,dN2dX,X)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DmxLag::integrate (Integrand& integrand, int lIndex, + GlobalIntegral& glInt, + const TimeDomain& time, + const LintegralVec& locInt) +{ + if (!svol) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Find the parametric direction of the face normal {-3,-2,-1, 1, 2, 3} + const int faceDir = (lIndex+1)/(lIndex%2 ? -2 : 2); + + const int t0 = abs(faceDir); // unsigned normal direction of the face + const int t1 = 1 + t0%3; // first tangent direction of the face + const int t2 = 1 + t1%3; // second tangent direction of the face + + // Order of basis in the three parametric directions (order = degree + 1) + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + // The second basis is assumed one order lower in each direction + const int q1 = p1 - 1; + const int q2 = p2 - 1; + const int q3 = p3 - 1; + + // Number of elements in each direction + const int nelx = (nx2-1)/(q1-1); + const int nely = (ny2-1)/(q2-1); + const int nelz = (nz2-1)/(q3-1); + + Vector N1(p1*p2*p3), N2(q1*q2*q3); + Matrix dN1du, dN2du, dN1dX, dN2dX, Xnod, Jac; + Vec4 X; + Vec3 normal; + double xi[3]; + + + // === Assembly loop over all elements on the patch face ===================== + + int iel = 1; + for (int i3 = 0; i3 < nelz; i3++) + for (int i2 = 0; i2 < nely; i2++) + for (int i1 = 0; i1 < nelx; i1++, iel++) + { + // Skip elements that are not on current boundary face + bool skipMe = false; + switch (faceDir) + { + case -1: if (i1 > 0) skipMe = true; break; + case 1: if (i1 < nelx-1) skipMe = true; break; + case -2: if (i2 > 0) skipMe = true; break; + case 2: if (i2 < nely-1) skipMe = true; break; + case -3: if (i3 > 0) skipMe = true; break; + case 3: if (i3 < nelz-1) skipMe = true; break; + } + if (skipMe) continue; + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + // Initialize element quantities + IntVec::iterator f2start = MNPC[iel-1].begin() + N1.size(); + if (!integrand.initElementBou(IntVec(MNPC[iel-1].begin(),f2start), + IntVec(f2start,MNPC[iel-1].end()),nb1)) + return false; + + // Caution: Unless locInt is empty, we assume it points to an array of + // LocalIntegral pointers, of length at least the number of elements in + // the model (as defined by the highest number in the MLGE array). + // If the array is shorter than this, expect a segmentation fault. + LocalIntegral* elmInt = locInt.empty() ? 0 : locInt[MLGE[iel-1]-1]; + + + // --- Integration loop over all Gauss points in each direction -------- + + for (int j = 0; j < nGauss; j++) + for (int i = 0; i < nGauss; i++) + { + // Weight of current integration point + double weight = wg[i]*wg[j]; + + // Gauss point coordinates on the face + xi[t0-1] = faceDir < 0 ? -1.0 : 1.0; + xi[t1-1] = xg[i]; + xi[t2-1] = xg[j]; + + // Compute the basis functions and their derivatives, using + // tensor product of one-dimensional Lagrange polynomials + if (!Lagrange::computeBasis(N1,dN1du,p1,xi[0],p2,xi[1],p3,xi[2])) + return false; + if (!Lagrange::computeBasis(N2,dN2du,q1,xi[0],q2,xi[1],q3,xi[2])) + return false; + + // Compute basis function derivatives and the edge normal + double dS = utl::Jacobian(Jac,normal,dN1dX,Xnod,dN1du,t1,t2); + if (dS == 0.0) continue; // skip singular points + + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + + if (faceDir < 0) normal *= -1.0; + + // Cartesian coordinates of current integration point + X = Xnod * N1; + X.t = time.t; + + // Evaluate the integrand and accumulate element contributions + if (!integrand.evalBou(elmInt,time,dS*weight, + N1,N2,dN1dX,dN2dX,X,normal)) + return false; + } + + // Assembly of global system integral + if (!glInt.assemble(elmInt,MLGE[iel-1])) + return false; + } + + return true; +} + + +bool ASMs3DmxLag::evalSolution (Matrix& sField, const Vector& locSol, + const int*) const +{ + size_t nc1 = nf1; + size_t nc2 = 0; + if (nc1*nb1 < locSol.size()) + nc2 = (locSol.size() - nc1*nb1)/nb2; + else + nc1 = locSol.size()/nb1; + + if (nc1*nb1 + nc2*nb2 != locSol.size()) + return false; + + // TODO: Add evaluation second field at the nodes of the first field + size_t nPoints = nb1; + size_t nComp = nc1; + size_t i, n, ip = 0; + sField.resize(nComp,nPoints); + for (n = 1; n <= nPoints; n++) + for (i = 1; i <= nComp; i++) + sField(i,n) = locSol(++ip); + + return true; +} + + +bool ASMs3DmxLag::evalSolution (Matrix& sField, const Integrand& integrand, + const int*) const +{ + sField.resize(0,0); + + if (!svol) return false; + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + const int q1 = p1 - 1; + const int q2 = p2 - 1; + const int q3 = p3 - 1; + double incx = 2.0/double(q1); + double incy = 2.0/double(q2); + double incz = 2.0/double(q3); + + size_t nPoints = nb1; + IntVec check(nPoints,0); + + Vector N1(p1*p2*p3), N2(q1*q2*q3), solPt; + std::vector globSolPt(nPoints); + Matrix dN1du, dN2du, dN1dX, dN2dX, Xnod, Jac; + + // Evaluate the secondary solution field at each point + for (int iel = 1; iel <= this->getNoElms(); iel++) + { + IntVec::const_iterator f2start = MNPC[iel-1].begin() + p1*p2*p3; + IntVec mnpc1(MNPC[iel-1].begin(),f2start); + IntVec mnpc2(f2start,MNPC[iel-1].end()); + + this->getElementCoordinates(Xnod,iel); + + int i, j, k, loc = 0; + for (k = 0; k < p3; k++) + for (j = 0; j < p2; j++) + for (i = 0; i < p1; i++, loc++) + { + double xi = -1.0 + i*incx; + double eta = -1.0 + j*incy; + double zeta = -1.0 + k*incz; + if (!Lagrange::computeBasis(N1,dN1du,p1,xi,p2,eta,p3,zeta)) + return false; + if (!Lagrange::computeBasis(N2,dN2du,q1,xi,q2,eta,q3,zeta)) + return false; + + // Compute the Jacobian inverse + if (utl::Jacobian(Jac,dN1dX,Xnod,dN1du) == 0.0) // Jac=(Xnod*dN1du)^-1 + continue; // skip singular points + else + dN2dX.multiply(dN2du,Jac); // dN2dX = dN2du * J^-1 + + // Now evaluate the solution field + if (!integrand.evalSol(solPt,N1,N2,dN1dX,dN2dX,Xnod*N1,mnpc1,mnpc2)) + return false; + else if (sField.empty()) + sField.resize(solPt.size(),nPoints,true); + + if (++check[mnpc1[loc]] == 1) + globSolPt[mnpc1[loc]] = solPt; + else + globSolPt[mnpc1[loc]] += solPt; + } + } + + for (size_t i = 0; i < nPoints; i++) + sField.fillColumn(1+i,globSolPt[i]/=check[i]); + + return true; +} diff --git a/src/ASM/ASMs3DmxLag.h b/src/ASM/ASMs3DmxLag.h new file mode 100644 index 00000000..11bd4864 --- /dev/null +++ b/src/ASM/ASMs3DmxLag.h @@ -0,0 +1,128 @@ +// $Id: ASMs3DmxLag.h,v 1.1 2010-12-29 18:41:39 kmo Exp $ +//============================================================================== +//! +//! \file ASMs3DmxLag.h +//! +//! \date Dec 28 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Driver for assembly of structured 3D Lagrange mixed FE models. +//! +//============================================================================== + +#ifndef _ASM_S3D_MX_LAG_H +#define _ASM_S3D_MX_LAG_H + +#include "ASMs3DLag.h" +#include "ASMmxBase.h" + + +/*! + \brief Driver for assembly of structured 3D Lagrange mixed FE models. + \details This class implements a two-field mixed formulation with Lagrangian + basis functions. The geometry and the first field are of equal order and + is one order higher than the second field. +*/ + +class ASMs3DmxLag : public ASMs3DLag, private ASMmxBase +{ +public: + //! \brief Constructor creating an instance by reading the given file. + ASMs3DmxLag(const char* fileName, bool checkRHS = false, + unsigned char n_f1 = 3, unsigned char n_f2 = 1); + //! \brief Constructor creating an instance by reading the given input stream. + ASMs3DmxLag(std::istream& is, bool checkRHS = false, + unsigned char n_f1 = 3, unsigned char n_f2 = 1); + //! \brief Default constructor creating an empty patch. + ASMs3DmxLag(bool checkRHS = false, + unsigned char n_f1 = 3, unsigned char n_f2 = 1); + //! \brief Empty destructor. + virtual ~ASMs3DmxLag() {} + + + // Methods for model generation + // ============================ + + //! \brief Generates the finite element topology data for the patch. + //! \details The data generated are the element-to-node connectivity array, + //! the nodal coordinate array, as well as global node and element numbers. + virtual bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + virtual void clear(); + + //! \brief Returns the number of solution fields. + virtual unsigned char getNoFields(int basis = 0) const; + //! \brief Returns the number of DOFs per node. + //! \param[in] inod 1-based node index local to current patch + virtual unsigned char getNodalDOFs(size_t inod) const; + + //! \brief Initializes the patch level MADOF array for mixed problems. + virtual void initMADOF(const int* sysMadof); + + + // Methods for integration of finite element quantities. + // These are the main computational methods of the ASM class hierarchy. + // ==================================================================== + + //! \brief Evaluates an integral over the interior patch domain. + //! \param integrand Object with problem-specific data and methods + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + //! \brief Evaluates a boundary integral over a patch face. + //! \param integrand Object with problem-specific data and methods + //! \param[in] lIndex Local index [1,6] of the boundary face + //! \param glbInt The integrated quantity + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param locInt Vector of element-wise contributions to \a glbInt + virtual bool integrate(Integrand& integrand, int lIndex, + GlobalIntegral& glbInt, const TimeDomain& time, + const LintegralVec& locInt = LintegralVec()); + + + // Post-processing methods + // ======================= + + //! \brief Evaluates the primary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] locSol Solution vector in DOF-order + virtual bool evalSolution(Matrix& sField, const Vector& locSol, + const int*) const; + + //! \brief Evaluates the secondary solution field at all visualization points. + //! \param[out] sField Solution field + //! \param[in] integrand Object with problem-specific data and methods + virtual bool evalSolution(Matrix& sField, const Integrand& integrand, + const int*) const; + + //! \brief Extracts nodal results for this patch from the global vector. + //! \param[in] globVec Global solution vector in DOF-order + //! \param[out] nodeVec Nodal result vector for this patch + virtual void extractNodeVec(const Vector& globVec, Vector& nodeVec, + unsigned char = 0) const; + +protected: + + // Internal utility methods + // ======================== + + //! \brief Returns the number of nodal points in each parameter direction. + //! \param[out] n1 Number of nodes in first (u) direction + //! \param[out] n2 Number of nodes in second (v) direction + //! \param[out] n3 Number of nodes in third (w) direction + //! \param[in] basis Which basis to return size parameters for + virtual bool getSize(int& n1, int& n2, int& n3, int basis = 0) const; + +private: + size_t nx2; //!< Number of nodes in 1st parameter direction for second basis + size_t ny2; //!< Number of nodes in 2nd parameter direction for second basis + size_t nz2; //!< Number of nodes in 3rd parameter direction for second basis +}; + +#endif diff --git a/src/ASM/ASMstruct.C b/src/ASM/ASMstruct.C new file mode 100644 index 00000000..c3728fe8 --- /dev/null +++ b/src/ASM/ASMstruct.C @@ -0,0 +1,32 @@ +// $Id: ASMstruct.C,v 1.4 2010-03-24 12:27:07 rho Exp $ +//============================================================================== +//! +//! \file ASMstruct.C +//! +//! \date Sep 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for structured spline-based FE assembly drivers. +//! +//============================================================================== + +#include "ASMstruct.h" +#include "GoTools/geometry/GeomObject.h" + + +int ASMstruct::gEl = 0; +int ASMstruct::gNod = 0; + + +ASMstruct::ASMstruct (unsigned char n_p, unsigned char n_s, unsigned char n_f) + : ASMbase(n_p,n_s,n_f) +{ + geo = 0; +} + + +ASMstruct::~ASMstruct () +{ + if (geo) delete geo; +} diff --git a/src/ASM/ASMstruct.h b/src/ASM/ASMstruct.h new file mode 100644 index 00000000..34a850c3 --- /dev/null +++ b/src/ASM/ASMstruct.h @@ -0,0 +1,59 @@ +// $Id: ASMstruct.h,v 1.8 2010-12-29 18:02:10 kmo Exp $ +//============================================================================== +//! +//! \file ASMstruct.h +//! +//! \date Sep 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for structured spline-based FE assembly drivers. +//! +//============================================================================== + +#ifndef _ASM_STRUCT_H +#define _ASM_STRUCT_H + +#include "ASMbase.h" + +namespace Go { + class GeomObject; +} + +/*! + \brief Base class for structured spline-based FE assembly drivers. + \details This class contains methods common for structured spline patches. +*/ + +class ASMstruct : public ASMbase +{ +protected: + //! \brief The constructor sets the number of space dimensions and fields. + //! \param[in] n_p Number of parameter dimensions + //! \param[in] n_s Number of spatial dimensions + //! \param[in] n_f Number of primary solution fields + ASMstruct(unsigned char n_p, unsigned char n_s, unsigned char n_f); + +public: + //! \brief The destructor frees the dynamically allocated spline object. + virtual ~ASMstruct(); + + //! \brief Checks if the patch is empty. + virtual bool empty() const { return geo == 0; } + + //! \brief Defines the numerical integration scheme to use. + //! \param[in] ng Number of Gauss points in each parameter direction + void setGauss(int ng) { nGauss = ng; } + + //! \brief Resets the global element and node counters. + static void resetNumbering() { gEl = gNod = 0; } + +protected: + Go::GeomObject* geo; //!< Pointer to the actual spline geometry object + + int nGauss; //!< Numerical integration scheme + static int gEl; //!< Global element counter + static int gNod; //!< Global node counter +}; + +#endif diff --git a/src/ASM/AlgEqSystem.C b/src/ASM/AlgEqSystem.C new file mode 100644 index 00000000..e41699f3 --- /dev/null +++ b/src/ASM/AlgEqSystem.C @@ -0,0 +1,165 @@ +// $Id: AlgEqSystem.C,v 1.13 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file AlgEqSystem.C +//! +//! \date Nov 11 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Storage of an algebraic equation system for a FEM problem. +//! +//============================================================================== + +#include "AlgEqSystem.h" +#include "ElmMats.h" +#include "SAM.h" +#include "LinSolParams.h" + + +void AlgEqSystem::init (SystemMatrix::Type mtype, const LinSolParams* spar, + size_t nmat, size_t nvec, int num_threads_SLU) +{ + size_t i; + for (i = nmat; i < A.size(); i++) + if (A[i]._A) delete A[i]._A; + + for (i = nvec; i < b.size(); i++) + if (b[i]) delete b[i]; + + A.resize(nmat); + b.resize(nvec,0); + R.clear(); + + for (i = 0; i < A.size(); i++) + { + if (!A[i]._A) + if (spar) + A[i]._A = SystemMatrix::create(mtype,*spar); + else + A[i]._A = SystemMatrix::create(mtype,num_threads_SLU); + A[i]._b = 0; + } + + for (i = 0; i < b.size(); i++) + if (!b[i]) + if (mtype == SystemMatrix::PETSC) + b[i] = SystemVector::create(SystemVector::PETSC); + else + b[i] = SystemVector::create(SystemVector::STD); +} + + +void AlgEqSystem::init (bool initLHS) +{ + size_t i; + + if (initLHS) + for (i = 0; i < A.size(); i++) + if (A[i]._A) A[i]._A->init(); + + for (i = 0; i < b.size(); i++) + if (b[i]) b[i]->init(); + + R.fill(0.0); +} + + +void AlgEqSystem::clear () +{ + size_t i; + + for (i = 0; i < A.size(); i++) + if (A[i]._A) delete A[i]._A; + + for (i = 0; i < b.size(); i++) + if (b[i]) delete b[i]; + + A.clear(); + b.clear(); + R.clear(); +} + + +bool AlgEqSystem::setAssociatedVector (size_t imat, size_t ivec) +{ + if (imat < A.size() && ivec < b.size()) + A[imat]._b = b[ivec]; + else + return false; + + return true; +} + + +void AlgEqSystem::initAssembly () +{ + size_t i; + + for (i = 0; i < A.size(); i++) + if (A[i]._A) A[i]._A->initAssembly(sam); + + if (A.size() == 1 && b.size() == 1) + sam.initForAssembly(*b.front(),&R); + else for (i = 0; i < b.size(); i++) + if (b[i]) + b[i]->redim(sam.getNoEquations()); +} + + +bool AlgEqSystem::assemble (const LocalIntegral* elmObj, int elmId) +{ + const ElmMats* elMat = dynamic_cast(elmObj); + if (!elMat) return false; + + bool status = true; + if (A.size() == 1 && b.size() == 1) + { + // The algebraic system consists of one system matrix and one RHS-vector. + // Extract the element-level Newton matrix and associated RHS-vector for + // general time-dependent and/or nonlinear problems. + status = sam.assembleSystem(*b.front(), elMat->getRHSVector(), elmId, &R); + if (status && elMat->withLHS) // we have LHS element matrices + if (elMat->rhsOnly) // we only want the RHS system vector + status = sam.assembleSystem(*b.front(), + elMat->getNewtonMatrix(), elmId, &R); + else // we want both the LHS system matrix and the RHS system vector + status = sam.assembleSystem(*A.front()._A, *b.front(), + elMat->getNewtonMatrix(), elmId, &R); + } + else + { + size_t i; +#if SP_DEBUG > 2 + if (elMat->withLHS && !elMat->rhsOnly) + for (i = 0; i < elMat->A.size() && i < A.size(); i++) + std::cout <<"Coefficient matrix A"<< i <<" for element " + << elmId << elMat->A[i] << std::endl; + for (i = 0; i < elMat->b.size() && i < b.size(); i++) + std::cout <<"Right-hand-side vector b"<< i <<" for element " + << elmId << elMat->b[i] << std::endl; +#endif + + // Assembly of system right-hand-side vectors + for (i = 0; i < b.size() && i < elMat->b.size() && status; i++) + status = sam.assembleSystem(*b[i], elMat->b[i], elmId); + + // Assembly of system coefficient matrices, possibly with right-hand-side + // contributions too, due to multi-point constraints + if (elMat->withLHS) + for (i = 0; i < A.size() && i < elMat->A.size() && status; i++) + if (A[i]._b) + if (elMat->rhsOnly) // we only want the RHS system vectors + status = sam.assembleSystem(*A[i]._b, elMat->A[i], elmId); + else // we want both LHS system matrices and RHS system vectors + status = sam.assembleSystem(*A[i]._A, *A[i]._b, elMat->A[i], elmId); + else if (!elMat->rhsOnly) // we want LHS system matrices only + status = sam.assembleSystem(*A[i]._A, elMat->A[i], elmId); + } + + if (!status) + std::cerr <<" *** AlgEqSystem::assemble: Failure for element "<< elmId + <<": size(A)="<< A.size() <<","<< elMat->A.size() + <<" size(b)="<< b.size() <<","<< elMat->b.size() << std::endl; + return status; +} diff --git a/src/ASM/AlgEqSystem.h b/src/ASM/AlgEqSystem.h new file mode 100644 index 00000000..7658db27 --- /dev/null +++ b/src/ASM/AlgEqSystem.h @@ -0,0 +1,98 @@ +// $Id: AlgEqSystem.h,v 1.8 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file AlgEqSystem.h +//! +//! \date Nov 11 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Storage of an algebraic equation system for a FEM problem. +//! +//============================================================================== + +#ifndef _ALG_EQ_SYSTEM_H +#define _ALG_EQ_SYSTEM_H + +#include "GlobalIntegral.h" +#include "SystemMatrix.h" + +class SAM; +class LinSolParams; +class LocalIntegral; + + +/*! + \brief Class for storage of general algebraic system of equations. +*/ + +class AlgEqSystem : public GlobalIntegral +{ +public: + //! \brief The constructor only sets its reference to the SAM object. + AlgEqSystem(const SAM& _sam) : sam(_sam) {} + + //! \brief The destructor frees the dynamically allocated objects. + virtual ~AlgEqSystem() { this->clear(); } + + //! \brief Allocates the system matrices of the specified format. + //! \param[in] mtype The matrix format to use for all matrices + //! \param[in] spar Input parameters for the linear equation solver + //! \param[in] nmat Number of system matrices to allocate + //! \param[in] nvec Number of system vectors to allocate + //! \param[in] num_threads_SLU Number of threads for SuperLU_MT + void init(SystemMatrix::Type mtype, const LinSolParams* spar, + size_t nmat, size_t nvec, int num_thread_SLU = 1); + + //! \brief Initializes the system matrices to zero. + //! \param[in] initLHS If \e false, only initialize the right-hand-side vector + void init(bool initLHS = true); + + //! \brief Erases the system matrices and frees dynamically allocated storage. + void clear(); + + //! \brief Associates a system vector to a system matrix. + //! \param[in] imat Index of a coefficient matrix + //! \param[in] ivec Index of the system vector to associate with the matrix + //! + //! \details The purpose of this method is to define which right-hand-side + //! vector (if any) should receive contributions when assembling a + //! coefficient matrix, when the system to be assembled has explicit + //! constraint equations for which the dependent DOFs have been eliminated. + bool setAssociatedVector(size_t imat, size_t ivec); + + //! Initializes the matrices to proper size for element assembly. + void initAssembly(); + + //! \brief Adds a set of element matrices into the algebraic equation system. + //! \param[in] elmObj Pointer to the element matrices to add into \a *this + //! \param[in] elmId Global number of the element associated with \a *elmObj + virtual bool assemble(const LocalIntegral* elmObj, int elmId); + + //! \brief Returns the \a i'th matrix of the equation system. + SystemMatrix* getMatrix(size_t i = 0) { return i < A.size() ? A[i]._A : 0; } + + //! \brief Returns the \a i'th right-hand-side vector of the equation system. + SystemVector* getVector(size_t i = 0) { return i < b.size() ? b[i] : 0; } + + //! \brief Returns a pointer to the nodal reaction forces, if any. + const Vector* getReactions() const { return R.empty() ? 0 : &R; } + +private: + //! \brief Struct defining a coefficient matrix and an associated RHS-vector. + struct SysMatrixPair + { + SystemMatrix* _A; //!< The coefficient matrix + SystemVector* _b; //!< Pointer to the associated right-hand-side vector + //! \brief Constructor initializing the pointers to zero. + SysMatrixPair() { _A = 0; _b = 0; } + }; + + std::vector A; //!< The actual coefficient matrices + std::vector b; //!< The actual right-hand-side vectors + Vector R; //!< Nodal reaction forces + + const SAM& sam; //!< Data for FE assembly management +}; + +#endif diff --git a/src/ASM/ElmMats.h b/src/ASM/ElmMats.h new file mode 100644 index 00000000..b5dac6da --- /dev/null +++ b/src/ASM/ElmMats.h @@ -0,0 +1,80 @@ +// $Id: ElmMats.h,v 1.4 2011-01-07 18:07:06 kmo Exp $ +//============================================================================== +//! +//! \file ElmMats.h +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of the element matrices for a FEM problem. +//! +//============================================================================== + +#ifndef _ELM_MATS_H +#define _ELM_MATS_H + +#include "LocalIntegral.h" +#include "MatVec.h" + + +/*! + \brief Class collecting the element matrices associated with a FEM problem. + \details The class is derived from LocalIntegral such that it may be passed + as argument to the Integrand::evalInt and Integrand::evalBou methods. + + The class has two virtual methods returning the Newton matrix and the + associated right-hand-side vector, which can be reimplemented by sub-classes + to implement different time-integration schemes, mixed formulations, etc. + The default versions use the first element matrix as the Newton matrix, and + the first element vector as the associated right-hand-side vector. +*/ + +class ElmMats : public LocalIntegral +{ +public: + //! \brief Default constructor. + ElmMats() { rhsOnly = withLHS = false; } + //! \brief Empty destructor. + virtual ~ElmMats() {} + + //! \brief Defines the number of element matrices and vectors. + //! \param[in] nA Number of element matrices + //! \param[in] nB Number of element vectors + void resize(size_t nA, size_t nB) { A.resize(nA); b.resize(nB); } + + //! \brief Returns the element-level Newton matrix. + virtual const Matrix& getNewtonMatrix() const + { + if (A.empty()) + std::cerr <<" *** ElMats::getNewtonMatrix: No element matrices!" + << std::endl; +#if SP_DEBUG > 2 + else + std::cout <<"Element coefficient matrix"<< A.front() << std::endl; +#endif + return A.front(); + } + + //! \brief Returns the element-level right-hand-side vector + //! associated with the Newton matrix. + virtual const Vector& getRHSVector() const + { + if (b.empty()) + std::cerr <<" *** ElMats::getNewtonMatrix: No element vectors!" + << std::endl; +#if SP_DEBUG > 2 + else + std::cout <<"Element right-hand-side vector"<< b.front() << std::endl; +#endif + return b.front(); + } + + std::vector A; //!< The element coefficient matrices + std::vector b; //!< The element right-hand-side vectors + + bool rhsOnly; //!< If \e true, only the right-hand-sides are assembled + bool withLHS; //!< If \e true, left-hand-side element matrices are present +}; + +#endif diff --git a/src/ASM/ElmNorm.h b/src/ASM/ElmNorm.h new file mode 100644 index 00000000..1f3748bc --- /dev/null +++ b/src/ASM/ElmNorm.h @@ -0,0 +1,44 @@ +// $Id: ElmNorm.h,v 1.2 2010-01-07 16:20:09 kmo Exp $ +//============================================================================== +//! +//! \file ElmNorm.h +//! +//! \date Dec 09 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of integrated norm quantities over an element. +//! +//============================================================================== + +#ifndef _ELM_NORM_H +#define _ELM_NORM_H + +#include "LocalIntegral.h" + + +/*! + \brief Class representing integrated norm quantities over an element. + \details The class is essentially just a double array, but is derived from + LocalIntegral such that it may be passed as argument to the + Integrand::evalInt and Integrand::evalBou methods. +*/ + +class ElmNorm : public LocalIntegral +{ +public: + //! \brief The constructor assigns the internal pointer. + ElmNorm(double* p) : ptr(p) {} + //! \brief Empty destructor. + virtual ~ElmNorm() {} + + //! \brief Indexing operator for assignment. + double& operator[](size_t i) { return ptr[i]; } + //! \brief Indexing operator for referencing. + const double& operator[](size_t i) const { return ptr[i]; } + +private: + double* ptr; //!< Pointer to the actual norm values +}; + +#endif diff --git a/src/ASM/GlbNorm.C b/src/ASM/GlbNorm.C new file mode 100644 index 00000000..e1d84d61 --- /dev/null +++ b/src/ASM/GlbNorm.C @@ -0,0 +1,63 @@ +// $Id: GlbNorm.C,v 1.2 2010-01-16 16:02:54 kmo Exp $ +//============================================================================== +//! +//! \file GlbNorm.C +//! +//! \date Dec 09 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of integrated norm quantities over an element. +//! +//============================================================================== + +#include "GlbNorm.h" +#include "ElmNorm.h" +#include + + +GlbNorm::GlbNorm (std::vector& vec, FinalOp op) : myVals(vec) +{ + myOp = op; +} + + +GlbNorm::~GlbNorm () +{ + for (size_t i = 0; i < myVals.size(); i++) + this->applyFinalOp(myVals[i]); +} + + +bool GlbNorm::assemble (const LocalIntegral* elmObj, int elmId) +{ + const ElmNorm* ptr = dynamic_cast(elmObj); + if (!ptr) return false; + + ElmNorm& elVals = *const_cast(ptr); + for (size_t i = 0; i < myVals.size(); i++) + { + myVals[i] += elVals[i]; + this->applyFinalOp(elVals[i]); + } + + return true; +} + + +void GlbNorm::applyFinalOp (double& value) const +{ + switch (myOp) + { + case ABS: + value = fabs(value); + break; + + case SQRT: + if (value < 0.0) + value = -sqrt(-value); + else + value = sqrt(value); + break; + } +} diff --git a/src/ASM/GlbNorm.h b/src/ASM/GlbNorm.h new file mode 100644 index 00000000..8134b650 --- /dev/null +++ b/src/ASM/GlbNorm.h @@ -0,0 +1,53 @@ +// $Id: GlbNorm.h,v 1.4 2010-01-21 14:55:42 kmo Exp $ +//============================================================================== +//! +//! \file GlbNorm.h +//! +//! \date Dec 09 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of integrated global norm quantities. +//! +//============================================================================== + +#ifndef _GLB_NORM_H +#define _GLB_NORM_H + +#include "GlobalIntegral.h" +#include + + +/*! + \brief Class representing integrated global norms. + \details The class is essentially a vector of doubles, but is derived from + GlobalIntegral such that it may be passed as argument to ASMbase::integrate. +*/ + +class GlbNorm : public GlobalIntegral +{ +public: + //! \brief Operations to applied after summing element contributions. + enum FinalOp { NONE, ABS, SQRT }; + + //! \brief The constructor initializes a reference to the global norm vector. + //! \param[in] vec Vector of global norm quantities + //! \param[in] op Operation to be performed after accumulating element norms + GlbNorm(std::vector& vec, FinalOp op = NONE); + //! \brief The destructor applies the operation \a myOp on \a myVals + virtual ~GlbNorm(); + + //! \brief Adds element norm quantities into the global norm object. + //! \param[in] elmObj Pointer to the element norms to add into \a *this + //! \param[in] elmId Global number of the element associated with \a *elmObj + virtual bool assemble(const LocalIntegral* elmObj, int elmId); + +private: + //! \brief Applies the operation \a myOp on the given \a value. + void applyFinalOp(double& value) const; + + std::vector& myVals; //!< Reference to a vector of global norm values + FinalOp myOp; //!< Operation to be perfomed on summed values +}; + +#endif diff --git a/src/ASM/GlobalIntegral.h b/src/ASM/GlobalIntegral.h new file mode 100644 index 00000000..9f542c44 --- /dev/null +++ b/src/ASM/GlobalIntegral.h @@ -0,0 +1,40 @@ +// $Id: GlobalIntegral.h,v 1.1 2009-12-03 17:45:34 kmo Exp $ +//============================================================================== +//! +//! \file GlobalIntegral.h +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Abstract interface for classes representing integrated quantities. +//! +//============================================================================== + +#ifndef _GLOBAL_INTEGRAL_H +#define _GLOBAL_INTEGRAL_H + +class LocalIntegral; + + +/*! + \brief Abstract base class representing a system level integrated quantity. +*/ + +class GlobalIntegral +{ +protected: + //! \brief The default constructor is protected to allow sub-classes only. + GlobalIntegral() {} + +public: + //! \brief Empty destructor. + virtual ~GlobalIntegral() {} + + //! \brief Adds a LocalIntegral object into a corresponding global object. + //! \param[in] elmObj The local integral object to add into \a *this. + //! \param[in] elmId Global number of the element associated with elmObj + virtual bool assemble(const LocalIntegral* elmObj, int elmId) = 0; +}; + +#endif diff --git a/src/ASM/IntegrandBase.h b/src/ASM/IntegrandBase.h new file mode 100644 index 00000000..e03aacc9 --- /dev/null +++ b/src/ASM/IntegrandBase.h @@ -0,0 +1,434 @@ +// $Id: IntegrandBase.h,v 1.20 2011-02-08 12:10:25 rho Exp $ +//============================================================================== +//! +//! \file IntegrandBase.h +//! +//! \date Nov 11 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Abstract interface for classes representing FEM integrands. +//! +//============================================================================== + +#ifndef _INTEGRAND_BASE_H +#define _INTEGRAND_BASE_H + +#include "SIMenums.h" +#include "Function.h" +#include "MatVec.h" + +struct TimeDomain; +class LocalIntegral; +class NormBase; +class AnaSol; +class Vec3; +class VTF; + + +/*! + \brief Abstract base class representing a system level integrated quantity. + \details This class defines the interface between the finite element + assembly drivers of the ASM-hierarchy and the problem-dependent classes + containing all physical properties for the problem to be solved. + + The interface has several overloaded versions of the \a evalInt method, for + evaluation of the coefficient matrix contributions at an element-interior + integration point. Only one of these methods needs to be implemented, and + which one to use is governed by the \a getIntegrandType method. + There is also one method intended for mixed field interpolation problems. +*/ + +class Integrand +{ +protected: + //! \brief The default constructor is protected to allow sub-classes only. + Integrand() {} + +public: + //! \brief Empty destructor. + virtual ~Integrand() {} + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream&) const {} + + //! \brief Defines the solution mode before the element assembly is started. + virtual void setMode(SIM::SolutionMode) {} + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + //! \param[in] X0 Cartesian coordinates of the element center + //! \param[in] nPt Number of integration points in this element + //! + //! \details This method is invoked once before starting the numerical + //! integration loop over the Gaussian quadrature points over an element. + //! It is supposed to perform all the necessary internal initializations + //! needed before the numerical integration is started for current element. + //! The default implementation forwards to an overloaded method not taking + //! \a X0 and \a nPt as arguments. + //! Reimplement this method for problems requiring the element center and/or + //! the number of integration points during/before the integrand evaluations. + virtual bool initElement(const std::vector& MNPC, + const Vec3& X0, size_t nPt) + { + return this->initElement(MNPC); + } + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + //! + //! \details Reimplement this method for problems not requiring the + //! the element center nor the number of integration points before the + //! integration loop is started. + virtual bool initElement(const std::vector& MNPC) + { + std::cerr <<" *** Integrand::initElement not implemented."<< std::endl; + return false; + } + //! \brief Initializes current element for numerical integration (mixed). + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + //! \param[in] n1 Number of nodes in basis 1 on this patch + virtual bool initElement(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1) + { + std::cerr <<" *** Integrand::initElement not implemented."<< std::endl; + return false; + } + + //! \brief Initializes current element for boundary integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElementBou(const std::vector& MNPC) + { + std::cerr <<" *** Integrand::initElementBou not implemented."<< std::endl; + return false; + } + //! \brief Initializes current element for boundary integration (mixed). + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + //! \param[in] n1 Number of nodes in basis 1 on this patch + virtual bool initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1) + { + std::cerr <<" *** Integrand::initElementBou not implemented."<< std::endl; + return false; + } + + //! \brief Finalizes the element quantities after the numerical integration. + //! \details This method is invoked once for each element, after the numerical + //! integration loop is finished, and before the resulting element quantities + //! are assembled into their system level equivalents. + //! It can also be used to implement multiple integration point loops within + //! the same element, provided the necessary integration point values are + //! stored internally in the object during the first integration loop. + virtual bool finalizeElement(LocalIntegral*&) { return true; } + + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { + return this->evalInt(elmInt,detJW,N,dNdX,X); + } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const + { + return this->evalInt(elmInt,detJW,N,dNdX,d2NdX2,X,h); + } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Volume-averaged basis function values over the element + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 3. + //! Use when the integrand requires second-derivatives of the basis functions. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { + return this->evalInt(elmInt,detJW,N,dNdX,Navg,X); + } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field 1 + //! \param[in] N2 Basis function values, field 2 + //! \param[in] dN1dX Basis function gradients, field 1 + //! \param[in] dN2dX Basis function gradients, field 2 + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used for mixed formulations only. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const + { + return this->evalInt(elmInt,detJW,N1,N2,dN1dX,dN2dX,X); + } + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const + { + return this->evalBou(elmInt,detJW,N,dNdX,X,normal); + } + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N1 Basis function values, field 1 + //! \param[in] N2 Basis function values, field 2 + //! \param[in] dN1dX Basis function gradients, field 1 + //! \param[in] dN2dX Basis function gradients, field 2 + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details This interface is used for mixed formulations. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X, const Vec3& normal) const + { + return this->evalBou(elmInt,detJW,N1,N2,dN1dX,dN2dX,X,normal); + } + +protected: + //! \brief Evaluates the integrand at interior points for stationary problems. + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const { return false; } + //! \brief Evaluates the integrand at interior points for stationary problems. + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const { return false; } + //! \brief Evaluates the integrand at interior points for stationary problems. + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, const Vector& Navg, + const Vec3& X) const { return false; } + //! \brief Evaluates the integrand at interior points for stationary problems. + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const { return false; } + + //! \brief Evaluates the integrand at boundary points for stationary problems. + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, const Vec3& X, + const Vec3& normal) const { return false; } + //! \brief Evaluates the integrand at boundary points for stationary problems. + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, const Vec3& X, + const Vec3& normal) const { return false; } + +public: + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s The solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const + { + std::cerr <<" *** Integrand::evalSol not implemented"<< std::endl; + return false; + } + + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s The solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + virtual bool evalSecSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const + { + return false; + } + + //! \brief Evaluates the secondary solution at a result point (mixed problem). + //! \param[out] s The solution field values at current point + //! \param[in] N1 Basis function values at current point, field 1 + //! \param[in] N2 Basis function values at current point, field 2 + //! \param[in] dN1dX Basis function gradients at current point, field 1 + //! \param[in] dN2dX Basis function gradients at current point, field 2 + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + virtual bool evalSol(Vector& s, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, const Vec3& X, + const std::vector& MNPC1, + const std::vector& MNPC2) const + { + return this->evalSol(s,N1,dN1dX,X,MNPC1); + } + + //! \brief Evaluates the analytical solution at an integration point. + //! \param[out] s The solution field values at current point + //! \param[in] asol The analytical solution field (tensor field) + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSol(Vector& s, const VecFunc& asol, const Vec3& X) const + { + std::cerr <<" *** Integrand::evalSol (exact) not implemented"<< std::endl; + return false; + } + + //! \brief Evaluates the analytical secondary solution at an integration point. + //! \param[out] s The solution field values at current point + //! \param[in] asol The analytical solution field (tensor field) + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSecSol(Vector& s, const TensorFunc& asol, const Vec3& X) const + { + return false; + } + + //! \brief Evaluates the analytical scalar solution at an integration point. + //! \param[out] s The solution field values at current point + //! \param[in] asol The analytical solution field (vector field) + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSolScal(real& s, const RealFunc& asol, const Vec3& X) const + { + std::cerr <<" *** Integrand::evalSol (exact) not implemented"<< std::endl; + return false; + } + + //! \brief Evaluates the analytical secondary scalar solution at an integration point. + //! \param[out] s The solution field values at current point + //! \param[in] asol The analytical solution field (vector field) + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSecSolScal(Vector& s, const VecFunc& asol, const Vec3& X) const + { + return false; + } + + //! \brief Writes surface tractions/fluxes for a given time step to VTF-file. + //! \param vtf The VTF-file object to receive the tractions + //! \param[in] iStep Load/time step identifier + //! \param nBlck Running result block counter + virtual bool writeGlvT(VTF* vtf, int iStep, int& nBlck) const { return true; } + + //! \brief Returns whether there are any traction/flux values to write to VTF. + virtual bool hasTractionValues() const { return false; } + + //! \brief Returns which integrand to be used. + virtual int getIntegrandType() const { return 1; } + + //! \brief Returns the number of secondary solution field components. + virtual size_t getNoFields() const { return 0; } + + //! \brief Returns the number of secondary solution field components. + virtual size_t getNoSecFields() const { return 0; } + + //! \brief Returns the name of a secondary solution field component. + //! \param[in] i Field component index + //! \param[in] prefix Name prefix for all components + virtual const char* getFieldLabel(size_t i, const char* prefix = 0) const + { + return 0; + } + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \param[in] asol Pointer to the analytical solution field (optional) + virtual NormBase* getNormIntegrand(AnaSol* asol = 0) const { return 0; } + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \param[in] asol Pointer to the analytical solution field (optional) + //! + //! \details This version is used for scalar problems only. + virtual NormBase* getNormIntegrandScal(AnaSol* asol = 0) const { return 0; } + + //! \brief Returns the number of solution vectors. + size_t getNoSolutions() const { return primsol.size(); } + + //! \brief Accesses the primary solution vector of current patch. + Vector& getSolution(size_t n = 0) { return primsol[n]; } + +protected: + Vectors primsol; //!< Primary solution vectors for this patch +}; + + +/*! + \brief Abstract base class representing a system level norm quantity. +*/ + +class NormBase : public Integrand +{ +protected: + //! \brief The default constructor is protected to allow sub-classes only. + NormBase() {} + +public: + //! \brief Empty destructor. + virtual ~NormBase() {} + + //! \brief Returns whether this norm has explicit boundary contributions. + virtual bool hasBoundaryTerms() const { return false; } + + //! \brief Returns a 1-based index of the external energy norm. + virtual size_t indExt() const { return 0; } +}; + +#endif diff --git a/src/ASM/Lagrange.C b/src/ASM/Lagrange.C new file mode 100644 index 00000000..3b863e3b --- /dev/null +++ b/src/ASM/Lagrange.C @@ -0,0 +1,183 @@ +// $Id: Lagrange.C,v 1.2 2010-10-05 07:25:25 kmo Exp $ +//============================================================================== +//! +//! \file Lagrange.C +//! +//! \date Feb 10 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Evaluation of Lagrange basis functions. +//! +//============================================================================== + +#include "Lagrange.h" + + +bool Lagrange::evalPol (int polnum, double xi, double& retval) const +{ + // Check if evaluation point is inside the range of interpolation points + if (xi < points.front() || xi > points.back()) + { + std::cerr <<" *** Lagrange::evalPol: Evaluation point out of range: " + << xi <<", must be in the interval ["<< points.front() + <<","<< points.back() <<"]"<< std::endl; + return false; + } + + // Degree of polynomials = number of polynomials + size_t degree = points.size(); + + // Check if the polynomial number is feasible + if (polnum < 0 || polnum > degree-1) + { + std::cerr <<" *** Lagrange::evalPol: Poynomial number out of range: " + << polnum <<", must be in the interval [0,"<< degree-1 + <<"]"<< std::endl; + return false; + } + + // Evaluate value of polynomial number polnum in point x + retval = 1.0; + for (size_t j = 0; j < degree; j++) + if (j != polnum) + retval *= (xi - points[j]) / (points[polnum] - points[j]); + + return true; +} + + +bool Lagrange::evalDer (int polnum, double xi, double& retval) const +{ + // Check if evaluation point is inside the range of interpolation points + if (xi < points.front() || xi > points.back()) + { + std::cerr <<" *** Lagrange::evalDer: Evaluation point out of range: " + << xi <<", must be in the interval ["<< points.front() + <<","<< points.back() <<"]"<< std::endl; + return false; + } + + // Degree of polynomials = number of polynomials + size_t degree = points.size(); + + // Check if the polynomial number is feasible + if (polnum < 0 || polnum > degree-1) + { + std::cerr <<" *** Lagrange::evalDer: Poynomial number out of range: " + << polnum <<", must be in the interval [0,"<< degree-1 + <<"]"<< std::endl; + return false; + } + + // Evaluate derivative of polynomial number polnum in point x + retval = 0.0; + for (size_t k = 0; k < degree; k++) + if (k != polnum) + { + double prod = 1.0; + for (size_t j = 0; j < degree; j++) + if (j != polnum && j != k) + prod *= (xi - points[j]) / (points[polnum] - points[j]); + retval += prod / (points[polnum] - points[k]); + } + + return true; +} + + +bool Lagrange::computeBasis (Vector& val, + Matrix& derval, + int p1, double x1, + int p2, double x2, + int p3, double x3) +{ + // Define the Lagrangian interpolation points + RealArray points1(p1), points2(p2), points3(p3); + for (int i = 0; i < p1; i++) points1[i] = -1.0 + (i+i)/double(p1-1); + for (int j = 0; j < p2; j++) points2[j] = -1.0 + (j+j)/double(p2-1); + for (int k = 0; k < p3; k++) points3[k] = -1.0 + (k+k)/double(p3-1); + + return Lagrange::computeBasis(val,derval,points1,x1,points2,x2,points3,x3); +} + + +bool Lagrange::computeBasis (Vector& val, + Matrix& derval, + const RealArray& points1, double x1, + const RealArray& points2, double x2, + const RealArray& points3, double x3) +{ + int p1 = points1.size(); + int p2 = points2.size(); + int p3 = points3.size(); + + // Number of nodes per element + int n1 = p1 > 0 ? p1 : 1; + int n2 = p2 > 0 ? p2 : 1; + int n3 = p3 > 0 ? p3 : 1; + int nen = n1*n2*n3; + + // Set up the 1D Lagrangian polynomials + Lagrange L1(points1); + Lagrange L2(points2); + Lagrange L3(points3); + + // Vectors of values for the one dimensional polynomials + RealArray tempval1(n1,1.0), tempval2(n2,1.0), tempval3(n3,1.0); + + int i, j, k; + + // Evaluating values in each direction + for (i = 0; i < p1; i++) + if (!L1.evalPol(i,x1,tempval1[i])) + return false; + + for (j = 0; j < p2; j++) + if (!L2.evalPol(j,x2,tempval2[j])) + return false; + + for (k = 0; k < p3; k++) + if (!L3.evalPol(k,x3,tempval3[k])) + return false; + + // Evaluate values of the 3-dimensional basis functions in the point x + val.resize(nen); + + size_t count = 1; + for (k = 0; k < n3; k++) + for (j = 0; j < n2; j++) + for (i = 0; i < n1; i++, count++) + val(count) = tempval1[i]*tempval2[j]*tempval3[k]; + + // Vectors of derivative values for the one dimensional polynomials + RealArray tempder1(p1), tempder2(p2), tempder3(p3); + + // Evaluating values in each direction + for (i = 0; i < p1; i++) + if (!L1.evalDer(i,x1,tempder1[i])) + return false; + + for (j = 0; j < p2; j++) + if (!L2.evalDer(j,x2,tempder2[j])) + return false; + + for (k = 0; k < p3; k++) + if (!L3.evalDer(k,x3,tempder3[k])) + return false; + + // Evaluate derivatives of the 3-dimensional basis functions in the point x + derval.resize(nen, p3 > 0 ? 3 : (p2 > 0 ? 2 : 1)); + + count = 1; + for (k = 0; k < n3; k++) + for (j = 0; j < n2; j++) + for (i = 0; i < n1; i++, count++) + { + if (p1 > 0) derval(count,1) = tempder1[i]*tempval2[j]*tempval3[k]; + if (p2 > 0) derval(count,2) = tempval1[i]*tempder2[j]*tempval3[k]; + if (p3 > 0) derval(count,3) = tempval1[i]*tempval2[j]*tempder3[k]; + } + + return true; +} diff --git a/src/ASM/Lagrange.h b/src/ASM/Lagrange.h new file mode 100644 index 00000000..a215c119 --- /dev/null +++ b/src/ASM/Lagrange.h @@ -0,0 +1,84 @@ +// $Id: Lagrange.h,v 1.2 2010-10-05 07:25:25 kmo Exp $ +//============================================================================== +//! +//! \file Lagrange.h +//! +//! \date Feb 10 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Evaluation of Lagrange basis functions. +//! +//============================================================================== + +#ifndef _LAGRANGE_H +#define _LAGRANGE_H + +#include "MatVec.h" + + +/*! + \brief Evaluation of Lagrange basis functions. +*/ + +class Lagrange +{ +public: + //! \brief Constructor initializing the reference to natural coordinates. + //! \param[in] p Natural interpolation point coordinates in range [-1,1] + Lagrange(const RealArray& p) : points(p) {} + + //! \brief Evaluates a 1D Lagrange polynomial. + //! \param[in] polnum Which polynomial of the basis to evaluate + //! \param[in] xi Natural coordinate of the evaluation point + //! \param[out] retval The computed polynomial value + bool evalPol(int polnum, double xi, double& retval) const; + //! \brief Evaluates the first derivative of a 1D Lagrange polynomial. + //! \param[in] polnum Which polynomial of the basis to evaluate + //! \param[in] xi Natural coordinate of the evaluation point + //! \param[out] retval The computed polynomial derivative + bool evalDer(int polnum, double xi, double& retval) const; + + //! \brief Evaluates a 1D, 2D or 3D Lagrangian basis at a given point. + //! \param[out] val Values of all basis functions + //! \param[out] derval Derivatives of all basis functions + //! \param[in] p1 Polynomial degree in first parameter direction + //! \param[in] x1 Natural coordinate in first parameter direction + //! \param[in] p2 Polynomial degree in second parameter direction + //! \param[in] x2 Natural coordinate in second parameter direction + //! \param[in] p3 Polynomial degree in third parameter direction + //! \param[in] x3 Natural coordinate in third parameter direction + //! + //! \details If derval is a 0-pointer, the derivatives are not computed. + //! If \a p2 is zero, a 1D basis is assumed. Otherwise, + //! if \a p3 is zero, a 2D basis is assumed. If \a p1, \a p2, and \a p3 + //! all are non-zero, a 3D basis is assumed. + static bool computeBasis (Vector& val, Matrix& derval, + int p1 = 0, double x1 = 0.0, + int p2 = 0, double x2 = 0.0, + int p3 = 0, double x3 = 0.0); + + //! \brief Evaluates a 1D, 2D or 3D Lagrangian basis at a given point. + //! \param[out] val Values of all basis functions + //! \param[out] derval Derivatives of all basis functions + //! \param[in] p1 Natural point coordinates in first parameter direction + //! \param[in] x1 Natural coordinate in first parameter direction + //! \param[in] p2 Natural point coordinates in second parameter direction + //! \param[in] x2 Natural coordinate in second parameter direction + //! \param[in] p3 Natural point coordinates in third parameter direction + //! \param[in] x3 Natural coordinate in third parameter direction + //! + //! \details If derval is a 0-pointer, the derivatives are not computed. + //! If \a p2 is empty, a 1D basis is assumed. Otherwise, + //! if \a p3 is empty, a 2D basis is assumed. If \a p1, \a p2, and \a p3 + //! all are non-empty, a 3D basis is assumed. + static bool computeBasis (Vector& val, Matrix& derval, + const RealArray& p1, double x1, + const RealArray& p2 = RealArray(), double x2 = 0.0, + const RealArray& p3 = RealArray(), double x3 = 0.0); + +private: + const RealArray& points; //!< Natural coordinates of the interpolation points +}; + +#endif diff --git a/src/ASM/LocalIntegral.h b/src/ASM/LocalIntegral.h new file mode 100644 index 00000000..68ccebff --- /dev/null +++ b/src/ASM/LocalIntegral.h @@ -0,0 +1,32 @@ +// $Id: LocalIntegral.h,v 1.1 2009-12-03 17:45:34 kmo Exp $ +//============================================================================== +//! +//! \file LocalIntegral.h +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Abstract interface for classes representing integrated quantities. +//! +//============================================================================== + +#ifndef _LOCAL_INTEGRAL_H +#define _LOCAL_INTEGRAL_H + + +/*! + \brief Abstract base class representing an element level integrated quantity. +*/ + +class LocalIntegral +{ +protected: + //! \brief The default constructor is protected to allow sub-classes only. + LocalIntegral() {} +public: + //! \brief Empty destructor. + virtual ~LocalIntegral() {} +}; + +#endif diff --git a/src/ASM/SAMpatch.C b/src/ASM/SAMpatch.C new file mode 100644 index 00000000..cd485dd3 --- /dev/null +++ b/src/ASM/SAMpatch.C @@ -0,0 +1,239 @@ +// $Id: SAMpatch.C,v 1.10 2011-01-02 16:33:04 kmo Exp $ +//============================================================================== +//! +//! \file SAMpatch.C +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices for multi-patch models. +//! +//============================================================================== + +#include "SAMpatch.h" +#include "ASMbase.h" +#include "MPC.h" + + +bool SAMpatch::init (const ASMVec& model, int numNod) +{ + if (model.empty()) return false; + + // Initialize some model size parameters + nnod = numNod; + for (size_t i = 0; i < model.size(); i++) + { + nel += model[i]->getNoElms(); + nceq += model[i]->getNoMPCs(); + if (numNod == 0) nnod += model[i]->getNoNodes(); + } + + // Initialize the node/dof arrays (madof,msc) and compute ndof + this->initNodeDofs(model); + + std::cout <<"\n\n >>> SAM model summary <<<" + <<"\nNumber of elements "<< nel + <<"\nNumber of nodes "<< nnod + <<"\nNumber of dofs "<< ndof << std::endl; + + // Initialize the element connectivity arrays (mpmnpc,mmnpc) + this->initElementConn(model); + + // Initialize the constraint equation arrays (mpmceq,mmceq,ttcc) + this->initConstraintEqs(model); + if (nceq > 0) + std::cout <<"Number of constraints "<< nceq << std::endl; + + // Initialize the dof-to-equation connectivity array (meqn) + bool status = this->initSystemEquations(); + std::cout <<"Number of unknowns "<< neq << std::endl; + return status; +} + + +void SAMpatch::initNodeDofs (const ASMVec& model) +{ + if (nnod < 1) return; + + int n; + size_t i, j; + + // Initialize the array of accumulated DOFs for the nodes + madof = new int[nnod+1]; + memset(madof,0,(nnod+1)*sizeof(int)); + + for (i = 0; i < model.size(); i++) + for (j = 0; j < model[i]->getNoNodes(); j++) + if ((n = model[i]->getNodeID(j+1)) > 0 && n <= nnod) + madof[n] = model[i]->getNodalDOFs(j+1); + + madof[0] = 1; + for (n = 0; n < nnod; n++) + madof[n+1] += madof[n]; + + for (i = 0; i < model.size(); i++) + model[i]->initMADOF(madof); + + // Initialize the array of DOF status codes + ndof = madof[nnod]-1; + msc = new int[ndof]; + for (n = 0; n < ndof; n++) + msc[n] = 1; + + ASMbase::BCVec::const_iterator bit; + for (j = 0; j < model.size(); j++) + for (bit = model[j]->begin_BC(); bit != model[j]->end_BC(); bit++) + { + int idof1 = madof[bit->node-1]; + int nndof = madof[bit->node] - idof1; + if (nndof > 0) msc[idof1-1] *= bit->CX; + if (nndof > 1) msc[idof1 ] *= bit->CY; + if (nndof > 2) msc[idof1+1] *= bit->CZ; + } +} + + +void SAMpatch::initElementConn (const ASMVec& model) +{ + if (nel < 1) return; + + // Find the size of the element connectivity array + size_t i, j; + IntMat::const_iterator eit; + for (j = 0; j < model.size(); j++) + for (i = 1, eit = model[j]->begin_elm(); eit != model[j]->end_elm(); eit++) + if (model[j]->getElmID(i++) > 0) + nmmnpc += eit->size(); + + // Initialize the element connectivity arrays + mpmnpc = new int[nel+1]; + mmnpc = new int[nmmnpc]; + int ip = mpmnpc[0] = 1; + for (j = 0; j < model.size(); j++) + for (i = 1, eit = model[j]->begin_elm(); eit != model[j]->end_elm(); eit++) + if (model[j]->getElmID(i++) > 0) + { + mpmnpc[ip] = mpmnpc[ip-1]; + for (size_t i = 0; i < eit->size(); i++) + mmnpc[(mpmnpc[ip]++)-1] = model[j]->getNodeID(1+(*eit)[i]); + ip++; + } +} + + +void SAMpatch::initConstraintEqs (const ASMVec& model) +{ + // Estimate the size of the constraint equation array + size_t j; + MPCIter cit; + for (j = 0; j < model.size(); j++) + for (cit = model[j]->begin_MPC(); cit != model[j]->end_MPC(); cit++) + nmmceq += 1 + (*cit)->getNoMaster(); + + // Initialize the constraint equation arrays + mpmceq = new int[nceq+1]; + int ip = mpmceq[0] = 1; + if (nceq < 1) return; + mmceq = new int[nmmceq]; + ttcc = new real[nmmceq]; + for (j = 0; j < model.size(); j++) + for (cit = model[j]->begin_MPC(); cit != model[j]->end_MPC(); cit++, ip++) + { + mpmceq[ip] = mpmceq[ip-1]; + + // Slave dof ... + int idof = madof[(*cit)->getSlave().node-1] + (*cit)->getSlave().dof - 1; + if (msc[idof-1] == 0) + { + std::cerr <<"SAM: Ignoring constraint equation for dof " + << idof <<" ("<< (*cit)->getSlave() + <<").\n This dof is already marked as FIXED."<< std::endl; + ip--; + nceq--; + continue; + } + else if (msc[idof-1] < 0) + { + std::cerr <<"SAM: Ignoring constraint equation for dof " + << idof <<" ("<< (*cit)->getSlave() + <<").\n This dof is already marked as SLAVE."<< std::endl; + ip--; + nceq--; + continue; + } + + int ipslv = (mpmceq[ip]++) - 1; + mmceq[ipslv] = idof; + ttcc[ipslv] = (*cit)->getSlave().coeff; + msc[idof-1] = -ip; + + // Master dofs ... + for (size_t i = 0; i < (*cit)->getNoMaster(); i++) + { + idof = madof[(*cit)->getMaster(i).node-1] + (*cit)->getMaster(i).dof-1; + if (msc[idof-1] > 0) + { + int ipmst = (mpmceq[ip]++) - 1; + mmceq[ipmst] = idof; + ttcc[ipmst] = (*cit)->getMaster(i).coeff; + } + else if (msc[idof-1] < 0) + { + // Master dof is constrained (unresolved chaining) + std::cerr <<"SAM: Chained MPCs detected" + <<", slave "<< (*cit)->getSlave() + <<", master "<< (*cit)->getMaster(i) + <<" (ignored)."<< std::endl; + mpmceq[ip] = mpmceq[ip-1]; + ip--; + nceq--; + break; + } + } + + (*cit)->iceq = ip-1; // index into mpmceq for this MPC equation + } + + // Reset the negative values in msc before calling SYSEQ + for (ip = 0; ip < ndof; ip++) + if (msc[ip] < 0) msc[ip] = 0; +} + + +bool SAMpatch::updateConstraintEqs (const ASMVec& model, const Vector* prevSol) +{ + if (nceq < 1) return true; // No constraints in this model + + MPCIter cit; + for (size_t j = 0; j < model.size(); j++) + for (cit = model[j]->begin_MPC(); cit != model[j]->end_MPC(); cit++) + { + if ((*cit)->iceq < 0) continue; // Skip the ignored constraint equations + + // Slave dof ... + int idof = madof[(*cit)->getSlave().node-1] + (*cit)->getSlave().dof - 1; + int ipeq = mpmceq[(*cit)->iceq] - 1; + if (msc[idof-1] > 0 || mmceq[ipeq] != idof) + { + std::cerr <<" *** Corrupted SAM arrays detected in update."<< std::endl; + return false; + } + else if (!prevSol) + ttcc[ipeq] = 0.0; + else if (idof <= prevSol->size()) + ttcc[ipeq] = (*cit)->getSlave().coeff - (*prevSol)(idof); + else + ttcc[ipeq] = (*cit)->getSlave().coeff; + + // Master dofs ... + for (size_t i = 0; prevSol && i < (*cit)->getNoMaster(); i++) + { + idof = madof[(*cit)->getMaster(i).node-1] + (*cit)->getMaster(i).dof-1; + if (msc[idof-1] > 0 && mmceq[++ipeq] == idof) + ttcc[ipeq] = (*cit)->getMaster(i).coeff; + } + } + + return true; +} diff --git a/src/ASM/SAMpatch.h b/src/ASM/SAMpatch.h new file mode 100644 index 00000000..3e33b8b1 --- /dev/null +++ b/src/ASM/SAMpatch.h @@ -0,0 +1,57 @@ +// $Id: SAMpatch.h,v 1.5 2011-01-02 16:33:04 kmo Exp $ +//============================================================================== +//! +//! \file SAMpatch.h +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices for multi-patch models. +//! +//============================================================================== + +#ifndef _SAM_PATCH_H +#define _SAM_PATCH_H + +#include "SAM.h" + +class ASMbase; + + +/*! + \brief This is a sub-class of SAM with added functionality for spline models. + \details It contains some additional functions for initializing the SAM-data + for an FE model based on ASMbase patches. +*/ + +class SAMpatch : public SAM +{ +public: + //! \brief Empty default constructor. + SAMpatch() {} + //! \brief Empty destructor. + virtual ~SAMpatch() {} + + //! \brief Allocates the dynamic arrays and populates them with data. + //! \param[in] model All spline patches in the model + //! \param[in] numNod Total number of unique nodes in the model + bool init(const std::vector& model, int numNod = 0); + + //! \brief Updates the multi-point constraint array \a TTCC. + //! \param[in] model All spline patches in the model + //! \param[in] prevSol Previous primary solution vector in DOF-order + virtual bool updateConstraintEqs(const std::vector& model, + const Vector* prevSol = 0); + +protected: + //! \brief Initializes the nodal arrays \a MINEX, \a MADOF and \a MSC. + void initNodeDofs(const std::vector& model); + //! \brief Initializes the element topology arrays \a MPMNPC and \a MMNPC. + void initElementConn(const std::vector& model); + //! \brief Initializes the multi-point constraint arrays + //! \a MPMCEQ, \a MMCEQ and \a TTCC. + virtual void initConstraintEqs(const std::vector& model); +}; + +#endif diff --git a/src/ASM/SAMpatchPara.C b/src/ASM/SAMpatchPara.C new file mode 100644 index 00000000..52fbe61c --- /dev/null +++ b/src/ASM/SAMpatchPara.C @@ -0,0 +1,490 @@ +// $Id: SAMpatchPara.C,v 1.5 2011-02-08 11:59:51 rho Exp $ +//============================================================================== +//! +//! \file SAMpatchPara.C +//! +//! \date Sep 6 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices for multi-patch models. +//! +//============================================================================== + +#include "SAMpatchPara.h" +#include "PETScMatrix.h" +#include "ASMbase.h" +#include "MPC.h" + + +SAMpatchPara::SAMpatchPara(const IntVec& l2gn_mp) +{ + l2gn = l2gn_mp; +#ifdef PARALLEL_PETSC + MPI_Comm_size(PETSC_COMM_WORLD,&nProc); +#else + nProc = 1; +#endif +} + + +SAMpatchPara::~SAMpatchPara () +{ +#ifdef PARALLEL_PETSC + ISDestroy(iglob); + ISDestroy(iloc); +#endif +} + + +bool SAMpatchPara::getNoDofCouplings (int ifirst, int ilast, + IntVec& d_nnz, IntVec& o_nnz) const +{ +#ifdef PARALLEL_PETSC + int i, e; + int d_ldof, d_gdof, o_ldof, o_gdof; + size_t j, k; + + // Find number of dof couplings for each node + std::vector d_dofc(ndof), o_dofc(ndof); + for (e = 1; e <= nel; e++) + { + IntVec meen; + if (!this->getElmEqns(meen,e)) + return false; + + for (j = 0; j < meen.size(); j++) + if (meen[j] > 0) { + d_ldof = meen[j]-1; + d_gdof = meqn[d_ldof]-1; + if (d_gdof >= ifirst && d_gdof < ilast) { + for (k = 0; k < meen.size(); k++) + if (meen[k] > 0) { + o_ldof = meen[k]-1; + o_gdof = meqn[o_ldof]-1; + if (o_gdof >= ifirst && o_gdof < ilast) + d_dofc[d_ldof].insert(o_ldof); + else + o_dofc[d_ldof].insert(o_ldof); + } + } + else { + for (k = 0; k < meen.size(); k++) + if (meen[k] > 0) { + o_ldof = meen[k]-1; + o_gdof = meqn[o_ldof]-1; + if (o_gdof >= ifirst && o_gdof < ilast) + o_dofc[d_ldof].insert(o_ldof); + } + } + } + } + + // Generate nnz for diagonal block + int locsize = ilast-ifirst; + d_nnz.resize(locsize,0); + for (i = 0;i < ndof;i++) { + d_gdof = meqn[i]-1; + if (d_gdof >= ifirst && d_gdof < ilast) + d_nnz[d_gdof-ifirst] = d_dofc[i].size(); + } + + // Generate nnz for off-diagonal block + IntVec l2g; + Vector nnz; + l2g.resize(ndof); + nnz.resize(ndof); + for (i = 0;i < ndof;i++) { + l2g[i] = meqn[i]-1; + nnz[i] = o_dofc[i].size(); + } + + Vec x; + VecCreate(PETSC_COMM_WORLD,&x); + VecSetSizes(x,locsize,PETSC_DECIDE); + VecSetFromOptions(x); + VecSet(x,0.0); + VecSetValues(x,ndof,&(l2g[0]),&(nnz[0]),ADD_VALUES); + VecAssemblyBegin(x); + VecAssemblyEnd(x); + + PetscScalar *vec; + VecGetArray(x,&vec); + + o_nnz.resize(locsize); + for (i = 0;i < locsize;i++) + o_nnz[i] = ceil(vec[i]); + + VecRestoreArray(x,&vec); + VecDestroy(x); + +#else + this->SAM::getNoDofCouplings(d_nnz); + o_nnz = IntVec(ndof,0); +#endif + return true; +} + + +bool SAMpatchPara::assembleSystem (SystemVector& sysRHS, + const Matrix& eK, int iel, + Vector* reactionForces) const +{ + return this->SAM::assembleSystem(sysRHS,eK,iel,reactionForces); +} + + +bool SAMpatchPara::assembleSystem (SystemVector& sysRHS, + const RealArray& eS, int iel, + Vector* reactionForces) const +{ +#ifdef PARALLEL_PETSC + IntVec l2g; + if (!this->getElmEqns(l2g,iel,eS.size())) + return false; + + RealArray eSv(eS); + for (size_t i = 0; i < l2g.size(); i++) { + if (mpmceq[--l2g[i]] != 0) + eSv[i] = real(0); + l2g[i] = meqn[l2g[i]]-1; + } + + PETScVector* pvec = dynamic_cast(&sysRHS); + if (!pvec) return false; + + // Add contributions to SV (righthand side) + VecSetValues(pvec->getVector(),nedof,&l2g[0],&eSv[0],ADD_VALUES); + + // Add contributions to reaction forces + if (reactionForces) + this->assembleReactions(*reactionForces,eS,iel); + + return true; +#else + return this->SAM::assembleSystem(sysRHS,eS,iel,reactionForces); +#endif +} + + +bool SAMpatchPara::getElmEqns (IntVec& meen, int iel, int nedof) const +{ + if (iel < 1 || iel > nel) return false; + + int ip = mpmnpc[iel-1]; + int nenod = mpmnpc[iel] - ip; + if (nedof < 1) nedof = nenod*ndof/nnod; + +#ifdef USE_F77SAM + int neldof, neslv, neprd; + meen.resize(nedof,0); + elmeq_(madof,mmnpc+ip-1,mpmceq,meqn,nenod,&meen.front(),neldof,neslv,neprd); +#else + meen.clear(); + meen.reserve(nedof); + for (int i = 0; i < nenod; i++, ip++) + { + int node = mmnpc[ip-1]; + for (int j = madof[node-1]; j < madof[node]; j++) + meen.push_back(j); + } + int neldof = meen.size(); +#endif + if (neldof == nedof) return true; + + std::cerr <<"SAMpatchPara::getElmEqns: Invalid element matrix dimension " + << nedof <<" (should have been "<< neldof <<")"<< std::endl; + return false; +} + + +bool SAMpatchPara::expandSolution (const SystemVector& solVec, + Vector& dofVec) const +{ + if (solVec.dim() < (size_t)nleq) return false; + +#ifdef PARALLEL_PETSC + Vec solution; + VecScatter ctx; + + dofVec.resize(ndof,true); + + SystemVector* sv = const_cast(&solVec); + PETScVector* svec = dynamic_cast(sv); + if (!svec) return false; + + VecCreateSeqWithArray(PETSC_COMM_SELF,dofVec.size(),&(dofVec[0]),&solution); + VecScatterCreate(svec->getVector(),iglob,solution,iloc,&ctx); + VecScatterBegin(ctx,svec->getVector(),solution,INSERT_VALUES,SCATTER_FORWARD); + VecScatterEnd(ctx,svec->getVector(),solution,INSERT_VALUES,SCATTER_FORWARD); + VecScatterDestroy(ctx); + VecDestroy(solution); + + return true; +#else + return this->expandVector(solVec.getRef(),dofVec); +#endif +} + + +real SAMpatchPara::dot (const Vector& x, const Vector& y) const +{ + real globVal = this->SAM::dot(x,y); + +#ifdef PARALLEL_PETSC + if (nProc > 1) + { + real locVal = globVal; + for (size_t i = 0; i < ghostNodes.size(); i++) + { + int inod = ghostNodes[i]; + if (madof[inod] - madof[inod-1] == mpar[17]) + for (int j = madof[inod-1]; j < madof[inod]; j++) + locVal -= x(j)*y(j); + } + + MPI_Allreduce(&locVal,&globVal,1,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD); + } +#endif + + return globVal; +} + + +real SAMpatchPara::normL2 (const Vector& x) const +{ +#ifdef PARALLEL_PETSC + if (nProc > 1 && nnodGlob > 1) + return this->norm2(x)/sqrt(mpar[17]*nnodGlob); +#endif + return this->SAM::normL2(x); +} + + +real SAMpatchPara::normInf (const Vector& x, size_t& comp) const +{ + real locmax = this->SAM::normInf(x,comp); +#ifdef PARALLEL_PETSC + int nProc, myRank; + MPI_Comm_size(PETSC_COMM_WORLD,&nProc); + MPI_Comm_rank(PETSC_COMM_WORLD,&myRank); + + if (nProc > 1) { + Vector locval, globval; + locval.resize(2*nProc,0.0); + globval.resize(2*nProc,0.0); + + comp = meqn[(comp-1)*mpar[17]]/mpar[17]+1; + + locval[2*myRank] = locmax; + locval[2*myRank+1] = 1.0*comp; + + MPI_Allreduce(&locval[0],&globval[0],2*nProc,MPI_DOUBLE,MPI_MAX,PETSC_COMM_WORLD); + + for (size_t n = 0;n < nProc;n++) + if (globval[2*n] > locmax) { + locmax = globval[2*n]; + comp = (size_t) globval[2*n+1]; + } + } +#endif + return locmax; +} + + +void SAMpatchPara::initConstraintEqs (const std::vector& model) +{ + // TODO: Rewrite this calling the parent-class method, and then replacing + // the mpmceq array into the ndof-sized array used here (why is it needed?) + + // Estimate the size of the constraint equation array + size_t j; + MPCIter cit; + for (j = 0; j < model.size(); j++) + for (cit = model[j]->begin_MPC(); cit != model[j]->end_MPC(); cit++) + nmmceq += 1 + (*cit)->getNoMaster(); + + // Initialize the constraint equation arrays + mpmceq = new int[ndof]; + memset(mpmceq,0,ndof*sizeof(int)); + if (nceq < 1) return; + mmceq = new int[nmmceq]; + ttcc = new real[nmmceq]; + int ip = 1; + for (j = 0; j < model.size(); j++) + for (cit = model[j]->begin_MPC(); cit != model[j]->end_MPC(); cit++, ip++) + { + // Slave dof ... + int idof = madof[(*cit)->getSlave().node-1] + (*cit)->getSlave().dof - 1; + + if (msc[idof-1] == 0) + { + std::cerr <<"SAM: Ignoring constraint equation for dof " + << idof <<" ("<< (*cit)->getSlave() + <<").\n This dof is already marked as FIXED."<< std::endl; + ip--; + nceq--; + continue; + } + else if (msc[idof-1] < 0) + { + std::cerr <<"SAM: Ignoring constraint equation for dof " + << idof <<" ("<< (*cit)->getSlave() + <<").\n This dof is already marked as SLAVE."<< std::endl; + ip--; + nceq--; + continue; + } + + mpmceq[idof-1] = ip; + int ipslv = ip - 1; + + mmceq[ipslv] = idof; + ttcc[ipslv] = (*cit)->getSlave().coeff; + msc[idof-1] = -ip; + + // Master dofs ... + for (size_t i = 0; i < (*cit)->getNoMaster(); i++) + { + idof = madof[(*cit)->getMaster(i).node-1] + (*cit)->getMaster(i).dof-1; + if (msc[idof-1] > 0) + { + int ipmst = (mpmceq[idof]++) - 1; + mmceq[ipmst] = idof; + ttcc[ipmst] = (*cit)->getMaster(i).coeff; + } + else if (msc[idof-1] < 0) + { + // Master dof is constrained (unresolved chaining) + std::cerr <<"SAM: Chained MPCs detected" + <<", slave "<< (*cit)->getSlave() + <<", master "<< (*cit)->getMaster(i) + <<" (ignored)."<< std::endl; + mpmceq[idof] = mpmceq[idof-1]; + ip--; + nceq--; + break; + } + } + } + + // Reset the negative values in msc before calling SYSEQ + for (ip = 0; ip < ndof; ip++) + if (msc[ip] < 0) msc[ip] = 0; +} + + +bool SAMpatchPara::updateConstraintEqs (const std::vector& model, + const Vector* prevSol) +{ + if (nceq < 1) return true; // No constraints in this model + + MPCIter cit; + for (size_t j = 0; j < model.size(); j++) + for (cit = model[j]->begin_MPC(); cit != model[j]->end_MPC(); cit++) + { + // Slave dof ... + int idof = madof[(*cit)->getSlave().node-1] + (*cit)->getSlave().dof - 1; + int ipeq = mpmceq[idof-1] - 1; + if (msc[idof-1] > 0 || mmceq[ipeq] != idof) + { + std::cerr <<" *** Corrupted SAM arrays detected in update."<< std::endl; + return false; + } + else if (!prevSol) + ttcc[ipeq] = 0.0; + else if (idof <= prevSol->size()) + ttcc[ipeq] = (*cit)->getSlave().coeff - (*prevSol)(idof); + else + ttcc[ipeq] = (*cit)->getSlave().coeff; + + // Master dofs ... + for (size_t i = 0; prevSol && i < (*cit)->getNoMaster(); i++) + { + idof = madof[(*cit)->getMaster(i).node-1] + (*cit)->getMaster(i).dof-1; + if (msc[idof-1] > 0 && mmceq[++ipeq] == idof) + ttcc[ipeq] = (*cit)->getMaster(i).coeff; + } + } + + return true; +} + + +bool SAMpatchPara::initSystemEquations () +{ + int i, j; + + // Initialize matrix-of-equation-numbers + meqn = new int[ndof]; + if (!l2gn.empty()) + { + int min = l2gn.front(); + int max = min; + + for (i = 1; i < nnod; i++) + if (l2gn[i] < min) + min = l2gn[i]; + else if (max < l2gn[i]) + max = l2gn[i]; + +#ifdef PARALLEL_PETSC + int myRank, nProc; + MPI_Status status; + MPI_Comm_rank(PETSC_COMM_WORLD,&myRank); + MPI_Comm_size(PETSC_COMM_WORLD,&nProc); + if (myRank < nProc-1) + MPI_Send(&max,1,MPI_INT,myRank+1,101,PETSC_COMM_WORLD); + if (myRank > 0) { + MPI_Recv(&min,1,MPI_INT,myRank-1,101,PETSC_COMM_WORLD,&status); + min++; + } + + // Find number of global nodes + // RUNAR + //MPI_Scatter(&max,1,MPI_INT,&nnodGlob,1,MPI_INT,nProc-1,PETSC_COMM_WORLD); + MPI_Allreduce(&max,&nnodGlob,1,MPI_INT,MPI_MAX,PETSC_COMM_WORLD); + + // Generate list of ghost nodes + for (size_t k = 0; k < l2gn.size(); k++) + if (l2gn[k] < min) ghostNodes.push_back(k+1); +#endif + + // TODO: Fix this for mixed field interpolations (varying DOFs per node) + int nndof = ndof/nnod; + nleq = (max-min+1)*nndof; + for (i = 0; i < nnod; i++) + for (j = 0; j < nndof; j++) + meqn[i*nndof+j] = (l2gn[i]-1)*nndof + j + 1; + } + else { + nleq = ndof; + for (i = 0; i < ndof; i++) + meqn[i] = i+1; + } + +#ifdef PARALLEL_PETSC + // Generate 0-based local-to-global dof mapping + IntVec l2g(ndof); + for (i = 0; i < ndof; i++) + l2g[i] = meqn[i]-1; + + // Generate global and local index sets + ISCreateGeneral(PETSC_COMM_WORLD,ndof,&(l2g[0]),&iglob); + ISCreateStride(PETSC_COMM_WORLD,ndof,0,1,&iloc); +#endif + + // Number of equations equals number of dofs + neq = ndof; + + // Initialize the number of nodal DOFs + int inod, nndof; + mpar[16] = mpar[17] = madof[1]-madof[0]; + for (int inod = 1; inod < nnod && madof; inod++) + if ((nndof = madof[1]-madof[0]) < mpar[16]) + mpar[16] = nndof; + else if (nndof > mpar[17]) + mpar[17] = nndof; + + return true; +} diff --git a/src/ASM/SAMpatchPara.h b/src/ASM/SAMpatchPara.h new file mode 100644 index 00000000..0d0f72e2 --- /dev/null +++ b/src/ASM/SAMpatchPara.h @@ -0,0 +1,143 @@ +// $Id: SAMpatchPara.h,v 1.4 2011-02-08 12:09:30 rho Exp $ +//============================================================================== +//! +//! \file SAMpatchPara.h +//! +//! \date Sep 6 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices for multi-patch models. +//! +//============================================================================== + +#ifndef _SAM_PATCH_PARA_H +#define _SAM_PATCH_PARA_H + +#include "SAMpatch.h" +#ifdef PARALLEL_PETSC +#include "petscksp.h" +#endif + + +/*! + \brief This is a sub-class of SAMpatch with support for parallel processing. +*/ + +class SAMpatchPara : public SAMpatch +{ +public: + //! \brief The constructor initializes the \a l2gn array. + SAMpatchPara(const IntVec& l2gn_mp); + //! \brief The destructor destroys the index set arrays. + virtual ~SAMpatchPara(); + + //! \brief Returns the number of equations (free DOFs) on this processor. + virtual int getNoEquations() const { return nleq; } + + //! \brief Computes number of couplings for each local dof + //! in a distributed matrix. + //! \param[in] ifirst Global number of first local DOF + //! \param[in] ilast Global number of last local DOF pluss 1 + //! \param[out] d_nnz Number of diagonal couplings for each local DOF + //! \param[out] o_nnz Number of off-diagonal couplings for each local DOF + //! \return \e false if number of couplings is not computed, otherwise \e true + virtual bool getNoDofCouplings(int ifirst, int ilast, + IntVec& d_nnz, IntVec& o_nnz) const; + + //! \brief Adds element stiffness contributions to the system load vector. + //! \param sysRHS The right-hand-side system load vector + //! \param[in] eK The element stiffness matrix + //! \param[in] iel Identifier for the element that \a eK belongs to + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e true on successful assembly, otherwise \e false + //! + //! \details When multi-point constraints are present, contributions from + //! these are added into the right-hand-side system load vector. + virtual bool assembleSystem(SystemVector& sysRHS, + const Matrix& eK, int iel = 0, + Vector* reactionForces = 0) const; + + //! \brief Adds an element load vector into the system load vector. + //! \param sysRHS The right-hand-side system load vector + //! \param[in] eS The element load vector + //! \param[in] iel Identifier for the element that \a eS belongs to + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e true on successful assembly, otherwise \e false + virtual bool assembleSystem(SystemVector& sysRHS, + const RealArray& eS, int iel = 0, + Vector* reactionForces = 0) const; + + //! \brief Finds the matrix of equation numbers for an element. + //! \param[out] meen Matrix of element equation numbers + //! \param[in] iel Identifier for the element to get the equation numbers for + //! \param[in] nedof Number of degrees of freedom in the element + //! (used for internal consistency checking, unless zero) + virtual bool getElmEqns(IntVec& meen, int iel, int nedof = 0) const; + + //! \brief Updates the multi-point constraint array \a TTCC. + virtual bool updateConstraintEqs(const std::vector& model, + const Vector* prevSol = 0); + + //! \brief Expands a solution vector from equation-ordering to DOF-ordering. + //! \param[in] solVec Solution vector, length = NEQ + //! \param[out] displ Displacement vector, length = NDOF = 3*NNOD + //! \return \e false if the length of \a solVec is invalid, otherwise \e true + //! + //! \details The size of the solution vector that comes out of the linear + //! equation solver equals the number of free DOFs in the system (=NEQ). + //! That is, all fixed or constrained (slave) DOFs are not present. + //! Before we can compute derived element quantities we therefore need to + //! extract the resulting displacement values also for the constrained DOFs. + virtual bool expandSolution(const SystemVector& solVec, Vector& displ) const; + + //! \brief Computes the dot-product of two vectors. + //! \param[in] x First vector in dot-product. + //! \param[in] y Second vector in dot-product. + //! \return Value of dot-product + //! + //! \details Both vectors are defined over all nodes in the patch, i.e. + //! for parallel vectors the ghost entries are also included. + virtual real dot(const Vector& x, const Vector& y) const; + + //! \brief Computes the L2-norm of a vector. + //! \param[in] x Vector for norm computation + //! \return Value of L2-norm + //! + //! \details The vector is defined over all nodes in the patch, i.e. + //! for parallel vectors the ghost entries are also included. + virtual real normL2(const Vector& x) const; + + //! \brief Computes the inf-norm of a vector. + //! \param[in] x Vector for norm computation + //! \param comp Local nodal DOF on input, index of the largest value on output + //! \return Value of inf-norm + //! + //! \details The vector is defined over all nodes in the patch, i.e. + //! for parallel vectors the ghost entries are also included. + virtual real normInf(const Vector& x, size_t& comp) const; + +protected: + //! \brief Initializes the multi-point constraint arrays + //! \a MPMCEQ, \a MMCEQ and \a TTCC. + virtual void initConstraintEqs(const std::vector& model); + + //! \brief Initializes the DOF-to-equation connectivity array \a MEQN. + virtual bool initSystemEquations(); + +private: + typedef std::set IntSet; //!< General integer set + + // Parameters for parallel computing + int nProc; //!< Number of processes + int nleq; //!< Number of equations for this processor + int nnodGlob; //!< Number of global nodes; + IntVec ghostNodes; //!< Indices for ghost nodes + IntVec l2gn; //!< Local-to-global node numbers for this processor +#ifdef PARALLEL_PETSC + IS iglob; //!< Index set for global numbering + IS iloc; //!< Index set for local numbering +#endif +}; + +#endif diff --git a/src/ASM/TimeDomain.h b/src/ASM/TimeDomain.h new file mode 100644 index 00000000..38ac3372 --- /dev/null +++ b/src/ASM/TimeDomain.h @@ -0,0 +1,32 @@ +// $Id: TimeDomain.h,v 1.1 2010-06-01 09:40:25 kmo Exp $ +//============================================================================== +//! +//! \file TimeDomain.h +//! +//! \date May 27 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Time domain representation for time-dependent and nonlinear solvers. +//! +//============================================================================== + +#ifndef _TIME_DOMAIN_H +#define _TIME_DOMAIN_H + + +/*! + \brief Struct for representing the time domain. +*/ + +struct TimeDomain +{ + double t; //!< Current time (or pseudo time) + double dt; //!< Current time increment (or load parameter increment) + int it; //!< Current iteration within current time/load step + + //! \brief Default constructor. + TimeDomain() { t = dt = 0.0; it = 0; } +}; + +#endif diff --git a/src/Doxyfile b/src/Doxyfile new file mode 100644 index 00000000..837c20ee --- /dev/null +++ b/src/Doxyfile @@ -0,0 +1,1511 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = ICADA + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 90A354 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ../doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = Apps/FiniteDefElasticity Apps/Stokes Apps/Poisson \ + Apps SIM ASM Integrands Eig LinAlg Utility ../doc + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.C *.h *.f *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = DERR INDEX CHECK_INDEX THIS + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = NO + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/src/Doxyfile.proto b/src/Doxyfile.proto new file mode 100644 index 00000000..258d4610 --- /dev/null +++ b/src/Doxyfile.proto @@ -0,0 +1,1510 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = ICADA + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 90A354 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . Eig LinAlg Utility + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.C *.h *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = DERR index CHECK_INDEX THIS + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = NO + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/src/Eig/EigSolver.C b/src/Eig/EigSolver.C new file mode 100644 index 00000000..fb41200d --- /dev/null +++ b/src/Eig/EigSolver.C @@ -0,0 +1,206 @@ +// $Id: EigSolver.C,v 1.3 2010-03-29 16:48:57 kmo Exp $ +//============================================================================== +//! +//! \file EigSolver.C +//! +//! \date Apr 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Interface to LAPack and ARPack eigenvalue solvers. +//! +//============================================================================== + +#include "EigSolver.h" +#include "DenseMatrix.h" +#include "SPRMatrix.h" +#include "PETScMatrix.h" + +#if defined(_WIN32) +#define eig_av_ EIG_AV +#define eig_mv_ EIG_MV +#define eig_sol_ EIG_SOL +#define eig_drv1_ EIG_DRV1 +#define eig_drv2_ EIG_DRV2 +#define eig_drv3_ EIG_DRV3 +#define eig_drv4_ EIG_DRV4 +#define eig_drv5_ EIG_DRV5 +#define eig_drv6_ EIG_DRV6 +#elif defined(_AIX) +#define eig_av_ eig_av +#define eig_mv_ eig_mv +#define eig_sol_ eig_sol +#define eig_drv1_ eig_drv1 +#define eig_drv2_ eig_drv2 +#define eig_drv3_ eig_drv3 +#define eig_drv4_ eig_drv4 +#define eig_drv5_ eig_drv5 +#define eig_drv6_ eig_drv6 +#endif + +extern "C" { +//! \brief Driver to solve a standard eigenvalue problem. +//! \details The regular mode is used. +//! This is a FORTRAN 77 subroutine based on the example driver DSDRV1 from +//! the ARPACK distribution. +//! \sa ARPACK documentation. +void eig_drv1_(const int& n, const int& nev, const int& ncv, + double* d, double* v, double* work, int& ierr); +//! \brief Driver to solve a standard eigenvalue problem. +//! \details The shift-and-invert mode is used. +//! This is a FORTRAN 77 subroutine based on the example driver DSDRV2 from +//! the ARPACK distribution. +//! \sa ARPACK documentation. +void eig_drv2_(const int& n, const int& nev, const int& ncv, const double& sig, + double* d, double* v, double* work, int& ierr); +//! \brief Driver to solve a generalized eigenvalue problem. +//! \details The inverse mode is used. +//! This is a FORTRAN 77 subroutine based on the example driver DSDRV3 from +//! the ARPACK distribution. +//! \sa ARPACK documentation. +void eig_drv3_(const int& n, const int& nev, const int& ncv, + double* d, double* v, double* work, int& ierr); +//! \brief Driver to solve a generalized eigenvalue problem. +//! \details The shift-and-invert mode is used. +//! This is a FORTRAN 77 subroutine based on the example driver DSDRV4 from +//! the ARPACK distribution. +//! \sa ARPACK documentation. +void eig_drv4_(const int& n, const int& nev, const int& ncv, const double& sig, + double* d, double* v, double* work, int& ierr); +//! \brief Driver to solve a generalized eigenvalue problem. +//! \details The buckling mode is used. +//! This is a FORTRAN 77 subroutine based on the example driver DSDRV5 from +//! the ARPACK distribution. +//! \sa ARPACK documentation. +void eig_drv5_(const int& n, const int& nev, const int& ncv, const double& sig, + double* d, double* v, double* work, int& ierr); +//! \brief Driver to solve a generalized eigenvalue problem. +//! \details The Cayley mode is used. +//! This is a FORTRAN 77 subroutine based on the example driver DSDRV6 from +//! the ARPACK distribution. +//! \sa ARPACK documentation. +void eig_drv6_(const int& n, const int& nev, const int& ncv, const double& sig, + double* d, double* v, double* work, int& ierr); +} + +static SystemMatrix* K = 0; //!< Pointer to coefficient matrix A +static SystemMatrix* M = 0; //!< Pointer to coefficient matrix B +static SystemMatrix* AM = 0; //!< Pointer to the matrix to invert + + +bool eig::solve (SystemMatrix* A, SystemMatrix* B, + Vector& eigVal, Matrix& eigVec, int nev) +{ + DenseMatrix* dK = dynamic_cast(A); + DenseMatrix* dM = dynamic_cast(B); + + if (dK) + if (dM) // Try the LAPack solver DSYGVX + return dK->solveEig(*dM,eigVal,eigVec,nev); + else // Try the LAPack solver DSYEVX + return dK->solveEig(eigVal,eigVec,nev); + + SPRMatrix* sK = dynamic_cast(A); + SPRMatrix* sM = dynamic_cast(B); + + if (sK && sM) // Try the Lanczos solver SPRLAN + return sK->solveEig(*sM,eigVal,eigVec,nev); + +#ifdef HAS_SLEPC + PETScMatrix *pK = dynamic_cast(A); + PETScMatrix *pM = dynamic_cast(B); + + if (pK && pM) // Try the SLEPc solver EPSSolve + return pK->solveEig(*pM,eigVal,eigVec,nev); +#endif + + return false; +} + + +bool eig::solve (SystemMatrix* A, SystemMatrix* B, + Vector& eigVal, Matrix& eigVec, int nev, int ncv, + int mode, double shift) +{ + K = A; + M = B; + int ierr = 1; + int n = K->dim(); + int nwork = 4*n+ncv*(ncv+9); + if (mode == 6) nwork += n; + eigVal.resize(2*ncv); + eigVec.resize(n,ncv); + double* work = new double[nwork]; + + switch (mode) { + case 1: + eig_drv1_(n,nev,ncv,eigVal.ptr(),eigVec.ptr(),work,ierr); + break; + case 2: + AM = K->copy(); + if (shift != 0.0) AM->add(-shift); + eig_drv2_(n,nev,ncv,shift,eigVal.ptr(),eigVec.ptr(),work,ierr); + break; + case 3: + AM = M->copy(); + eig_drv3_(n,nev,ncv,eigVal.ptr(),eigVec.ptr(),work,ierr); + break; + case 4: + AM = K->copy(); + if (shift != 0.0) AM->add(*M,-shift); + eig_drv4_(n,nev,ncv,shift,eigVal.ptr(),eigVec.ptr(),work,ierr); + break; + case 5: + AM = K->copy(); + if (shift != 0.0) AM->add(*M,+shift); // Notice the +sign on the shift! + eig_drv5_(n,nev,ncv,shift,eigVal.ptr(),eigVec.ptr(),work,ierr); + break; + case 6: + AM = K->copy(); + if (shift != 0.0) AM->add(*M,-shift); + eig_drv6_(n,nev,ncv,shift,eigVal.ptr(),eigVec.ptr(),work,ierr); + break; + } + + delete[] work; + if (AM) delete AM; + AM = 0; + K = 0; + M = 0; + return ierr == 0; +} + + +//! \brief Performs the matrix-vector multiplication \b y = \b K * \b x. +extern "C" void eig_av_(const double* x, double* y) +{ + if (!K) return; + + StdVector Y; + K->multiply(StdVector(x,K->dim()),Y); + memcpy(y,Y.ptr(),Y.dim()*sizeof(double)); +} + + +//! \brief Performs the matrix-vector multiplication \b y = \b M * \b x. +extern "C" void eig_mv_(const double* x, double* y) +{ + if (!M) return; + + StdVector Y; + M->multiply(StdVector(x,M->dim()),Y); + memcpy(y,Y.ptr(),Y.dim()*sizeof(double)); +} + + +//! \brief Solves the linear system of equations \b AM \b y = \b x. +//! \details The matrix \b AM depends on the eigensolver method. +extern "C" void eig_sol_(const double* x, double* y, int& ierr) +{ + ierr = -99; + if (!AM) return; + + StdVector RHS(x,AM->dim()); + ierr = AM->solve(RHS) ? 0 : -1; + memcpy(y,RHS.ptr(),RHS.dim()*sizeof(double)); +} diff --git a/src/Eig/EigSolver.h b/src/Eig/EigSolver.h new file mode 100644 index 00000000..134dc52d --- /dev/null +++ b/src/Eig/EigSolver.h @@ -0,0 +1,48 @@ +// $Id: EigSolver.h,v 1.2 2010-03-24 12:01:36 rho Exp $ +//============================================================================== +//! +//! \file EigSolver.h +//! +//! \date Apr 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Interface to LAPack, ARPack and SLEPc eigenvalue solvers. +//! +//============================================================================== + +#ifndef _EIG_SOLVER_H +#define _EIG_SOLVER_H + +#include "MatVec.h" + +class SystemMatrix; + + +namespace eig //! Top-level functions for invoking eigenproblem solvers. +{ + //! \brief Solves the eigenvalue problem using LAPACK::DSYGVX. + //! \details For dense matrices only and no shift. + //! \param A The system stiffness matrix + //! \param B The system mass matrix + //! \param[out] eigVal Computed eigenvalues + //! \param[out] eigVec Computed eigenvectors + //! \param[in] nev Number of eigenvalues/vector to compute + bool solve(SystemMatrix* A, SystemMatrix* B, + Vector& eigVal, Matrix& eigVec, int nev); + + //! \brief Solves the eigenvalue problem (A-lambda*B)*x = 0 using ARPACK. + //! \param A The system stiffness matrix + //! \param B The system mass matrix + //! \param[out] eigVal Computed eigenvalues + //! \param[out] eigVec Computed eigenvectors + //! \param[in] nev Number of eigenvalues/vectors (see ARPack documentation) + //! \param[in] ncv Number of Arnoldi vectors (see ARPack documentation) + //! \param[in] mode Eigensolver method (1,...5, see ARPack documentation) + //! \param[in] shift Eigenvalue shift + bool solve(SystemMatrix* A, SystemMatrix* B, + Vector& eigVal, Matrix& eigVec, int nev, int ncv, + int mode = 4, double shift = 0.0); +}; + +#endif diff --git a/src/Eig/eig_drv1.f b/src/Eig/eig_drv1.f new file mode 100644 index 00000000..fa3fd47c --- /dev/null +++ b/src/Eig/eig_drv1.f @@ -0,0 +1,327 @@ + subroutine eig_drv1 (n,nev,ncv,d,v,work,ierr) +c +c $Id: eig_drv1.f,v 1.1 2009-12-08 15:32:49 kmo Exp $ +c----------------------------------------------------------------------- +c\BeginDoc +c +c\Name: eig_drv1 +c +c\Description: +c Driver to solve a standard eigenvalue problem using the +c reverse communication interface in ARPACK, regular mode. +c This subroutine is based on the test driver DSDRV1 from the +c symmetric examples of the ARPACK distribution. +c +c ... Suppose we want to solve A*x = lambda*x in regular mode, +c where A is the stiffness matrix. +c +c ... OP = A and B = I. +c +c ... Use mode 1 of DSAUPD. +c +c\Usage: +c call eig_drv1 ( N, NEV, NCV, D, V, WORK, IERR ) +c +c\Arguments +c N Integer. (INPUT) +c Dimension of the eigenproblem. +c +c NEV Integer. (INPUT) +c Number of eigenvalues of OP to be computed. 0 < NEV < N. +c +c NCV Integer. (INPUT) +c Number of columns of the matrix V (less than or equal to N). +c +c D Double precision NCV by 2 array. (OUTPUT) +c The first column of D contain the eigenvalues. +c +c V Double precision N by NCV array. (OUTPUT) +c The NCV columns of V contain the Lanczos basis vectors. +c +c WORK Double precision work array. (OUTPUT/WORKSPACE) +c +c +c IERR Integer. (OUTPUT) +c Error flag. +c +c\EndDoc +c----------------------------------------------------------------------- +c\BeginLib +c +c\Routines called: +c dsaupd ARPACK reverse communication interface routine. +c dseupd ARPACK routine that returns Ritz values and vectors. +c daxpy Level 1 BLAS that computes y <- alpha*x+y. +c dnrm2 Level 1 BLAS that computes the norm of a vector. +c eig_av Matrix vector multiplication routine that computes A*x. +c +c\Author +c Knut Morten Okstad +c Dept. of Applied Mathematics +c SINTEF ICT +c Trondheim, Norway +c +c\EndLib +c----------------------------------------------------------------------- +c + implicit none +c +c %-----------% +c | Arguments | +c %-----------% +C + integer n, nev, ncv, ierr + Double precision d(ncv,2), v(n,ncv), work(*) +c +c %--------------% +c | Local Arrays | +c %--------------% +c + integer iparam(11), ipntr(11) +c +c %---------------% +c | Local Scalars | +c %---------------% +c + character bmat*1, which*2 + integer ipresid, ipselec, ipworkd, ipworkl, lworkl, + & ido, j, nconv, maxitr, ishfts, mode + Double precision tol, sigma +c +c %-----------------------------% +c | BLAS & LAPACK routines used | +c %-----------------------------% +c + Double precision dnrm2 + external dnrm2, daxpy, eig_sol, eig_av, eig_mv +c +c %--------------------% +c | Intrinsic function | +c %--------------------% +c + intrinsic abs +c +c %-----------------------% +c | Executable statements | +c %-----------------------% +c + print *, ' ' + print *, ' Entering EIG_DRV1',n,nev,ncv +c +c %----------------------------------------------------% +c | The number N is the dimension of the matrix. A | +c | standard eigenvalue problem is solved (BMAT = 'I').| +c | NEV is the number of eigenvalues to be | +c | approximated. The user can modify NEV, NCV, WHICH | +c | to solve problems of different sizes, and to get | +c | different parts of the spectrum. | +c %----------------------------------------------------% +c + bmat = 'I' + which = 'SM' +c +c %--------------------------------------------------% +c | The work array WORKL is used in DSAUPD as | +c | workspace. Its dimension LWORKL is set as | +c | illustrated below. The parameter TOL determines | +c | the stopping criterion. If TOL<=0, machine | +c | precision is used. The variable IDO is used for | +c | reverse communication and is initially set to 0. | +c | Setting INFO=0 indicates that a random vector is | +c | generated in DSAUPD to start the Arnoldi | +c | iteration. | +c %--------------------------------------------------% +c + lworkl = ncv*(ncv+8) + tol = 0.0D0 + ido = 0 + ierr = 0 +c +c %---------------------------------------------------% +c | This program uses exact shifts with respect to | +c | the current Hessenberg matrix (IPARAM(1) = 1). | +c | IPARAM(3) specifies the maximum number of Arnoldi | +c | iterations allowed. Mode 1 specified in the | +c | documentation of DSAUPD is used (IPARAM(7) = 1). | +c | All these options may be changed by the user. | +c | For details, see the documentation in DSAUPD. | +c %---------------------------------------------------% +c + ishfts = 1 + maxitr = 300 + mode = 1 +c + iparam(1) = ishfts + iparam(3) = maxitr + iparam(7) = mode +c + ipworkd = 1 + ipworkl = ipworkd + 3*n + ipresid = ipworkl + lworkl + ipselec = ipresid + n +c +c %-------------------------------------------% +c | M A I N L O O P (Reverse communication) | +c %-------------------------------------------% +c + 10 continue +c +c %---------------------------------------------% +c | Repeatedly call the routine DSAUPD and take | +c | actions indicated by parameter IDO until | +c | either convergence is indicated or maxitr | +c | has been exceeded. | +c %---------------------------------------------% +c + call dsaupd (ido, bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, work(ipworkd), + & work(ipworkl), lworkl, ierr) + + if (ido .eq. -1 .or. ido .eq. 1) then +c +c %--------------------------------------% +c | Perform matrix vector multiplication | +c | y <--- OP*x | +c | The user should supply his/her own | +c | matrix vector multiplication routine | +c | here that takes workd(ipntr(1)) as | +c | the input, and return the result to | +c | workd(ipntr(2)). | +c %--------------------------------------% +c + call eig_av (work(ipntr(1)), work(ipntr(2))) +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + end if +c +c %-----------------------------------------% +c | Either we have convergence, or there is | +c | an error. | +c %-----------------------------------------% +c + if (ierr .lt. 0) then +c +c %--------------------------% +c | Error message, check the | +c | documentation in DSAUPD. | +c %--------------------------% +c + print *, ' ' + print *, ' Error with dsaupd, info = ',ierr + print *, ' Check the documentation of dsaupd' + print *, ' ' + return +c + else if (ierr .eq. 1) then + print *, ' ' + print *, ' Maximum number of iterations reached.' + print *, ' ' + else if (ierr .eq. 3) then + print *, ' ' + print *, ' No shifts could be applied during implicit', + & ' Arnoldi update, try increasing NCV.' + print *, ' ' + end if +c +c %-------------------------------------------% +c | No fatal errors occurred. | +c | Post-Process using DSEUPD. | +c | | +c | Computed eigenvalues may be extracted. | +c | | +c | Eigenvectors may also be computed now if | +c | desired. (indicated by rvec = .true.) | +c %-------------------------------------------% +c + call dseupd (.true., 'All', work(ipselec), d, v, n, sigma, + & bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, + & work(ipworkd), work(ipworkl), lworkl, ierr) +c +c %----------------------------------------------% +c | Eigenvalues are returned in the first column | +c | of the two dimensional array D and the | +c | corresponding eigenvectors are returned in | +c | the first NEV columns of the two dimensional | +c | array V if requested. Otherwise, an | +c | orthogonal basis for the invariant subspace | +c | corresponding to the eigenvalues in D is | +c | returned in V. | +c %----------------------------------------------% +c + if (ierr .ne. 0) then +c +c %------------------------------------% +c | Error condition: | +c | Check the documentation of DSEUPD. | +c %------------------------------------% +c + print *, ' ' + print *, ' Error with dseupd, info = ', ierr + print *, ' Check the documentation of dseupd' + print *, ' ' + return +c + end if +c + nconv = iparam(5) + do 30 j = 1, nconv +c +c %---------------------------% +c | Compute the residual norm | +c | | +c | || A*x - lambda*x || | +c | | +c | for the NCONV accurately | +c | computed eigenvalues and | +c | eigenvectors. (iparam(5) | +c | indicates how many are | +c | accurate to the requested | +c | tolerance) | +c %---------------------------% +c + call eig_av (v(1,j), work) + call daxpy (n, -d(j,1), v(1,j), 1, work, 1) + d(j,2) = dnrm2(n, work, 1) + d(j,2) = d(j,2) / abs(d(j,1)) +c + 30 continue +c +c %-------------------------------% +c | Display computed residuals | +c %-------------------------------% +c + call dmout (6, nconv, 2, d, ncv, -6, + & 'Ritz values and relative residuals') +c +c +c %------------------------------------------% +c | Print additional convergence information | +c %------------------------------------------% +c + print *, ' ' + print *, ' EIG_DRV1' + print *, ' ========' + print *, ' ' + print *, ' Size of the matrix is ', n + print *, ' The number of Ritz values requested is ', nev + print *, ' The number of Arnoldi vectors generated', + & ' (NCV) is ', ncv + print *, ' What portion of the spectrum: ', which + print *, ' The number of converged Ritz values is ', nconv + print *, ' The number of Implicit Arnoldi update', + & ' iterations taken is ', iparam(3) + print *, ' The number of OP*x is ', iparam(9) + print *, ' The convergence criterion is ', tol + print *, ' ' +c +c %----------------------------% +c | Done with program eig_drv1 | +c %----------------------------% +c + end diff --git a/src/Eig/eig_drv2.f b/src/Eig/eig_drv2.f new file mode 100644 index 00000000..229c5a32 --- /dev/null +++ b/src/Eig/eig_drv2.f @@ -0,0 +1,337 @@ + subroutine eig_drv2 (n,nev,ncv,sigma,d,v,work,ierr) +c +c $Id: eig_drv2.f,v 1.1 2009-12-08 15:32:49 kmo Exp $ +c----------------------------------------------------------------------- +c\BeginDoc +c +c\Name: eig_drv2 +c +c\Description: +c Driver to solve a standard eigenvalue problem using the +c reverse communication interface in ARPACK, shift and invert mode. +c This subroutine is based on the test driver DSDRV2 from the +c symmetric examples of the ARPACK distribution. +c +c ... Suppose we want to solve A*x = lambda*x in shift-invert mode, +c where A is the stiffness matrix. +c +c ... OP = (inv[A - sigma*I]) and B = I. +c +c ... Use mode 3 of DSAUPD. +c +c\Usage: +c call eig_drv2 ( N, NEV, NCV, SIGMA, D, V, WORK, IERR ) +c +c\Arguments +c N Integer. (INPUT) +c Dimension of the eigenproblem. +c +c NEV Integer. (INPUT) +c Number of eigenvalues of OP to be computed. 0 < NEV < N. +c +c NCV Integer. (INPUT) +c Number of columns of the matrix V (less than or equal to N). +c +c SIGMA Double precision. (INPUT) +c The shift value. +c +c D Double precision NCV by 2 array. (OUTPUT) +c The first column of D contain the eigenvalues. +c +c V Double precision N by NCV array. (OUTPUT) +c The NCV columns of V contain the Lanczos basis vectors. +c +c WORK Double precision work array. (OUTPUT/WORKSPACE) +c +c +c IERR Integer. (OUTPUT) +c Error flag. +c +c\EndDoc +c----------------------------------------------------------------------- +c\BeginLib +c +c\Routines called: +c dsaupd ARPACK reverse communication interface routine. +c dseupd ARPACK routine that returns Ritz values and vectors. +c daxpy Level 1 BLAS that computes y <- alpha*x+y. +c dnrm2 Level 1 BLAS that computes the norm of a vector. +c eig_sol Linear solver routine that computes inv[A - sigma*I]*x +c eig_av Matrix vector multiplication routine that computes A*x. +c +c\Author +c Knut Morten Okstad +c Dept. of Applied Mathematics +c SINTEF ICT +c Trondheim, Norway +c +c\EndLib +c----------------------------------------------------------------------- +c + implicit none +c +c %-----------% +c | Arguments | +c %-----------% +C + integer n, nev, ncv, ierr + Double precision sigma, d(ncv,2), v(n,ncv), work(*) +c +c %--------------% +c | Local Arrays | +c %--------------% +c + integer iparam(11), ipntr(11) +c +c %---------------% +c | Local Scalars | +c %---------------% +c + character bmat*1, which*2 + integer ipresid, ipselec, ipworkd, ipworkl, lworkl, + & ido, j, nconv, maxitr, ishfts, mode + Double precision tol +c +c %-----------------------------% +c | BLAS & LAPACK routines used | +c %-----------------------------% +c + Double precision dnrm2 + external dnrm2, daxpy, eig_sol, eig_av, eig_mv +c +c %--------------------% +c | Intrinsic function | +c %--------------------% +c + intrinsic abs +c +c %-----------------------% +c | Executable statements | +c %-----------------------% +c + print *, ' ' + print *, ' Entering EIG_DRV2',n,nev,ncv,sigma +c +c %----------------------------------------------------% +c | The number N is the dimension of the matrix. A | +c | standard eigenvalue problem is solved (BMAT = 'I').| +c | NEV is the number of eigenvalues (closest to | +c | SIGMA) to be approximated. Since the shift-invert | +c | mode is used, WHICH is set to 'LM'. The user can | +c | modify NEV, NCV, SIGMA to solve problems of | +c | different sizes, and to get different parts of the | +c | spectrum. | +c %----------------------------------------------------% +c + bmat = 'I' + which = 'LM' +c +c %--------------------------------------------------% +c | The work array WORKL is used in DSAUPD as | +c | workspace. Its dimension LWORKL is set as | +c | illustrated below. The parameter TOL determines | +c | the stopping criterion. If TOL<=0, machine | +c | precision is used. The variable IDO is used for | +c | reverse communication and is initially set to 0. | +c | Setting INFO=0 indicates that a random vector is | +c | generated in DSAUPD to start the Arnoldi | +c | iteration. | +c %--------------------------------------------------% +c + lworkl = ncv*(ncv+8) + tol = 0.0D0 + ido = 0 + ierr = 0 +c +c %---------------------------------------------------% +c | This program uses exact shifts with respect to | +c | the current Hessenberg matrix (IPARAM(1) = 1). | +c | IPARAM(3) specifies the maximum number of Arnoldi | +c | iterations allowed. Mode 3 specified in the | +c | documentation of DSAUPD is used (IPARAM(7) = 3). | +c | All these options may be changed by the user. | +c | For details, see the documentation in DSAUPD. | +c %---------------------------------------------------% +c + ishfts = 1 + maxitr = 300 + mode = 3 +c + iparam(1) = ishfts + iparam(3) = maxitr + iparam(7) = mode +c + ipworkd = 1 + ipworkl = ipworkd + 3*n + ipresid = ipworkl + lworkl + ipselec = ipresid + n +c +c %-------------------------------------------% +c | M A I N L O O P (Reverse communication) | +c %-------------------------------------------% +c + 10 continue +c +c %---------------------------------------------% +c | Repeatedly call the routine DSAUPD and take | +c | actions indicated by parameter IDO until | +c | either convergence is indicated or maxitr | +c | has been exceeded. | +c %---------------------------------------------% +c + call dsaupd (ido, bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, work(ipworkd), + & work(ipworkl), lworkl, ierr) + + if (ido .eq. -1 .or. ido .eq. 1) then +c +c %----------------------------------------% +c | Perform y <-- OP*x = inv[A-SIGMA*I]*x. | +c | The user only need the linear system | +c | solver here that takes workd(ipntr(1)) | +c | as the input, returns the result to | +c | workd(ipntr(2)). | +c %----------------------------------------% +c + call eig_sol (work(ipntr(1)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV2' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + end if +c +c %-----------------------------------------% +c | Either we have convergence, or there is | +c | an error. | +c %-----------------------------------------% +c + if (ierr .lt. 0) then +c +c %--------------------------% +c | Error message, check the | +c | documentation in DSAUPD. | +c %--------------------------% +c + print *, ' ' + print *, ' Error with dsaupd, info = ',ierr + print *, ' Check the documentation of dsaupd' + print *, ' ' + return +c + else if (ierr .eq. 1) then + print *, ' ' + print *, ' Maximum number of iterations reached.' + print *, ' ' + else if (ierr .eq. 3) then + print *, ' ' + print *, ' No shifts could be applied during implicit', + & ' Arnoldi update, try increasing NCV.' + print *, ' ' + end if +c +c %-------------------------------------------% +c | No fatal errors occurred. | +c | Post-Process using DSEUPD. | +c | | +c | Computed eigenvalues may be extracted. | +c | | +c | Eigenvectors may also be computed now if | +c | desired. (indicated by rvec = .true.) | +c %-------------------------------------------% +c + call dseupd (.true., 'All', work(ipselec), d, v, n, sigma, + & bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, + & work(ipworkd), work(ipworkl), lworkl, ierr) +c +c %----------------------------------------------% +c | Eigenvalues are returned in the first column | +c | of the two dimensional array D and the | +c | corresponding eigenvectors are returned in | +c | the first NEV columns of the two dimensional | +c | array V if requested. Otherwise, an | +c | orthogonal basis for the invariant subspace | +c | corresponding to the eigenvalues in D is | +c | returned in V. | +c %----------------------------------------------% +c + if (ierr .ne. 0) then +c +c %------------------------------------% +c | Error condition: | +c | Check the documentation of DSEUPD. | +c %------------------------------------% +c + print *, ' ' + print *, ' Error with dseupd, info = ', ierr + print *, ' Check the documentation of dseupd' + print *, ' ' + return +c + end if +c + nconv = iparam(5) + do 30 j = 1, nconv +c +c %---------------------------% +c | Compute the residual norm | +c | | +c | || A*x - lambda*x || | +c | | +c | for the NCONV accurately | +c | computed eigenvalues and | +c | eigenvectors. (iparam(5) | +c | indicates how many are | +c | accurate to the requested | +c | tolerance) | +c %---------------------------% +c + call eig_av (v(1,j), work) + call daxpy (n, -d(j,1), v(1,j), 1, work, 1) + d(j,2) = dnrm2(n, work, 1) + d(j,2) = d(j,2) / abs(d(j,1)) +c + 30 continue +c +c %-------------------------------% +c | Display computed residuals | +c %-------------------------------% +c + call dmout (6, nconv, 2, d, ncv, -6, + & 'Ritz values and relative residuals') +c +c +c %------------------------------------------% +c | Print additional convergence information | +c %------------------------------------------% +c + print *, ' ' + print *, ' EIG_DRV2' + print *, ' ========' + print *, ' ' + print *, ' Size of the matrix is ', n + print *, ' The number of Ritz values requested is ', nev + print *, ' The number of Arnoldi vectors generated', + & ' (NCV) is ', ncv + print *, ' What portion of the spectrum: ', which + print *, ' The number of converged Ritz values is ', nconv + print *, ' The number of Implicit Arnoldi update', + & ' iterations taken is ', iparam(3) + print *, ' The number of OP*x is ', iparam(9) + print *, ' The convergence criterion is ', tol + print *, ' ' +c +c %----------------------------% +c | Done with program eig_drv2 | +c %----------------------------% +c + end diff --git a/src/Eig/eig_drv3.f b/src/Eig/eig_drv3.f new file mode 100644 index 00000000..aac31200 --- /dev/null +++ b/src/Eig/eig_drv3.f @@ -0,0 +1,358 @@ + subroutine eig_drv3 (n,nev,ncv,d,v,work,ierr) +c +c $Id: eig_drv3.f,v 1.1 2009-12-08 15:32:49 kmo Exp $ +c----------------------------------------------------------------------- +c\BeginDoc +c +c\Name: eig_drv3 +c +c\Description: +c Driver to solve a generalized eigenvalue problem using the +c reverse communication interface in ARPACK, inverse mode. +c This subroutine is based on the test driver DSDRV3 from the +c symmetric examples of the ARPACK distribution. +c +c ... Suppose we want to solve A*x = lambda*M*x in inverse mode, +c where A and M are stiffness and mass matrix, respectively. +c +c ... OP = inv[M]*A and B = M. +c +c ... Use mode 2 of DSAUPD. +c +c\Usage: +c call eig_drv3 ( N, NEV, NCV, D, V, WORK, IERR ) +c +c\Arguments +c N Integer. (INPUT) +c Dimension of the eigenproblem. +c +c NEV Integer. (INPUT) +c Number of eigenvalues of OP to be computed. 0 < NEV < N. +c +c NCV Integer. (INPUT) +c Number of columns of the matrix V (less than or equal to N). +c +c D Double precision NCV by 2 array. (OUTPUT) +c The first column of D contain the eigenvalues. +c +c V Double precision N by NCV array. (OUTPUT) +c The NCV columns of V contain the Lanczos basis vectors. +c +c WORK Double precision work array. (OUTPUT/WORKSPACE) +c +c +c IERR Integer. (OUTPUT) +c Error flag. +c +c\EndDoc +c----------------------------------------------------------------------- +c\BeginLib +c +c\Routines called: +c dsaupd ARPACK reverse communication interface routine. +c dseupd ARPACK routine that returns Ritz values and vectors. +c daxpy Level 1 BLAS that computes y <- alpha*x+y. +c dnrm2 Level 1 BLAS that computes the norm of a vector. +c eig_sol Linear solver routine that computes inv[M]*x +c eig_av Matrix vector multiplication routine that computes A*x. +c eig_mv Matrix vector multiplication routine that computes M*x. +c +c\Author +c Knut Morten Okstad +c Dept. of Applied Mathematics +c SINTEF ICT +c Trondheim, Norway +c +c\EndLib +c----------------------------------------------------------------------- +c + implicit none +c +c %-----------% +c | Arguments | +c %-----------% +C + integer n, nev, ncv, ierr + Double precision d(ncv,2), v(n,ncv), work(*) +c +c %--------------% +c | Local Arrays | +c %--------------% +c + integer iparam(11), ipntr(11) +c +c %---------------% +c | Local Scalars | +c %---------------% +c + character bmat*1, which*2 + integer ipresid, ipselec, ipworkd, ipworkl, lworkl, + & ido, j, nconv, maxitr, ishfts, mode + Double precision tol, sigma +c +c %-----------------------------% +c | BLAS & LAPACK routines used | +c %-----------------------------% +c + Double precision dnrm2 + external dnrm2, daxpy, eig_sol, eig_av, eig_mv +c +c %--------------------% +c | Intrinsic function | +c %--------------------% +c + intrinsic abs +c +c %-----------------------% +c | Executable statements | +c %-----------------------% +c + print *, ' ' + print *, ' Entering EIG_DRV3',n,nev,ncv +c +c %----------------------------------------------------% +c | The number N is the dimension of the matrix. A | +c | generalized eigenvalue problem is solved (BMAT = | +c | 'G'.) NEV is the number of eigenvalues to be | +c | approximated. The user can modify NEV, NCV, WHICH | +c | to solve problems of different sizes, and to get | +c | different parts of the spectrum. | +c %----------------------------------------------------% +c + bmat = 'G' + which = 'LM' +c +c %--------------------------------------------------% +c | The work array WORKL is used in DSAUPD as | +c | workspace. Its dimension LWORKL is set as | +c | illustrated below. The parameter TOL determines | +c | the stopping criterion. If TOL<=0, machine | +c | precision is used. The variable IDO is used for | +c | reverse communication and is initially set to 0. | +c | Setting INFO=0 indicates that a random vector is | +c | generated in DSAUPD to start the Arnoldi | +c | iteration. | +c %--------------------------------------------------% +c + lworkl = ncv*(ncv+8) + tol = 0.0D0 + ido = 0 + ierr = 0 +c +c %---------------------------------------------------% +c | This program uses exact shifts with respect to | +c | the current Hessenberg matrix (IPARAM(1) = 1). | +c | IPARAM(3) specifies the maximum number of Arnoldi | +c | iterations allowed. Mode 2 specified in the | +c | documentation of DSAUPD is used (IPARAM(7) = 2). | +c | All these options may be changed by the user. | +c | For details, see the documentation in DSAUPD. | +c %---------------------------------------------------% +c + ishfts = 1 + maxitr = 300 + mode = 2 +c + iparam(1) = ishfts + iparam(3) = maxitr + iparam(7) = mode +c + ipworkd = 1 + ipworkl = ipworkd + 3*n + ipresid = ipworkl + lworkl + ipselec = ipresid + n +c +c %-------------------------------------------% +c | M A I N L O O P (Reverse communication) | +c %-------------------------------------------% +c + 10 continue +c +c %---------------------------------------------% +c | Repeatedly call the routine DSAUPD and take | +c | actions indicated by parameter IDO until | +c | either convergence is indicated or maxitr | +c | has been exceeded. | +c %---------------------------------------------% +c + call dsaupd (ido, bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, work(ipworkd), + & work(ipworkl), lworkl, ierr) + + if (ido .eq. -1 .or. ido .eq. 1) then +c +c %--------------------------------------% +c | Perform y <--- OP*x = inv[M]*A*x. | +c | The user should supply his/her own | +c | matrix vector multiplication (A*x) | +c | routine and a linear system solver | +c | here. The matrix vector | +c | multiplication routine takes | +c | workd(ipntr(1)) as the input vector. | +c | The final result is returned to | +c | workd(ipntr(2)). The result of A*x | +c | overwrites workd(ipntr(1)). | +c %--------------------------------------% +c + call eig_av (work(ipntr(1)), work(ipntr(1))) + call eig_sol (work(ipntr(1)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV3' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 2) then +c +c %-----------------------------------------% +c | Perform y <--- M*x | +c | Need the matrix vector multiplication | +c | routine here that takes workd(ipntr(1)) | +c | as the input and returns the result to | +c | workd(ipntr(2)). | +c %-----------------------------------------% +c + call eig_mv (work(ipntr(1)), work(ipntr(2))) +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + end if +c +c %-----------------------------------------% +c | Either we have convergence, or there is | +c | an error. | +c %-----------------------------------------% +c + if (ierr .lt. 0) then +c +c %--------------------------% +c | Error message, check the | +c | documentation in DSAUPD. | +c %--------------------------% +c + print *, ' ' + print *, ' Error with dsaupd, info = ',ierr + print *, ' Check the documentation of dsaupd' + print *, ' ' + return +c + else if (ierr .eq. 1) then + print *, ' ' + print *, ' Maximum number of iterations reached.' + print *, ' ' + else if (ierr .eq. 3) then + print *, ' ' + print *, ' No shifts could be applied during implicit', + & ' Arnoldi update, try increasing NCV.' + print *, ' ' + end if +c +c %-------------------------------------------% +c | No fatal errors occurred. | +c | Post-Process using DSEUPD. | +c | | +c | Computed eigenvalues may be extracted. | +c | | +c | Eigenvectors may also be computed now if | +c | desired. (indicated by rvec = .true.) | +c %-------------------------------------------% +c + call dseupd (.true., 'All', work(ipselec), d, v, n, sigma, + & bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, + & work(ipworkd), work(ipworkl), lworkl, ierr) +c +c %----------------------------------------------% +c | Eigenvalues are returned in the first column | +c | of the two dimensional array D and the | +c | corresponding eigenvectors are returned in | +c | the first NEV columns of the two dimensional | +c | array V if requested. Otherwise, an | +c | orthogonal basis for the invariant subspace | +c | corresponding to the eigenvalues in D is | +c | returned in V. | +c %----------------------------------------------% +c + if (ierr .ne. 0) then +c +c %------------------------------------% +c | Error condition: | +c | Check the documentation of DSEUPD. | +c %------------------------------------% +c + print *, ' ' + print *, ' Error with dseupd, info = ', ierr + print *, ' Check the documentation of dseupd' + print *, ' ' + return +c + end if +c + nconv = iparam(5) + do 30 j = 1, nconv +c +c %---------------------------% +c | Compute the residual norm | +c | | +c | || A*x - lambda*M*x || | +c | | +c | for the NCONV accurately | +c | computed eigenvalues and | +c | eigenvectors. (iparam(5) | +c | indicates how many are | +c | accurate to the requested | +c | tolerance) | +c %---------------------------% +c + call eig_av (v(1,j), work) + call eig_mv (v(1,j), work(n+1)) + call daxpy (n, -d(j,1), work(n+1), 1, work, 1) + d(j,2) = dnrm2(n, work, 1) + d(j,2) = d(j,2) / abs(d(j,1)) +c + 30 continue +c +c %-----------------------------% +c | Display computed residuals. | +c %-----------------------------% +c + call dmout (6, nconv, 2, d, ncv, -6, + & 'Ritz values and relative residuals') +c +c +c %------------------------------------------% +c | Print additional convergence information | +c %------------------------------------------% +c + print *, ' ' + print *, ' EIG_DRV3' + print *, ' ========' + print *, ' ' + print *, ' Size of the matrix is ', n + print *, ' The number of Ritz values requested is ', nev + print *, ' The number of Arnoldi vectors generated', + & ' (NCV) is ', ncv + print *, ' What portion of the spectrum: ', which + print *, ' The number of converged Ritz values is ', nconv + print *, ' The number of Implicit Arnoldi update', + & ' iterations taken is ', iparam(3) + print *, ' The number of OP*x is ', iparam(9) + print *, ' The convergence criterion is ', tol + print *, ' ' +c +c %----------------------------% +c | Done with program eig_drv3 | +c %----------------------------% +c + end diff --git a/src/Eig/eig_drv4.f b/src/Eig/eig_drv4.f new file mode 100644 index 00000000..ba041ee9 --- /dev/null +++ b/src/Eig/eig_drv4.f @@ -0,0 +1,383 @@ + subroutine eig_drv4 (n,nev,ncv,sigma,d,v,work,ierr) +c +c $Id: eig_drv4.f,v 1.1 2009-12-08 15:32:49 kmo Exp $ +c----------------------------------------------------------------------- +c\BeginDoc +c +c\Name: eig_drv4 +c +c\Description: +c Driver to solve a generalized eigenvalue problem using the +c reverse communication interface in ARPACK, shift and invert mode. +c This subroutine is based on the test driver DSDRV4 from the +c symmetric examples of the ARPACK distribution. +c +c ... Suppose we want to solve A*x = lambda*M*x in shift-invert mode, +c where A and M are stiffness and mass matrix, respectively. +c +c ... OP = (inv[A - sigma*M])*M and B = M. +c +c ... Use mode 3 of DSAUPD. +c +c\Usage: +c call eig_drv4 ( N, NEV, NCV, SIGMA, D, V, WORK, IERR ) +c +c\Arguments +c N Integer. (INPUT) +c Dimension of the eigenproblem. +c +c NEV Integer. (INPUT) +c Number of eigenvalues of OP to be computed. 0 < NEV < N. +c +c NCV Integer. (INPUT) +c Number of columns of the matrix V (less than or equal to N). +c +c SIGMA Double precision. (INPUT) +c The shift value. +c +c D Double precision NCV by 2 array. (OUTPUT) +c The first column of D contain the eigenvalues. +c +c V Double precision N by NCV array. (OUTPUT) +c The NCV columns of V contain the Lanczos basis vectors. +c +c WORK Double precision work array. (OUTPUT/WORKSPACE) +c +c +c IERR Integer. (OUTPUT) +c Error flag. +c +c\EndDoc +c----------------------------------------------------------------------- +c\BeginLib +c +c\Routines called: +c dsaupd ARPACK reverse communication interface routine. +c dseupd ARPACK routine that returns Ritz values and vectors. +c daxpy Level 1 BLAS that computes y <- alpha*x+y. +c dnrm2 Level 1 BLAS that computes the norm of a vector. +c eig_sol Linear solver routine that computes inv[A - sigma*M]*x +c eig_av Matrix vector multiplication routine that computes A*x. +c eig_mv Matrix vector multiplication routine that computes M*x. +c +c\Author +c Knut Morten Okstad +c Dept. of Applied Mathematics +c SINTEF ICT +c Trondheim, Norway +c +c\EndLib +c----------------------------------------------------------------------- +c + implicit none +c +c %-----------% +c | Arguments | +c %-----------% +C + integer n, nev, ncv, ierr + Double precision sigma, d(ncv,2), v(n,ncv), work(*) +c +c %--------------% +c | Local Arrays | +c %--------------% +c + integer iparam(11), ipntr(11) +c +c %---------------% +c | Local Scalars | +c %---------------% +c + character bmat*1, which*2 + integer ipresid, ipselec, ipworkd, ipworkl, lworkl, + & ido, j, nconv, maxitr, ishfts, mode + Double precision tol +c +c %-----------------------------% +c | BLAS & LAPACK routines used | +c %-----------------------------% +c + Double precision dnrm2 + external dnrm2, daxpy, eig_sol, eig_av, eig_mv +c +c %--------------------% +c | Intrinsic function | +c %--------------------% +c + intrinsic abs +c +c %-----------------------% +c | Executable statements | +c %-----------------------% +c + print *, ' ' + print *, ' Entering EIG_DRV4',n,nev,ncv,sigma +c +c %----------------------------------------------------% +c | The number N is the dimension of the matrix. A | +c | generalized eigenvalue problem is solved (BMAT = | +c | 'G'.) NEV is the number of eigenvalues (closest to | +c | the shift SIGMA) to be approximated. Since the | +c | shift-invert mode is used, WHICH is set to 'LM'. | +c | The user can modify NEV, NCV, SIGMA to solve | +c | problems of different sizes, and to get different | +c | parts of the spectrum. | +c %----------------------------------------------------% +c + bmat = 'G' + which = 'LM' +c +c %--------------------------------------------------% +c | The work array WORKL is used in DSAUPD as | +c | workspace. Its dimension LWORKL is set as | +c | illustrated below. The parameter TOL determines | +c | the stopping criterion. If TOL<=0, machine | +c | precision is used. The variable IDO is used for | +c | reverse communication and is initially set to 0. | +c | Setting INFO=0 indicates that a random vector is | +c | generated in DSAUPD to start the Arnoldi | +c | iteration. | +c %--------------------------------------------------% +c + lworkl = ncv*(ncv+8) + tol = 0.0D0 + ido = 0 + ierr = 0 +c +c %---------------------------------------------------% +c | This program uses exact shifts with respect to | +c | the current Hessenberg matrix (IPARAM(1) = 1). | +c | IPARAM(3) specifies the maximum number of Arnoldi | +c | iterations allowed. Mode 3 specified in the | +c | documentation of DSAUPD is used (IPARAM(7) = 3). | +c | All these options may be changed by the user. | +c | For details, see the documentation in DSAUPD. | +c %---------------------------------------------------% +c + ishfts = 1 + maxitr = 300 + mode = 3 +c + iparam(1) = ishfts + iparam(3) = maxitr + iparam(7) = mode +c + ipworkd = 1 + ipworkl = ipworkd + 3*n + ipresid = ipworkl + lworkl + ipselec = ipresid + n +c +c %-------------------------------------------% +c | M A I N L O O P (Reverse communication) | +c %-------------------------------------------% +c + 10 continue +c +c %---------------------------------------------% +c | Repeatedly call the routine DSAUPD and take | +c | actions indicated by parameter IDO until | +c | either convergence is indicated or maxitr | +c | has been exceeded. | +c %---------------------------------------------% +c + call dsaupd (ido, bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, work(ipworkd), + & work(ipworkl), lworkl, ierr) + + if (ido .eq. -1) then +c +c %--------------------------------------------% +c | Perform y <--- OP*x = inv[A-SIGMA*M]*M*x | +c | to force the starting vector into the | +c | range of OP. The user should supply | +c | his/her own matrix vector multiplication | +c | routine and a linear system solver here. | +c | The matrix vector multiplication routine | +c | takes workd(ipntr(1)) as the input vector. | +c | The final result is returned to | +c | workd(ipntr(2)). | +c %--------------------------------------------% +c + call eig_mv (work(ipntr(1)), work(ipntr(2))) + call eig_sol (work(ipntr(2)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV4' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 1) then +c +c %-----------------------------------------% +c | Perform y <-- OP*x = inv[A-sigma*M]*M*x | +c | M*x has been saved in workd(ipntr(3)). | +c | the user only needs the linear system | +c | solver here that takes workd(ipntr(3)) | +c | as input, and returns the result to | +c | workd(ipntr(2)). | +c %-----------------------------------------% +c + call eig_sol (work(ipntr(3)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV4' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 2) then +c +c %-----------------------------------------% +c | Perform y <--- M*x | +c | Need the matrix vector multiplication | +c | routine here that takes workd(ipntr(1)) | +c | as the input and returns the result to | +c | workd(ipntr(2)). | +c %-----------------------------------------% +c + call eig_mv (work(ipntr(1)), work(ipntr(2))) +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + end if +c +c %-----------------------------------------% +c | Either we have convergence, or there is | +c | an error. | +c %-----------------------------------------% +c + if (ierr .lt. 0) then +c +c %--------------------------% +c | Error message, check the | +c | documentation in DSAUPD. | +c %--------------------------% +c + print *, ' ' + print *, ' Error with dsaupd, info = ',ierr + print *, ' Check the documentation of dsaupd' + print *, ' ' + return +c + else if (ierr .eq. 1) then + print *, ' ' + print *, ' Maximum number of iterations reached.' + print *, ' ' + else if (ierr .eq. 3) then + print *, ' ' + print *, ' No shifts could be applied during implicit', + & ' Arnoldi update, try increasing NCV.' + print *, ' ' + end if +c +c %-------------------------------------------% +c | No fatal errors occurred. | +c | Post-Process using DSEUPD. | +c | | +c | Computed eigenvalues may be extracted. | +c | | +c | Eigenvectors may also be computed now if | +c | desired. (indicated by rvec = .true.) | +c %-------------------------------------------% +c + call dseupd (.true., 'All', work(ipselec), d, v, n, sigma, + & bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, + & work(ipworkd), work(ipworkl), lworkl, ierr) +c +c %----------------------------------------------% +c | Eigenvalues are returned in the first column | +c | of the two dimensional array D and the | +c | corresponding eigenvectors are returned in | +c | the first NEV columns of the two dimensional | +c | array V if requested. Otherwise, an | +c | orthogonal basis for the invariant subspace | +c | corresponding to the eigenvalues in D is | +c | returned in V. | +c %----------------------------------------------% +c + if (ierr .ne. 0) then +c +c %------------------------------------% +c | Error condition: | +c | Check the documentation of DSEUPD. | +c %------------------------------------% +c + print *, ' ' + print *, ' Error with dseupd, info = ', ierr + print *, ' Check the documentation of dseupd' + print *, ' ' + return +c + end if +c + nconv = iparam(5) + do 30 j = 1, nconv +c +c %---------------------------% +c | Compute the residual norm | +c | | +c | || A*x - lambda*M*x || | +c | | +c | for the NCONV accurately | +c | computed eigenvalues and | +c | eigenvectors. (iparam(5) | +c | indicates how many are | +c | accurate to the requested | +c | tolerance) | +c %---------------------------% +c + call eig_av (v(1,j), work) + call eig_mv (v(1,j), work(n+1)) + call daxpy (n, -d(j,1), work(n+1), 1, work, 1) + d(j,2) = dnrm2(n, work, 1) + d(j,2) = d(j,2) / abs(d(j,1)) +c + 30 continue +c + call dmout (6, nconv, 2, d, ncv, -6, + & 'Ritz values and relative residuals') +c +c +c %------------------------------------------% +c | Print additional convergence information | +c %------------------------------------------% +c + print *, ' ' + print *, ' EIG_DRV4' + print *, ' ========' + print *, ' ' + print *, ' Size of the matrix is ', n + print *, ' The number of Ritz values requested is ', nev + print *, ' The number of Arnoldi vectors generated', + & ' (NCV) is ', ncv + print *, ' What portion of the spectrum: ', which + print *, ' The number of converged Ritz values is ', nconv + print *, ' The number of Implicit Arnoldi update', + & ' iterations taken is ', iparam(3) + print *, ' The number of OP*x is ', iparam(9) + print *, ' The convergence criterion is ', tol + print *, ' ' +c +c %----------------------------% +c | Done with program eig_drv4 | +c %----------------------------% +c + end diff --git a/src/Eig/eig_drv5.f b/src/Eig/eig_drv5.f new file mode 100644 index 00000000..b78a9706 --- /dev/null +++ b/src/Eig/eig_drv5.f @@ -0,0 +1,390 @@ + subroutine eig_drv5 (n,nev,ncv,sigma,d,v,work,ierr) +c +c $Id: eig_drv5.f,v 1.1 2009-12-08 15:32:49 kmo Exp $ +c----------------------------------------------------------------------- +c\BeginDoc +c +c\Name: eig_drv5 +c +c\Description: +c Driver to solve a generalized eigenvalue problem using the +c reverse communication interface in ARPACK, Buckling mode. +c This subroutine is based on the test driver DSDRV5 from the +c symmetric examples of the ARPACK distribution. +c +c ... Suppose we want to solve K*x = lambda*Kg*x in Buckling mode, +c where K and Kg are material and geometric stiffness matrices, +c respectively. +c +c ... OP = (inv[K - sigma*Kg])*K and B = K. +c +c ... Use mode 4 of DSAUPD. +c +c\Usage: +c call eig_drv5 ( N, NEV, NCV, SIGMA, D, V, WORK, IERR ) +c +c\Arguments +c N Integer. (INPUT) +c Dimension of the eigenproblem. +c +c NEV Integer. (INPUT) +c Number of eigenvalues of OP to be computed. 0 < NEV < N. +c +c NCV Integer. (INPUT) +c Number of columns of the matrix V (less than or equal to N). +c +c SIGMA Double precision. (INPUT) +c The shift value. +c +c D Double precision NCV by 2 array. (OUTPUT) +c The first column of D contain the eigenvalues. +c +c V Double precision N by NCV array. (OUTPUT) +c The NCV columns of V contain the Lanczos basis vectors. +c +c WORK Double precision work array. (OUTPUT/WORKSPACE) +c +c +c IERR Integer. (OUTPUT) +c Error flag. +c +c\EndDoc +c----------------------------------------------------------------------- +c\BeginLib +c +c\References: +c 1. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c SIAM J. Matr. Anal. Apps., January (1993). +c +c\Routines called: +c dsaupd ARPACK reverse communication interface routine. +c dseupd ARPACK routine that returns Ritz values and vectors. +c daxpy Level 1 BLAS that computes y <- alpha*x+y. +c dnrm2 Level 1 BLAS that computes the norm of a vector. +c eig_sol Linear solver routine that computes inv[K - sigma*Kg]*x +c eig_av Matrix vector multiplication routine that computes K*x. +c eig_mv Matrix vector multiplication routine that computes Kg*x. +c +c\Author +c Knut Morten Okstad +c Dept. of Applied Mathematics +c SINTEF ICT +c Trondheim, Norway +c +c\EndLib +c----------------------------------------------------------------------- +c + implicit none +c +c %-----------% +c | Arguments | +c %-----------% +C + integer n, nev, ncv, ierr + Double precision sigma, d(ncv,2), v(n,ncv), work(*) +c +c %--------------% +c | Local Arrays | +c %--------------% +c + integer iparam(11), ipntr(11) +c +c %---------------% +c | Local Scalars | +c %---------------% +c + character bmat*1, which*2 + integer ipresid, ipselec, ipworkd, ipworkl, lworkl, + & ido, j, nconv, maxitr, ishfts, mode + Double precision tol +c +c %-----------------------------% +c | BLAS & LAPACK routines used | +c %-----------------------------% +c + Double precision dnrm2 + external dnrm2, daxpy, eig_sol, eig_av, eig_mv +c +c %--------------------% +c | Intrinsic function | +c %--------------------% +c + intrinsic abs +c +c %-----------------------% +c | Executable statements | +c %-----------------------% +c + print *, ' ' + print *, ' Entering EIG_DRV5',n,nev,ncv,sigma +c +c %--------------------------------------------------% +c | The number N is the dimension of the matrix. A | +c | generalized eigenvalue problem is solved (BMAT = | +c | 'G'.) NEV is the number of eigenvalues to be | +c | approximated. Since the buckling mode is used, | +c | WHICH is set to 'LM'. The user can modify NEV, | +c | NCV, SIGMA to solve problems of different sizes, | +c | and to get different parts of the spectrum. | +c | | +c | The shift SIGMA cannot be zero !!! | +c %--------------------------------------------------% +c + bmat = 'G' + which = 'LM' +c +c %--------------------------------------------------% +c | The work array WORKL is used in DSAUPD as | +c | workspace. Its dimension LWORKL is set as | +c | illustrated below. The parameter TOL determines | +c | the stopping criterion. If TOL<=0, machine | +c | precision is used. The variable IDO is used for | +c | reverse communication and is initially set to 0. | +c | Setting INFO=0 indicates that a random vector is | +c | generated in DSAUPD to start the Arnoldi | +c | iteration. | +c %--------------------------------------------------% +c + lworkl = ncv*(ncv+8) + tol = 0.0D0 + ido = 0 + ierr = 0 +c +c %---------------------------------------------------% +c | This program uses exact shifts with respect to | +c | the current Hessenberg matrix (IPARAM(1) = 1). | +c | IPARAM(3) specifies the maximum number of Arnoldi | +c | iterations allowed. Mode 4 specified in the | +c | documentation of DSAUPD is used (IPARAM(7) = 4). | +c | All these options may be changed by the user. | +c | For details, see the documentation in DSAUPD. | +c %---------------------------------------------------% +c + ishfts = 1 + maxitr = 300 + mode = 4 +c + iparam(1) = ishfts + iparam(3) = maxitr + iparam(7) = mode +c + ipworkd = 1 + ipworkl = ipworkd + 3*n + ipresid = ipworkl + lworkl + ipselec = ipresid + n +c +c %-------------------------------------------% +c | M A I N L O O P (Reverse communication) | +c %-------------------------------------------% +c + 10 continue +c +c %---------------------------------------------% +c | Repeatedly call the routine DSAUPD and take | +c | actions indicated by parameter IDO until | +c | either convergence is indicated or maxitr | +c | has been exceeded. | +c %---------------------------------------------% +c + call dsaupd (ido, bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, work(ipworkd), + & work(ipworkl), lworkl, ierr) + + if (ido .eq. -1) then +c +c %--------------------------------------------% +c | Perform y <--- OP*x = inv[K-SIGMA*Kg]*K*x | +c | to force the starting vector into the | +c | range of OP. The user should supply | +c | his/her own matrix vector multiplication | +c | routine and a linear system solver here. | +c | The matrix vector multiplication routine | +c | takes workd(ipntr(1)) as the input vector. | +c | The final result is returned to | +c | workd(ipntr(2)). | +c %--------------------------------------------% +c + call eig_av (work(ipntr(1)), work(ipntr(2))) + call eig_sol (work(ipntr(2)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV5' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 1) then +c +c %------------------------------------------% +c | Perform y <-- OP*x = inv[K-sigma*Kg]*K*x | +c | K*x has been saved in workd(ipntr(3)). | +c | the user only needs the linear system | +c | solver here that takes workd(ipntr(3)) | +c | as input, and returns the result to | +c | workd(ipntr(2)). | +c %------------------------------------------% +c + call eig_sol (work(ipntr(3)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV5' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 2) then +c +c %-----------------------------------------% +c | Perform y <--- K*x | +c | Need the matrix vector multiplication | +c | routine here that takes workd(ipntr(1)) | +c | as the input and returns the result to | +c | workd(ipntr(2)). | +c %-----------------------------------------% +c + call eig_av (work(ipntr(1)), work(ipntr(2))) +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + end if +c +c %-----------------------------------------% +c | Either we have convergence, or there is | +c | an error. | +c %-----------------------------------------% +c + if (ierr .lt. 0) then +c +c %--------------------------% +c | Error message, check the | +c | documentation in DSAUPD. | +c %--------------------------% +c + print *, ' ' + print *, ' Error with dsaupd, info = ',ierr + print *, ' Check the documentation of dsaupd' + print *, ' ' + return +c + else if (ierr .eq. 1) then + print *, ' ' + print *, ' Maximum number of iterations reached.' + print *, ' ' + else if (ierr .eq. 3) then + print *, ' ' + print *, ' No shifts could be applied during implicit', + & ' Arnoldi update, try increasing NCV.' + print *, ' ' + end if +c +c %-------------------------------------------% +c | No fatal errors occurred. | +c | Post-Process using DSEUPD. | +c | | +c | Computed eigenvalues may be extracted. | +c | | +c | Eigenvectors may also be computed now if | +c | desired. (indicated by rvec = .true.) | +c %-------------------------------------------% +c + call dseupd (.true., 'All', work(ipselec), d, v, n, sigma, + & bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, + & work(ipworkd), work(ipworkl), lworkl, ierr) +c +c %----------------------------------------------% +c | Eigenvalues are returned in the first column | +c | of the two dimensional array D and the | +c | corresponding eigenvectors are returned in | +c | the first NEV columns of the two dimensional | +c | array V if requested. Otherwise, an | +c | orthogonal basis for the invariant subspace | +c | corresponding to the eigenvalues in D is | +c | returned in V. | +c %----------------------------------------------% +c + if (ierr .ne. 0) then +c +c %------------------------------------% +c | Error condition: | +c | Check the documentation of DSEUPD. | +c %------------------------------------% +c + print *, ' ' + print *, ' Error with dseupd, info = ', ierr + print *, ' Check the documentation of dseupd' + print *, ' ' + return +c + end if +c + nconv = iparam(5) + do 30 j = 1, nconv +c +c %---------------------------% +c | Compute the residual norm | +c | | +c | || K*x - lambda*Kg*x || | +c | | +c | for the NCONV accurately | +c | computed eigenvalues and | +c | eigenvectors. (iparam(5) | +c | indicates how many are | +c | accurate to the requested | +c | tolerance) | +c %---------------------------% +c + call eig_av (v(1,j), work) + call eig_mv (v(1,j), work(n+1)) + call daxpy (n, -d(j,1), work(n+1), 1, work, 1) + d(j,2) = dnrm2(n, work, 1) + d(j,2) = d(j,2) / abs(d(j,1)) +c + 30 continue +c + call dmout (6, nconv, 2, d, ncv, -6, + & 'Ritz values and relative residuals') +c +c +c %------------------------------------------% +c | Print additional convergence information | +c %------------------------------------------% +c + print *, ' ' + print *, ' EIG_DRV5' + print *, ' ========' + print *, ' ' + print *, ' Size of the matrix is ', n + print *, ' The number of Ritz values requested is ', nev + print *, ' The number of Arnoldi vectors generated', + & ' (NCV) is ', ncv + print *, ' What portion of the spectrum: ', which + print *, ' The number of converged Ritz values is ', nconv + print *, ' The number of Implicit Arnoldi update', + & ' iterations taken is ', iparam(3) + print *, ' The number of OP*x is ', iparam(9) + print *, ' The convergence criterion is ', tol + print *, ' ' +c +c %----------------------------% +c | Done with program eig_drv5 | +c %----------------------------% +c + end diff --git a/src/Eig/eig_drv6.f b/src/Eig/eig_drv6.f new file mode 100644 index 00000000..eebfd0bb --- /dev/null +++ b/src/Eig/eig_drv6.f @@ -0,0 +1,393 @@ + subroutine eig_drv6 (n,nev,ncv,sigma,d,v,work,ierr) +c +c $Id: eig_drv6.f,v 1.1 2009-12-08 15:32:49 kmo Exp $ +c----------------------------------------------------------------------- +c\BeginDoc +c +c\Name: eig_drv6 +c +c\Description: +c Driver to solve a generalized eigenvalue problem using the +c reverse communication interface in ARPACK, Cayley mode. +c This subroutine is based on the test driver DSDRV6 from the +c symmetric examples of the ARPACK distribution. +c +c ... Suppose we want to solve A*x = lambda*M*x in Cayley mode, +c where A and M are stiffness and mass matrix, respectively. +c +c ... OP = (inv[A - sigma*M])*(A + sigma*M) and B = M. +c +c ... Use mode 5 of DSAUPD. +c +c\Usage: +c call eig_drv6 ( N, NEV, NCV, SIGMA, D, V, WORK, IERR ) +c +c\Arguments +c N Integer. (INPUT) +c Dimension of the eigenproblem. +c +c NEV Integer. (INPUT) +c Number of eigenvalues of OP to be computed. 0 < NEV < N. +c +c NCV Integer. (INPUT) +c Number of columns of the matrix V (less than or equal to N). +c +c SIGMA Double precision. (INPUT) +c The shift value. +c +c D Double precision NCV by 2 array. (OUTPUT) +c The first column of D contain the eigenvalues. +c +c V Double precision N by NCV array. (OUTPUT) +c The NCV columns of V contain the Lanczos basis vectors. +c +c WORK Double precision work array. (OUTPUT/WORKSPACE) +c +c +c IERR Integer. (OUTPUT) +c Error flag. +c +c\EndDoc +c----------------------------------------------------------------------- +c\BeginLib +c +c\References: +c 1. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c SIAM J. Matr. Anal. Apps., January (1993). +c +c\Routines called: +c dsaupd ARPACK reverse communication interface routine. +c dseupd ARPACK routine that returns Ritz values and vectors. +c daxpy Level 1 BLAS that computes y <- alpha*x+y. +c dnrm2 Level 1 BLAS that computes the norm of a vector. +c eig_sol Linear solver routine that computes inv[A - sigma*M]*x +c eig_av Matrix vector multiplication routine that computes A*x. +c eig_mv Matrix vector multiplication routine that computes M*x. +c +c\Author +c Knut Morten Okstad +c Dept. of Applied Mathematics +c SINTEF ICT +c Trondheim, Norway +c +c\EndLib +c----------------------------------------------------------------------- +c + implicit none +c +c %-----------% +c | Arguments | +c %-----------% +C + integer n, nev, ncv, ierr + Double precision sigma, d(ncv,2), v(n,ncv), work(*) +c +c %--------------% +c | Local Arrays | +c %--------------% +c + integer iparam(11), ipntr(11) +c +c %---------------% +c | Local Scalars | +c %---------------% +c + character bmat*1, which*2 + integer ipresid, ipselec, iptemp, ipworkd, ipworkl, + & lworkl, ido, j, nconv, maxitr, ishfts, mode + Double precision tol +c +c %-----------------------------% +c | BLAS & LAPACK routines used | +c %-----------------------------% +c + Double precision dnrm2 + external dnrm2, daxpy, eig_sol, eig_av, eig_mv +c +c %--------------------% +c | Intrinsic function | +c %--------------------% +c + intrinsic abs +c +c %-----------------------% +c | Executable statements | +c %-----------------------% +c + print *, ' ' + print *, ' Entering EIG_DRV6',n,nev,ncv,sigma +c +c %---------------------------------------------------% +c | The number N is the dimension of the matrix. A | +c | generalized eigenvalue problem is solved (BMAT = | +c | 'G'.) NEV is the number of eigenvalues to be | +c | approximated. Since the Cayley mode is used, | +c | WHICH is set to 'LM'. The user can modify NEV, | +c | NCV, SIGMA to solve problems of different sizes, | +c | and to get different parts of the spectrum. | +c %---------------------------------------------------% +c + bmat = 'G' + which = 'LM' +c +c %--------------------------------------------------% +c | The work array WORKL is used in DSAUPD as | +c | workspace. Its dimension LWORKL is set as | +c | illustrated below. The parameter TOL determines | +c | the stopping criterion. If TOL<=0, machine | +c | precision is used. The variable IDO is used for | +c | reverse communication and is initially set to 0. | +c | Setting INFO=0 indicates that a random vector is | +c | generated in DSAUPD to start the Arnoldi | +c | iteration. | +c %--------------------------------------------------% +c + lworkl = ncv*(ncv+8) + tol = 0.0D0 + ido = 0 + ierr = 0 +c +c %---------------------------------------------------% +c | This program uses exact shifts with respect to | +c | the current Hessenberg matrix (IPARAM(1) = 1). | +c | IPARAM(3) specifies the maximum number of Arnoldi | +c | iterations allowed. Mode 5 specified in the | +c | documentation of DSAUPD is used (IPARAM(7) = 5). | +c | All these options may be changed by the user. | +c | For details, see the documentation in DSAUPD. | +c %---------------------------------------------------% +c + ishfts = 1 + maxitr = 300 + mode = 5 +c + iparam(1) = ishfts + iparam(3) = maxitr + iparam(7) = mode +c + ipworkd = 1 + ipworkl = ipworkd + 3*n + ipresid = ipworkl + lworkl + ipselec = ipresid + n + iptemp = ipselec + ncv +c +c %-------------------------------------------% +c | M A I N L O O P (Reverse communication) | +c %-------------------------------------------% +c + 10 continue +c +c %---------------------------------------------% +c | Repeatedly call the routine DSAUPD and take | +c | actions indicated by parameter IDO until | +c | either convergence is indicated or maxitr | +c | has been exceeded. | +c %---------------------------------------------% +c + call dsaupd (ido, bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, work(ipworkd), + & work(ipworkl), lworkl, ierr) + + if (ido .eq. -1) then +c +c %------------------------------------------------------% +c | Perform y <--- OP*x = inv[A-SIGMA*M]*(A+SIGMA*M)*x | +c | to force the starting vector into the range of OP. | +c | The user should supply his/her own matrix vector | +c | multiplication routine and a linear system solver | +c | here. The matrix vector multiplication routine takes | +c | takes workd(ipntr(1)) as the input vector. | +c | The final result is returned to workd(ipntr(2)). | +c %------------------------------------------------------% +c + call eig_av (work(ipntr(1)), work(ipntr(2))) + call eig_mv (work(ipntr(1)), work(iptemp)) + call daxpy (n, sigma, work(iptemp), 1 ,work(ipntr(2)), 1) + call eig_sol (work(ipntr(2)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV6' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 1) then +c +c %----------------------------------------------------% +c | Perform y <-- OP*x = inv[A-SIGMA*M]*(A+SIGMA*M)*x. | +c | M*x has been saved in workd(ipntr(3)). The user | +c | need the matrix vector multiplication (A*x) | +c | routine and a linear system solver here. The | +c | matrix vector multiplication routine takes | +c | workd(ipntr(1)) as the input, and the result is | +c | combined with workd(ipntr(3)) to form the input | +c | for the linear system solver. The final result is | +c | returned to workd(ipntr(2)). | +c %----------------------------------------------------% +c + call eig_av (work(ipntr(1)), work(ipntr(2))) + call daxpy (n, sigma, work(ipntr(3)), 1, work(ipntr(2)), 1) + call eig_sol (work(ipntr(2)), work(ipntr(2)), ierr) + if (ierr .ne. 0) then + print *, ' ' + print *, ' Error with eig_sol in EIG_DRV6' + print *, ' ' + return + end if +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + else if (ido .eq. 2) then +c +c %-----------------------------------------% +c | Perform y <--- M*x | +c | Need the matrix vector multiplication | +c | routine here that takes workd(ipntr(1)) | +c | as the input and returns the result to | +c | workd(ipntr(2)). | +c %-----------------------------------------% +c + call eig_mv (work(ipntr(1)), work(ipntr(2))) +c +c %-----------------------------------------% +c | L O O P B A C K to call DSAUPD again. | +c %-----------------------------------------% +c + go to 10 +c + end if +c +c %-----------------------------------------% +c | Either we have convergence, or there is | +c | an error. | +c %-----------------------------------------% +c + if (ierr .lt. 0) then +c +c %--------------------------% +c | Error message, check the | +c | documentation in DSAUPD. | +c %--------------------------% +c + print *, ' ' + print *, ' Error with dsaupd, info = ',ierr + print *, ' Check the documentation of dsaupd' + print *, ' ' + return +c + else if (ierr .eq. 1) then + print *, ' ' + print *, ' Maximum number of iterations reached.' + print *, ' ' + else if (ierr .eq. 3) then + print *, ' ' + print *, ' No shifts could be applied during implicit', + & ' Arnoldi update, try increasing NCV.' + print *, ' ' + end if +c +c %-------------------------------------------% +c | No fatal errors occurred. | +c | Post-Process using DSEUPD. | +c | | +c | Computed eigenvalues may be extracted. | +c | | +c | Eigenvectors may also be computed now if | +c | desired. (indicated by rvec = .true.) | +c %-------------------------------------------% +c + call dseupd (.true., 'All', work(ipselec), d, v, n, sigma, + & bmat, n, which, nev, tol, work(ipresid), + & ncv, v, n, iparam, ipntr, + & work(ipworkd), work(ipworkl), lworkl, ierr) +c +c %----------------------------------------------% +c | Eigenvalues are returned in the first column | +c | of the two dimensional array D and the | +c | corresponding eigenvectors are returned in | +c | the first NEV columns of the two dimensional | +c | array V if requested. Otherwise, an | +c | orthogonal basis for the invariant subspace | +c | corresponding to the eigenvalues in D is | +c | returned in V. | +c %----------------------------------------------% +c + if (ierr .ne. 0) then +c +c %------------------------------------% +c | Error condition: | +c | Check the documentation of DSEUPD. | +c %------------------------------------% +c + print *, ' ' + print *, ' Error with dseupd, info = ', ierr + print *, ' Check the documentation of dseupd' + print *, ' ' + return +c + end if +c + nconv = iparam(5) + do 30 j = 1, nconv +c +c %---------------------------% +c | Compute the residual norm | +c | | +c | || A*x - lambda*M*x || | +c | | +c | for the NCONV accurately | +c | computed eigenvalues and | +c | eigenvectors. (iparam(5) | +c | indicates how many are | +c | accurate to the requested | +c | tolerance) | +c %---------------------------% +c + call eig_av (v(1,j), work) + call eig_mv (v(1,j), work(n+1)) + call daxpy (n, -d(j,1), work(n+1), 1, work, 1) + d(j,2) = dnrm2(n, work, 1) + d(j,2) = d(j,2) / abs(d(j,1)) +c + 30 continue +c + call dmout (6, nconv, 2, d, ncv, -6, + & 'Ritz values and relative residuals') +c +c +c %------------------------------------------% +c | Print additional convergence information | +c %------------------------------------------% +c + print *, ' ' + print *, ' EIG_DRV6' + print *, ' ========' + print *, ' ' + print *, ' Size of the matrix is ', n + print *, ' The number of Ritz values requested is ', nev + print *, ' The number of Arnoldi vectors generated', + & ' (NCV) is ', ncv + print *, ' What portion of the spectrum: ', which + print *, ' The number of converged Ritz values is ', nconv + print *, ' The number of Implicit Arnoldi update', + & ' iterations taken is ', iparam(3) + print *, ' The number of OP*x is ', iparam(9) + print *, ' The convergence criterion is ', tol + print *, ' ' +c +c %----------------------------% +c | Done with program eig_drv6 | +c %----------------------------% +c + end diff --git a/src/Integrands/AnalyticSolutions.C b/src/Integrands/AnalyticSolutions.C new file mode 100644 index 00000000..9a38e129 --- /dev/null +++ b/src/Integrands/AnalyticSolutions.C @@ -0,0 +1,272 @@ +// $Id: AnalyticSolutions.C,v 1.11 2011-02-08 12:19:52 rho Exp $ +//============================================================================== +//! +//! \file AnalyticSolutions.C +//! +//! \date Jul 1 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Analytic solutions for some linear elasticity and Poisson problems. +//! +//============================================================================== + +#include "AnalyticSolutions.h" +#include "Vec3.h" +#include "Vec3Oper.h" +#include + + +/*! + \class Hole + + Smooth plane strain problem. + + Reference: Zienkiewicz & Zhu, "The superconvergent patch recovery...", + IJNME, 33, 1331-1364, 1992, page 1355 (eq. 36). +*/ + +Tensor Hole::evaluate (const Vec3& X) const +{ + double R = hypot(X.x,X.y); + double th = atan2(X.y,X.x); + double C2 = cos(2.0*th); + double C4 = cos(4.0*th); + double S2 = sin(2.0*th); + double S4 = sin(4.0*th); + double R2 = R <= a ? 1.0 : a*a/(R*R); + double R4 = R <= a ? 1.0 : R2*R2; + + SymmTensor sigma(is3D ? 3 : 2); + sigma(1,1) = F0 * (1.0 - R2*(1.5*C2 + C4) + 1.5*R4*C4); + sigma(2,2) = F0 * ( - R2*(0.5*C2 - C4) - 1.5*R4*C4); + sigma(1,2) = F0 * ( - R2*(0.5*S2 + S4) + 1.5*R4*S4); + if (is3D) sigma(3,3) = F0 * nu*(1.0 - 2.0*R2*C2); + + return sigma; +} + + +/*! + \class Lshape + + Symmetric (mode 1) loading. Plane strain problem with a singular point. + + Reference: Szabo & Babuska, "Finite Element Analysis", 1991, page 188-192. + + \code + y ^ + | + + (-a,a) +--------+(0,a) + | | + | | + | | + | +--------+ (a,0) - - -> x + | (0,0) | + | | + | | + (-a,-a) +-----------------+ (a,-a) + + \endcode +*/ + +Lshape::Lshape (double r, double f, double P, bool use3D) + : a(r), F0(f), nu(P), is3D(use3D), T(use3D ? 3 : 2) +{ + // Set up the local-to-global transformation tensor + T(1,1) = T(2,2) = T(2,1) = -sqrt(0.5); + T(1,2) = sqrt(0.5); + + if (is3D) T(3,3) = 1.0; +} + + +Tensor Lshape::evaluate (const Vec3& X) const +{ + // Some constants (see Szabo & Babuska for an elaboration) + const double lambda = 0.544483737; + const double q = 0.543075579; + const double lp1 = lambda + 1.0; + const double lm1 = lambda - 1.0; + const double lm3 = lambda - 3.0; + const double tol = a*1.0e-32; + + // Find local (polar) coordinates + double x = X.x*T(1,1) + X.y*T(2,1); + double y = X.x*T(1,2) + X.y*T(2,2); + double r = hypot(x,y); + if (r < tol) r = tol; // truncate the singularity to avoid NaN values + double theta = atan2(y,x); + + // Set up the stress tensor in local system + SymmTensor sigma(is3D ? 3 : 2); + double c0 = F0*lambda*pow(r,lm1); + sigma(1,1) = c0*((2.0-q*lp1)*cos(lm1*theta) - lm1*cos(lm3*theta)); + sigma(2,2) = c0*((2.0+q*lp1)*cos(lm1*theta) + lm1*cos(lm3*theta)); + sigma(1,2) = c0*( q*lp1 *sin(lm1*theta) + lm1*sin(lm3*theta)); + if (is3D) sigma(3,3) = nu * (sigma(1,1)+sigma(2,2)); + + // Transform to global coordinates + return sigma.transform(T); +} + + +Tensor CanTS::evaluate (const Vec3& X) const +{ + double x = X.x/L; + double y = (is3D ? X.z : X.y)/H - 0.5; + double I = H*H*H / 12.0; + size_t n = is3D ? 3 : 2; + + SymmTensor sigma(n); + sigma(1,1) = F0*L*H/I * (x-1.0)*y; + sigma(1,n) = F0*H*H/I * 0.5*(0.25-y*y); + + return sigma; +} + + +Tensor CanTM::evaluate (const Vec3& X) const +{ + double y = (is3D ? X.z : X.y)/H - 0.5; + double I = H*H*H / 12.0; + + SymmTensor sigma(is3D ? 3 : 2); + sigma(1,1) = M0*H/I * y; + + return sigma; +} + + +/*! + \class CurvedBeam + + Smooth plane stress problem with a prescribed edge displacement. + + Reference: Zienkiewicz & Taylor, "The Finite Element Method", + pages 42-45. +*/ + +CurvedBeam::CurvedBeam (double u0, double Ri, double Ro, double E, bool use3D) + : a(Ri), b(Ro), is3D(use3D), T(use3D ? 3 : 2) +{ + if (is3D) T(3,3) = 1.0; + + PN = -u0*E/(M_PI*(a*a+b*b)); +} + + +Tensor CurvedBeam::evaluate (const Vec3& X) const +{ + // Find polar coordinates + double r = hypot(X.x,X.y); + double theta = atan2(X.y,X.x); + + // Set up the stress tensor in polar coordinates + SymmTensor sigma(is3D ? 3 : 2); + double c1 = a*a*b*b/(r*r*r); + double c2 = (a*a + b*b)/r; + double ct = cos(theta); + double st = sin(theta); + sigma(1,1) = PN*( r + c1 - c2)*st; + sigma(2,2) = PN*(3.0*r - c1 - c2)*st; + sigma(1,2) = PN*( -r - c1 + c2)*ct; + + // Transform to Cartesian coordinates + T(1,1) = ct; + T(2,1) = st; + T(1,2) = -st; + T(2,2) = ct; + return sigma.transform(T); +} + + +Vec3 Square2D::evaluate (const Vec3& X) const +{ + double x = X.x; + double y = X.y; + double pi = M_PI; + + Vec3 temp; + temp.x = -pi*sin(pi*x)*(2.0-y); + temp.y = -cos(pi*x); + + return temp; +} + + +double Square2DHeat::evaluate (const Vec3& X) const +{ + double x = X.x; + double y = X.y; + double pi = M_PI; + + return -pi*pi*cos(pi*x)*(2.0-y); +} + + +Vec3 LshapePoisson::evaluate (const Vec3& X) const +{ + double x = X.x; + double y = X.y; + double R = hypot(x,y); + double pi = M_PI; + double Rp = pow(R,-1.0/3.0); + double th = x > 0.0 ? asin(y/R) : pi-asin(y/R); + double frac = 2.0/3.0; + + Vec3 temp; + temp.x = frac*Rp*sin(th/3.0); + temp.y = -frac*Rp*cos(th/3.0); + + return temp; +} + + +Vec3 PoissonCube::evaluate (const Vec3& X) const +{ + double x = X.x; + double y = X.y; + double z = X.z; + double pi = M_PI; + + Vec3 temp; + temp.x = -pi*cos(x*pi)*sin(y*pi)*sin(z*pi); + temp.y = -pi*sin(x*pi)*cos(y*pi)*sin(z*pi); + temp.z = -pi*sin(x*pi)*sin(y*pi)*cos(z*pi); + + return temp; +} + + +double PoissonCubeSource::evaluate (const Vec3& X) const +{ + double x = X.x; + double y = X.y; + double z = X.z; + double pi = M_PI; + + return 3.0*pi*pi*sin(x*pi)*sin(y*pi)*sin(z*pi); +} + + +Vec3 PoissonLine::evaluate (const Vec3& X) const +{ + double x = X.x; + double pi = M_PI; + + Vec3 temp; + temp.x = -(pi/L)*cos(x*pi/L); + + return temp; +} + + +double PoissonLineSource::evaluate (const Vec3& X) const +{ + double x = X.x; + double pi = M_PI; + + return (pi*pi)/(L*L)*sin(pi*x/L); +} diff --git a/src/Integrands/AnalyticSolutions.h b/src/Integrands/AnalyticSolutions.h new file mode 100644 index 00000000..0af27885 --- /dev/null +++ b/src/Integrands/AnalyticSolutions.h @@ -0,0 +1,279 @@ +// $Id: AnalyticSolutions.h,v 1.10 2011-02-08 12:19:37 rho Exp $ +//============================================================================== +//! +//! \file AnalyticSolutions.h +//! +//! \date Jul 1 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Analytic solutions for some linear elasticity and Poisson problems. +//! +//============================================================================== + +#ifndef _ANALYTIC_SOLUTIONS_H +#define _ANALYTIC_SOLUTIONS_H + +#include "Function.h" +#include "Tensor.h" + + +/*! + \brief Analytic solution for an infinite plate with a hole. +*/ + +class Hole : public TensorFunc +{ +public: + //! \brief Constructor with some default parameters. + Hole(double r = 1.0, double f = 1.0, double P = 0.3, bool use3D = false) + : a(r), F0(f), nu(P), is3D(use3D) {} + //! \brief Empty destructor. + virtual ~Hole() {} + +protected: + //! \brief Evaluates the analytic stress tensor at the point \a x. + virtual Tensor evaluate(const Vec3& x) const; + +private: + bool is3D; //!< Flag telling whether to return a 3D stress tensor or not + double a; //!< Hole radius + double F0; //!< Load factor + double nu; //!< Poisson's ratio +}; + + +/*! + \brief Analytic solution for the L-shaped domain. +*/ + +class Lshape : public TensorFunc +{ +public: + //! \brief Constructor with some default parameters. + Lshape(double r = 1.0, double f = 1.0, double P = 0.3, bool use3D = false); + //! \brief Empty destructor. + virtual ~Lshape() {} + +protected: + //! \brief Evaluates the analytic stress tensor at the point \a x. + virtual Tensor evaluate(const Vec3& x) const; + +private: + bool is3D; //!< Flag telling whether to return a 3D stress tensor or not + double a; //!< Length parameter + double F0; //!< Load factor + double nu; //!< Poisson's ratio + + Tensor T; //!< Local-to-global stress transformation tensor +}; + + +/*! + \brief Analytic solution for the cantilever beam with a tip shear load. +*/ + +class CanTS : public TensorFunc +{ +public: + //! \brief Constructor with some default parameters. + CanTS(double l, double h, double f = 1.0, bool use3D = false) + : L(l), H(h), F0(f), is3D(use3D) {} + //! \brief Empty destructor. + virtual ~CanTS() {} + +protected: + //! \brief Evaluates the analytic stress tensor at the point \a x. + virtual Tensor evaluate(const Vec3& x) const; + +private: + bool is3D; //!< Flag telling whether to return a 3D stress tensor or not + double L; //!< Length + double H; //!< Height + double F0; //!< Load factor +}; + + +/*! + \brief Analytic solution for the cantilever beam with a tip moment load. +*/ + +class CanTM : public TensorFunc +{ +public: + //! \brief Constructor with some default parameters. + CanTM(double h, double m = 1.0, bool use3D = false) + : H(h), M0(m), is3D(use3D) {} + //! \brief Empty destructor. + virtual ~CanTM() {} + +protected: + //! \brief Evaluates the analytic stress tensor at the point \a x. + virtual Tensor evaluate(const Vec3& x) const; + +private: + bool is3D; //!< Flag telling whether to return a 3D stress tensor or not + double H; //!< Height + double M0; //!< Load factor +}; + + +/*! + \brief Analytic solution for the curved beam with end shear. +*/ + +class CurvedBeam : public TensorFunc +{ +public: + //! \brief Constructor with some default parameters. + CurvedBeam(double u0 = 0.1, double Ri = 1.0, double Ro = 2.0, + double E = 2.1e7, bool use3D = false); + //! \brief Empty destructor. + virtual ~CurvedBeam() {} + +protected: + //! \brief Evaluates the analytic stress tensor at the point \a x. + virtual Tensor evaluate(const Vec3& x) const; + +private: + bool is3D; //!< Flag telling whether to return a 3D stress tensor or not + double a; //!< Inner radius + double b; //!< Outer radius + double PN; //!< Load parameter + + mutable Tensor T; //!< Local-to-global stress transformation tensor +}; + + +/*! + \brief Analytic solution for the Poisson equation on a square. +*/ + +class Square2D : public VecFunc +{ +public: + //! \brief Empty constructor. + Square2D(double = 1.0) {} + //! \brief Empty destructor. + virtual ~Square2D() {} + +protected: + //! \brief Evaluates the analytic flux vector at the point \a X. + virtual Vec3 evaluate(const Vec3& X) const; +}; + + +/*! + \brief Heat source for the Poisson equation on a square. +*/ + +class Square2DHeat : public RealFunc +{ +public: + //! \brief Empty constructor. + Square2DHeat(double = 1.0) {} + //! \brief Empty destructor. + virtual ~Square2DHeat() {} + +protected: + //! \brief Evaluates the heat field at the point \a X. + virtual double evaluate(const Vec3& X) const; +}; + + +/*! + \brief Analytic solution for the L-shape problem for the Poisson equation. +*/ + +class LshapePoisson : public VecFunc +{ +public: + //! \brief Empty constructor. + LshapePoisson() {} + //! \brief Empty destructor. + virtual ~LshapePoisson() {} + +protected: + //! \brief Evaluates the analytic flux vector at the point \a X. + virtual Vec3 evaluate(const Vec3& X) const; +}; + + +/*! + \brief Analytic solution for the Poisson equation on a cube. +*/ + +class PoissonCube : public VecFunc +{ +public: + //! \brief Empty Constructor. + PoissonCube() {} + //! \brief Empty destructor. + virtual ~PoissonCube() {} + +protected: + //! \brief Evaluates the analytic flux vector at the point \a X. + virtual Vec3 evaluate(const Vec3& X) const; +}; + + +/*! + \brief Heat source for the Poisson equation on a cube. +*/ + +class PoissonCubeSource : public RealFunc +{ +public: + //! \brief Empty constructor. + PoissonCubeSource() {} + //! \brief Empty destructor. + virtual ~PoissonCubeSource() {} + +protected: + //! \brief Evaluates the heat field at the point \a X. + virtual double evaluate(const Vec3& X) const; +}; + + +/*! + \brief Analytic solution for the Poisson equation on a line. +*/ + +class PoissonLine : public VecFunc +{ +public: + //! \brief Empty Constructor. + PoissonLine(double r = 1.0) : L(r) {} + //! \brief Empty destructor. + virtual ~PoissonLine() {} + +protected: + //! \brief Evaluates the analytic flux vector at the point \a X. + virtual Vec3 evaluate(const Vec3& X) const; + +private: + double L; //!< Length parameter +}; + + +/*! + \brief Heat source for the Poisson equation on a line. +*/ + +class PoissonLineSource : public RealFunc +{ +public: + //! \brief Empty constructor. + PoissonLineSource(double r = 1.0) : L(r) {} + //! \brief Empty destructor. + virtual ~PoissonLineSource() {} + +protected: + //! \brief Evaluates the heat field at the point \a X. + virtual double evaluate(const Vec3& X) const; + +private: + double L; //!< Length parameter +}; + +#endif diff --git a/src/Integrands/AnalyticSolutionsStokes.C b/src/Integrands/AnalyticSolutionsStokes.C new file mode 100644 index 00000000..b465e7f0 --- /dev/null +++ b/src/Integrands/AnalyticSolutionsStokes.C @@ -0,0 +1,169 @@ +//============================================================================== +//! +//! \file AnalyticSolutionsStokes.C +//! +//! \date Des 10 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Analytic solutions for some Stokes and Navier-Stokes problems. +//! +//============================================================================== + +#include "AnalyticSolutionsStokes.h" +#include "Vec3.h" +#include "Vec3Oper.h" +#include "Tensor.h" +#include + +// Define pi +#define PI 3.141592653589793 + + +/*! + \class Poiseuille + + Channel flow +*/ + +Poiseuille::Poiseuille(double P, double diam, double len, double visc) + : Pin(P), D(diam), L(len), mu(visc) +{ + scalSol = new Pressure(*this); + vecSol = new Velocity(*this); + scalSecSol = new PressureGrad(*this); + vecSecSol = new VelocityGrad(*this); + //vecGrad = new VelocityGrad(*this); + + scalarSol = true; + vectorSol = true; +} + + +Poiseuille::~Poiseuille() {} + + +// Pressure solution +real Poiseuille::Pressure::evaluate(const Vec3& x) const +{ + return params.Pin*(1.0-x[0]/params.L); +} + + +// Velocity solution +Vec3 Poiseuille::Velocity::evaluate(const Vec3& x) const +{ + Vec3 U; + U = 0.0; + + double dPdx = -params.Pin/params.L; + double coeff = -dPdx/(2.0*params.mu); + + U[0] = coeff*x[1]*(params.D-x[1]); + + return U; +} + + +// Pressure gradient +Vec3 Poiseuille::PressureGrad::evaluate(const Vec3& x) const +{ + Vec3 dPdX; + dPdX = 0.0; + + dPdX[0] = -params.Pin/params.L; + + return dPdX; +} + + +// Velocity gradient +Tensor Poiseuille::VelocityGrad::evaluate(const Vec3& x) const +{ + Tensor dUdX(3); + dUdX.zero(); + dUdX(1,2) = params.Pin/(2.0*params.L*params.mu)*(1.0-2.0*x[1]); + + return dUdX; +} + + +/*! + \class TestSolution + + Analytical test solution using trigonometrical functions +*/ + +TestSolution::TestSolution(double density, double visc) + : rho(density), mu(visc) +{ + scalSol = new Pressure(); + vecSol = new Velocity(); + scalSecSol = new PressureGrad(); + vecSecSol = new VelocityGrad(); + //vecGrad = new VelocityGrad(*this); + + scalarSol = true; + vectorSol = true; +} + + +TestSolution::~TestSolution() {} + + +// Pressure +real TestSolution::Pressure::evaluate(const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + double t = X->t; + + return cos(PI*x[0])*sin(PI*x[1])*sin(t); +} + + +// Velocity +Vec3 TestSolution::Velocity::evaluate(const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + double t = X->t; + + Vec3 u; + u[0] = pow(sin(PI*x[0]),2.0)*sin(2.0*PI*x[1])*sin(t); + u[1] = -sin(2.0*PI*x[0])*pow(sin(PI*x[1]),2.0)*sin(t); + u[2] = 0.0; + + return u; +} + + +// Pressure gradient +Vec3 TestSolution::PressureGrad::evaluate(const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + double t = X->t; + + Vec3 dPdX; + dPdX[0] = -PI*sin(PI*x[0])*sin(PI*x[1])*sin(t); + dPdX[1] = PI*cos(PI*x[0])*cos(PI*x[1])*sin(t); + dPdX[2] = 0.0; + + return dPdX; +} + + +// Velocity gradient +Tensor TestSolution::VelocityGrad::evaluate(const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + double t = X->t; + + Tensor dUdX(3); + dUdX.zero(); + dUdX(1,1) = 2.0*PI*sin(PI*x[0])*cos(PI*x[0])*sin(2.0*PI*x[1])*sin(t); + dUdX(1,2) = 2.0*PI*pow(sin(PI*x[0]),2.0)*cos(2.0*PI*x[1])*sin(t); + dUdX(2,1) = -2.0*PI*cos(2.0*PI*x[0])*pow(sin(PI*x[1]),2.0)*sin(t); + dUdX(2,2) = - 2.0*PI*sin(2*PI*x[0])*sin(PI*x[1])*cos(PI*x[1])*sin(t); + + return dUdX; +} + diff --git a/src/Integrands/AnalyticSolutionsStokes.h b/src/Integrands/AnalyticSolutionsStokes.h new file mode 100644 index 00000000..41ea40dd --- /dev/null +++ b/src/Integrands/AnalyticSolutionsStokes.h @@ -0,0 +1,160 @@ +//============================================================================== +//! +//! \file AnalyticSolutionsStokes.h +//! +//! \date Des 10 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Analytical solutions for some Stokes and Navier-Stokes problems. +//! +//============================================================================== + +#ifndef _ANALYTIC_SOLUTIONS_STOKES_H +#define _ANALYTIC_SOLUTIONS_STOKES_H + +#include "AnaSol.h" + + +/*! + \brief Analytic solution for Poiseuille flow +*/ + +class Poiseuille : public AnaSol +{ + public: + //! \brief Constructor for pressure inlet BC + Poiseuille(double P = 1.0, double diam = 1.0, double len = 1.0, double visc = 1.0); + //! \brief Destructor + virtual ~Poiseuille(); + + private: + double D; // Diameter of channel + double L; // Length of channel + double mu; // Fluid viscosity + double Pin; // Inlet value + + // Analytical pressure + class Pressure : public RealFunc + { + private: + Poiseuille& params; + + protected: + real evaluate(const Vec3& x) const; + + public: + Pressure(Poiseuille& p) : params(p) {} + virtual ~Pressure() {} + }; + + // Analytical velocity + class Velocity : public VecFunc + { + private: + Poiseuille& params; + + protected: + Vec3 evaluate(const Vec3& x) const; + + public: + Velocity(Poiseuille& p) : params(p) {} + virtual ~Velocity() {} + }; + + + // Analytical pressure gradient + class PressureGrad : public VecFunc + { + private: + Poiseuille& params; + + protected: + Vec3 evaluate(const Vec3& x) const; + + public: + PressureGrad(Poiseuille& p) : params(p) {} + virtual ~PressureGrad() {} + }; + + // Analytical velocity gradient + class VelocityGrad : public TensorFunc + { + private: + Poiseuille& params; + + protected: + Tensor evaluate(const Vec3& x) const; + + public: + VelocityGrad(Poiseuille& p) : params(p) {} + virtual ~VelocityGrad() {} + }; +}; + + +/*! + \brief Artificial solution for Navier-Stokes equations +*/ + +class TestSolution : public AnaSol +{ + public: + //! \brief Constructor + //! \param[in] rho Fluid density + //! \param[in] mu Fluid viscosity + TestSolution(double rho = 1.0, double mu = 1.0); + //! \brief Destructor + virtual ~TestSolution(); + + private: + double rho; // Fluid density + double mu; // Fluid viscosity + + // Analytical pressure + class Pressure : public RealFunc + { + protected: + real evaluate(const Vec3& x) const; + + public: + Pressure() {} + virtual ~Pressure() {} + }; + + // Analytical velocity + class Velocity : public VecFunc + { + protected: + Vec3 evaluate(const Vec3& x) const; + + public: + Velocity() {} + virtual ~Velocity() {} + }; + + + // Analytical pressure gradient + class PressureGrad : public VecFunc + { + protected: + Vec3 evaluate(const Vec3& x) const; + + public: + PressureGrad() {} + virtual ~PressureGrad() {} + }; + + // Analytical velocity gradient + class VelocityGrad : public TensorFunc + { + protected: + Tensor evaluate(const Vec3& x) const; + + public: + VelocityGrad() {} + virtual ~VelocityGrad() {} + }; +}; + +#endif diff --git a/src/Integrands/ChorinPressCorr.C b/src/Integrands/ChorinPressCorr.C new file mode 100644 index 00000000..1b0d6cea --- /dev/null +++ b/src/Integrands/ChorinPressCorr.C @@ -0,0 +1,330 @@ +//============================================================================== +//! +//! \file ChorinPressCorr.C +//! +//! \date Sep 30 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for pressure correction in Chorin's method +//! +//============================================================================== + +#include "ChorinPressCorr.h" +#include "Utilities.h" +#include "Vec3Oper.h" + +ChorinPressCorr::ChorinPressCorr(unsigned short int n, double B0, + bool incPress, bool mixed) + : nsd(n), Beta0(B0), incPressure(incPress), mixedFEM(mixed) +{ + // Set default values for density and viscosity + rho = mu = 1.0; + + // Initialize pointers to zero + tracFld = 0; + + // Initialize element matrix and vector + myMats = new ElmMats(); + myMats->A.resize(1); + myMats->b.resize(1); + myMats->rhsOnly = false; + + eM = &myMats->A[0]; + eS = &myMats->b[0]; + + // Current velocity and pressure solutions are needed + int nUsols = 1; + usol.resize(nUsols); + eVs.resize(nUsols); + for (int i = 0;i < nUsols;i++) + eVs[i] = new Vector(); + int nPsols = 1; + primsol.resize(nPsols); + ePs.resize(nPsols); + for (int i = 0;i < nPsols;i++) + ePs[i] = new Vector(); +} + + +ChorinPressCorr::~ChorinPressCorr() +{ + int i; + + // Delete element matrix/vector + if (myMats) delete myMats; + + // Delete element solution vectors + if (!eVs.empty()) + for (i = 0;i < eVs.size();i++) + if (eVs[i]) delete eVs[i]; + + if (!ePs.empty()) + for (i = 0;i < ePs.size();i++) + if (ePs[i]) delete ePs[i]; +} + + +bool ChorinPressCorr::initElement(const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eM) eM->resize(nen,nen,true); + if (eS) eS->resize(nen,true); + + int ierr = 0; + if (!ePs.empty() && !primsol.empty()) + for (int i = 0;i < ePs.size();i++) + if (ierr = utl::gather(MNPC,1,primsol[i],*ePs[i])) + std::cerr << "*** ChorinPressCorr::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + if (!eVs.empty() && !usol.empty()) + for (int i = 0;i < eVs.size();i++) + if (ierr = utl::gather(MNPC,nsd,usol[i],*eVs[i])) + std::cerr << "*** ChorinPressCorr::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool ChorinPressCorr::initElement(const std::vector& MNPC1, + const std::vector& MNPC2, + size_t n1) +{ + // Only pressure degrees of freedom + const size_t nen2 = MNPC2.size(); + + if (eM) eM->resize(nen2,nen2,true); + if (eS) eS->resize(nen2,true); + + // RUNAR + std::vector MNPC = MNPC2; + for (int i = 0;i < MNPC.size();i++) + MNPC[i] -= n1; + + // Extract element pressure vectors + int ierr = 0; + if (!ePs.empty() && !primsol.empty()) + for (int i = 0;i < ePs.size();i++) + if (ierr = utl::gather(MNPC2,1,primsol[i],*ePs[i])) + std::cerr << "*** ChorinPressCorrMixed:initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + // Extract element velocity vectors + if (!eVs.empty() && !usol.empty()) + for (int i = 0;i < eVs.size();i++) + if (ierr = utl::gather(MNPC1,nsd,usol[i],*eVs[i])) + std::cerr << "*** ChorinPressCorrMixed::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool ChorinPressCorr::initElementBou(const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eS) eS->resize(nsd*nen,true); + + myMats->withLHS = false; + return true; +} + + +bool ChorinPressCorr::initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, + size_t n1) +{ + // Only pressure degrees of freedom + const size_t nen2 = MNPC2.size(); + + if (eS) eS->resize(nen2,true); + + // Extract the element level velocity vector + // Extract element pressure vectors + int ierr = 0; + if (!ePs.empty() && !primsol.empty()) + if (ierr = utl::gather(MNPC2,1,primsol[0],*ePs[0])) + std::cerr << "*** ChorinPressCorrMixed:initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + // Extract element velocity vectors + if (eVs.empty() && !usol.empty()) + if (ierr = utl::gather(MNPC1,nsd,usol[0],*eVs[0])) + std::cerr << "*** ChorinPressCorrMixed::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + myMats->withLHS = false; + return ierr == 0; +} + + +bool ChorinPressCorr::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, j, k; + double divU; + double laplace, rhoDtInv; + double B0; + + // Time coefficient + rhoDtInv = 1.0/time.dt; + rhoDtInv *= rho*detJW; + + // Time integration parameter + if (fabs(time.t - time.dt) < 1.0e-8) + B0 = 1.0; + else + B0 = Beta0; + + // Divergence of velocity at integration point + divU = 0.0; + Vector& EV = *eVs[0]; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + divU += EV((i-1)*nsd+k)*dNdX(i,k); + + // Assembling lhs terms + if (eM && !myMats->rhsOnly) { + Matrix& EM = *eM; + + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= detJW; + + EM(j,i) += laplace; + } + } + + // Assembling rhs terms + if (eS) { + Vector& ES = *eS; + + for (i = 1;i <= N.size();i++) + ES(i) -= B0*rhoDtInv*divU*N(i); + } + + return getIntegralResult(elmInt); +} + + +bool ChorinPressCorr::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, + double detJW, + const Vector& N1, + const Vector& N2, + const Matrix& dN1dX, + const Matrix& dN2dX, + const Vec3& X) const +{ + int i, j, k; + double divU; + double laplace, rhoDtInv; + double B0; + + // Time coefficient + rhoDtInv = 1.0/time.dt; + rhoDtInv *= rho*detJW; + + // Time integration parameter + if (fabs(time.t - time.dt) < 1.0e-10) + B0 = 1.0; + else + B0 = Beta0; + + // Divergence of velocity at integration point + divU = 0.0; + Vector& EV = *eVs[0]; + for (i = 1;i <= N1.size();i++) + for(k = 1;k <= nsd;k++) + divU += EV((i-1)*nsd+k)*dN1dX(i,k); + + // Assembling matrix terms + if (eM && !myMats->rhsOnly) { + Matrix& EM = *eM; + + for (i = 1;i <= N2.size();i++) + for (j = 1;j <= N2.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dN2dX(i,k)*dN2dX(j,k); + laplace *= detJW; + + EM(j,i) += laplace; + } + } + + // Assembling rhs terms + if (eS) { + Vector& ES = *eS; + + for (i = 1;i <= N2.size();i++) + ES(i) -= B0*rhoDtInv*divU*N2(i); + } + + return getIntegralResult(elmInt); +} + + +bool ChorinPressCorr::evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + return getIntegralResult(elmInt); +} + + +bool ChorinPressCorr::evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const +{ + int i, k; + Vector P; + + if (primsol.empty()) { + std::cerr << " *** ChorinPressCorr::evalSol: No primary solution." + << std::endl; + return false; + } + + // Element pressure vector + int ierr = 0; + if (ierr = utl::gather(MNPC,1,primsol[0],P)) { + std::cerr << " *** ChorinPressCorr::evalSol: Detected " + << ierr << " node numbers out of range." << std::endl; + return false; + } + + // Evaluate flux + s.resize(nsd,true); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + s(k) -= P(i)*dNdX(i,k); + + return true; +} + + +bool ChorinPressCorr::evalSolScal(Vector& s, + const VecFunc& asol, const Vec3& X) const +{ + s = Vector(asol(X).ptr(),nsd); + return true; +} + + +bool ChorinPressCorr::getIntegralResult (LocalIntegral*& elmInt) const +{ + elmInt = myMats; + return elmInt ? true : false; +} diff --git a/src/Integrands/ChorinPressCorr.h b/src/Integrands/ChorinPressCorr.h new file mode 100644 index 00000000..5909ca10 --- /dev/null +++ b/src/Integrands/ChorinPressCorr.h @@ -0,0 +1,218 @@ +//============================================================================== +//! +//! \file ChorinPressCorr.h +//! +//! \date Sep 30 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for pressure correction in Chorin's method +//! +//============================================================================== + +#ifndef _CHORIN_PRESS_CORR_H +#define _CHORIN_PRESS_CORR_H + +#include "IntegrandBase.h" +#include "SIMenums.h" +#include "ElmMats.h" +#include "Vec3.h" +#include "TimeDomain.h" +#include + +class LocalSystem; +class VTF; + +/*! + \brief Class representing the integrand of the pressure correction step + in Chorin's method. + \details This class implements the pressure correction step of Chorin's + method using NURBS based FEM with equal order elements for velocity and + pressure. The resulting system is a Poisson equation for the pressure + increment. +*/ + +class ChorinPressCorr : public Integrand +{ + public: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + ChorinPressCorr(unsigned short int n = 3, double B0 = 1.0, + bool incPress = false, bool mixed = false); + //! \brief The destructor frees the dynamically allocated data objects. + virtual ~ChorinPressCorr(); + + //! \brief Defines the traction field to use in Neumann boundary conditions. + void setTraction(VecFunc* tf) { tracFld = tf; } + + //! \brief Clears the integration point traction values. + void clearTracVal() { tracVal.clear(); } + + //! \brief Defines fluid properties for current volume patch. + //! \param[in] Density Mass density + //! \param[in] Viscosity Dynamic viscosity + void setFluidProperties(double Density, double Viscosity) + { rho = Density; mu = Viscosity; } + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + bool initElement(const std::vector& MNPC); + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC1 Nodal point correspondance for the velocity + //! \param[in] MNPC2 Nodal point correspondance for the pressure + //! \param[in] n1 Number of nodes in basis 1 on this patch + bool initElement(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1); + + //! \brief Initializes current element for boundary integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + bool initElementBou(const std::vector& MNPC); + //! \brief Initializes current element for numerical boundary integration. + //! \param[in] MNPC1 Nodal point correspondance for the velocity + //! \param[in] MNPC2 Nodal point correspondance for the pressure + bool initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, size_t); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the mixed field problem integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field velocity + //! \param[in] N2 Basis function values, field pressure + //! \param[in] dN1dX Basis function gradients, velocity + //! \param[in] dN2dX Basis function gradients, pressure + //! \param[in] X Cartesian coordinates of current integration point + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N2 Basis function values, pressure + //! \param[in] dN1dX Basis function gradients, pressure + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details The boundary integral is the same as that of the parent class. + //! It does not depend on the pressure and volumetric change fields. + //! Thus, this call is forwarded to the single-field parent class method. + virtual bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector&, const Vector& N2, + const Matrix&, const Matrix& dN2dX, + const Vec3& X, const Vec3& normal) const + { + return this->evalBou(elmInt,time,detJW,N2,dN2dX,X,normal); + } + + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s Array of solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the secondary solution at a result point (mixed problem). + //! \param[out] s The solution field values at current point + //! \param[in] N2 Basis function values at current point, pressure + //! \param[in] dN2dX Basis function gradients at current point, pressure + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC2 Nodal point correspondance for the pressure + virtual bool evalSol(Vector& s, + const Vector&, const Vector& N2, + const Matrix&, const Matrix& dN2dX, const Vec3& X, + const std::vector& MNPC2) const + { + return this->ChorinPressCorr::evalSol(s,N2,dN2dX,X,MNPC2); + } + + //! \brief Evaluates the analytical solution at an integration point. + //! \param[out] s The analytical secondary solution values at current point + //! \param[in] asol The analytical solution field + //! \param[in] X Cartesian coordinates of current point + bool evalSolScal(Vector& s, const VecFunc& asol, const Vec3& X) const; + + //! \brief Returns whether there are any traction values to write to VTF. + bool hasTractionValues() const { return !tracVal.empty(); } + + //! \brief Returns whether an incremental pressure formulation is used. + bool incrementalPressure() const { return incPressure; } + + //! \brief If a mixed FE formulation is used + bool mixedFormulation() const { return mixedFEM; } + + //! \brief Accesses the velocity solution vectors of current patch + Vector& getVelocity(int n = 0) { return usol[n]; } + + //! \brief Accesses the pressure solution vectors of current patch + Vector& getPressure(int n = 0) { return getSolution(n); } + + //! \brief If the element matrix should be assembled + void assembleMatrix(bool newMat) { myMats->rhsOnly = !newMat; } + + //! \brief Get number of velocity solutions + size_t getNoVelocities() const { return usol.size(); } + + //! \brief Get number of pressure solutions + size_t getNoPressures() const { return this->getNoSolutions(); } + + protected: + //! \brief Utility used by the virtual \a evalInt and \a evalBou methods. + //! \param elmInt Pointer to the integrated element quantities + bool getIntegralResult(LocalIntegral*& elmInt) const; + + // Physical properties (constant) + double mu; //!< Dynamic viscosity + double rho; //!< Fluid density + + unsigned short int nsd; //!< Number of space dimensions (1, 2, or 3) + + bool incPressure; //!< Incremental pressure flag + bool mixedFEM; //!< Mixed FE formulation + + double Beta0; //!< Time integration parameter + + // Finite element quantities + Matrix* eM; //!< Pointer to element matrix + Vector* eS; //!< Pointer to element rhs vector + std::vector eVs; //!< Pointer to element velocity vectors + std::vector ePs; //!< Pointer to element pressure vectors + + std::vector usol; //!< Velocity solution vectors for patch + + ElmMats* myMats; //!< Local element matrices + + VecFunc* tracFld; //!< Pointer to boundary traction field + mutable std::map tracVal; //!< Traction field point values +}; + +#endif diff --git a/src/Integrands/ChorinVelCorr.C b/src/Integrands/ChorinVelCorr.C new file mode 100644 index 00000000..6558d0bb --- /dev/null +++ b/src/Integrands/ChorinVelCorr.C @@ -0,0 +1,349 @@ +//============================================================================== +//! +//! \file ChorinVelCorr.C +//! +//! \date Sep 30 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for velocity correction in Chorin's method +//! +//============================================================================== + +#include "ChorinVelCorr.h" +#include "Utilities.h" +#include "Vec3Oper.h" + +ChorinVelCorr::ChorinVelCorr(unsigned short int n, double B0, + bool incPress, bool mixed) + : nsd(n), Beta0(B0), incPressure(incPress), mixedFEM(mixed) +{ + // Set default values for density and viscosity + rho = mu = 1.0; + + // Initialize pointers to zero + tracFld = 0; + + // Initialize element matrix and vector + myMats = new ElmMats(); + myMats->A.resize(1); + myMats->b.resize(1); + myMats->rhsOnly = false; + + eM = &myMats->A[0]; + eS = &myMats->b[0]; + + // Velocity, temporary velocity and pressure correction is needed + int nUsols = 1; + primsol.resize(nUsols); + eVs.resize(nUsols); + for (int i = 0;i < nUsols;i++) + eVs[i] = new Vector(); + + int nPsols = 1; + psol.resize(nPsols); + ePs.resize(nPsols); + for (int i = 0;i < nPsols;i++) + ePs[i] = new Vector(); +} + + +ChorinVelCorr::~ChorinVelCorr() +{ + int i; + + // Delete element matrix/vector + if (myMats) delete myMats; + + // Delete element solution vectors + if (!eVs.empty()) + for (i = 0;i < eVs.size();i++) + if (eVs[i]) delete eVs[i]; + + if (!ePs.empty()) + for (i = 0;i < ePs.size();i++) + if (ePs[i]) delete ePs[i]; +} + + +bool ChorinVelCorr::initElement(const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eM) eM->resize(nsd*nen,nsd*nen,true); + if (eS) eS->resize(nsd*nen,true); + + int ierr = 0; + if (!eVs.empty() && !primsol.empty()) + for (int i = 0;i < eVs.size();i++) + if (ierr = utl::gather(MNPC,nsd,primsol[i],*eVs[i])) + std::cerr << "*** ChorinVelCorr::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + if (!ePs.empty() && !psol.empty()) + for (int i = 0;i < ePs.size();i++) + if (ierr = utl::gather(MNPC,1,psol[i],*ePs[i])) + std::cerr << "*** ChorinVelCorr::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool ChorinVelCorr::initElement(const std::vector& MNPC1, + const std::vector& MNPC2, + size_t n1) +{ + // Only velocity degrees of freedom + const size_t nen1 = MNPC1.size(); + + if (eM) eM->resize(nsd*nen1,nsd*nen1,true); + if (eS) eS->resize(nsd*nen1,true); + + // Extract element velocity vector + int ierr = 0; + if (!eVs.empty() && !primsol.empty()) + for (int i = 0;i < eVs.size();i++) + if (ierr = utl::gather(MNPC1,nsd,primsol[i],*eVs[i])) + std::cerr << "*** ChorinVelCorrMixed::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + if (!ePs.empty() && !psol.empty()) + for (int i = 0;i < ePs.size();i++) + if (ierr = utl::gather(MNPC2,1,psol[i],*ePs[i])) + std::cerr << "*** ChorinVelCorrMixed::initElement: Detected " + << ierr << " node numbers out of range." << std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool ChorinVelCorr::initElementBou(const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eS) eS->resize(nsd*nen,true); + + myMats->withLHS = false; + return true; +} + + +bool ChorinVelCorr::initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, + size_t n1) +{ + // Only velocity degrees of freedom + const size_t nen1 = MNPC1.size(); + + if (eS) eS->resize(nsd*nen1,true); + + // Extract element velocity vector + int ierr = 0; + if (!eVs.empty() && !primsol.empty()) + if (ierr = utl::gather(MNPC1,nsd,primsol[0],*eVs[0])) + std::cerr << "*** ChorinVelCorrMixed:initElementBou: Detected " + << ierr << " node numbers out of range." << std::endl; + + // Extract element pressure vector + if (!ePs.empty() && !psol.empty()) + if (ierr = utl::gather(MNPC2,1,psol[0],*ePs[0])) + std::cerr << "*** ChorinVelCorr:initElementBou: Detected " + << ierr << " node numbers out of range." << std::endl; + + myMats->withLHS = false; + return ierr == 0; +} + + +bool ChorinVelCorr::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, j, k; + double divU, P; + double B0; + double mass, rhoDtInv; + Vector U(nsd), dPdX(nsd); + + // Time coefficient + rhoDtInv = 1.0/time.dt; + rhoDtInv *= rho*detJW; + + // Time integration parameter + if (fabs(time.t-time.dt) < 1.0e-8) + B0 = 1.0; + else + B0 = Beta0; + + // Velocity at integration point + U.fill(0.0); + Vector& EV = *eVs[0]; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + U(k) += EV((i-1)*nsd + k)*N(i); + + // Pressure at integration point + Vector& EP = *ePs[0]; + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EP(i)*N(i); + + // Pressure gradient at integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += EP(i)*dNdX(i,k); + + // Assembling lhs terms + if (eM) { + Matrix& EM = *eM; + + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + mass = N(i)*N(j)*B0*rhoDtInv; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd+k,(i-1)*nsd+k) += mass; + } + } + + // Assembling rhs terms + if (eS) { + Vector& ES = *eS; + + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + //ES((i-1)*nsd+k) += B0*rhoDtInv*U(k)*N(i) - dPdX(k)*N(i)*detJW; + ES((i-1)*nsd+k) += B0*rhoDtInv*U(k)*N(i) + P*dNdX(i,k)*detJW; + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelCorr::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const +{ + int i, j, k; + double divU, P; + double B0; + double mass, rhoDtInv; + Vector U(nsd), dPdX(nsd); + + + // Time coefficient + rhoDtInv = 1.0/time.dt; + rhoDtInv *= rho*detJW; + + // Time integration parameter + if (fabs(time.t-time.dt) < 1.0e-10) + B0 = 1.0; + else + B0 = Beta0; + + // Velocity at integration point + U.fill(0.0); + Vector& EV = *eVs[0]; + for (i = 1;i <= N1.size();i++) + for (k = 1;k <= nsd;k++) + U(k) += EV((i-1)*nsd + k)*N1(i); + + // Pressure at integration point + Vector& EP = *ePs[0]; + P = 0.0; + for (i = 1;i <= N2.size();i++) + P += EP(i)*N2(i); + + // Pressure gradient at integration point + dPdX.fill(0.0); + for (i = 1;i <= N2.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += EP(i)*dN2dX(i,k); + + // Assembling lhs terms + if (eM) { + Matrix& EM = *eM; + + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + mass = N1(i)*N1(j)*B0*rhoDtInv; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd+k,(i-1)*nsd+k) += mass; + } + } + + // Assembling rhs terms + if (eS) { + Vector& ES = *eS; + + for (i = 1;i <= N1.size();i++) + for (k = 1;k <= nsd;k++) + //ES((i-1)*nsd+k) += B0*rhoDtInv*U(k)*N1(i) - dPdX(k)*N1(i)*detJW; + ES((i-1)*nsd+k) += B0*rhoDtInv*U(k)*N1(i) + P*dN1dX(i,k)*detJW; + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelCorr::evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + if (incPressure) + return getIntegralResult(elmInt); + + if (!tracFld) { + std::cerr <<" *** ChorinVelCorr::evalBou: No tractions."<< std::endl; + return false; + } + else if (!eS) { + std::cerr <<" *** ChorinVelCorr::evalBou: No load vector."<< std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (short int d = 1; d <= nsd; d++) + ES(nsd*(a-1)+d) -= T[d-1]*N(a)*detJW; + + return getIntegralResult(elmInt); +} + + +bool ChorinVelCorr::evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const +{ + return false; +} + + +bool ChorinVelCorr::evalSolScal(Vector& s, + const VecFunc& asol, const Vec3& X) const +{ + s = Vector(asol(X).ptr(),nsd); + return true; +} + + +bool ChorinVelCorr::getIntegralResult (LocalIntegral*& elmInt) const +{ + elmInt = myMats; + return elmInt ? true : false; +} diff --git a/src/Integrands/ChorinVelCorr.h b/src/Integrands/ChorinVelCorr.h new file mode 100644 index 00000000..6fd896dd --- /dev/null +++ b/src/Integrands/ChorinVelCorr.h @@ -0,0 +1,226 @@ +//============================================================================== +//! +//! \file ChorinVelCorr.h +//! +//! \date Sep 30 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for pressure correction in Chorin's method +//! +//============================================================================== + +#ifndef _CHORIN_VEL_CORR_H +#define _CHORIN_VEL_CORR_H + +#include "IntegrandBase.h" +#include "SIMenums.h" +#include "ElmMats.h" +#include "Vec3.h" +#include "TimeDomain.h" +#include + +class LocalSystem; +class VTF; + +/*! + \brief Class representing the integrand of the velocity correction step + in Chorin's method. + \details This class implements the velocity correction step of Chorin's + method using NURBS based FEM with equal order elements for velocity and + pressure. The resulting system is a mass equation for the velocity + increment. +*/ + +class ChorinVelCorr : public Integrand +{ + public: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + ChorinVelCorr(unsigned short int n = 3, double B0 = 1.0, + bool incPress = false, bool mixed = false); + //! \brief The destructor frees the dynamically allocated data objects. + virtual ~ChorinVelCorr(); + + //! \brief Defines the traction field to use in Neumann boundary conditions. + void setTraction(TractionFunc* tf) { tracFld = tf; } + + //! \brief Clears the integration point traction values. + void clearTracVal() { tracVal.clear(); } + + //! \brief Defines fluid properties for current volume patch. + //! \param[in] Density Mass density + //! \param[in] Viscosity Dynamic viscosity + void setFluidProperties(double Density, double Viscosity) + { rho = Density; mu = Viscosity; } + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + bool initElement(const std::vector& MNPC); + + //! \brief Initializes current element for numerical integration (mixed). + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + //! \param[in] n1 Number of nodes in basis 1 on this patch + bool initElement(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1); + + //! \brief Initializes current element for boundary integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + bool initElementBou(const std::vector& MNPC); + //! \brief Initializes current element for boundary integration (mixed). + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + //! \param[in] n1 Number of nodes in basis 1 on this patch + bool initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1); + + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field 1 + //! \param[in] N2 Basis function values, field 2 + //! \param[in] dN1dX Basis function gradients, field 1 + //! \param[in] dN2dX Basis function gradients, field 2 + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used for mixed formulations only. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N1 Basis function values, field 1 + //! \param[in] N2 Basis function values, field 2 + //! \param[in] dN1dX Basis function gradients, field 1 + //! \param[in] dN2dX Basis function gradients, field 2 + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details This interface is used for mixed formulations. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X, const Vec3& normal) const + { return this->evalBou(elmInt,time,detJW,N1,dN1dX,X,normal); } + + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s Array of solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + bool evalSol(Vector& s, const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the secondary solution at a result point (mixed problem). + //! \param[out] s The solution field values at current point + //! \param[in] N1 Basis function values at current point, field 1 + //! \param[in] N2 Basis function values at current point, field 2 + //! \param[in] dN1dX Basis function gradients at current point, field 1 + //! \param[in] dN2dX Basis function gradients at current point, field 2 + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC1 Nodal point correspondance for the basis 1 + //! \param[in] MNPC2 Nodal point correspondance for the basis 2 + bool evalSol(Vector& s, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X, + const std::vector& MNPC1, + const std::vector& MNPC2) const + { return this->evalSol(s,N1,dN1dX,X,MNPC1); } + + //! \brief Evaluates the analytical solution at an integration point. + //! \param[out] s The analytical secondary solution values at current point + //! \param[in] asol The analytical solution field + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSolScal(Vector& s, const VecFunc& asol, + const Vec3& X) const; + + //! \brief Returns whether there are any traction values to write to VTF. + bool hasTractionValues() const { return !tracVal.empty(); } + + //! \brief Mixed FE formulation + bool mixedFormulation() const { return mixedFEM; } + + //! \brief Accesses the velocity solution vectors of current patch + Vector& getVelocity(int n = 0) { return this->getSolution(n); } + + //! \brief Accesses the pressure solution vectors of the current patch + Vector& getPressure(int n = 0) { return psol[n]; } + + //! \brief If the element matrix should be assembled + void assembleMatrix(bool newMat) { myMats->rhsOnly = !newMat; } + + //! \brief Get number of velocity solutions + size_t getNoVelocities() const { return this->getNoSolutions(); } + + //! \brief Get number of pressure solutions + size_t getNoPressures() const { return psol.size(); } + + protected: + //! \brief Utility used by the virtual \a evalInt and \a evalBou methods. + //! \param elmInt Pointer to the integrated element quantities + bool getIntegralResult(LocalIntegral*& elmInt) const; + + // Physical properties (constant) + double mu; //!< Dynamic viscosity + double rho; //!< Fluid density + + unsigned short int nsd; //!< Number of space dimensions (1, 2, or 3) + + bool incPressure; //!< Incremental pressure formulation + bool mixedFEM; //!< Mixed FE formulation + + double Beta0; //!< Time integration parameter + + // Finite element quantities + Matrix* eM; //!< Pointer to element matrix + Vector* eS; //!< Pointer to element rhs vector + std::vector eVs; //!< Pointer to element velocity vectors + std::vector ePs; //!< Pointer to element pressure vectors + + std::vector psol; //!< Pressure solution vectors for patch + + ElmMats* myMats; //!< Local element matrices + + TractionFunc* tracFld; //!< Pointer to boundary traction field + mutable std::map tracVal; //!< Traction field point values +}; + +#endif diff --git a/src/Integrands/ChorinVelPred.C b/src/Integrands/ChorinVelPred.C new file mode 100644 index 00000000..569198d6 --- /dev/null +++ b/src/Integrands/ChorinVelPred.C @@ -0,0 +1,558 @@ +//$Id: ChorinVelPred.C,v 1.3 2011-02-08 12:32:28 rho Exp $ +//============================================================================== +//! +//! \file ChorinVelPred.C +//! +//! \date Sep 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for velocity prediction in Chorin's method +//! +//============================================================================== + +#include "ChorinVelPred.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "Utilities.h" + + +ChorinVelPred::ChorinVelPred(short int n, ProblemFormulation form, + int itg, bool incPress, bool mixed) + : Stokes(n,(SIM::Formulation)form,itg), incPressure(incPress), mixedFEM(mixed) +{ + // Number of fields equals number of space dimensions + nf = nsd; +} + + +ChorinVelPred::~ChorinVelPred() +{ + // Free memory for pressure element solution vectors + if (!ePs.empty()) + for (int n = 0;n < ePs.size();n++) + if (ePs[n]) delete ePs[n]; +} + + +bool ChorinVelPred::initElement(const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eM) eM->resize(nsd*nen,nsd*nen,true); + if (eS) eS->resize(nsd*nen,true); + + int ierr = 0; + if (!eVs.empty() && !primsol.empty()) + for (int i = 0;i < primsol.size();i++) + if (ierr = utl::gather(MNPC,nsd,primsol[i],*eVs[i])) + std::cerr <<" *** ChorinVelPred::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + if (!ePs.empty() && !psol.empty()) + for (int i = 0;i < psol.size();i++) + if (ierr = utl::gather(MNPC,1,psol[i],*ePs[i])) + std::cerr <<" *** ChorinVelPred::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool ChorinVelPred::initElement(const std::vector& MNPC1, + const std::vector& MNPC2, + size_t n1) +{ + // Only velocity degrees of freedom + const size_t nen1 = MNPC1.size(); + + if (eM) eM->resize(nsd*nen1,nsd*nen1,true); + if (eS) eS->resize(nsd*nen1,true); + + // Extract element velocity vectors + int ierr = 0; + if (!eVs.empty() && !primsol.empty()) + for (int i = 0;i < primsol.size();i++) + if (ierr = utl::gather(MNPC1,nsd,primsol[i],*eVs[i])) + std::cerr <<" *** ChorinVelPredMixed::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + // Extract element pressure vectors + if (!ePs.empty() && !psol.empty()) + for (int i = 0;i < psol.size();i++) + if (ierr = utl::gather(MNPC2,1,psol[i],*ePs[i])) + std::cerr <<" *** ChorinVelPredMixed::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool ChorinVelPred::initElementBou(const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eS) eS->resize(nsd*nen,true); + + myMats->withLHS = false; + return true; +} + + +bool ChorinVelPred::initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, + size_t) +{ + // Only velocity degrees of freedom + const size_t nen1 = MNPC1.size(); + + if (eS) eS->resize(nsd*nen1,true); + + myMats->withLHS = false; + return true; +} + + + +NormBase* ChorinVelPred::getNormIntegrand (AnaSol* asol) const +{ + return new ChorinStokesNorm(*const_cast(this),asol); +} + + +bool ChorinVelPred::evalSol (Vector& s, const Vector& N, + const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const +{ + int ierr = 0; + if (!eVs.empty() && eVs[0] && !primsol.front().empty()) + if (ierr = utl::gather(MNPC,nsd,primsol.front(),*(eVs[0]))) { + std::cerr << " *** ChorinVelPred::evalSol: Detected " + << ierr << " node numbers out of range." << std::endl; + return false; + } + if (!ePs.empty() && ePs[0] && !psol.front().empty()) + if (ierr = utl::gather(MNPC,1,psol.front(),*(ePs[0]))) { + std::cerr << " *** ChorinVelPred::evalSol: Detected " + << ierr << " node numbers out of range." << std::endl; + return false; + } + + // Evaluate stress tensor, sigma + SymmTensor sigma(nsd); + if (!this->stress(N,dNdX,sigma)) + return false; + + s = sigma; + return true; +} + + +bool ChorinVelPred::evalSol(Vector& s, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X, + const std::vector& MNPC1, + const std::vector& MNPC2) const +{ + // Extract element velocity vector + int ierr = 0; + if (!eVs.empty() && eVs[0] && !primsol.front().empty()) + if (ierr = utl::gather(MNPC1,nsd,primsol.front(),*(eVs[0]))) { + std::cerr << " *** ChorinVelPredMixed::evalSol: Detected " + << ierr << " node numbers out of range." << std::endl; + return false; + } + + // Extract element pressure vector + if (!ePs.empty() && ePs[0] && !psol.front().empty()) + if (ierr = utl::gather(MNPC2,1,psol.front(),*(ePs[0]))) { + std::cerr << " *** ChorinVelPredMixed::evalSol: Detected " + << ierr << " node numbers out of range." << std::endl; + return false; + } + + // Evaluate stress tensor, sigma + SymmTensor sigma(nsd); + if (!this->stress(N1,N2,dN1dX,dN2dX,sigma)) + return false; + + s = sigma; + return true; +} + + +bool ChorinVelPred::evalSol (Vector& s, const TensorFunc& asol, + const Vec3& X) const +{ + s = asol(X); + return true; +} + + +bool ChorinVelPred::strain(const Matrix& dNdX, SymmTensor& eps) const +{ + int i, k, l; + + if (dNdX.cols() < nsd) { + std::cerr <<" *** ChorinVelPred::strain: Invalid dimension on dNdX " + << dNdX.rows() <<" "<< dNdX.cols() << std::endl; + return false; + } + + if (!eVs.empty() && !eVs[0]->empty() && eps.dim() > 0) { + Vector& EV = *(eVs[0]); + for (i = 1;i <= dNdX.rows();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= k;l++) + eps(k,l) = dNdX(i,l)*EV((i-1)*nsd+k); + dNdX(i,k)*EV((i-1)*nsd+l); + + eps *= mu; + } + + return true; +} + + +bool ChorinVelPred::stress(const Vector& N, const Matrix& dNdX, SymmTensor& sigma) const +{ + int i, k; + + // Compute strain + if (!this->strain(dNdX,sigma)) + return false; + + // Compute pressure + if (!ePs.empty() && !ePs[0]->empty() && sigma.dim() > 0) { + double P = 0.0; + Vector& EP = *(ePs[0]); + for (i = 1;i <= N.size();i++) + P += EP(i)*N(i); + + // Add pressure and strain contributions + for (k = 1;k <= nsd;k++) + sigma(k,k) -= P; + } + + return true; +} + + +bool ChorinVelPred::stress(const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + SymmTensor& sigma) const +{ + int i, k; + + // Compute strain + if (!this->strain(dN1dX,sigma)) + return false; + + // Add pressure + if (!ePs.empty() && ePs[0]) { + double P = 0.0; + Vector& EP = *ePs[0]; + for (i = 1;i <= N2.size();i++) + P += EP(i)*N2(i); + + // Add pressure to strain + for (k = 1;k <= nsd;k++) + sigma(k,k) -= P; + } + + return true; +} + + +bool ChorinStokesNorm::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, k, l; + double value, eps, epsh; + + const int nsd = dNdX.cols(); + const int nf = nsd+1; + const int nbf = N.size(); + + if (!anasol->hasScalarSol() && !anasol->hasVectorSol()) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No analytical solution."<< std::endl; + return false; + } + + ElmNorm* eNorm = dynamic_cast(elmInt); + if (!eNorm) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No norm array."<< std::endl; + return false; + } + + // Find current element velocity vector + Vector* eV = const_cast(problem.getElementVelocity()); + if (!eV) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No velocity" << std::endl; + return false; + } + Vector& EV = *eV; + + // Find current element pressure vector + Vector* eP = const_cast(problem.getElementPressure()); + if (!eP) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No pressure" << std::endl; + return false; + } + Vector& EP = *eP; + + ElmNorm& pnorm = *eNorm; + int ip = 0; + // Pressure norms + if (anasol->getScalarSol()) { + // Analytical pressure + real P = (*anasol->getScalarSol())(X); + + // Computed pressure + real Ph = 0.0; + for (i = 1;i <= nbf;i++) + Ph += EP(i)*N(i); + + // Integral of computed pressure + pnorm[ip++] += Ph*detJW; + + // Integral of basis functions + pnorm[ip++] += detJW; + + // L2-norm of pressure error + P -= Ph; + pnorm[ip++] += P*P*detJW; + } + + // Velocity norms + if (anasol->getVectorSol()) { + // Analytical velocity + Vec3 U = (*anasol->getVectorSol())(X); + + // Computed velocity + Vec3 Uh; Uh = 0.0; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + Uh[k-1] += EV((i-1)*nsd + k)*N(i); + + // L2-norm of velocity error + U -= Uh; + pnorm[ip++] += U*U*detJW; + } + + // Pressure gradient norm + if (anasol->getScalarSecSol()) { + // Analytical pressure gradient + Vec3 dP = (*anasol->getScalarSecSol())(X); + + // Computed pressure gradient + Vec3 dPh; dPh = 0.0; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPh[k-1] += EP(i)*dNdX(i,k); + + // H1-seminorm of pressure + dP -= dPh; + pnorm[ip++] += dP*dP*detJW; + } + + // Energy norms + if (anasol->getVectorSecSol()) { + // Viscosity + const double mu = problem.getViscosity(X); + + // Analytical velocity gradient + Tensor gradU = (*anasol->getVectorSecSol())(X); + + // Computed velocity gradient + Matrix gradUh(nsd,nsd); + gradUh.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + gradUh(k,l) += EV((i-1)*nsd + k)*dNdX(i,l); + + if (problem.getFormulation() == Stokes::LAPLACE) { + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + // Energy norm of analytical solution + pnorm[ip] += mu*gradU(k,l)*gradU(k,l)*detJW; + // Energy norm of numerical solution + pnorm[ip+1] += mu*gradUh(k,l)*gradUh(k,l)*detJW; + // Energy norm of error + value = gradU(k,l)-gradUh(k,l); + pnorm[ip+2] += mu*value*value*detJW; + } + } + else { + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + // Strain of analytical solution + eps = 0.5*(gradU(k,l) + gradU(l,k)); + // Strain of computed solution + epsh = 0.5*(gradUh(k,l) + gradUh(l,k)); + // Energy norm of analytical solution + pnorm[ip] += mu*eps*eps*detJW; + // Energy norm of computed solution + pnorm[ip+1] += mu*epsh*epsh*detJW; + // Energy norm of error + eps -= epsh; + pnorm[ip+2] += mu*epsh*epsh*detJW; + } + } + } + + return true; +} + + +bool ChorinStokesNorm::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const +{ + int i, k, l; + double value, eps, epsh; + + const int nsd = dN1dX.cols(); + const int nf = nsd+1; + const int nbfU = N1.size(); + const int nbfP = N2.size(); + + if (!anasol->hasScalarSol() && !anasol->hasVectorSol()) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No analytical solution."<< std::endl; + return false; + } + + ElmNorm* eNorm = dynamic_cast(elmInt); + if (!eNorm) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No norm array."<< std::endl; + return false; + } + + // Find current element velocity vector + Vector* eV = const_cast(problem.getElementVelocity()); + if (!eV) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No svelocity" << std::endl; + return false; + } + Vector& EV = *eV; + + // Find current element pressure vector + Vector* eP = const_cast(problem.getElementPressure()); + if (!eP) { + std::cerr <<" *** ChorinStokesNorm::evalInt: No pressure" << std::endl; + return false; + } + Vector& EP = *eP; + + ElmNorm& pnorm = *eNorm; + int ip = 0; + // Pressure norms + if (anasol->getScalarSol()) { + // Analytical pressure + real P = (*anasol->getScalarSol())(X); + + // Computed pressure + real Ph = 0.0; + for (i = 1;i <= nbfP;i++) + Ph += EP(i)*N2(i); + + // Integral of computed pressure + pnorm[ip++] += Ph*detJW; + + // Integral of basis functions + pnorm[ip++] += detJW; + + // L2-norm of pressure error + P -= Ph; + pnorm[ip++] += P*P*detJW; + } + + // Velocity norms + if (anasol->getVectorSol()) { + // Analytical velocity + Vec3 U = (*anasol->getVectorSol())(X); + + // Computed velocity + Vec3 Uh; Uh = 0.0; + for (i = 1;i <= nbfU;i++) + for (k = 1;k <= nsd;k++) + Uh[k-1] += EV((i-1)*nsd + k)*N1(i); + + // L2-norm of velocity error + U -= Uh; + pnorm[ip++] += U*U*detJW; + } + + // Pressure gradient norm + if (anasol->getScalarSecSol()) { + // Analytical pressure gradient + Vec3 dP = (*anasol->getScalarSecSol())(X); + + // Computed pressure gradient + Vec3 dPh; dPh = 0.0; + for (i = 1;i <= nbfP;i++) + for (k = 1;k <= nsd;k++) + dPh[k-1] += EP(i)*dN2dX(i,k); + + // H1-seminorm of pressure + dP -= dPh; + pnorm[ip++] += dP*dP*detJW; + } + + // Energy norms + if (anasol->getVectorSecSol()) { + // Viscosity + const double mu = problem.getViscosity(X); + + // Analytical velocity gradient + Tensor gradU = (*anasol->getVectorSecSol())(X); + + // Computed velocity gradient + Matrix gradUh(nsd,nsd); + gradUh.fill(0.0); + for (i = 1;i <= nbfU;i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + gradUh(k,l) += EV((i-1)*nsd + k)*dN1dX(i,l); + + if (problem.getFormulation() == Stokes::LAPLACE) { + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + // Energy norm of analytical solution + pnorm[ip] += mu*gradU(k,l)*gradU(k,l)*detJW; + // Energy norm of numerical solution + pnorm[ip+1] += mu*gradUh(k,l)*gradUh(k,l)*detJW; + // Energy norm of error + value = gradU(k,l)-gradUh(k,l); + pnorm[ip+2] += mu*value*value*detJW; + } + } + else { + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + // Strain of analytical solution + eps = 0.5*(gradU(k,l) + gradU(l,k)); + // Strain of computed solution + epsh = 0.5*(gradUh(k,l) + gradUh(l,k)); + // Energy norm of analytical solution + pnorm[ip] += mu*eps*eps*detJW; + // Energy norm of computed solution + pnorm[ip+1] += mu*epsh*epsh*detJW; + // Energy norm of error + eps -= epsh; + pnorm[ip+2] += mu*epsh*epsh*detJW; + } + } + } + + return true; +} + + diff --git a/src/Integrands/ChorinVelPred.h b/src/Integrands/ChorinVelPred.h new file mode 100644 index 00000000..96670a72 --- /dev/null +++ b/src/Integrands/ChorinVelPred.h @@ -0,0 +1,276 @@ +//============================================================================== +//! +//! \file ChorinVelPred.h +//! +//! \date Sep 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementation for velocity prediction in Chorin's method +//! +//============================================================================== + +#ifndef _CHORIN_VEL_PRED_H +#define _CHORIN_VEL_PRED_H + +#include "Stokes.h" +#include "ElmMats.h" +#include "Vec3.h" +#include "TimeDomain.h" +#include + +class LocalSystem; +class VTF; + + +/*! + \brief Class representing the integrand of the velocity prediction step + in Chorin's method. + \details This class implements the velocity prediction step of Chorin's + method using NURBS based FEM with equal order elements for velocity and + pressure. +*/ + +class ChorinVelPred : public Stokes +{ + protected: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] form The solution formulation to use (Laplace/Stress) + //! \param[in] itg The integrandtype to use + //! \param[in] incPress \e true if incremental pressure formulation + ChorinVelPred(short int n, ProblemFormulation form, + int itg, bool incPress, bool mixed = false); + +public: + //! \brief The destructor frees dynamically allocated objects. + virtual ~ChorinVelPred(); + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + bool initElement(const std::vector& MNPC); + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC1 Nodal point correspondance for the velocity + //! \param[in] MNPC2 Nodal point correspondance for the pressure + //! \param[in] n1 Number of nodes in velocity basis on this patch + bool initElement(const std::vector& MNPC1, + const std::vector& MNPC2, size_t n1); + + //! \brief Initializes current element for boundary integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + bool initElementBou(const std::vector& MNPC); + //! \brief Initializes current element for numerical boundary integration. + //! \param[in] MNPC1 Nodal point correspondance for the velocity + //! \param[in] MNPC2 Nodal point correspondance for the pressure + bool initElementBou(const std::vector& MNPC1, + const std::vector& MNPC2, size_t); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const + { return false; } + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, const TimeDomain& time, + double detJW, const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const + { return false; } + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, velocity + //! \param[in] dN1dX Basis function gradients, velocity + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + //! + //! \details The boundary integral is the same as that of the parent class. + //! It does not depend on the pressure and volumetric change fields. + //! Thus, this call is forwarded to the single-field parent class method. + bool evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector&, + const Matrix& dN1dX, const Matrix&, + const Vec3& X, const Vec3& normal) const + { + return this->evalBou(elmInt,time,detJW,N1,dN1dX,X,normal); + } + + //! \brief Evaluates the secondary solution at current integration point. + //! \param[out] s The solution field values + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] MNPC Matrix of nodal point correspondance + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the secondary solution at a result point (mixed problem). + //! \param[out] s The solution field values at current point + //! \param[in] N1 Basis function values at current point, velocity + //! \param[in] N2 Basis function values at current point, pressure + //! \param[in] dN1dX Basis function gradients at current point, velocity + //! \param[in] dN2dX Basis function gradients at current point, pressure + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC1 Nodal point correspondance for the velocity + //! \param[in] MNPC2 Nodal point correspondance for the pressure + bool evalSol(Vector& s, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, const Vec3& X, + const std::vector& MNPC1, + const std::vector& MNPC2) const; + + //! \brief Evaluates the analytical secondary solution at the given point. + //! \param[out] s The solution field values + //! \param[in] asol The analytical solution field + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalSol(Vector& s, const TensorFunc& asol, const Vec3& X) const; + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \note The Integrand is allocated dynamically and has to be deleted + //! manually when leaving the scope of the pointer returned. + //! \param[in] asol Pointer to analytical solution field (optional) + virtual NormBase* getNormIntegrand(AnaSol* asol = 0) const; + + //! \brief Accesses the velocity solution vectors of current patch + Vector& getVelocity(int n = 0) { return this->getSolution(n); } + + //! \brief Accesses the pressure solution vectors of the current patch + Vector& getPressure(int n = 0) { return psol[n]; } + + //! \brief Returns a pointer to current element solution vector. + const Vector* getElementVelocity() const { return eVs[0]; } + + //! \brief Returns a pointer to current element solution vector. + const Vector* getElementPressure() const { return ePs[0]; } + + //! \brief If an incremental pressure formulation is used + bool incrementalPressure () const { return incPressure; } + + //! \brief If a mixed FE formulation is used + bool mixedFormulation() const { return mixedFEM; } + + //! \brief Get number of velocity solutions + size_t getNoVelocities() const { return this->getNoSolutions(); } + + //! \brief Get number of pressure solutions + size_t getNoPressures() const { return psol.size(); } + + protected: + //! \brief Calculates viscous part of stress tensor at current point. + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] eps Strain tensor at current point + virtual bool strain(const Matrix& dNdX, SymmTensor& eps) const; + + //! \brief Calculates the (Cauchy) stress tensor at current point + //! \param[in] N Basis functions at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] sigma Strain tensor at current point + bool stress(const Vector& N, const Matrix& dNdX, SymmTensor& sigma) const; + + //! \brief Calculates the (Cauchy) stress tensor at current point + //! \param[in] N1 Velocity basis functions at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] sigma Strain tensor at current point + bool stress(const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + SymmTensor& sigma) const; + + bool incPressure; //!< Incremental pressure formulation + bool mixedFEM; //!< Mixed FE formulation + + // Finite element quantities + std::vector ePs; //!< Pointers to element pressure vectors + std::vector psol; //!< Pressure solution vectors for this patch +}; + + +/*! + \brief Class representing the integrand of Stokes norms for Chorin method. +*/ + + +class ChorinStokesNorm : public StokesNorm +{ + public: + //! \brief The only constructor initializes its data members. + //! \param[in] p The Stokes problem to evaluate norms for + //! \param[in] a The analytical velocity and pressure fields (optional) + ChorinStokesNorm(Stokes& p, AnaSol* a) : StokesNorm(p,a) {} + //! \brief Empty destructor. + virtual ~ChorinStokesNorm() {} + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, velocity + //! \param[in] N2 Basis function values, pressure + //! \param[in] dN1dX Basis function gradients, velocity + //! \param[in] dN2dX Basis function gradients, pressure + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used for mixed formulations only. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const; + + //! \brief Returns the number of primary norm quantities. + virtual size_t getNoFields() const { return anasol ? 4 : 2; } + + //! \brief Returns the number of secondary norm quantities. + virtual size_t getNoSecFields() const { return anasol ? 4 : 1; } +}; + + +#endif diff --git a/src/Integrands/ChorinVelPredBDF2.C b/src/Integrands/ChorinVelPredBDF2.C new file mode 100644 index 00000000..ab4bd182 --- /dev/null +++ b/src/Integrands/ChorinVelPredBDF2.C @@ -0,0 +1,568 @@ +//============================================================================== +//! +//! \file ChorinVelPredBDF2.C +//! +//! \date Nov 23 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief BDF2 implementations for velocity prediction in Chorin's method +//! +//============================================================================== + +#include "ChorinVelPredBDF2.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "Utilities.h" + + +ChorinVelPredBDF2::ChorinVelPredBDF2(short int n, ProblemFormulation form, + int itg, bool mixed) + : ChorinVelPredBE(n,form,itg,true,mixed) +{ + int i; + + // Resize velocity element solution vectors + int nUsols = 3; + eVs.resize(nUsols); + for (i = 0;i < nUsols;i++) + eVs[i] = new Vector(); + primsol.resize(nUsols); + + // Resize pressure element solution vectors + int nPsols = 3; + ePs.resize(nPsols); + for (i = 0;i < nPsols;i++) + ePs[i] = new Vector(); + psol.resize(nPsols); +} + + +ChorinVelPredBDF2::~ChorinVelPredBDF2() {} + + +bool ChorinVelPredBDF2::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, j; + int k, l; + int dof; + real dtInv, temp, mass; + real P, conv, diff, laplace; + real B0, B1, B2; + Vector U(nsd), UP(nsd), UPP(nsd), dPdX(nsd); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + // Element velocity vectors + Vector& EVP = *eVs[1]; + Vector& EVPP = *eVs[2]; + + // Element pressure vector + Vector& EP = *ePs[1]; + Vector& EPP = *ePs[2]; + + if (fabs(time.t-time.dt) < 1.0e-8) { + EVPP = EVP; + EPP = EP; + + B0 = 1.0; + B1 = -1.0; + B2 = 0.0; + } + else { + B0 = 1.5; + B1 = -2.0; + B2 = 0.5; + } + + // Velocity in integration point at previous timesteps + UP.fill(0.0); UPP.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) { + dof = (i-1)*nsd + k; + UP(k) += EVP(dof)*N(i); + UPP(k) += EVPP(dof)*N(i); + } + + // Convective velocity in integration point + U = UP; + U *= 2.0; + U -= UPP; + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EP(i)*N(i); + + // Pressure gradient in integration point +// dPdX.fill(0.0); +// for (i = 1;i <= N.size();i++) +// for (k = 1;k <= nsd;k++) +// dPdX(k) += (2.0*EP(i) - EPP(i))*dNdX(i,k); + + // Loop over basis functions + + // Lefthand size terms + if (eM) { + Matrix& EM = *eM; + + // Time derivative terms + temp = rho*dtInv*detJW; + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + mass = B0*temp*N(i)*N(j); + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += mass; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += U(k)*dNdX(i,k); + conv *= rho*N(j)*detJW; + + // Same convection contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += conv; + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= mu*detJW; + + // Same viscous contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += laplace; + + // Extra terms for stress-strain formulation + if (formulation == STRESS) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nsd + l,(i-1)*nsd + k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + } + } + + // Righthand side terms + if (eS) { + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; +// Vector fb(nsd); +// this->getBodyForce(X,fb); +// fb *= detJW; + + for (i = 1;i <= N.size();i++) { + // Time derivative terms + mass = rho*dtInv*N(i)*detJW; + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += mass*(-B1*UP(k)-B2*UPP(k)); + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += fb(k)*N(i); + + + // Pressure term + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += P*dNdX(i,k)*detJW; + //ES((i-1)*nsd + k) -= dPdX(k)*N(i)*detJW; + } + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelPredBDF2::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h) const +{ + int i, j; + int k, l, m; + int dof; + real P, Uabs, delta; + real dtInv, dUdt, temp; + real conv, convI, convJ, diff, laplace; + real B0, B1, B2; + Vector U(nsd), UP(nsd), UPP(nsd), dPdX(nsd); + Matrix dUdX(nsd,nsd); + Matrix3D d2UdX2(nsd,nsd,nsd); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + // Element velocity vectors + Vector& EVP = *eVs[1]; + Vector& EVPP = *eVs[2]; + + // Element pressure vector + Vector& EP = *ePs[1]; + Vector& EPP = *ePs[2]; + + if (fabs(time.t-time.dt) < 1.0e-8) { + EVPP = EVP; + EPP = EP; + + B0 = 1.0; + B1 = -1.0; + B2 = 0.0; + } + else { + B0 = 1.5; + B1 = -2.0; + B2 = 0.5; + } + + // Previous velocities in integration point + UP.fill(0.0); UPP.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) { + dof = (i-1)*nsd + k; + UP(k) += EVP(dof)*N(i); + UPP(k) += EVPP(dof)*N(i); + } + + // Convective velocity in integration point + U = UP; + U *= 2.0; + U -= UPP; + + // Velocity magnitude + Uabs = U.norm2(); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EP(i)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EVP((i-1)*nsd + k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EP(i); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EVP((i-1)*nsd + k); + + // Stabilization parameters + if (mu < rho*Uabs*h) { + if (time.dt == 0) + delta = 0.5/sqrt(1.0/(h*h) + (Uabs*Uabs)/(h*h)); + else + delta = 0.5/sqrt(dtInv*dtInv + (Uabs*Uabs)/(h*h)); + } + delta *= detJW; + + // Loop over basis functions + + // Lefthand size terms + if (eM) { + Matrix& EM = *eM; + + // Time derivative terms + temp = rho*dtInv*detJW; + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + dUdt = B0*temp*N(i)*N(j); + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += dUdt; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += U(k)*dNdX(i,k); + conv *= rho*N(j)*detJW; + + // Same convection contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += conv; + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= mu*detJW; + + // Same viscous contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += laplace; + + // Extra terms for stress-strain formulation + if (formulation == STRESS) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nsd + l,(i-1)*nsd + k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + } + + // SUPG stabilization terms + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += U(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += U(k)*dNdX(j,k); + convJ *= rho*delta; + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nsd + k,(i-1)*nsd + k) += convI*convJ; + } + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) -= diff*convJ; + } + } + } + + // Righthand side terms + if (eS) { + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X); + + for (i = 1;i <= N.size();i++) { + // Time derivative terms + dUdt = rho*N(i)*dtInv*detJW; + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += dUdt*(-B1*UP(k)-B2*UPP(k)); + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += U(k)*dNdX(i,k); + convI *= rho*delta; + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + l) += fb(k)*(N(i)*detJW + convI); + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += P*dNdX(i,k)*detJW - dPdX(k)*convI; + //ES((i-1)*nsd + k) -= dPdX(k)*N(i)*detJW; + } + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelPredBDF2::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const +{ + int i, j; + int k, l; + int dof; + real dtInv, temp, mass; + real P, conv, diff, laplace; + real B0, B1, B2; + Vector U(nsd), UP(nsd), UPP(nsd), dPdX(nsd); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + // Element velocity vectors + Vector& EVP = *eVs[1]; + Vector& EVPP = *eVs[2]; + + // Element pressure vector + Vector& EP = *ePs[1]; + Vector& EPP = *ePs[2]; + + if (fabs(time.t-time.dt) < 1.0e-8) { + EVPP = EVP; + EPP = EP; + + B0 = 1.0; + B1 = -1.0; + B2 = 0.0; + } + else { + B0 = 1.5; + B1 = -2.0; + B2 = 0.5; + } + + // Velocity in integration point at previous timesteps + UP.fill(0.0); UPP.fill(0.0); + for (i = 1;i <= N1.size();i++) + for (k = 1;k <= nsd;k++) { + dof = (i-1)*nsd + k; + UP(k) += EVP(dof)*N1(i); + UPP(k) += EVPP(dof)*N1(i); + } + + // Convective velocity in integration point + U = UP; + U *= 2.0; + U -= UPP; + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N2.size();i++) + P += EP(i)*N2(i); + + // Loop over basis functions + + // Lefthand size terms + if (eM) { + Matrix& EM = *eM; + + // Time derivative terms + temp = rho*dtInv*detJW; + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + mass = B0*temp*N1(i)*N1(j); + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += mass; + } + + // Convection terms + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += U(k)*dN1dX(i,k); + conv *= rho*N1(j)*detJW; + + // Same convection contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += conv; + } + + // Viscous terms + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dN1dX(i,k)*dN1dX(j,k); + laplace *= mu*detJW; + + // Same viscous contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += laplace; + + // Extra terms for stress-strain formulation + if (formulation == STRESS) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nsd + l,(i-1)*nsd + k) += mu*dN1dX(i,k)*dN1dX(j,l)*detJW; + } + } + + // Righthand side terms + if (eS) { + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + + for (i = 1;i <= N1.size();i++) { + // Time derivative terms + mass = rho*dtInv*N1(i)*detJW; + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += mass*(-B1*UP(k)-B2*UPP(k)); + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += fb(k)*N1(i); + + + // Pressure term + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += P*dN1dX(i,k)*detJW; + //ES((i-1)*nsd + k) -= dPdX(k)*N(i)*detJW; + } + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelPredBDF2::evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + if (!tracFld) { + std::cerr <<" *** ChorinVelPredBDF2::evalBou: No tractions."<< std::endl; + return false; + } + else if (!eS) { + std::cerr <<" *** ChorinVelPredBDF2::evalBou: No load vector."<< std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (short int d = 1; d <= nsd; d++) + ES(nsd*(a-1)+d) -= T[d-1]*N(a)*detJW; + + return getIntegralResult(elmInt); +} diff --git a/src/Integrands/ChorinVelPredBDF2.h b/src/Integrands/ChorinVelPredBDF2.h new file mode 100644 index 00000000..199a8398 --- /dev/null +++ b/src/Integrands/ChorinVelPredBDF2.h @@ -0,0 +1,94 @@ +//============================================================================== +//! +//! \file ChorinVelPredBDF2.h +//! +//! \date Nov 23 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief BDF2 implementation for velocity prediction in Chorin's method +//! +//============================================================================== + +#ifndef _CHORIN_VEL_PRED_BDF2_H +#define _CHORIN_VEL_PRED_BDF2_H + +#include "ChorinVelPredBE.h" + +/*! + \brief Class representing the integrand of the velocity prediction step + in Chorin's method. + \details This class implements the velocity prediction step of Chorin's + method using NURBS based FEM with equal order elements for velocity and + pressure. Semi-implicit BDF2 time discretization. +*/ + +class ChorinVelPredBDF2 : public ChorinVelPredBE +{ +public: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] form The solution formulation to use + //! \param[in] itg The integrandtype to use + ChorinVelPredBDF2(short int n, ProblemFormulation form, + int itg, bool mixed = false); + //! \brief The destructor frees dynamically allocated objects. + virtual ~ChorinVelPredBDF2(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const; + + //! \brief Evaluates the mixed field problem integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field velocity + //! \param[in] N2 Basis function values, field pressure + //! \param[in] dN1dX Basis function gradients, velocity + //! \param[in] dN2dX Basis function gradients, pressure + //! \param[in] X Cartesian coordinates of current integration point + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + bool evalBou(LocalIntegral*& elmInt, const TimeDomain& time, + double detJW, const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; +}; +#endif diff --git a/src/Integrands/ChorinVelPredBE.C b/src/Integrands/ChorinVelPredBE.C new file mode 100644 index 00000000..a796a5ad --- /dev/null +++ b/src/Integrands/ChorinVelPredBE.C @@ -0,0 +1,519 @@ +//============================================================================== +//! +//! \file ChorinVelPredBE.C +//! +//! \date Sep 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for velocity prediction in Chorin's method +//! +//============================================================================== + +#include "ChorinVelPredBE.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "Utilities.h" + + +ChorinVelPredBE::ChorinVelPredBE(short int n, ProblemFormulation form, + int itg, bool incPress, bool mixed) + : ChorinVelPred(n,form,itg,incPress,mixed) +{ + int i; + + // Resize velocity element solution vectors + int nUsols = 2; + eVs.resize(nUsols); + for (i = 0;i < nUsols;i++) + eVs[i] = new Vector(); + primsol.resize(nUsols); + + // Resize pressure element solution vectors + int nPsols = 2; + ePs.resize(nPsols); + for (i = 0;i < nPsols;i++) + ePs[i] = new Vector(); + psol.resize(nPsols); +} + + +ChorinVelPredBE::~ChorinVelPredBE() {} + + +bool ChorinVelPredBE::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, j; + int k, l; + double dtInv, temp, mass, P; + double conv, diff, laplace; + Vector U(nsd), UP(nsd), dPdX(nsd); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + // Element velocity vectors + Vector& EV = *eVs[0]; + Vector& EVP = *eVs[0]; + + // Velocity in integration point + U.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + U(k) += EV((i-1)*nsd + k)*N(i); + + // Velocity in integration point at previous timestep + UP.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + UP(k) += EVP((i-1)*nsd + k)*N(i); + + // Element pressure vector + Vector& EP = *ePs[0]; + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EP(i)*N(i); + + // Pressure gradient in integration point +// dPdX.fill(0.0); +// for (i = 1;i <= N.size();i++) +// for (k = 1;k <= nsd;k++) +// dPdX(k) += EP(i)*dNdX(i,k); + + // Loop over basis functions + + // Lefthand size terms + if (eM) { + Matrix& EM = *eM; + + // Time derivative terms + temp = rho*dtInv*detJW; + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + mass = temp*N(i)*N(j); + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += mass; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += U(k)*dNdX(i,k); + conv *= rho*N(j)*detJW; + + // Same convection contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += conv; + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= mu*detJW; + + // Same viscous contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += laplace; + + // Extra terms for stress-strain formulation + if (formulation == STRESS) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nsd + l,(i-1)*nsd + k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + } + } + + // Righthand side terms + if (eS) { + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + // Vector fb(nsd); +// this->getBodyForce(X,fb); +// fb *= detJW; + + for (i = 1;i <= N.size();i++) { + // Time derivative terms + mass = rho*dtInv*N(i)*detJW; + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += mass*UP(k); + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += fb(k)*N(i); + } + + // Only for incremental pressure + if (incPressure) + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += P*dNdX(i,k)*detJW; + //ES((i-1)*nsd + k) -= dPdX(k)*N(i)*detJW; + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelPredBE::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h) const +{ + int i, j; + int k, l, m; + double P, Uabs, delta; + double dtInv, dUdt, temp; + double conv, convI, convJ, diff, laplace; + Vector U(nsd), UP(nsd), dPdX(nsd), res(nsd); + Matrix dUdX(nsd,nsd); + Matrix3D d2UdX2(nsd,nsd,nsd); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + // Element velocity vectors + Vector& EV = *eVs[0]; + Vector& EVP = *eVs[0]; + + // Velocity in integration point + U.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + U(k) += EV((i-1)*nsd + k)*N(i); + + // Velocity magnitude + Uabs = U.norm2(); + + // Previous velocity in integration point + UP.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + UP(k) += EVP((i-1)*nsd + k)*N(i); + + // Element pressure vector + Vector& EP = *ePs[0]; + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EP(i)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nsd + k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EP(i); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nsd + k); + + // Stabilization parameters + if (mu < rho*Uabs*h) { + if (time.dt == 0) + delta = 0.5/sqrt(1.0/(h*h) + (Uabs*Uabs)/(h*h)); + else + delta = 0.5/sqrt(dtInv*dtInv + (Uabs*Uabs)/(h*h)); + } + delta *= detJW; + + // Loop over basis functions + + // Lefthand size terms + if (eM) { + Matrix& EM = *eM; + + // Time derivative terms + temp = rho*dtInv*detJW; + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + dUdt = temp*N(i)*N(j); + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += dUdt; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += U(k)*dNdX(i,k); + conv *= rho*N(j)*detJW; + + // Same convection contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += conv; + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= mu*detJW; + + // Same viscous contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += laplace; + + // Extra terms for stress-strain formulation + if (formulation == STRESS) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nsd + l,(i-1)*nsd + k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + } + + // SUPG stabilization terms + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += U(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += U(k)*dNdX(j,k); + convJ *= rho*delta; + + // Convection terms + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += convI*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) -= diff*convJ; + } + } + } + + // Righthand side terms + if (eS) { + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X); + + for (i = 1;i <= N.size();i++) { + // Time derivative terms + dUdt = rho*N(i)*dtInv*detJW; + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += dUdt*UP(k); + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + l) += fb(k)*N(i); + + // Convection for test functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += U(k)*dNdX(i,k); + convI *= rho*delta; + + // Body force + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + l) += fb(k)*(N(i)*detJW + convI); + + // Pressure term + if (incPressure) + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += P*dNdX(i,k)*detJW - dPdX(k)*convI; + // ES((i-1)*nsd + k) -= dPdX(k)*(N(i)*detJW + convI); + } + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelPredBE::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const +{ + int i, j; + int k, l; + double dtInv, temp, mass, P; + double conv, diff, laplace; + Vector U(nsd), UP(nsd), dPdX(nsd); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + // Element velocity vectors + Vector& EV = *eVs[0]; + Vector& EVP = *eVs[0]; + + // Velocity in integration point + U.fill(0.0); + for (i = 1;i <= N1.size();i++) + for (k = 1;k <= nsd;k++) + U(k) += EV((i-1)*nsd + k)*N1(i); + + // Velocity in integration point at previous timestep + UP.fill(0.0); + for (i = 1;i <= N1.size();i++) + for (k = 1;k <= nsd;k++) + UP(k) += EVP((i-1)*nsd + k)*N1(i); + + // Element pressure vector + Vector& EP = *ePs[0]; + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N2.size();i++) + P += EP(i)*N2(i); + + // Loop over basis functions + + // Lefthand size terms + if (eM) { + Matrix& EM = *eM; + + // Time derivative terms + temp = rho*dtInv*detJW; + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + mass = temp*N1(i)*N1(j); + + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += mass; + } + + // Convection terms + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += U(k)*dN1dX(i,k); + conv *= rho*N1(j)*detJW; + + // Same convection contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += conv; + } + + // Viscous terms + for (i = 1;i <= N1.size();i++) + for (j = 1;j <= N1.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dN1dX(i,k)*dN1dX(j,k); + laplace *= mu*detJW; + + // Same viscous contribution to each velocity component + for (k = 1;k <= nsd;k++) + EM((j-1)*nsd + k,(i-1)*nsd + k) += laplace; + + // Extra terms for stress-strain formulation + if (formulation == STRESS) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nsd + l,(i-1)*nsd + k) += mu*dN1dX(i,k)*dN1dX(j,l)*detJW; + } + } + + // Righthand side terms + if (eS) { + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + + for (i = 1;i <= N1.size();i++) { + // Time derivative terms + mass = rho*dtInv*N1(i)*detJW; + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += mass*UP(k); + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += fb(k)*N1(i); + } + + // Only for incremental pressure + if (incPressure) + for (i = 1;i <= N1.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nsd + k) += P*dN1dX(i,k)*detJW; + } + + return getIntegralResult(elmInt); +} + + +bool ChorinVelPredBE::evalBou(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + if (incPressure) { + if (!tracFld) { + std::cerr <<" *** ChorinVelPredBE::evalBou: No tractions."<< std::endl; + return false; + } + else if (!eS) { + std::cerr <<" *** ChorinVelPredBE::evalBou: No load vector."<< std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (short int d = 1; d <= nsd; d++) + ES(nsd*(a-1)+d) -= T[d-1]*N(a)*detJW; + } + + return getIntegralResult(elmInt); +} + diff --git a/src/Integrands/ChorinVelPredBE.h b/src/Integrands/ChorinVelPredBE.h new file mode 100644 index 00000000..b70fe06e --- /dev/null +++ b/src/Integrands/ChorinVelPredBE.h @@ -0,0 +1,96 @@ +//============================================================================== +//! +//! \file ChorinVelPredBE.h +//! +//! \date Sep 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Implicit Euler implementation for velocity prediction in Chorin's method +//! +//============================================================================== + +#ifndef _CHORIN_VEL_PRED_BE_H +#define _CHORIN_VEL_PRED_BE_H + +#include "ChorinVelPred.h" + +/*! + \brief Class representing the integrand of the velocity prediction step + in Chorin's method. + \details This class implements the velocity prediction step of Chorin's + method using NURBS based FEM with equal order elements for velocity and + pressure. Semi-implicit Euler time discretization. +*/ + +class ChorinVelPredBE : public ChorinVelPred +{ +public: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] form The solution formulation to use + //! \param[in] itg The integrandtype to use + //! \param[in] incPress \e true if incremental pressure formulation + ChorinVelPredBE(short int n, ProblemFormulation form, int itg, + bool incPress = false, bool mixed = false); + //! \brief The destructor frees dynamically allocated objects. + virtual ~ChorinVelPredBE(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const; + + //! \brief Evaluates the mixed field problem integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N1 Basis function values, field velocity + //! \param[in] N2 Basis function values, field pressure + //! \param[in] dN1dX Basis function gradients, velocity + //! \param[in] dN2dX Basis function gradients, pressure + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N1, const Vector& N2, + const Matrix& dN1dX, const Matrix& dN2dX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, const TimeDomain& time, + double detJW, const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; +}; + +#endif diff --git a/src/Integrands/Elasticity.C b/src/Integrands/Elasticity.C new file mode 100644 index 00000000..e5ba741c --- /dev/null +++ b/src/Integrands/Elasticity.C @@ -0,0 +1,721 @@ +// $Id: Elasticity.C,v 1.1 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file Elasticity.C +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for linear and nonlinear elasticity problems. +//! +//============================================================================== + +#include "Elasticity.h" +#include "Utilities.h" +#include "ElmMats.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "VTF.h" + + +Elasticity::Elasticity (unsigned short int n, bool ps) +{ + nsd = n; + planeStress = ps; + + // Default material properties - typical values for steel (SI units) + Emod = 2.05e11; + nu = 0.29; + rho = 7.85e3; + + myMats = new ElmMats(); + + locSys = 0; + tracFld = 0; + bodyFld = 0; + eM = eKm = eKg = 0; + eS = eV = iS = 0; +} + + +Elasticity::~Elasticity () +{ + if (myMats) delete myMats; + if (locSys) delete locSys; +} + + +void Elasticity::print (std::ostream& os) const +{ + std::cout <<"Elasticity: "<< nsd <<"D"; + if (nsd == 2) + std::cout <<" plane "<< (planeStress ? "stress" : "strain"); + std::cout <<", E = "<< Emod <<", nu = "<< nu <<", rho = "<< rho + <<", gravity ="; + for (unsigned short int d = 0; d < nsd; d++) + std::cout <<" "<< g[d]; + std::cout << std::endl; +} + + +void Elasticity::setMode (SIM::SolutionMode mode) +{ + if (!myMats) return; + + myMats->rhsOnly = false; + eM = eKm = eKg = 0; + eS = eV = iS = 0; + + switch (mode) + { + case SIM::STATIC: + myMats->resize(1,1); + eKm = &myMats->A[0]; + eS = &myMats->b[0]; + tracVal.clear(); + break; + + case SIM::DYNAMIC: + myMats->resize(2,1); + eKm = &myMats->A[0]; + eM = &myMats->A[1]; + eS = &myMats->b[0]; + tracVal.clear(); + break; + + case SIM::VIBRATION: + myMats->resize(2,0); + eKm = &myMats->A[0]; + eM = &myMats->A[1]; + break; + + case SIM::BUCKLING: + myMats->resize(2,1); + eKm = &myMats->A[0]; + eKg = &myMats->A[1]; + eV = &myMats->b[0]; + break; + + case SIM::STIFF_ONLY: + myMats->resize(1,0); + eKm = &myMats->A[0]; + break; + + case SIM::MASS_ONLY: + myMats->resize(1,0); + eM = &myMats->A[0]; + break; + + case SIM::RHS_ONLY: + myMats->rhsOnly = true; + if (myMats->b.empty()) + myMats->b.resize(1); + eS = &myMats->b[0]; + tracVal.clear(); + break; + + case SIM::RECOVERY: + myMats->rhsOnly = true; + if (myMats->b.empty()) + myMats->b.resize(1); + eV = &myMats->b[0]; + break; + + default: + myMats->resize(0,0); + tracVal.clear(); + } +} + + +bool Elasticity::getIntegralResult (LocalIntegral*& elmInt) const +{ + elmInt = myMats; + return elmInt ? true : false; +} + + +void Elasticity::setTraction (TractionFunc* tf) +{ + tracFld = tf; + + if (myMats) + myMats->rhsOnly = true; +} + + +bool Elasticity::initElement (const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eKm) eKm->resize(nsd*nen,nsd*nen,true); + if (eKg) eKg->resize(nsd*nen,nsd*nen,true); + if (eM) eM->resize(nsd*nen,nsd*nen,true); + if (eS) eS->resize(nsd*nen,true); + + int ierr = 0; + if (eV && !primsol.front().empty()) + if ((ierr = utl::gather(MNPC,nsd,primsol.front(),*eV))) + std::cerr <<" *** Elasticity::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool Elasticity::initElementBou (const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eS) eS->resize(nsd*nen,true); + + int ierr = 0; + if (eV && !primsol.front().empty()) + if ((ierr = utl::gather(MNPC,nsd,primsol.front(),*eV))) + std::cerr <<" *** Elasticity::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats->withLHS = false; + return ierr == 0; +} + + +Vec3 Elasticity::getTraction (const Vec3& X, const Vec3& n) const +{ + if (tracFld) + return (*tracFld)(X,n); + else + return Vec3(); +} + + +Vec3 Elasticity::getBodyforce (const Vec3& X) const +{ + Vec3 f(g[0],g[1],g[2]); + f *= this->getMassDensity(X); + + if (bodyFld) + f += (*bodyFld)(X); + + return f; +} + + +bool Elasticity::haveLoads () const +{ + if (tracFld) return true; + if (bodyFld) return true; + + if (rho != 0.0) + for (unsigned short int i = 0; i < nsd; i++) + if (g[i] != 0.0) + return true; + + return false; +} + + +bool Elasticity::writeGlvT (VTF* vtf, int iStep, int& nBlock) const +{ + if (tracVal.empty()) + return true; + else if (!vtf) + return false; + + // Write boundary tractions as discrete point vectors to the VTF-file + if (!vtf->writeVectors(tracVal,++nBlock)) + return false; + + return vtf->writeVblk(nBlock,"Tractions",1,iStep); +} + + +/*! + The strain-displacement matrix for a continuum element is formally defined as: + \f[ \mbox{In 3D,~~} + [B] = \left[\begin{array}{ccc} + \frac{\partial}{\partial x} & 0 & 0 \\ + 0 & \frac{\partial}{\partial y} & 0 \\ + 0 & 0 & \frac{\partial}{\partial z} \\ + \frac{\partial}{\partial y} & \frac{\partial}{\partial x} & 0 \\ + 0 & \frac{\partial}{\partial z} & \frac{\partial}{\partial y} \\ + \frac{\partial}{\partial z} & 0 & \frac{\partial}{\partial x} + \end{array}\right] [N] \hskip 5mm\mbox{and in 2D,~~} + [B] = \left[\begin{array}{ccc} + \frac{\partial}{\partial x} & 0 \\ + 0 & \frac{\partial}{\partial y} \\ + \frac{\partial}{\partial y} & \frac{\partial}{\partial x} + \end{array}\right] [N] + \f] + where + [\a N ] is the element basis functions arranged in a [nsd][nsd*NENOD] matrix. +*/ + +bool Elasticity::kinematics (const Matrix& dNdX, SymmTensor& eps) const +{ + const size_t nenod = dNdX.rows(); + const size_t nstrc = nsd*(nsd+1)/2; + Bmat.resize(nstrc*nsd,nenod,true); + if (dNdX.cols() < nsd) + { + std::cerr <<" *** Elasticity::kinematics: Invalid dimension on dNdX, " + << dNdX.rows() <<"x"<< dNdX.cols() <<"."<< std::endl; + return false; + } + +#define INDEX(i,j) i+nstrc*(j-1) + + switch (nsd) { + case 1: + + // Strain-displacement matrix for 1D elements: + // + // [B] = | d/dx | * [N] + + for (size_t i = 1; i <= nenod; i++) + Bmat(1,i) = dNdX(i,1); + break; + + case 2: + + // Strain-displacement matrix for 2D elements: + // + // | d/dx 0 | + // [B] = | 0 d/dy | * [N] + // | d/dy d/dx | + + for (size_t i = 1; i <= nenod; i++) + { + // Normal strain part + Bmat(INDEX(1,1),i) = dNdX(i,1); + Bmat(INDEX(2,2),i) = dNdX(i,2); + // Shear strain part + Bmat(INDEX(3,1),i) = dNdX(i,2); + Bmat(INDEX(3,2),i) = dNdX(i,1); + } + break; + + case 3: + + // Strain-displacement matrix for 3D elements: + // + // | d/dx 0 0 | + // | 0 d/dy 0 | + // [B] = | 0 0 d/dz | * [N] + // | d/dy d/dx 0 | + // | d/dz 0 d/dx | + // | 0 d/dz d/dy | + + for (size_t i = 1; i <= nenod; i++) + { + // Normal strain part + Bmat(INDEX(1,1),i) = dNdX(i,1); + Bmat(INDEX(2,2),i) = dNdX(i,2); + Bmat(INDEX(3,3),i) = dNdX(i,3); + // Shear strain part + Bmat(INDEX(4,1),i) = dNdX(i,2); + Bmat(INDEX(4,2),i) = dNdX(i,1); + Bmat(INDEX(5,2),i) = dNdX(i,3); + Bmat(INDEX(5,3),i) = dNdX(i,2); + Bmat(INDEX(6,1),i) = dNdX(i,3); + Bmat(INDEX(6,3),i) = dNdX(i,1); + } + break; + + default: + std::cerr <<" *** Elasticity::kinematics: nsd="<< nsd << std::endl; + return false; + } + + Bmat.resize(nstrc,nsd*nenod); + + // Evaluate the strains + if (eV && !eV->empty() && eps.dim() > 0) + return Bmat.multiply(*eV,eps); // eps = B*eV + + return true; +} + + +bool Elasticity::formBmatrix (const Matrix& dNdX) const +{ + static SymmTensor dummy(0); + return this->Elasticity::kinematics(dNdX,dummy); +} + + +/*! + The consitutive matrix for Isotrophic linear elastic problems + is defined as follows: + + For 2D plain stress: \f[ + [C] = \frac{E}{(1-\nu^2)} \left[\begin{array}{ccc} + 1 & \ \ \nu & 0 \\ + \nu & \ \ 1 & 0 \\ + 0 & \ \ 0 & \frac{1}{2}(1-\nu) + \end{array}\right] \f] + + For 2D plain strain: \f[ + [C] = \frac{E}{(1+\nu)(1-2\nu)} \left[\begin{array}{ccc} + 1-\nu & \nu & 0 \\ + \nu & 1-\nu & 0 \\ + 0 & 0 & \frac{1}{2}-\nu + \end{array}\right] \f] + + For 3D: \f[ + [C] = \frac{E}{(1+\nu)(1-2\nu)} \left[\begin{array}{cccccc} + 1-\nu & \nu & \nu & 0 & 0 & 0 \\ + \nu & 1-\nu & \nu & 0 & 0 & 0 \\ + \nu & \nu & 1-\nu & 0 & 0 & 0 \\ + 0 & 0 & 0 & \frac{1}{2}-\nu & 0 & 0 \\ + 0 & 0 & 0 & 0 & \frac{1}{2}-\nu & 0 \\ + 0 & 0 & 0 & 0 & 0 & \frac{1}{2}-\nu + \end{array}\right] \f] +*/ + +bool Elasticity::formCmatrix (Matrix& C, const Vec3&, bool invers) const +{ + const double one = 1.0; + const size_t nst = nsd*(nsd+1)/2; + C.resize(nst,nst,true); + if (nsd == 1) + { + // Special for 1D problems + C(1,1) = invers ? one/Emod : Emod; + return true; + } + else if (nu < 0.0 || nu >= 0.5) + { + std::cerr <<" *** Elasticity::formCmatrix: Poisson's ratio "<< nu + <<" out of range [0,0.5>."<< std::endl; + return false; + } + + if (invers) + if (nsd == 3 || (nsd == 2 && planeStress)) + { + C(1,1) = one / Emod; + C(2,1) = -nu / Emod; + } + else // 2D plain strain + { + C(1,1) = (one - nu*nu) / Emod; + C(2,1) = (-nu - nu*nu) / Emod; + } + + else + if (nsd == 2 && planeStress) + { + C(1,1) = Emod / (one - nu*nu); + C(2,1) = C(1,1) * nu; + } + else // 2D plain strain or 3D + { + double fact = Emod / ((one + nu) * (one - nu - nu)); + C(1,1) = fact * (one - nu); + C(2,1) = fact * nu; + } + + C(1,2) = C(2,1); + C(2,2) = C(1,1); + + const double G = Emod / (2.0 + nu + nu); + C(nsd+1,nsd+1) = invers ? one / G : G; + + if (nsd > 2) + { + C(3,1) = C(2,1); + C(3,2) = C(2,1); + C(1,3) = C(2,1); + C(2,3) = C(2,1); + C(3,3) = C(1,1); + C(5,5) = C(4,4); + C(6,6) = C(4,4); + } + + return true; +} + + +bool Elasticity::constitutive (Matrix& C, SymmTensor& sigma, double&, + const SymmTensor& eps, const Vec3& X, + char calcStress) const +{ + // Set up the constitutive matrix, C, at this point + if (!this->formCmatrix(C,X)) + return false; + else if (!calcStress) + return true; + + // Calculate the stress vector, sigma = C*eps + return C.multiply(eps,sigma); +} + + +void Elasticity::formKG (Matrix& EM, const Matrix& dNdX, + const Tensor& sigma, double detJW) const +{ +#ifdef INT_DEBUG + std::cout <<"Elasticity::eV ="<< *eV; + std::cout <<"Elasticity::sigma =\n"<< sigma; + std::cout <<"Elasticity::kg ="; +#endif + + unsigned short int i, j; + for (size_t a = 1; a <= dNdX.rows(); a++) + for (size_t b = 1; b <= dNdX.rows(); b++) + { + double kg = 0.0; + for (i = 1; i <= nsd; i++) + for (j = 1; j <= nsd; j++) + kg += dNdX(a,i)*sigma(i,j)*dNdX(b,j); +#ifdef INT_DEBUG + std::cout << (b == 1 ? '\n' : ' ') << kg; +#endif + + for (i = 1; i <= nsd; i++) + EM(nsd*(a-1)+i,nsd*(b-1)+i) += kg*detJW; + } +#ifdef INT_DEBUG + std::cout << std::endl; +#endif +} + + +void Elasticity::formMassMatrix (Matrix& EM, const Vector& N, + const Vec3& X, double detJW) const +{ + double rhow = this->getMassDensity(X)*detJW; + if (rhow == 0.0) return; + + for (size_t a = 1; a <= N.size(); a++) + for (size_t b = 1; b <= N.size(); b++) + for (unsigned short int i = 1; i <= nsd; i++) + EM(nsd*(a-1)+i,nsd*(b-1)+i) += rhow*N(a)*N(b); +} + + +void Elasticity::formBodyForce (Vector& ES, const Vector& N, + const Vec3& X, double detJW) const +{ + Vec3 f = this->getBodyforce(X); + if (f.isZero()) return; + + f *= detJW; + for (size_t a = 1; a <= N.size(); a++) + for (unsigned short int i = 1; i <= nsd; i++) + ES(nsd*(a-1)+i) += f[i-1]*N(a); +} + + +Vec3 Elasticity::evalSol (const Vector& N) const +{ + Vec3 u; + if (eV && eV->size() == N.size()*nsd) + for (unsigned short int i = 0; i < nsd; i++) + u[i] = eV->dot(N,i,nsd); + + return u; +} + + +bool Elasticity::evalSol (Vector& s, const Vector&, + const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const +{ + int ierr = 0; + if (eV && !primsol.front().empty()) + if ((ierr = utl::gather(MNPC,nsd,primsol.front(),*eV))) + { + std::cerr <<" *** Elasticity::evalSol: Detected " + << ierr <<" node numbers out of range."<< std::endl; + return false; + } + + // Evaluate the strain tensor, eps + SymmTensor eps(nsd); + if (!this->kinematics(dNdX,eps)) + return false; + + // Calculate the stress tensor through the constitutive relation + SymmTensor sigma(nsd); double U = 0.0; + if (!this->constitutive(Cmat,sigma,U,eps,X)) + return false; + + // Congruence transformation to local coordinate system at current point + if (locSys) sigma.transform(locSys->getTmat(X)); + + s = sigma; + s.push_back(sigma.vonMises()); + return true; +} + + +bool Elasticity::evalSol (Vector& s, const Matrix& dNdX, const Vec3& X) const +{ + if (!eV || eV->empty()) + { + std::cerr <<" *** Elasticity::evalSol: No displacement vector." + << std::endl; + return false; + } + else if (eV->size() != dNdX.rows()*nsd) + { + std::cerr <<" *** Elasticity::evalSol: Invalid displacement vector." + <<"\n size(eV) = "<< eV->size() <<" size(dNdX) = " + << dNdX.rows() <<","<< dNdX.cols() << std::endl; + return false; + } + + // Evaluate the strain tensor, eps + SymmTensor eps(nsd); + if (!this->kinematics(dNdX,eps)) + return false; + + // Calculate the stress tensor through the constitutive relation + SymmTensor sigma(nsd); double U = 0.0; + if (!this->constitutive(Cmat,sigma,U,eps,X)) + return false; + + s = sigma; + return true; +} + + +bool Elasticity::evalSol (Vector& s, const TensorFunc& sol, const Vec3& X) const +{ + s = sol(X); + s.push_back(SymmTensor(s).vonMises()); + return true; +} + + +size_t Elasticity::getNoFields () const +{ + return nsd*(nsd+1)/2 + 1; +} + + +const char* Elasticity::getFieldLabel (size_t i, const char* prefix) const +{ + static const char* s1[1] = { "Axial stress" }; + static const char* s2[3] = { "s_xx","s_yy","s_xy" }; + static const char* s3[6] = { "s_xx","s_yy","s_zz","s_xy","s_yz","s_xz" }; + + static std::string label; + if (prefix) + label = prefix + std::string(" "); + else + label.clear(); + + if (i >= (size_t)(nsd*(nsd+1)/2)) + label += "von Mises stress"; + else switch (nsd) { + case 1: label += s1[i]; break; + case 2: label += s2[i]; break; + case 3: label += s3[i]; break; + } + + return label.c_str(); +} + + +NormBase* Elasticity::getNormIntegrand (TensorFunc* sol) const +{ + return new ElasticityNorm(*const_cast(this),sol); +} + + +bool ElasticityNorm::initElement (const std::vector& MNPC) +{ + return problem.initElement(MNPC); +} + + +bool ElasticityNorm::initElementBou (const std::vector& MNPC) +{ + return problem.initElementBou(MNPC); +} + + +size_t ElasticityNorm::getNoFields () const +{ + return anasol ? 4 : 2; +} + + +ElmNorm& ElasticityNorm::getElmNormBuffer (LocalIntegral*& elmInt) +{ + ElmNorm* eNorm = dynamic_cast(elmInt); + if (eNorm) return *eNorm; + + static double data[4]; + static ElmNorm buf(data); + memset(data,0,4*sizeof(double)); + elmInt = &buf; + return buf; +} + + +bool ElasticityNorm::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + ElmNorm& pnorm = ElasticityNorm::getElmNormBuffer(elmInt); + + // Evaluate the inverse constitutive matrix at this point + Matrix Cinv; + if (!problem.formCmatrix(Cinv,X,true)) return false; + + // Evaluate the finite element stress field + Vector sigmah, sigma; + if (!problem.evalSol(sigmah,dNdX,X)) return false; + + // Integrate the energy norm a(u^h,u^h) + pnorm[0] += sigmah.dot(Cinv*sigmah)*detJW; + + if (problem.haveLoads()) + { + // Evaluate the body load + Vec3 f = problem.getBodyforce(X); + // Evaluate the displacement field + Vec3 u = problem.evalSol(N); + // Integrate the external energy (f,u^h) + pnorm[1] += f*u*detJW; + } + + if (anasol) + { + // Evaluate the analytical stress field + sigma = (*anasol)(X); + // Integrate the energy norm a(u,u) + pnorm[2] += sigma.dot(Cinv*sigma)*detJW; + // Integrate the error in energy norm a(u-u^h,u-u^h) + sigma -= sigmah; + pnorm[3] += sigma.dot(Cinv*sigma)*detJW; + } + + return true; +} + + +bool ElasticityNorm::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix&, + const Vec3& X, const Vec3& normal) const +{ + if (!problem.haveLoads()) return true; + + ElmNorm& pnorm = ElasticityNorm::getElmNormBuffer(elmInt); + + // Evaluate the surface traction + Vec3 T = problem.getTraction(X,normal); + // Evaluate the displacement field + Vec3 u = problem.evalSol(N); + + // Integrate the external energy + pnorm[1] += T*u*detJW; + return true; +} diff --git a/src/Integrands/Elasticity.h b/src/Integrands/Elasticity.h new file mode 100644 index 00000000..9c74ad99 --- /dev/null +++ b/src/Integrands/Elasticity.h @@ -0,0 +1,303 @@ +// $Id: Elasticity.h,v 1.1 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file Elasticity.h +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for linear and nonlinear elasticity problems. +//! +//============================================================================== + +#ifndef _ELASTICITY_H +#define _ELASTICITY_H + +#include "IntegrandBase.h" +#include "Vec3.h" +#include + +class LocalSystem; +class ElmMats; +class ElmNorm; +class Tensor; +class VTF; + + +/*! + \brief Base class representing the integrand of elasticity problems. + \details Implements common features for linear and nonlinear elasticity + problems. None of the \a evalInt and \a evalBou methods are implemented here. + Thus, it is regarded as an abstract base class with a protected constructor. +*/ + +class Elasticity : public Integrand +{ +protected: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] ps If \e true, assume plane stress in 2D + Elasticity(unsigned short int n = 3, bool ps = true); + +public: + //! \brief The destructor frees the dynamically allocated data objects. + virtual ~Elasticity(); + + //! \brief Prints out problem definition to the given output stream. + virtual void print(std::ostream& os) const; + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Defines the traction field to use in Neumann boundary conditions. + void setTraction(TractionFunc* tf); + //! \brief Clears the integration point traction values. + void clearTracVal() { tracVal.clear(); } + + //! \brief Defines the gravitation vector. + void setGravity(double gx, double gy, double gz) + { g[0] = gx; g[1] = gy; g[2] = gz; } + + //! \brief Defines the body force field. + void setBodyForce(VecFunc* bf) { bodyFld = bf; } + + //! \brief Defines material properties for current volume patch. + //! \param[in] Young Young's modulus + //! \param[in] Poiss Poisson's ratio + //! \param[in] Density Mass density + virtual void setMaterial(double Young, double Poiss, double Density) + { Emod = Young; nu = Poiss; rho = Density; } + + //! \brief Defines the local coordinate system for stress output. + void setLocalSystem(LocalSystem* cs) { locSys = cs; } + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElement(const std::vector& MNPC); + //! \brief Initializes current element for boundary numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElementBou(const std::vector& MNPC); + + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s Array of solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the finite element (FE) solution at an integration point. + //! \param[out] s The FE stress values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSol(Vector& s, const Matrix& dNdX, const Vec3& X) const; + + //! \brief Evaluates the analytical solution at an integration point. + //! \param[out] s The analytical stress values at current point + //! \param[in] sol The analytical solution field + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSol(Vector& s, const TensorFunc& sol, const Vec3& X) const; + + //! \brief Evaluates the primary solution at a result point. + //! \param[in] N Basis function values at current point + //! \return Primary solution vector at current point + Vec3 evalSol(const Vector& N) const; + + //! \brief Evaluates the boundary traction field (if any) at specified point. + Vec3 getTraction(const Vec3& X, const Vec3& n) const; + //! \brief Evaluates the body force field (if any) at specified point. + virtual Vec3 getBodyforce(const Vec3& X) const; + //! \brief Returns whether an external load is defined + virtual bool haveLoads() const; + + //! \brief Writes the surface tractions for a given time step to VTF-file. + //! \param vtf The VTF-file object to receive the tractions + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvT(VTF* vtf, int iStep, int& nBlock) const; + + //! \brief Returns whether there are any traction values to write to VTF. + virtual bool hasTractionValues() const { return !tracVal.empty(); } + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \note The Integrand object is allocated dynamically and has to be deleted + //! manually when leaving the scope of the pointer variable receiving the + //! returned pointer value. + //! \param[in] sol Pointer to analytical solution field (optional) + virtual NormBase* getNormIntegrand(TensorFunc* sol = 0) const; + + //! \brief Returns the number of secondary solution fields. + virtual size_t getNoFields() const; + + //! \brief Returns the name of a secondary solution field component. + //! \param[in] i Field component index + //! \param[in] prefix Name prefix for all components + virtual const char* getFieldLabel(size_t i, const char* prefix = 0) const; + +protected: + //! \brief Evaluates the mass density at current point. + virtual double getMassDensity(const Vec3&) const { return rho; } + + //! \brief Calculates some kinematic quantities at current point. + //! \param[in] dNdX Basis function gradients at current point + //! \param[out] eps Strain tensor at current point + //! + //! \details The strain displacement matrix \b B is established + //! and stored in the mutable class member \a Bmat. + virtual bool kinematics(const Matrix& dNdX, SymmTensor& eps) const; + + //! \brief Evaluates the constitutive relation at current point. + //! \param[out] C Constitutive matrix at current point + //! \param[out] sigma Stress tensor at current point + //! \param[out] U Strain energy density + //! \param[in] eps Strain tensor at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] calcStress If \e false, claculate the C-matrix only + virtual bool constitutive(Matrix& C, SymmTensor& sigma, double& U, + const SymmTensor& eps, const Vec3& X, + char calcStress = true) const; + + //! \brief Calculates integration point geometric stiffness contributions. + //! \param EM Element matrix to receive the stiffness contributions + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] sigma Stress tensor at current point + //! \param[in] detJW Jacobian determinant times integration point weight + void formKG(Matrix& EM, const Matrix& dNdX, + const Tensor& sigma, double detJW) const; + + //! \brief Calculates integration point mass matrix contributions. + //! \param EM Element matrix to receive the mass contributions + //! \param[in] N Basis function values at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] detJW Jacobian determinant times integration point weight + void formMassMatrix(Matrix& EM, const Vector& N, + const Vec3& X, double detJW) const; + + //! \brief Calculates integration point body force vector contributions. + //! \brief Evaluates the body force vector at current point. + //! \param ES Element vector to receive the body force contributions + //! \param[in] N Basis function values at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] detJW Jacobian determinant times integration point weight + void formBodyForce(Vector& ES, const Vector& N, + const Vec3& X, double detJW) const; + + //! \brief Calculates the strain-displacement matrix \b B at current point. + //! \param[in] dNdX Basis function gradients at current point + bool formBmatrix(const Matrix& dNdX) const; + + //! \brief Utility used by the virtual \a evalInt and \a evalBou methods. + //! \param elmInt Pointer to the integrated element quantities + bool getIntegralResult(LocalIntegral*& elmInt) const; + +public: + //! \brief Sets up the tangential constitutive matrix at current point. + //! \param[out] C \f$6\times6\f$-matrix (in 3D) or \f$3\times3\f$-matrix + //! (in 2D), representing the constitutive tensor + //! \param[in] X Cartesian coordinates of current point + //! \param[in] invers If \e true, set up the inverse matrix instead + virtual bool formCmatrix(Matrix& C, const Vec3& X, bool invers = false) const; + +private: + // Physical properties (constant) + double Emod; //!< Young's modulus + double nu; //!< Poisson's ratio + double rho; //!< Mass density + double g[3]; //!< Gravitation vector + +protected: + // Finite element quantities + Matrix* eKm; //!< Pointer to element material stiffness matrix + Matrix* eKg; //!< Pointer to element geometric stiffness matrix + Matrix* eM; //!< Pointer to element mass matrix + Vector* eS; //!< Pointer to element load vector + Vector* iS; //!< Pointer to element internal force vector + Vector* eV; //!< Pointer to element displacement vector + + ElmMats* myMats; //!< Local element matrices, result of numerical integration + + LocalSystem* locSys; //!< Local coordinate system for result output + TractionFunc* tracFld; //!< Pointer to boundary traction field + VecFunc* bodyFld; //!< Pointer to body force field + + mutable std::map tracVal; //!< Traction field point values + + unsigned short int nsd; //!< Number of space dimensions (1, 2 or, 3) + bool planeStress; //!< Plane stress/strain option for 2D problems + + // Work arrays declared as members to avoid frequent re-allocation + // within the numerical integration loop (for reduced overhead) + mutable Matrix Bmat; //!< Strain-displacement matrix + mutable Matrix Cmat; //!< Constitutive matrix +}; + + +/*! + \brief Class representing the integrand of linear elasticity energy norms. +*/ + +class ElasticityNorm : public NormBase +{ +public: + //! \brief The only constructor initializes its data members. + //! \param[in] p The linear elasticity problem to evaluate norms for + //! \param[in] a The analytical stress field (optional) + ElasticityNorm(Elasticity& p, TensorFunc* a) : problem(p), anasol(a) {} + //! \brief Empty destructor. + virtual ~ElasticityNorm() {} + + //! \brief Returns whether this norm has explicit boundary contributions. + virtual bool hasBoundaryTerms() const { return true; } + //! \brief Returns a 1-based index of the external energy norm. + virtual size_t indExt() const { return 2; } + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElement(const std::vector& MNPC); + //! \brief Initializes current element for boundary numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElementBou(const std::vector& MNPC); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Returns the number of norm quantities. + virtual size_t getNoFields() const; + +protected: + //! \brief Get the element norm object to use in the integration. + //! \param elmInt The local integral object to receive norm contributions + //! + //! \details If \a elmInt is NULL or cannot be casted to a ElmNorm pointer, + //! a local static buffer is used instead. + static ElmNorm& getElmNormBuffer(LocalIntegral*& elmInt); + + Elasticity& problem; //!< The problem-specific data + +private: + TensorFunc* anasol; //!< Analytical stress field +}; + +#endif diff --git a/src/Integrands/LinearElasticity.C b/src/Integrands/LinearElasticity.C new file mode 100644 index 00000000..f2702332 --- /dev/null +++ b/src/Integrands/LinearElasticity.C @@ -0,0 +1,109 @@ +// $Id: LinearElasticity.C,v 1.27 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file LinearElasticity.C +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for linear elasticity problems. +//! +//============================================================================== + +#include "LinearElasticity.h" +#include "Tensor.h" +#include "Vec3Oper.h" + + +LinearElasticity::LinearElasticity (unsigned short int n, bool ps) + : Elasticity(n,ps) +{ + // Only the current solution is needed + primsol.resize(1); +} + + +bool LinearElasticity::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + bool lHaveStrains = false; + + SymmTensor eps(nsd); + if (eKm || eKg || iS) + { + // Compute the strain-displacement matrix B from dNdX + // and evaluate the symmetric strain tensor if displacements are available + if (!this->kinematics(dNdX,eps)) return false; + if (!eps.isZero(1.0e-16)) lHaveStrains = true; + + // Evaluate the constitutive matrix at this point + if (!this->formCmatrix(Cmat,X)) return false; + } + + if (eKm) + { + // Integrate the material stiffness matrix + CB.multiply(Cmat,Bmat).multiply(detJW); // CB = C*B*|J|*w + eKm->multiply(Bmat,CB,true,false,true); // EK += B^T * CB + } + + SymmTensor sigma(nsd); + if ((eKg || iS) && lHaveStrains) + // Evaluate the symmetric stress tensor + Cmat.multiply(eps,sigma); // sigma = C*eps + + if (eKg && lHaveStrains) + // Integrate the geometric stiffness matrix + this->formKG(*eKg,dNdX,sigma,detJW); + + if (eM) + // Integrate the mass matrix + this->formMassMatrix(*eM,N,X,detJW); + + if (iS && lHaveStrains) + { + // Integrate the internal forces + sigma *= -detJW; + if (!Bmat.multiply(sigma,*iS,true,true)) // ES -= B^T*sigma + return false; + } + + if (eS) + // Integrate the load vector due to gravitation and other body forces + this->formBodyForce(*eS,N,X,detJW); + + return this->getIntegralResult(elmInt); +} + + +bool LinearElasticity::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + if (!tracFld) + { + std::cerr <<" *** LinearElasticity::evalBou: No tractions."<< std::endl; + return false; + } + else if (!eS) + { + std::cerr <<" *** LinearElasticity::evalBou: No load vector."<< std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (unsigned short int i = 1; i <= nsd; i++) + ES(nsd*(a-1)+i) += T[i-1]*N(a)*detJW; + + return this->getIntegralResult(elmInt); +} diff --git a/src/Integrands/LinearElasticity.h b/src/Integrands/LinearElasticity.h new file mode 100644 index 00000000..85421d61 --- /dev/null +++ b/src/Integrands/LinearElasticity.h @@ -0,0 +1,63 @@ +// $Id: LinearElasticity.h,v 1.21 2011-02-08 09:06:02 kmo Exp $ +//============================================================================== +//! +//! \file LinearElasticity.h +//! +//! \date Nov 12 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Integrand implementations for linear elasticity problems. +//! +//============================================================================== + +#ifndef _LINEAR_ELASTICITY_H +#define _LINEAR_ELASTICITY_H + +#include "Elasticity.h" + + +/*! + \brief Class representing the integrand of the linear elasticity problem. + \details This class supports constant isotrophic material properties only. + Properties with spatial variation has to be implemented as sub-classes. +*/ + +class LinearElasticity : public Elasticity +{ +public: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] ps If \e true, assume plane stress in 2D + LinearElasticity(unsigned short int n = 3, bool ps = true); + //! \brief Empty destructor. + virtual ~LinearElasticity() {} + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + +private: + // Work array declared as member to avoid frequent re-allocation + // within the numerical integration loop (for reduced overhead) + mutable Matrix CB; //!< Result of the matrix-matrix product C*B +}; + +#endif diff --git a/src/Integrands/NavierStokesG2.C b/src/Integrands/NavierStokesG2.C new file mode 100644 index 00000000..e57ecd76 --- /dev/null +++ b/src/Integrands/NavierStokesG2.C @@ -0,0 +1,624 @@ +// $Id: NavierStokesG2.C,v 1.4 2011-02-08 12:38:23 rho Exp $ +//============================================================================== +//! +//! \file NavierStokesG2.C +//! +//! \date May 28 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for G2 stabilized Navier-Stokes problems. +//! +//============================================================================== + +#include "NavierStokesG2.h" +#include "TimeDomain.h" +#include "Utilities.h" + + +NavierStokesG2::NavierStokesG2(short int n, ProblemFormulation form, int itg) + : StabilizedStokes(n,form,itg) +{ + mu = 1.0e-3; + rho = 1.0; + + // Need current solution and solution at previous timestep + eVs.resize(2); + for (int i = 0;i < 2;i++) + eVs[i] = new Vector(); + primsol.resize(2); +} + + +NavierStokesG2::~NavierStokesG2() +{ +} + + +bool NavierStokesG2::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h) const +{ + int i, j; + int k, l, m; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real delta1, delta2; + real dtInv, dUdt; + Vector vel(3), pvel(3), conv(3), res(3), dPdX(3); + Matrix dUdX(3,3); + Matrix3D d2UdX2(3,3,3); + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + Vector& EVP = *eVs[1]; + // Previous velocity at integration point + pvel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + pvel(k) += EVP((i-1)*nf + k)*N(i); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + if (time.dt == 0.0) + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + else + delta1 = 0.5/sqrt(dtInv*dtInv + (U*U)/(h*h)); + + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= 0.000001*detJW; + delta2 *= 0.000001*detJW; + + if (eM) { + Matrix& EM = *eM; + + // Time derivative term lhs + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + dUdt = rho*N(i)*N(j)*dtInv*detJW; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k, (i-1)*nf + k) += dUdt; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= rho*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += rho*dUdX(l,k)*N(j)*N(i)*detJW; + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == SIM::STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + + res(k) += dPdX(k); + res(k) *= delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += convI*convJ; + + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,(i-1)*nf + k) += rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += convJ*rho*dUdX(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Time derivative terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += rho*(pvel(k)-vel(k))*N(i)*dtInv*detJW; + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m); + + ES((i-1)*nf + k) -= rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += P*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdX(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= res(k)*convI; + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= res(k)*dNdX(i,k); + } + } + + return getIntegralResult(elmInt); +} + + +bool NavierStokesG2::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h) const +{ + int i, j; + int k, l, m; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real delta1, delta2; + Vector vel(3), conv(3), res(3), dPdX(3); + Matrix dUdX(3,3); + Matrix3D d2UdX2(3,3,3); + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= detJW; + delta2 *= detJW; + + if (eM) { + Matrix& EM = *eM; + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= rho*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += rho*dUdX(l,k)*N(j)*N(i)*detJW; + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == SIM::STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + + res(k) += dPdX(k); + res(k) *= delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += convI*convJ; + + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,(i-1)*nf + k) += rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += convJ*rho*dUdX(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m); + + ES((i-1)*nf + k) -= rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += P*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdX(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= res(k)*convI; + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= res(k)*dNdX(i,k); + } + } + + return getIntegralResult(elmInt); +} + + +bool NavierStokesG2::evalSol (Vector& s, const Vector& N, + const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const +{ + if (primsol.empty()) + { + std::cerr <<" *** NavierStokesG2::evalSol: No primary solution." + << std::endl; + return false; + } + + int ierr = 0; + if (ierr = utl::gather(MNPC,nsd,primsol[0],*eVs[0])) + { + std::cerr <<" *** NavierStokesG2::evalSol: Detected " + << ierr <<" node numbers out of range."<< std::endl; + return false; + } + + return true; +} diff --git a/src/Integrands/NavierStokesG2.h b/src/Integrands/NavierStokesG2.h new file mode 100644 index 00000000..fc0d4108 --- /dev/null +++ b/src/Integrands/NavierStokesG2.h @@ -0,0 +1,135 @@ +//============================================================================== +//! +//! \file NavierStokesG2.h +//! +//! \date May 28 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for Navier-Stokes equations with G2 stabilization. +//! +//============================================================================== + +#ifndef _NAVIER_STOKES_G2_H +#define _NAVIER_STOKES_G2_H + +#include "StabilizedStokes.h" + +/*! + \brief Class representing the integrand of the pressure stabilized Navier-Stokes problem. + \details This class supports G2-stabilized Navier-Stokes formulations using equal + order elements for velocity and pressure. +*/ + + +class NavierStokesG2 : public StabilizedStokes +{ + public: + + //! \brief The default constructor initializes all pointers to zero + //! \param[in] n Number of spatial dimensions + NavierStokesG2(short int n, ProblemFormulation form, int itg = 3); + //! \brief The destructor clears the static work arrays used internally. + virtual ~NavierStokesG2(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Volume-averaged basis function values over the element + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 3. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return true; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h characteristic element length + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h = 0.0) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Average value of basis function over element + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return false; } + + //! \brief Evaluates the secondary solution at current integration point. + //! \param[out] s The solution field values + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] MNPC Matrix of nodal point correspondance + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Returns which integrand to use + int getIntegrandType() const { return 2; } +}; + +#endif diff --git a/src/Integrands/NavierStokesG2CN.C b/src/Integrands/NavierStokesG2CN.C new file mode 100644 index 00000000..cc9b8730 --- /dev/null +++ b/src/Integrands/NavierStokesG2CN.C @@ -0,0 +1,639 @@ +// $Id: NavierStokesG2CN.C,v 1.3 2011-02-08 12:39:20 rho Exp $ +//============================================================================== +//! +//! \file NavierStokesG2CN.C +//! +//! \date August 9 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for G2 stabilized Navier-Stokes problems. +//! \details Time integration by the Crank-Nicholson method. +//! +//============================================================================== + +#include "NavierStokesG2CN.h" +#include "TimeDomain.h" +#include "ElmNorm.h" + + +NavierStokesG2CN::NavierStokesG2CN(short int n, ProblemFormulation form, int itg) + : NavierStokesG2(n,form,itg) {} + + +NavierStokesG2CN::~NavierStokesG2CN() {} + + +bool NavierStokesG2CN::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h) const +{ + int i, j; + int k, l, m; + real temp1, temp2; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real pconvI, pdiff, Pn; + real delta1, delta2; + real dtInv, dUdt; + Vector vel(3), conv(3), visc(3), res(3), dPdX(3); + Vector pvel(3), resp(3), dPdXn(3); + Matrix dUdX(3,3), dUdXn(3,3); + Matrix3D d2UdX2(3,3,3), d2UdX2n(3,3,3); + + const int nf = nsd + 1; + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + Vector& EVP = *eVs[1]; + // Previous velocity at integration point + pvel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + pvel(k) += EVP((i-1)*nf + k)*N(i); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Pressure in integration point at previous timestep + Pn = 0.0; + for (i = 1;i <= N.size();i++) + Pn += EVP(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Velocity gradient at previous timestep + dUdXn.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdXn(k,l) += dNdX(i,l)*EVP((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Pressure gradient in integration point at previous timestep + dPdXn.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdXn(k) += dNdX(i,k)*EVP(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Hessian for velocity at previous timestep + d2UdX2n.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2n(k,m,l) += d2NdX2(i,m,l)*EVP((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + if (time.dt == 0.0) + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + else + delta1 = 0.5/sqrt(dtInv*dtInv + (U*U)/(h*h)); + + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= detJW; + delta2 *= detJW; + + if (eM) { + Matrix& EM = *eM; + + // Time derivative term lhs + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + dUdt = rho*N(i)*N(j)*dtInv*detJW; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k, (i-1)*nf + k) += dUdt; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= 0.5*rho*N(j)*detJW; + + temp1 = 0.5*rho*N(i)*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += temp1*dUdX(l,k); + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= 0.5*mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + temp1 = 0.5*mu*detJW; + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += temp1*dNdX(i,k)*dNdX(j,l); + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= 0.5*divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + resp.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) { + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + resp(k) += rho*pvel(l)*dUdXn(k,l) - mu*d2UdX2n(k,l,l); + } + + res(k) += dPdX(k); + res(k) *= 0.5*delta1; + resp(k) += dPdXn(k); + resp(k) *= 0.5*delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += 0.5*convI*convJ; + + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,(i-1)*nf + k) += rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += 0.5*convJ*rho*dUdX(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += 0.5*dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= 0.5*mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += 0.5*delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += 0.5*delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= 0.5*delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= 0.5*delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Time derivative terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += rho*(pvel(k)-vel(k))*N(i)*dtInv*detJW; + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m) + pvel(m)*dUdXn(k,m); + + ES((i-1)*nf + k) -= 0.5*rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += 0.5*(P+Pn)*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= 0.5*mu*(dUdX(k,m) + dUdXn(k,m))*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + pconvI = 0.0; + for (k = 1;k <= nsd;k++) + pconvI += pvel(k)*dNdX(i,k); + pconvI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) { + ES((i-1)*nf+k) -= res(k)*convI; + ES((i-1)*nf+k) -= resp(k)*pconvI; + } + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= (res(k)+resp(k))*dNdX(i,k); + } + } + + return getIntegralResult(elmInt); +} + + +bool NavierStokesG2CN::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h) const +{ + int i, j; + int k, l, m; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real delta1, delta2; + Vector vel(3), conv(3), res(3), dPdX(3); + Matrix dUdX(3,3); + Matrix3D d2UdX2(3,3,3); + + const int nf = nsd + 1; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= detJW; + delta2 *= detJW; + + if (eM) { + Matrix& EM = *eM; + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= rho*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += rho*dUdX(l,k)*N(j)*N(i)*detJW; + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + + res(k) += dPdX(k); + res(k) *= delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += convI*convJ; + + for (l = 1;l <= nsd;l++) { + // RUNAR + //EM((j-1)*nf + l,(i-1)*nf + k) += rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += convJ*rho*dUdX(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m); + + ES((i-1)*nf + k) -= rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += P*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdX(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= res(k)*convI; + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= res(k)*dNdX(i,k); + } + } + + return getIntegralResult(elmInt); +} diff --git a/src/Integrands/NavierStokesG2CN.h b/src/Integrands/NavierStokesG2CN.h new file mode 100644 index 00000000..3430ef00 --- /dev/null +++ b/src/Integrands/NavierStokesG2CN.h @@ -0,0 +1,123 @@ +//============================================================================== +//! +//! \file NavierStokesG2CN.h +//! +//! \date August 9 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for Navier-Stokes equations with G2 stabilization +//! and +//! +//============================================================================== + +#ifndef _NAVIER_STOKES_G2_CN_H +#define _NAVIER_STOKES_G2_CN_H + +#include "NavierStokesG2.h" + +/*! + \brief Class representing the integrand of the pressure stabilized Navier-Stokes problem. + \details This class supports G2-stabilized Navier-Stokes formulations using equal + order elements for velocity and pressure. Time integration by the Crank-Nicholson method. +*/ + + +class NavierStokesG2CN : public NavierStokesG2 +{ + public: + + //! \brief The default constructor initializes all pointers to zero + //! \param[in] n Number of spatial dimensions + NavierStokesG2CN(short int n, ProblemFormulation form, int itg = 3); + //! \brief The destructor clears the static work arrays used internally. + virtual ~NavierStokesG2CN(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Volume-averaged basis function values over the element + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 3. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return true; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h characteristic element length + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h = 0.0) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Average value of basis function over element + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return false; } +}; + +#endif diff --git a/src/Integrands/NavierStokesG2GenTheta.C b/src/Integrands/NavierStokesG2GenTheta.C new file mode 100644 index 00000000..550fb1ac --- /dev/null +++ b/src/Integrands/NavierStokesG2GenTheta.C @@ -0,0 +1,660 @@ +//$Id: NavierStokesG2GenTheta.C,v 1.3 2011-02-08 12:40:31 rho Exp $ +//============================================================================== +//! +//! \file NavierStokesG2GenTheta.C +//! +//! \date August 3 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for G2 stabilized Navier-Stokes problems. +//! \details Time integration by the generalized theta method. +//! +//============================================================================== + +#include "NavierStokesG2GenTheta.h" +#include "TimeDomain.h" +#include "ElmNorm.h" + + +NavierStokesG2GenTheta::NavierStokesG2GenTheta(short int n, ProblemFormulation form, int itg) + : NavierStokesG2(n,form,itg) {} + + +NavierStokesG2GenTheta::~NavierStokesG2GenTheta() {} + + +bool NavierStokesG2GenTheta::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h) const +{ + int i, j; + int k, l, m; + real temp1, temp2; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real delta1, delta2; + real dtInv, dUdt; + Vector vel(3), pvel(3), conv(3), convp(3), diffp(3), res(3), resp(3), dPdX(3); + Matrix dUdX(3,3), dUdXn(3,3); + Matrix3D d2UdX2(3,3,3), d2UdX2n(3,3,3); + + // Time integration parameters + const real thetau = thetat; + const real thetap = thetam; + + const int nf = nsd + 1; + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + Vector& EVP = *eVs[1]; + // Previous velocity at integration point + pvel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + pvel(k) += EVP((i-1)*nf + k)*N(i); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Velocity gradient at previous timestep + dUdXn.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdXn(k,l) += dNdX(i,l)*EVP((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Hessian for velocity at previous timestep + d2UdX2n.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2n(k,m,l) += d2NdX2(i,m,l)*EVP((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + if (time.dt == 0.0) + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + else + delta1 = 0.5/sqrt(dtInv*dtInv + (U*U)/(h*h)); + + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= 0.01*detJW; + delta2 *= 0.01*detJW; + // RUNAR + //delta1 = 0.0001*h*h*detJW; + //delta2 = 0.0001*h*h*detJW; + + if (eM) { + Matrix& EM = *eM; + + // Time derivative term lhs + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + dUdt = rho*N(i)*N(j)*dtInv*detJW; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k, (i-1)*nf + k) += dUdt; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= thetau*rho*N(j)*detJW; + + temp1 = thetau*rho*N(i)*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += temp1*dUdX(l,k); + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= thetau*mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + temp1 = thetau*mu*detJW; + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += temp1*dNdX(i,k)*dNdX(j,l); + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= thetap*divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + resp.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) { + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + resp(k) += rho*pvel(l)*dUdXn(k,l) - mu*d2UdX2n(k,l,l); + } + + //res(k) += dPdX(k); + res(k) *= delta1; + resp(k) *= delta1; + } + + // Convection at previous timestep + convp.fill(0.0); + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + convp(k) += rho*pvel(l)*dUdXn(k,l); + + // Viscous terms at previous timestep + diffp.fill(0.0); + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + diffp(k) += mu*d2UdX2(k,l,l); + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += thetau*convI*convJ; + + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,(i-1)*nf + k) += thetau*rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += thetau*convJ*rho*dUdX(l,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += delta1*alpha*thetap*convp(l)*rho*N(i)*dNdX(j,k); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,i*nf) += thetap*dNdX(i,l)*convJ; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + l,(i-1)*nf + k) += delta1*thetap*dPdX(l)*rho*N(i)*dNdX(j,k); + } + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= thetau*mu; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,(i-1)*nf + k) -= delta1*alpha*thetap*diffp(l)*rho*N(i)*dNdX(j,k); + } + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += thetau*delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += thetau*delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= thetap*delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= thetau*delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Time derivative terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += rho*(pvel(k)-vel(k))*N(i)*dtInv*detJW; + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += thetap*fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m); + + ES((i-1)*nf + k) -= thetau*rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += thetap*P*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdX(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) { + ES((i-1)*nf+k) -= thetau*res(k)*convI; + ES((i-1)*nf+k) -= alpha*thetap*convp(k)*N(i)*detJW; + ES((i-1)*nf+k) -= delta1*alpha*thetap*convp(k)*convI; + } + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + ES((i-1)*nf+k) += alpha*thetap*mu*dUdXn(k,l)*dNdX(j,l)*detJW; + ES((i-1)*nf+k) += delta1*alpha*thetap*mu*diffp(k)*convI; + } + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) { + ES(i*nf) -= thetau*res(k)*dNdX(i,k); + ES(i*nf) -= alpha*thetap*resp(k)*dNdX(i,k); + } + } + } + + return getIntegralResult(elmInt); +} + + +bool NavierStokesG2GenTheta::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h) const +{ + int i, j; + int k, l, m; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real delta1, delta2; + Vector vel(3), conv(3), res(3), dPdX(3); + Matrix dUdX(3,3); + Matrix3D d2UdX2(3,3,3); + + const int nf = nsd + 1; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= detJW; + delta2 *= detJW; + // RUNAR + //delta1 = 0.0001*h*h*detJW; + //delta2 = 0.0001*h*h*detJW; + + if (eM) { + Matrix& EM = *eM; + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= rho*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += rho*dUdX(l,k)*N(j)*N(i)*detJW; + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + + res(k) += dPdX(k); + res(k) *= delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += convI*convJ; + + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,(i-1)*nf + k) += rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += convJ*rho*dUdX(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m); + + ES((i-1)*nf + k) -= rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += P*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdX(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= res(k)*convI; + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= res(k)*dNdX(i,k); + } + } + + return getIntegralResult(elmInt); +} diff --git a/src/Integrands/NavierStokesG2GenTheta.h b/src/Integrands/NavierStokesG2GenTheta.h new file mode 100644 index 00000000..4e905324 --- /dev/null +++ b/src/Integrands/NavierStokesG2GenTheta.h @@ -0,0 +1,137 @@ +//============================================================================== +//! +//! \file NavierStokesG2GenTheta.h +//! +//! \date August 3 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for Navier-Stokes equations with G2 stabilization +//! and generalized theta method for time integration +//! +//============================================================================== + +#ifndef _NAVIER_STOKES_G2_GEN_THETA_H +#define _NAVIER_STOKES_G2_GEN_THETA_H + +#include "NavierStokesG2.h" + +/*! + \brief Class representing the integrand of the pressure stabilized Navier-Stokes problem. + \details This class supports G2-stabilized Navier-Stokes formulations using equal + order elements for velocity and pressure. Time integration by the generalized theta method. +*/ + + +class NavierStokesG2GenTheta : public NavierStokesG2 +{ + public: + + //! \brief The default constructor initializes all pointers to zero + //! \param[in] n Number of spatial dimensions + NavierStokesG2GenTheta(short int n, ProblemFormulation form, int itg = 3); + //! \brief The destructor clears the static work arrays used internally. + virtual ~NavierStokesG2GenTheta(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Volume-averaged basis function values over the element + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 3. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return true; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h characteristic element length + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h = 0.0) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Average value of basis function over element + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return false; } + + private: + int step; // Current step (1, 2 or 3) + real dt; // Current (sub-)timestep size + //static const real theta = 0.292893219; // Time integration parameter + //static const real thetam = 0.414213562; // Time integration parameter + //static const real thetat = 0.171572875; // Time integration parameter + //static const real alpha = 0.585786438; // Time integration parameter + //static const real beta = 0.414213562; // Time integration parameter + static const real theta = 0.5; + static const real thetam = 0.5; + static const real thetat = 0.5; + static const real alpha = 1.0; + static const real beta = 1.0; +}; + +#endif diff --git a/src/Integrands/NavierStokesG2MP.C b/src/Integrands/NavierStokesG2MP.C new file mode 100644 index 00000000..cd4966e8 --- /dev/null +++ b/src/Integrands/NavierStokesG2MP.C @@ -0,0 +1,660 @@ +// $Id: NavierStokesG2MP.C,v 1.2 2010-10-05 09:26:12 kmo Exp $ +//============================================================================== +//! +//! \file NavierStokesG2MP.C +//! +//! \date August 11 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for G2 stabilized Navier-Stokes problems. +//! \details Time integration by the Crank-Nicholson method. +//! +//============================================================================== + +#include "NavierStokesG2MP.h" +#include "TimeDomain.h" +#include "ElmNorm.h" + + +NavierStokesG2MP::NavierStokesG2MP(short int n, ProblemFormulation form, int itg) + : NavierStokesG2(n,form,itg) {} + + +NavierStokesG2MP::~NavierStokesG2MP() {} + + +bool NavierStokesG2MP::evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h) const +{ + int i, j; + int k, l, m; + real temp1, temp2; + real diff, divU, divI, convI, convJ, laplace, U, P; + real Pb, Pn; + real delta1, delta2; + real dtInv, dUdt; + Vector vel(3), bvel(3), conv(3), res(3), dPdX(3), dPdXn(3), dPdXb(3); + Vector pvel(3); + Matrix dUdX(3,3), dUdXn(3,3), dUdXb(3,3); + Matrix3D d2UdX2(3,3,3), d2UdX2n(3,3,3), d2UdX2b(3,3,3); + + const int nf = nsd + 1; + + // Inverse of timestep size + if (time.dt > 0.0) + dtInv = 1.0/time.dt; + else + dtInv = 0.0; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + Vector& EVP = *eVs[1]; + // Previous velocity at integration point + pvel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + pvel(k) += EVP((i-1)*nf + k)*N(i); + + // Intermediate velocity at integration point + bvel.fill(0.0); + for (k = 1;k <= nsd;k++) + bvel(k) = 0.5*(vel(k)+pvel(k)); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Pressure in integration point at previous timestep + Pn = 0.0; + for (i = 1;i <= N.size();i++) + Pn += EVP(i*nf)*N(i); + + // Pressure in integration point at intermediate timestep + Pb = 0.5*(P+Pn); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Velocity gradient at previous timestep + dUdXn.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdXn(k,l) += dNdX(i,l)*EVP((i-1)*nf+k); + + // Velocity gradient at intermediate timestep + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdXb(k,l) = 0.5*(dUdX(k,l)+dUdXn(k,l)); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Pressure gradient at previous timestep + dPdXn.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdXn(k) += dNdX(i,k)*EVP(i*nf); + + // Pressure gradient at intermediate timestep + for (k = 1;k <= nsd;k++) + dPdXb(k) = 0.5*(dPdX(k) + dPdXn(k)); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Hessian for velocity at previous timestep + d2UdX2n.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2n(k,m,l) += d2NdX2(i,m,l)*EVP((i-1)*nf + k); + + // Hessian for velocity at intermediate timestep + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + for (l = 1;l <= nsd;l++) + d2UdX2b(k,m,l) = 0.5*(d2UdX2(k,m,l) + d2UdX2n(k,m,l)); + + // Stabilization parameters + if (mu < rho*U*h) { + // RUNAR + if (time.dt == 0.0) + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + else + delta1 = 0.5/sqrt(dtInv*dtInv + (U*U)/(h*h)); + + delta2 = h; + } + else + delta1 = delta2 = h*h; + // RUNAR + //delta1 *= 0.01*detJW; + //delta2 *= 0.01*detJW; + delta1 *= 0.001*detJW; + delta2 *= 0.001*detJW; + // RUNAR + //delta1 = 0.0001*h*h*detJW; + //delta2 = 0.0001*h*h*detJW; + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + + // Time derivative term lhs + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + dUdt = rho*N(i)*N(j)*dtInv*detJW; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k, (i-1)*nf + k) += dUdt; + } + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += bvel(k)*dNdX(i,k); + convJ *= 0.5*rho*N(j)*detJW; + + temp1 = 0.5*rho*N(i)*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += temp1*dUdXb(l,k); + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= 0.5*mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + temp1 = 0.5*mu*detJW; + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += temp1*dNdX(i,k)*dNdX(j,l); + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= 0.5*divI; + EM(nf*j,nf*(i-1)+k) -= 0.5*divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) + res(k) += rho*bvel(l)*dUdXb(k,l) - mu*d2UdX2b(k,l,l); + + res(k) += dPdXb(k); + res(k) *= delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += bvel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += bvel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += 0.5*convI*convJ; + + for (l = 1;l <= nsd;l++) { + EM((j-1)*nf + l,(i-1)*nf + k) += 0.5*rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += 0.5*convJ*rho*dUdXb(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += 0.5*dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= 0.5*mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) -= 0.5*delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += 0.5*delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += 0.5*delta1*rho*dUdXb(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= 0.5*delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= 0.5*delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Time derivative terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += rho*(pvel(k)-vel(k))*N(i)*dtInv*detJW; + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += bvel(m)*dUdXb(k,m); + + ES((i-1)*nf + k) -= rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += Pb*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdXb(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdXb(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= res(k)*convI; + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= res(k)*dNdX(i,k); + } + } + + myMats.rhsOnly = false; + return true; +} + + +bool NavierStokesG2MP::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h) const +{ + int i, j; + int k, l, m; + real diff, divU, divI, divJ, convI, convJ, laplace, U, P; + real delta1, delta2; + Vector vel(3), conv(3), res(3), dPdX(3); + Matrix dUdX(3,3); + Matrix3D d2UdX2(3,3,3); + + const int nf = nsd + 1; + + Vector& EV = *eVs[0]; + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + U = 0.0; + for (k = 1;k <= nsd;k++) + U += vel(k)*vel(k); + U = sqrt(U); + + // Pressure in integration point + P = 0.0; + for (i = 1;i <= N.size();i++) + P += EV(i*nf)*N(i); + + // Velocity gradient in integration point + dUdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + dUdX(k,l) += dNdX(i,l)*EV((i-1)*nf+k); + + // Pressure gradient in integration point + dPdX.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPdX(k) += dNdX(i,k)*EV(i*nf); + + // Hessian for velocity + d2UdX2.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + for (m = 1;m <= nsd;m++) + d2UdX2(k,m,l) += d2NdX2(i,m,l)*EV((i-1)*nf + k); + + // Stabilization parameters + if (mu < rho*U*h) { + // RUNAR + delta1 = 0.5/sqrt(1.0/(h*h) + (U*U)/(h*h)); + delta2 = h; + } + else + delta1 = delta2 = h*h; + delta1 *= detJW; + delta2 *= detJW; + // RUNAR + //delta1 = 0.0001*h*h*detJW; + //delta2 = 0.0001*h*h*detJW; + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(i,k); + convJ *= rho*N(j)*detJW; + + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf+k,(i-1)*nf+k) += convJ; + + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += rho*dUdX(l,k)*N(j)*N(i)*detJW; + } + } + + // Viscous terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1;k <= nsd;k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+l,nf*(i-1)+k) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + divI = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= divI; + EM(nf*j,nf*(i-1)+k) -= divI; + } + + // Stabilization terms + + // Computation of non-linear residual + res.fill(0.0); + for (k = 1;k <= nsd;k++) { + for (l = 1;l <= nsd;l++) + res(k) += rho*vel(l)*dUdX(k,l) - mu*d2UdX2(k,l,l); + + res(k) += dPdX(k); + res(k) *= delta1; + } + + // STABILIZATION TERMS + for (i = 1;i <= N.size();i++) { + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + for (j = 1;j <= N.size();j++) { + // Convection for test functions + convJ = 0.0; + for (k = 1;k <= nsd;k++) + convJ += vel(k)*dNdX(j,k); + convJ *= delta1*rho; + + //--- MOMENTUM STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) { + EM((j-1)*nf + k,(i-1)*nf + k) += convI*convJ; + + for (l = 1;l <= nsd;l++) { + // RUNAR + //EM((j-1)*nf + l,(i-1)*nf + k) += rho*res(l)*dNdX(j,k)*N(i); + EM((j-1)*nf + l,(i-1)*nf + k) += convJ*rho*dUdX(l,k)*N(i); + } + } + + // Pressure terms + for (l = 1;l <= nsd;l++) + EM((j-1)*nf + l,i*nf) += dNdX(i,l)*convJ; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= mu; + + for (k = 1;k <= nsd;k++) + EM((j-1)*nf + k,(i-1)*nf + k) -= diff*convJ; + + // Continuity-continuity stabilization + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM((j-1)*nf+l,(i-1)*nf+k) += delta2*dNdX(i,k)*dNdX(j,l); + + //--- CONTINUITY STABILIZATION --- + + // Convection terms + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) += delta1*convI*dNdX(j,k); + + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + EM(j*nf,(i-1)*nf+k) += delta1*rho*dUdX(l,k)*N(i)*dNdX(j,l); + + // Pressure terms + laplace = 0.0; + for (k = 1;k <= nsd;k++) + laplace += dNdX(i,k)*dNdX(j,k); + laplace *= delta1; + EM(j*nf,i*nf) += laplace; + + // Viscous terms + diff = 0.0; + for (k = 1;k <= nsd;k++) + diff += d2NdX2(i,k,k); + diff *= delta1*mu; + + for (k = 1;k <= nsd;k++) + EM(j*nf,(i-1)*nf+k) -= diff*dNdX(j,k); + } + } + } + + if (eS) { + // Integrate rhs vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) { + // --- Residual of Galerkin formulation --- + + // -- Momentum equations -- + + // Body forces + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Convection terms + for (k = 1;k <= nsd;k++) { + conv.fill(0.0); + for (m = 1;m <= nsd;m++) + conv(k) += vel(m)*dUdX(k,m); + + ES((i-1)*nf + k) -= rho*conv(k)*N(i)*detJW; + } + + // Pressure terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf + k) += P*dNdX(i,k)*detJW; + + // Viscous terms + for (k = 1;k <= nsd;k++) + for (m = 1;m <= nsd;m++) + ES((i-1)*nf + k) -= mu*dUdX(k,m)*dNdX(i,m)*detJW; + + // -- Continuity equation -- + divU = 0.0; + for (m = 1;m <= nsd;m++) + divU += dUdX(m,m); + + ES(i*nf) += divU*N(i)*detJW; + + //--- MOMENTUM STABILIZATION--- + + // Convection for solution basis functions + convI = 0.0; + for (k = 1;k <= nsd;k++) + convI += vel(k)*dNdX(i,k); + convI *= rho; + + // Convection terms + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= res(k)*convI; + + // Divergence of velocity field and velocity test function + divU = 0.0; + for (k = 1;k <= nsd;k++) + divU += dUdX(k,k); + + // Continuity residual term + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) -= delta2*divU*dNdX(i,k); + + //--- CONTINUITY STABILIZATION --- + + // Momentum residual term + for (k = 1;k <= nsd;k++) + ES(i*nf) -= res(k)*dNdX(i,k); + } + } + + myMats.rhsOnly = false; + return true; +} diff --git a/src/Integrands/NavierStokesG2MP.h b/src/Integrands/NavierStokesG2MP.h new file mode 100644 index 00000000..80853020 --- /dev/null +++ b/src/Integrands/NavierStokesG2MP.h @@ -0,0 +1,123 @@ +//============================================================================== +//! +//! \file NavierStokesG2MP.h +//! +//! \date August 11 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for Navier-Stokes equations with G2 stabilization +//! and. Time integration by mid-point method +//! +//============================================================================== + +#ifndef _NAVIER_STOKES_G2_MP_H +#define _NAVIER_STOKES_G2_MP_H + +#include "NavierStokesG2.h" + +/*! + \brief Class representing the integrand of the pressure stabilized Navier-Stokes problem. + \details This class supports G2-stabilized Navier-Stokes formulations using equal + order elements for velocity and pressure. Time integration by the mid-point method. +*/ + + +class NavierStokesG2MP : public NavierStokesG2 +{ + public: + + //! \brief The default constructor initializes all pointers to zero + //! \param[in] n Number of spatial dimensions + NavierStokesG2MP(short int n, ProblemFormulation form, int itg = 3); + //! \brief The destructor clears the static work arrays used internally. + virtual ~NavierStokesG2MP(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 1. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + //! + //! \details This interface is used when \a getIntegrandType returns 2. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, const Vec3& X, + double h = 0.0) const; + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] time Parameters for nonlinear and time-dependent simulations + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Volume-averaged basis function values over the element + //! \param[in] X Cartesian coordinates of current integration point + //! + //! \details This interface is used when \a getIntegrandType returns 3. + //! The default implementation forwards to the stationary version. + //! Reimplement this method for time-dependent or non-linear problems. + virtual bool evalInt(LocalIntegral*& elmInt, + const TimeDomain& time, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return true; } + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const + { return false; } + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h characteristic element length + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h = 0.0) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Average value of basis function over element + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const + { return false; } +}; + +#endif diff --git a/src/Integrands/Poisson.C b/src/Integrands/Poisson.C new file mode 100644 index 00000000..6d6d804e --- /dev/null +++ b/src/Integrands/Poisson.C @@ -0,0 +1,315 @@ +// $Id: Poisson.C,v 1.6 2010-12-27 18:29:01 kmo Exp $ +//============================================================================== +//! +//! \file Poisson.C +//! +//! \date Apr 16 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Integrand implementations for Poisson problems. +//! +//============================================================================== + +#include "Poisson.h" +#include "Utilities.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "VTF.h" + + +Poisson::Poisson (unsigned short int n) : nsd(n) +{ + kappa = 1.0; + + tracFld = 0; + heatSrc = 0; + eM = 0; + eS = eV = 0; + + // Only the current solution is needed + primsol.resize(1); +} + + +void Poisson::setMode (SIM::SolutionMode mode) +{ + myMats.rhsOnly = false; + eM = 0; + eS = eV = 0; + + switch (mode) + { + case SIM::STATIC: + myMats.A.resize(1); + myMats.b.resize(1); + eM = &myMats.A[0]; + eS = &myMats.b[0]; + tracVal.clear(); + break; + + case SIM::VIBRATION: + myMats.A.resize(1); + eM = &myMats.A[0]; + break; + + case SIM::RHS_ONLY: + myMats.rhsOnly = true; + if (myMats.b.empty()) + myMats.b.resize(1); + eS = &myMats.b[0]; + tracVal.clear(); + break; + + case SIM::RECOVERY: + myMats.rhsOnly = true; + if (myMats.b.empty()) + myMats.b.resize(1); + eV = &myMats.b[0]; + break; + + default: + myMats.A.clear(); + myMats.b.clear(); + tracVal.clear(); + } +} + + +bool Poisson::initElement (const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + if (eM) eM->resize(nen,nen,true); + if (eS) eS->resize(nen,true); + + int ierr = 0; + if (eV && !primsol.front().empty()) + if (ierr = utl::gather(MNPC,1,primsol.front(),*eV)) + std::cerr <<" *** Poisson::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats.withLHS = true; + return ierr == 0; +} + + +bool Poisson::initElementBou (const std::vector& MNPC) +{ + if (eS) eS->resize(MNPC.size(),true); + + myMats.withLHS = false; + return true; +} + + +bool Poisson::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + elmInt = &myMats; + + if (eM) + { + // Evaluate conductive matrix at this point. + if (!this->formCmatrix(C,X,false)) return false; + + CB.multiply(C,dNdX,false,true).multiply(detJW); // CB = C*dNdX^T*|J|*w + eM->multiply(dNdX,CB,false,false,true); // EK += dNdX * CB + } + + // Integrate heat source if defined + if (eS && heatSrc) + eS->add(N,(*heatSrc)(X)*detJW); + + return true; +} + + +bool Poisson::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + elmInt = &myMats; + if (!tracFld) + { + std::cerr <<" *** Poisson::evalBou: No tractions."<< std::endl; + return false; + } + else if (!eS) + { + std::cerr <<" *** Poisson::evalBou: No load vector."<< std::endl; + return false; + } + + // Evaluate the heat flux q at point X + Vec3 q = (*tracFld)(X); + + // Evaluate the Neumann value -q*n + double trac = -(q*normal); + + // Store traction value for visualization + if (abs(trac) > 1.0e8) + tracVal.insert(std::make_pair(X,trac*normal)); + + // Integrate the Neumann value + eS->add(N,trac*detJW); + + return true; +} + + +bool Poisson::writeGlvT (VTF* vtf, int iStep, int& nBlock) const +{ + if (tracVal.empty()) + return true; + else if (!vtf) + return false; + + // Write boundary tractions as discrete point vectors to the VTF-file + if (!vtf->writeVectors(tracVal,++nBlock)) + return false; + + return vtf->writeVblk(nBlock,"Tractions",1,iStep); +} + + +bool Poisson::formCmatrix (Matrix& C, const Vec3& X, bool inv) const +{ + double cx = this->getConductivety(X); + if (inv && cx != 0.0) cx = 1.0/cx; + + C.diag(cx,nsd); + return true; +} + + +bool Poisson::evalSol (Vector& q, const Vector&, + const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const +{ + if (primsol.front().empty()) + { + std::cerr <<" *** Poisson::evalSol: No primary solution." + << std::endl; + return false; + } + else if (!this->formCmatrix(C,X,false)) + return false; + + Vector Dtmp; + int ierr = 0; + if (ierr = utl::gather(MNPC,1,primsol.front(),Dtmp)) + { + std::cerr <<" *** Poisson::evalSol: Detected " + << ierr <<" node numbers out of range."<< std::endl; + return false; + } + + // Evaluate the heat flux vector + CB.multiply(C,dNdX,false,true).multiply(Dtmp,q); // q = C*dNdX^T*Dtmp + q *= -1.0; + + return true; +} + + +bool Poisson::evalSol (Vector& q, + const Matrix& dNdX, const Vec3& X) const +{ + if (!eV || eV->empty()) + { + std::cerr <<" *** Poisson::evalSol: No solution vector." + << std::endl; + return false; + } + else if (eV->size() != dNdX.rows()) + { + std::cerr <<" *** Poisson::evalSol: Invalid solution vector." + <<"\n size(eV) = "<< eV->size() <<" size(dNdX) = " + << dNdX.rows() <<","<< dNdX.cols() << std::endl; + return false; + } + else if (!this->formCmatrix(C,X,false)) + return false; + + // Evaluate the heat flux vector + CB.multiply(C,dNdX,false,true).multiply(*eV,q); // q = C*dNdX^T*eV + q *= -1.0; + + return true; +} + + +bool Poisson::evalSolScal (Vector& s, + const VecFunc& asol, const Vec3& X) const +{ + s = Vector(asol(X).ptr(),nsd); + return true; +} + + +const char* Poisson::getFieldLabel (size_t i, const char* prefix) const +{ + static const char* s3[3] = { "q_x","q_y","q_z" }; + + static std::string label; + if (prefix) + label = prefix + std::string(" "); + else + label.clear(); + + label += s3[i]; + + return label.c_str(); +} + + +NormBase* Poisson::getNormIntegrandScal (VecFunc* asol) const +{ + return new PoissonNorm(*const_cast(this),asol); +} + + +bool PoissonNorm::initElement (const std::vector& MNPC) +{ + return problem.initElement(MNPC); +} + + +bool PoissonNorm::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + ElmNorm* eNorm = dynamic_cast(elmInt); + if (!eNorm) + { + std::cerr <<" *** PoissonNorm::evalInt: No norm array."<< std::endl; + return false; + } + + // Evaluate the inverse conductivety matrix at this point + Matrix Cinv; + if (!problem.formCmatrix(Cinv,X,true)) return false; + + // Evaluate the finite element heat flux field + Vector sigmah; + if (!problem.evalSol(sigmah,dNdX,X)) return false; + + // Integrate the energy norm a(u^h,u^h) + ElmNorm& pnorm = *eNorm; + pnorm[0] += sigmah.dot(Cinv*sigmah)*detJW; + if (anasol) + { + // Evaluate the analytical heat flux + Vector sigma((*anasol)(X).ptr(),sigmah.size()); + // Integrate the energy norm a(u,u) + pnorm[1] += sigma.dot(Cinv*sigma)*detJW; + // Integrate the error in energy norm a(u-u^h,u-u^h) + sigma -= sigmah; + pnorm[2] += sigma.dot(Cinv*sigma)*detJW; + } + + return true; +} diff --git a/src/Integrands/Poisson.h b/src/Integrands/Poisson.h new file mode 100644 index 00000000..e625aca9 --- /dev/null +++ b/src/Integrands/Poisson.h @@ -0,0 +1,202 @@ +// $Id: Poisson.h,v 1.4 2010-12-27 18:29:02 kmo Exp $ +//============================================================================== +//! +//! \file Poisson.h +//! +//! \date Apr 16 2010 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Integrand implementations for Poisson problems. +//! +//============================================================================== + +#ifndef _POISSON_H +#define _POISSON_H + +#include "IntegrandBase.h" +#include "ElmMats.h" +#include "Vec3.h" +#include + +class VTF; + + +/*! + \brief Class representing the integrand of the Poisson problem. + \details This class supports constant isotrophic conductivity properties only. + Properties with spatial variation has to be implemented as sub-classes. +*/ + +class Poisson : public Integrand +{ +public: + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + Poisson(unsigned short int n = 3); + //! \brief Empty destructor. + virtual ~Poisson() {} + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + virtual void setMode(SIM::SolutionMode mode); + + //! \brief Defines the traction field to use in Neumann boundary conditions. + void setTraction(VecFunc* tf) { tracFld = tf; myMats.rhsOnly = true; } + //! \brief Clears the integration point traction values. + void clearTracVal() { tracVal.clear(); } + + //! \brief Defines the heat source. + void setSource(RealFunc* src) { heatSrc = src; } + + //! \brief Defines the conductivity. + void setMaterial(double K) { kappa = K; } + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElement(const std::vector& MNPC); + //! \brief Initializes current element for boundary numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElementBou(const std::vector& MNPC); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Evaluates the secondary solution at a result point. + //! \param[out] s Array of solution field values at current point + //! \param[in] N Basis function values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + //! \param[in] MNPC Nodal point correspondance for the basis function values + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the finite element (FE) solution at an integration point. + //! \param[out] s The FE solution values at current point + //! \param[in] dNdX Basis function gradients at current point + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSol(Vector& s, const Matrix& dNdX, const Vec3& X) const; + + //! \brief Evaluates the analytical solution at an integration point. + //! \param[out] s The analytical solution values at current point + //! \param[in] asol The analytical solution field + //! \param[in] X Cartesian coordinates of current point + virtual bool evalSolScal(Vector& s, const VecFunc& asol, const Vec3& X) const; + + //! \brief Writes the surface tractions for a given time step to VTF-file. + //! \param vtf The VTF-file object to receive the tractions + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvT(VTF* vtf, int iStep, int& nBlock) const; + + //! \brief Returns whether there are any traction values to write to VTF. + virtual bool hasTractionValues() const { return !tracVal.empty(); } + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \note The Integrand object is allocated dynamically and has to be deleted + //! manually when leaving the scope of the pointer variable receiving the + //! returned pointer value. + //! \param[in] asol Pointer to analytical solution field (optional) + virtual NormBase* getNormIntegrandScal(VecFunc* asol = 0) const; + + //! \brief Returns the number of secondary solution fields. + virtual size_t getNoFields() const { return nsd; } + + //! \brief Returns the name of a secondary solution field component. + //! \param[in] i Field component index + //! \param[in] prefix Name prefix for all components + virtual const char* getFieldLabel(size_t i, const char* prefix = 0) const; + +protected: + //! \brief Returns the conductivety at current point. + virtual double getConductivety(const Vec3&) const { return kappa; } + +public: + //! \brief Sets up the conductivity matrix at current point. + //! \param[out] C \f$ nsd\times nsd\f$-matrix + //! \param[in] X Cartesian coordinates of current point + //! \param[in] invers If \e true, set up the inverse matrix instead + virtual bool formCmatrix(Matrix& C, const Vec3& X, bool invers = false) const; + +private: + // Physical properties (constant) + double kappa; //!< Conductivity + +protected: + // Finite element quantities + Matrix* eM; //!< Pointer to element matrix + Vector* eS; //!< Pointer to element right-hand-side vector + Vector* eV; //!< Pointer to element solution vector + + mutable ElmMats myMats; //!< Local element matrices + + VecFunc* tracFld; //!< Pointer to boundary traction field + RealFunc* heatSrc; //!< Pointer to interior heat source + + mutable std::map tracVal; //!< Traction field point values + + unsigned short int nsd; //!< Number of space dimensions (1, 2 or, 3) + + // Work arrays declared as members to avoid frequent re-allocation + // within the numerical integration loop (for reduced overhead) + mutable Matrix C; //!< Conductivity matrix + mutable Matrix CB; //!< Result of the matrix-matrix product C*dNdX^T +}; + + +/*! + \brief Class representing the integrand of Poisson energy norms. +*/ + +class PoissonNorm : public NormBase +{ +public: + //! \brief The only constructor initializes its data members. + //! \param[in] p The Poisson problem to evaluate norms for + //! \param[in] a The analytical heat flux (optional) + PoissonNorm(Poisson& p, VecFunc* a) : problem(p), anasol(a) {} + //! \brief Empty destructor. + virtual ~PoissonNorm() {} + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElement(const std::vector& MNPC); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Returns the number of norm quantities. + virtual size_t getNoFields() const { return anasol ? 3 : 1; } + +private: + Poisson& problem; //!< The problem-specific data + VecFunc* anasol; //!< Analytical heat flux +}; + +#endif diff --git a/src/Integrands/StabilizedNavierStokes.C b/src/Integrands/StabilizedNavierStokes.C new file mode 100644 index 00000000..92a2fdc0 --- /dev/null +++ b/src/Integrands/StabilizedNavierStokes.C @@ -0,0 +1,326 @@ +// $Id: StabilizedNavierStokes.C,v 1.4 2010-12-30 17:38:19 kmo Exp $ +//============================================================================== +//! +//! \file StabilizedNavierStokes.C +//! +//! \date Mar 17 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for stabilized Navier-Stokes problems. +//! +//============================================================================== + +#include "StabilizedNavierStokes.h" +#include "Utilities.h" +#include "Vec3Oper.h" + + +StabilizedNavierStokes::StabilizedNavierStokes(short int n, ProblemFormulation form, int itg) + : StabilizedStokes(n,form,itg) +{ + mu = 1.0e-3; + rho = 1.0; +} + + +StabilizedNavierStokes::~StabilizedNavierStokes() +{ +} + + +bool StabilizedNavierStokes::evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, j; + int k, l; + double div, laplace, conv; + Vector vel(3); + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + Vector& EV = *eVs[0]; + + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += vel(k)*dNdX(j,k); + conv *= rho*N(i)*detJW; + + for (k = 1;k <= nsd;k++) + EM((i-1)*nf+k,(j-1)*nf+k) += conv; + } + + // Viscous terms + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) { + laplace = 0.0; + for (k = 1; k <= nsd; k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1; k <= nsd; k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == SIM::STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+k,nf*(i-1)+l) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + div = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= div; + EM(nf*j,nf*(i-1)+k) -= div; + } + + // Pressure stabilization + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + EM(nf*j,nf*i) -= N(i)*N(j)*detJW; + } + + if (eS) { + // Integrate body force vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + } + + return true; +} + + +bool StabilizedNavierStokes::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h) const +{ + int i, j; + int k, l; + double div, laplace, conv; + Vector vel(3); + + const real delta = 0.001*h*h*detJW; + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + Vector& EV = *eVs[0]; + + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + // Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += vel(k)*dNdX(j,k); + conv *= rho*N(i)*detJW; + + for (k = 1;k <= nsd;k++) + EM((i-1)*nf+k,(j-1)*nf+k) += conv; + } + + + // Viscous terms + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) { + laplace = 0.0; + for (k = 1; k <= nsd; k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1; k <= nsd; k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == SIM::STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+k,nf*(i-1)+l) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + div = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= div; + EM(nf*j,nf*(i-1)+k) -= div; + } + + // Pressure stabilization + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + for (k = 1;k <= nsd;k++) { + laplace = 0.0; + for (l = 1;l <= nsd;l++) + laplace += d2NdX2(i,l,l); + laplace *= delta*mu; + + EM(nf*j,nf*(i-1) + k) += laplace*dNdX(j,k); + EM(nf*j,nf*i) -= delta*dNdX(i,k)*dNdX(j,k); + } + } + + if (formulation == SIM::STRESS) + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + if (k != l) + EM(nf*j,nf*(i-1)+l) += delta*mu*d2NdX2(i,k,l)*dNdX(j,k); + } + + if (eS) { + // Integrate body force vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Pressure stabilization term + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES(nf*i) -= delta*fb(k)*dNdX(i,k); + } + + return true; +} + + +bool StabilizedNavierStokes::evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const +{ + int i, j; + int k, l; + double div, laplace, conv; + Vector vel(3); + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + Vector& EV = *eVs[0]; + + // Velocity in integration point + vel.fill(0.0); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + vel(k) += EV((i-1)*nf + k)*N(i); + + //Convection terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + // Sum convection for each direction + conv = 0.0; + for (k = 1;k <= nsd;k++) + conv += vel(k)*dNdX(j,k); + conv *= rho*detJW; + + for (k = 1;k <= nsd;k++) + EM((i-1)*nf+k,(j-1)*nf+k) += conv*N(i); + } + + // Viscous terms + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) { + laplace = 0.0; + for (k = 1; k <= nsd; k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1; k <= nsd; k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == SIM::STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+k,nf*(i-1)+l) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + div = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= div; + EM(nf*j,nf*(i-1)+k) -= div; + } + + // Pressure stabilization + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + EM(nf*j,nf*i) -= (N(i)-Navg(i))*(N(j)-Navg(j))*detJW; + } + + if (eS) { + // Integrate body force vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + } + + return true; +} + + +bool StabilizedNavierStokes::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + elmInt = &myMats; + if (!eS || !tracFld) + { + std::cerr <<" *** StabilizedNavierStokes::evalBou: Zero pointers."<< std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) + tracVal.insert(std::make_pair(X,T)); + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (short int d = 1; d <= nsd; d++) + ES(nf*(a-1)+d) += T[d-1]*N(a)*detJW; + + return true; +} diff --git a/src/Integrands/StabilizedNavierStokes.h b/src/Integrands/StabilizedNavierStokes.h new file mode 100644 index 00000000..b04697c8 --- /dev/null +++ b/src/Integrands/StabilizedNavierStokes.h @@ -0,0 +1,82 @@ +//============================================================================== +//! +//! \file StabilizedNavierStokes.h +//! +//! \date Mar 17 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for pressure stabilized Navier-Stokes problems. +//! +//============================================================================== + +#ifndef _STABILIZED_NAVIER_STOKES_H +#define _STABILIZED_NAVIER_STOKES_H + +#include "StabilizedStokes.h" + +/*! + \brief Class representing the integrand of the pressure stabilized Navier-Stokes problem. + \details This class supports pressure stabilized Navier-Stokes formulations using equal + order elements for velocity and pressure. +*/ + + +class StabilizedNavierStokes : public StabilizedStokes +{ + public: + + //! \brief The default constructor initializes all pointers to zero + //! \param[in] n Number of spatial dimensions + StabilizedNavierStokes(short int n, ProblemFormulation form, int itg = 3); + //! \brief The destructor clears the static work arrays used internally. + virtual ~StabilizedNavierStokes(); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h characteristic element length + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h = 0.0) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Average value of basis function over element + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const; + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Returns which integrand to use + int getIntegrandType() const { return 3; } +}; + +#endif diff --git a/src/Integrands/StabilizedStokes.C b/src/Integrands/StabilizedStokes.C new file mode 100644 index 00000000..fd0d6d1b --- /dev/null +++ b/src/Integrands/StabilizedStokes.C @@ -0,0 +1,247 @@ +// $Id: StabilizedStokes.C,v 1.6 2010-12-30 17:38:19 kmo Exp $ +//============================================================================== +//! +//! \file StabilizedStokes.C +//! +//! \date Feb 11 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for stabilized Stokes problems. +//! +//============================================================================== + +#include "StabilizedStokes.h" +#include "Utilities.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "VTF.h" + + +StabilizedStokes::StabilizedStokes (short int n, + Stokes::ProblemFormulation form, int itg) + : Stokes(n,(SIM::Formulation)form,itg) +{ + // Resize element solution vectors + eVs.resize(2); + for (int i = 0;i < 2;i++) + eVs[i] = new Vector(); + + primsol.resize(2); +} + + +bool StabilizedStokes::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + size_t i, j; + short int k, l; + double div, laplace; + + const int nf = nsd + 1; + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + + // Viscous terms + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) { + laplace = 0.0; + for (k = 1; k <= nsd; k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1; k <= nsd; k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+k,nf*(i-1)+l) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + div = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= div; + EM(nf*j,nf*(i-1)+k) -= div; + } + + // Pressure stabilization + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + EM(nf*j,nf*i) -= N(i)*N(j)*detJW; + } + + if (eS) { + // Integrate body force vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + } + + myMats.rhsOnly = false; + return true; +} + + +bool StabilizedStokes::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h) const +{ + size_t i, j; + short int k, l; + double div, laplace; + + const int nf = nsd + 1; + const real delta = 0.001*h*h*detJW; + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + + // Viscous terms + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) { + laplace = 0.0; + for (k = 1; k <= nsd; k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1; k <= nsd; k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+k,nf*(i-1)+l) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + div = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= div; + EM(nf*j,nf*(i-1)+k) -= div; + } + + // Pressure stabilization + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) { + for (k = 1;k <= nsd;k++) { + laplace = 0.0; + for (l = 1;l <= nsd;l++) + laplace += d2NdX2(i,l,l); + laplace *= delta*mu; + + EM(nf*j,nf*(i-1) + k) += laplace*dNdX(j,k); + EM(nf*j,nf*i) -= delta*dNdX(i,k)*dNdX(j,k); + } + } + + if (formulation == STRESS) + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + if (k != l) + EM(nf*j,nf*(i-1)+l) += delta*mu*d2NdX2(i,k,l)*dNdX(j,k); + } + + if (eS) { + // Integrate body force vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + + // Pressure stabilization term + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES(nf*i) -= delta*fb(k)*dNdX(i,k); + } + + myMats.rhsOnly = false; + return true; +} + + +bool StabilizedStokes::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const +{ + size_t i, j; + short int k, l; + double div, laplace; + + const int nf = nsd + 1; + + elmInt = &myMats; + if (eM) { + Matrix& EM = *eM; + + // Viscous terms + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) { + laplace = 0.0; + for (k = 1; k <= nsd; k++) + laplace += dNdX(i,k)*dNdX(j,k); + + laplace *= mu*detJW; + for (k = 1; k <= nsd; k++) + EM(nf*(j-1)+k,nf*(i-1)+k) += laplace; + } + + if (formulation == STRESS) + for (i = 1; i <= N.size(); i++) + for (j = 1; j <= N.size(); j++) + for (k = 1; k <= nsd; k++) + for (l = 1; l <= nsd; l++) + EM(nf*(j-1)+k,nf*(i-1)+l) += mu*dNdX(i,k)*dNdX(j,l)*detJW; + + // Pressure and divergence terms + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + for (k = 1;k <= nsd;k++) { + div = N(j)*dNdX(i,k)*detJW; + EM(nf*(i-1)+k,nf*j) -= div; + EM(nf*j,nf*(i-1)+k) -= div; + } + + // Pressure stabilization + for (i = 1;i <= N.size();i++) + for (j = 1;j <= N.size();j++) + EM(nf*j,nf*i) -= (N(i)-Navg(i))*(N(j)-Navg(j))*detJW; + } + + if (eS) { + // Integrate body force vector + Vector& ES = *eS; + Vector fb(g,nsd); + fb *= this->getMassDensity(X)*detJW; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + ES((i-1)*nf+k) += fb(k)*N(i); + } + + myMats.rhsOnly = false; + return true; +} + + diff --git a/src/Integrands/StabilizedStokes.h b/src/Integrands/StabilizedStokes.h new file mode 100644 index 00000000..47b1cdf2 --- /dev/null +++ b/src/Integrands/StabilizedStokes.h @@ -0,0 +1,72 @@ +// $Id: StabilizedStokes.h,v 1.4 2010-12-06 08:49:59 rho Exp $ +//============================================================================== +//! +//! \file StabilizedStokes.h +//! +//! \date Feb 11 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for pressure stabilized Stokes problems. +//! +//============================================================================== + +#ifndef _STABILIZED_STOKES_H +#define _STABILIZED_STOKES_H + +#include "Stokes.h" + +/*! + \brief Class representing the integrand of the pressure-stabilized + Stokes problem. + \details This class supports pressure stabilized Stokes formulations + using equal order elements for velocity and pressure. +*/ + +class StabilizedStokes : public Stokes +{ +public: + + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] form The solution formulation to use + //! \param[in] itg The integrandtype to use + StabilizedStokes(short int n, Stokes::ProblemFormulation form = LAPLACE, + int itg = 3); + //! \brief The destructor frees dynamically allocated objects. + virtual ~StabilizedStokes() {} + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] d2NdX2 Basis function second derivatives + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] h Characteristic element length + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Matrix3D& d2NdX2, + const Vec3& X, double h = 0.0) const; + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] Navg Volume-averaged basis function values over the element + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vector& Navg, const Vec3& X) const; +}; + +#endif diff --git a/src/Integrands/Stokes.C b/src/Integrands/Stokes.C new file mode 100644 index 00000000..da6b8fdb --- /dev/null +++ b/src/Integrands/Stokes.C @@ -0,0 +1,323 @@ +// $Id: Stokes.C,v 1.3 2011-02-08 12:23:54 rho Exp $ +//============================================================================== +//! +//! \file Stokes.C +//! +//! \date Sep 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Integrand implementations for Stokes problems. +//! +//============================================================================== + +#include "Stokes.h" +#include "Utilities.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "VTF.h" + + +Stokes::Stokes (unsigned short int n, SIM::Formulation form, int itg_type) +{ + nsd = n; + nf = n+1; + formulation = form; + integrandType = itg_type; + + mu = rho = 1.0; + + tracFld = 0; + + myMats = new ElmMats(); + myMats->resize(1,1); + myMats->rhsOnly = false; + + eM = &myMats->A[0]; + eS = &myMats->b[0]; +} + + +Stokes::~Stokes () +{ + if (myMats) delete myMats; + + for (int i = 0; i < 2; i++) + if (eVs[i]) delete eVs[i]; +} + + +bool Stokes::initElement (const std::vector& MNPC) +{ + const size_t nen = MNPC.size(); + + eM->resize(nf*nen,nf*nen,true); + eS->resize(nf*nen,true); + + int ierr = 0; + for (size_t i = 0; i < 2 && i < primsol.size() && eVs[i]; i++) + if (ierr = utl::gather(MNPC,nf,primsol[i],*eVs[i])) + std::cerr <<" *** Stokes::initElement: Detected " + << ierr <<" node numbers out of range."<< std::endl; + + myMats->withLHS = true; + return ierr == 0; +} + + +bool Stokes::initElementBou (const std::vector& MNPC) +{ + eS->resize(nf*MNPC.size(),true); + + myMats->withLHS = false; + return true; +} + + +bool Stokes::evalBou (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const +{ + if (!tracFld) + { + std::cerr <<" *** Stokes::evalBou: No tractions."<< std::endl; + return false; + } + + // Evaluate the surface traction + Vec3 T = (*tracFld)(X,normal); + + // Store the traction value for vizualization + if (!T.isZero()) tracVal[X] = T; + + // Integrate the force vector + Vector& ES = *eS; + for (size_t a = 1; a <= N.size(); a++) + for (short int d = 1; d <= nsd; d++) + ES(nf*(a-1)+d) += T[d-1]*N(a)*detJW; + + return getIntegralResult(elmInt); +} + + +bool Stokes::writeGlvT (VTF* vtf, int iStep, int& nBlock) const +{ + if (tracVal.empty()) + return true; + else if (!vtf) + return false; + + // Write boundary tractions as discrete point vectors to the VTF-file + if (!vtf->writeVectors(tracVal,++nBlock)) + return false; + + return vtf->writeVblk(nBlock,"Tractions",1,iStep); +} + + +bool Stokes::evalSol (Vector& s, const Vector& N, + const Matrix& dNdX, const Vec3& X, + const std::vector& MNPC) const +{ + return false; +} + + +bool Stokes::evalSol (Vector& s, const TensorFunc& asol, const Vec3& X) const +{ + s = asol(X); + return true; +} + + +const char* Stokes::getFieldLabel(size_t i, const char* prefix) const +{ + static const char* s[3] = { "u_x","u_y","u_z" }; + + static std::string label; + if (prefix) + label = prefix + std::string(" "); + else + label.clear(); + + label += s[i]; + return label.c_str(); +} + + +NormBase* Stokes::getNormIntegrand (AnaSol* asol) const +{ + return new StokesNorm(*const_cast(this),asol); +} + + +bool Stokes::getIntegralResult (LocalIntegral*& elmInt) const +{ + elmInt = myMats; + return elmInt ? true : false; +} + + +bool StokesNorm::initElement (const std::vector& MNPC) +{ + return problem.initElement(MNPC); +} + + +bool StokesNorm::evalInt (LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const +{ + int i, k, l; + double value, eps, epsh; + + const int nsd = dNdX.cols(); + const int nf = nsd+1; + const int nbf = N.size(); + + if (!anasol->hasScalarSol() && !anasol->hasVectorSol()) { + std::cerr <<" *** StokesNorm::evalInt: No analytical solution."<< std::endl; + return false; + } + + ElmNorm* eNorm = dynamic_cast(elmInt); + if (!eNorm) { + std::cerr <<" *** StokesNorm::evalInt: No norm array."<< std::endl; + return false; + } + + // Find current element solution vector + Vector* eV = const_cast(problem.getElementSolution()); + if (!eV) { + std::cerr <<" *** StokesNorm::evalInt: No solution"<< std::endl; + return false; + } + Vector& EV = *eV; + + ElmNorm& pnorm = *eNorm; + int ip = 0; + // Pressure norms + if (anasol->getScalarSol()) { + // Analytical pressure + real P = (*anasol->getScalarSol())(X); + + // Computed pressure + real Ph = 0.0; + for (i = 1;i <= nbf;i++) + Ph += EV(i*nf)*N(i); + + // L2-norm of pressure error + P -= Ph; + pnorm[ip++] += P*P*detJW; + } + + // Velocity norms + if (anasol->getVectorSol()) { + // Analytical velocity + Vec3 U = (*anasol->getVectorSol())(X); + + // Computed velocity + Vec3 Uh; Uh = 0.0; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + Uh[k-1] += EV((i-1)*nf + k)*N(i); + + // L2-norm of velocity error + U -= Uh; + pnorm[ip++] += U*U*detJW; + } + + // Pressure gradient norm + if (anasol->getScalarSecSol()) { + // Analytical pressure gradient + Vec3 dP = (*anasol->getScalarSecSol())(X); + + // Computed pressure gradient + Vec3 dPh; dPh = 0.0; + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + dPh[k-1] += EV(i*nf)*dNdX(i,k); + + // H1-seminorm of pressure + dP -= dPh; + pnorm[ip++] += dP*dP*detJW; + } + + // Energy norms + if (anasol->getVectorSecSol()) { + // Viscosity + const double mu = problem.getViscosity(X); + + // Analytical velocity gradient + Tensor gradU = (*anasol->getVectorSecSol())(X); + + // Computed velocity gradient + Matrix gradUh(nsd,nsd); + for (i = 1;i <= N.size();i++) + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) + gradUh(k,l) += EV((i-1)*nf + k)*dNdX(i,l); + + if (problem.getFormulation() == Stokes::LAPLACE) { + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + // Energy norm of analytical solution + pnorm[ip++] += mu*gradU(k,l)*gradU(k,l)*detJW; + // Energy norm of numerical solution + pnorm[ip++] += mu*gradUh(k,l)*gradUh(k,l)*detJW; + // Energy norm of error + value = gradU(k,l)-gradUh(k,l); + pnorm[ip++] += mu*value*value*detJW; + } + } + else { + for (k = 1;k <= nsd;k++) + for (l = 1;l <= nsd;l++) { + // Strain of analytical solution + eps = 0.5*(gradU(k,l) + gradU(l,k)); + // Strain of computed solution + epsh = 0.5*(gradUh(k,l) + gradUh(l,k)); + // Energy norm of analytical solution + pnorm[ip++] += mu*eps*eps*detJW; + // Energy norm of computed solution + pnorm[ip++] += mu*epsh*epsh*detJW; + // Energy norm of error + eps -= epsh; + pnorm[ip++] += mu*epsh*epsh*detJW; + } + } + } + + return true; +} + + +bool Stokes::getBodyForce(const Vec3& X, Vector& f) const +{ + const Vec4* Y = dynamic_cast(&X); + if (!Y) return false; + + const double PI = 3.141592653589793238462; + const double x = X[0]; + const double y = X[1]; + const double t = Y->t; + + f.fill(0.0); + + + f(1) = rho*pow(sin(PI*x),2.0)*sin(2.0*PI*y)*cos(t) + + rho*PI*pow(sin(PI*x)*sin(2.0*PI*y)*sin(t),2.0)*sin(2.0*PI*x) - + 2.0*rho*PI*pow(sin(PI*x)*sin(PI*y)*sin(t),2.0)*sin(2.0*PI*x)*cos(2.0*PI*y) - + PI*sin(PI*x)*sin(PI*y)*sin(t) - + 2.0*pow(PI,2.0)*mu*sin(2.0*PI*y)*sin(t)*(cos(2*PI*x)-2*pow(sin(PI*x),2.0)); + + f(2) = -rho*sin(2.0*PI*x)*pow(sin(PI*y),2.0)*cos(t) - + 2.0*rho*PI*pow(sin(PI*x)*sin(PI*y)*sin(t),2.0)*cos(2.0*PI*x)*sin(2.0*PI*y) + + rho*PI*pow(sin(2.0*PI*x)*sin(PI*y)*sin(t),2.0)*sin(2.0*PI*y) + + PI*cos(PI*x)*cos(PI*y)*sin(t) - + 4*mu*pow(PI*sin(PI*y),2.0)*sin(2.0*PI*x)*sin(t) + + 2.0*mu*pow(PI,2.0)*sin(2.0*PI*x)*cos(2.0*PI*y)*sin(t); + + return true; +} diff --git a/src/Integrands/Stokes.h b/src/Integrands/Stokes.h new file mode 100644 index 00000000..0bc51b47 --- /dev/null +++ b/src/Integrands/Stokes.h @@ -0,0 +1,221 @@ +// $Id: Stokes.h,v 1.4 2011-02-08 12:21:31 rho Exp $ +//============================================================================== +//! +//! \file Stokes.h +//! +//! \date Sep 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Base class for Integrand implementation of Stokes problems. +//! +//============================================================================== + +#ifndef _STOKES_H +#define _STOKES_H + +#include "IntegrandBase.h" +#include "ElmMats.h" +#include "Vec3.h" +#include "AnaSol.h" +#include + +class VTF; + + +/*! + \brief Base class representing the integrand of Stokes problems. + \details Implements common features for Stokes/Navier-Stokes problems. +*/ + +class Stokes : public Integrand +{ +public: + //TODO: Temporary; remove and use SIM-enum in all sub-classes instead (kmo). + enum ProblemFormulation { LAPLACE = SIM::LAPLACE, STRESS = SIM::STRESS }; + //END TODO: + + //! \brief The default constructor initializes all pointers to zero. + //! \param[in] n Number of spatial dimensions + //! \param[in] form The solution formulation to use + //! \param[in] itg_type The integrand type to use + Stokes(unsigned short int n, + SIM::Formulation form = SIM::LAPLACE, + int itg_type = 1); + //! \brief The destructor frees dynamically allocated objects. + virtual ~Stokes(); + + //! \brief Defines the traction field to use in Neumann boundary conditions. + void setTraction(TractionFunc* tf) { tracFld = tf; } + //! \brief Clears the integration point traction values. + void clearTracVal() { tracVal.clear(); } + + //! \brief Defines the gravitation vector. + void setGravity(double gx, double gy, double gz) + { g[0] = gx; g[1] = gy; g[2] = gz; } + + //! \brief Defines fluid properties for current volume patch. + //! \param[in] Density Mass density + //! \param[in] Viscosity Dynamic viscosity + void setFluidProperties(double Density, double Viscosity) + { rho = Density; mu = Viscosity; } + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElement(const std::vector& MNPC); + //! \brief Initializes current element for boundary numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElementBou(const std::vector& MNPC); + + //! \brief Evaluates the integrand at a boundary point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] normal Boundary normal vector at current integration point + virtual bool evalBou(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const Vec3& normal) const; + + //! \brief Evaluates the secondary solution at current integration point. + //! \param[out] s The solution field values + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + //! \param[in] MNPC Matrix of nodal point correspondance + virtual bool evalSol(Vector& s, + const Vector& N, const Matrix& dNdX, + const Vec3& X, const std::vector& MNPC) const; + + //! \brief Evaluates the analytical secondary solution at the given point. + //! \param[out] s The solution field values + //! \param[in] asol The analytical solution field + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalSol(Vector& s, const TensorFunc& asol, const Vec3& X) const; + + //! \brief Writes the surface tractions for a given time step to VTF-file. + //! \param vtf The VTF-file object to receive the tractions + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvT(VTF* vtf, int iStep, int& nBlock) const; + + //! \brief Returns a pointer to an Integrand for solution norm evaluation. + //! \note The Integrand is allocated dynamically and has to be deleted + //! manually when leaving the scope of the pointer returned. + //! \param[in] asol Pointer to analytical solution field (optional) + virtual NormBase* getNormIntegrand(AnaSol* asol = 0) const; + + //! \brief Returns problem formulation type. + SIM::Formulation getFormulation() const { return formulation; } + + //! \brief Returns which integrand to use. + virtual int getIntegrandType() const { return integrandType; } + + //! \brief Returns the mass density at current point. + virtual double getMassDensity(const Vec3&) const { return rho; } + + //! \brief Returns the body force per unit mass + //! \param[in] X Cartesian coordinate of current integration point + //! \param[out] f Body force vector per unit mass + virtual bool getBodyForce(const Vec3& X, Vector& f) const; + + //! \brief Returns viscosity at current point. + virtual double getViscosity(const Vec3&) const { return mu; } + + //! \brief Returns a pointer to current element solution vector. + const Vector* getElementSolution() const { return eVs[0]; } + + //! \brief Returns a pointer to current element solution vector. + virtual const Vector* getElementVelocity() const { return 0; } + + //! \brief Returns a pointer to current element solution vector. + virtual const Vector* getElementPressure() const { return 0; } + + //! \brief Returns the number of solution fields. + virtual size_t getNoFields() const { return nsd; } + + //! \brief Returns the number of secondary solution fields. + virtual size_t getNoSecFields() const { return nsd*nsd; } + + //! \brief Returns the name of the primary solution field component. + //! \param[in] i Field component index + //! \param[in] prefix Name prefix for all components + virtual const char* getFieldLabel(size_t i, const char* prefix = 0) const; + + //! \brief Returns the name of a secondary solution field component. + //! \param[in] i Field component index + //! \param[in] prefix Name prefix for all components + virtual const char* getSecFieldLabel(size_t i, const char* prefix = 0) const + { return 0; } + +protected: + //! \brief Utility used by the virtual \a evalInt and \a evalBou methods. + //! \param elmInt Pointer to the integrated element quantities + bool getIntegralResult(LocalIntegral*& elmInt) const; + + // Problem parameters + unsigned short int nsd; //!< Number of space dimensions (1, 2 or, 3) + unsigned short int nf; //!< Number of primary field variables + SIM::Formulation formulation; //!< Problem formulation flag + int integrandType; //!< Integrand type flag + + // Physical properties (constant) + double mu; //!< Dynamic viscosity + double rho; //!< Mass density + double g[3]; //!< Gravitation vector + + // Finite element quantities + Matrix* eM; //!< Pointer to element coefficient matrix + Vector* eS; //!< Pointer to element right-hand-side vector + std::vector eVs; //!< Pointers to element solution vectors + + ElmMats* myMats; //!< Local element matrices + + TractionFunc* tracFld; //!< Pointer to boundary traction field + + mutable std::map tracVal; //!< Traction field point values +}; + + +/*! + \brief Class representing the integrand of Stokes energy norms. +*/ + +class StokesNorm : public NormBase +{ +public: + //! \brief The only constructor initializes its data members. + //! \param[in] p The Stokes problem to evaluate norms for + //! \param[in] a The analytical velocity and pressure fields (optional) + StokesNorm(Stokes& p, AnaSol* a) : problem(p), anasol(a) {} + //! \brief Empty destructor. + virtual ~StokesNorm() {} + + //! \brief Initializes current element for numerical integration. + //! \param[in] MNPC Matrix of nodal point correspondance for current element + virtual bool initElement(const std::vector& MNPC); + + //! \brief Evaluates the integrand at an interior point. + //! \param elmInt The local integral object to receive the contributions + //! \param[in] detJW Jacobian determinant times integration point weight + //! \param[in] N Basis function values + //! \param[in] dNdX Basis function gradients + //! \param[in] X Cartesian coordinates of current integration point + virtual bool evalInt(LocalIntegral*& elmInt, double detJW, + const Vector& N, const Matrix& dNdX, + const Vec3& X) const; + + //! \brief Returns the number of primary norm quantities. + virtual size_t getNoFields() const { return anasol ? 2 : 0; } + + //! \brief Returns the number of secondary norm quantities. + virtual size_t getNoSecFields() const { return anasol ? 3 : 1; } + +protected: + Stokes& problem; //!< The problem-specific data + AnaSol* anasol; //!< Analytical solution fields +}; + + +#endif diff --git a/src/LinAlg/DenseMatrix.C b/src/LinAlg/DenseMatrix.C new file mode 100644 index 00000000..02a41523 --- /dev/null +++ b/src/LinAlg/DenseMatrix.C @@ -0,0 +1,541 @@ +// $Id: DenseMatrix.C,v 1.17 2010-12-06 09:06:08 rho Exp $ +//============================================================================== +//! +//! \file DenseMatrix.C +//! +//! \date Jan 4 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Dense system matrix representation. +//! +//============================================================================== + +#include "DenseMatrix.h" +#include "SparseMatrix.h" +#include "SAM.h" + + +#if defined(_WIN32) +#define addem2_ ADDEM2 +#define dgesv_ DGESV +#define dgetrs_ DGETRS +#define dsyevx_ DSYEVX +#define dsygvx_ DSYGVX +#define dgeev_ DGEEV +#elif defined(_AIX) +#define addem2_ addem2 +#define dgesv_ dgesv +#define dgetrs_ dgetrs +#define dsyevx_ dsyevx +#define dsygvx_ dsygvx +#define dgeev_ dgeev +#endif + +extern "C" { +//! \brief Adds an element matrix \a eK into the system matrix \a sysK. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void addem2_(const real* eK, const real* ttcc, const int* mpar, + const int* madof, const int* meqn, const int* mpmnpc, + const int* mmnpc, const int* mpmceq, const int* mmceq, + const int& iel, const int& nedof, const int& neq, + const int& lpu, const int& nrhs, real* sysK, real* sysRHS, + int* work, int& ierr); + +//! \brief Solves the linear equation system \a A*x=b. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dgesv_(const int& n, const int& nrhs, + real* A, const int& lda, int* ipiv, + real* B, const int& ldb, int& info); + +//! \brief Solves the equation system \a A*x=b when \a A is already factorized. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dgetrs_(const char* trans, const int& n, const int& nrhs, + real* A, const int& lda, int* ipiv, + real* B, const int& ldb, int& info); + +//! \brief Solves the standard eigenproblem \a A*x=(lambda)*x. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dsyevx_(const char* jobz, const char* range, const char* uplo, + const int& n, real* a, const int& lda, + const real& vl, const real& vu, const int& il, const int& iu, + const real& abstol, const int& m, real* w, real* z, const int& ldz, + real* work, const int& lwork, int* iwork, int* ifail, int& info); + +//! \brief Solves the non-symmetric eigenproblem \a A*x=(lambda)*x. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dgeev_(const char* jobvl, const char* jobvr, + const int& n, real* a, const int& lda, + real* wr, real* wi, real* vl, const int& ldvl, + real* vr, const int& ldvr, + real* work, const int& lwork, int& info); + +//! \brief Solves the generalized eigenproblem \a A*x=(lambda)*B*x. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dsygvx_(const int& itype, const char* jobz, const char* range, + const char* uplo, const int& n, real* a, const int& lda, + real* b, const int& ldb, const real& vl, const real& vu, + const int& il, const int& iu, const real& abstol, + const int& m, real* w, real* z, const int& ldz, + real* work, const int& lwork, int* iwork, int* ifail, int& info); +} + + +DenseMatrix::DenseMatrix (const DenseMatrix& A) +{ + myMat = A.myMat; + ipiv = 0; + if (A.ipiv) + std::cerr <<"DenseMatrix constructor: Copying factored matrix"<< std::endl; +} + + +DenseMatrix::DenseMatrix (const RealArray& data, size_t nrows) +{ + size_t ndata = data.size(); + if (nrows == 0) nrows = (size_t)sqrt((double)ndata); + size_t ncols = nrows ? ndata/nrows : 0; + + myMat.resize(nrows,ncols); + memcpy(myMat.ptr(),&data.front(),nrows*ncols*sizeof(real)); + ipiv = 0; +} + + +size_t DenseMatrix::dim (int idim) const +{ + if (idim == 1) + return myMat.rows(); + else if (idim == 2) + return myMat.cols(); + else + return myMat.size(); +} + + +void DenseMatrix::initAssembly (const SAM& sam) +{ + myMat.resize(sam.neq,sam.neq,true); +} + + +void DenseMatrix::init () +{ + myMat.fill(real(0)); + + // Delete pivotation vector of old factorization, if any + if (ipiv) delete ipiv; + ipiv = 0; +} + + +/*! + \brief This is a C++ version of the F77 subroutine ADDEM2 (SAM library). + \details It performs exactly the same tasks, except that \a NRHS always is 1. +*/ +#ifndef USE_F77SAM +static void assemDense (const Matrix& eM, Matrix& SM, Vector& SV, + const std::vector& meen, const int* meqn, + const int* mpmceq, const int* mmceq, const real* ttcc) +{ + // Add elements corresponding to free dofs in eM into SM + int i, j, ip, nedof = meen.size(); + for (j = 1; j <= nedof; j++) + { + int jeq = meen[j-1]; + if (jeq < 1) continue; + + SM(jeq,jeq) += eM(j,j); + + for (i = 1; i < j; i++) + { + int ieq = meen[i-1]; + if (ieq < 1) continue; + + SM(ieq,jeq) += eM(i,j); + SM(jeq,ieq) += eM(j,i); + } + } + + // Add (appropriately weighted) elements corresponding to constrained + // (dependent and prescribed) dofs in eM into SM and/or SV + for (j = 1; j <= nedof; j++) + { + int jceq = -meen[j-1]; + if (jceq < 1) continue; + + int jp = mpmceq[jceq-1]; + real c0 = ttcc[jp-1]; + + // Add contributions to SV (right-hand-side) + if (!SV.empty()) + for (i = 1; i <= nedof; i++) + { + int ieq = meen[i-1]; + int iceq = -ieq; + if (ieq > 0) + SV(ieq) -= c0*eM(i,j); + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + SV(ieq) -= c0*ttcc[ip]*eM(i,j); + } + } + + // Add contributions to SM + for (jp = mpmceq[jceq-1]; jp < mpmceq[jceq]-1; jp++) + if (mmceq[jp] > 0) + { + int jeq = meqn[mmceq[jp]-1]; + for (i = 1; i <= nedof; i++) + { + int ieq = meen[i-1]; + int iceq = -ieq; + if (ieq > 0) + { + SM(ieq,jeq) += ttcc[jp]*eM(i,j); + SM(jeq,ieq) += ttcc[jp]*eM(j,i); + } + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + SM(ieq,jeq) += ttcc[ip]*ttcc[jp]*eM(i,j); + } + } + } + } +} +#endif + +bool DenseMatrix::assemble (const Matrix& eM, const SAM& sam, int e) +{ + if (myMat.rows() != sam.neq || myMat.cols() < sam.neq) + return false; + + int ierr = 0; +#ifdef USE_F77SAM + real dummyRHS; + int* work = new int[eM.rows()]; + addem2_(eM.ptr(), sam.ttcc, sam.mpar, + sam.madof, sam.meqn, sam.mpmnpc, sam.mmnpc, sam.mpmceq, sam.mmceq, + e, eM.rows(), sam.neq, 6, 0, myMat.ptr(), &dummyRHS, work, ierr); + delete[] work; +#else + Vector dummyB; + std::vector meen; + if (sam.getElmEqns(meen,e,eM.rows())) + assemDense(eM,myMat,dummyB,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); + else + ierr = 1; +#endif + return ierr == 0; +} + + +bool DenseMatrix::assemble (const Matrix& eK, const SAM& sam, + SystemVector& B, int e) +{ + if (myMat.rows() != sam.neq || myMat.cols() < sam.neq) + return false; + + StdVector* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + + int ierr = 0; +#ifdef USE_F77SAM + int* work = new int[eK.rows()]; + addem2_(eK.ptr(), sam.ttcc, sam.mpar, + sam.madof, sam.meqn, sam.mpmnpc, sam.mmnpc, sam.mpmceq, sam.mmceq, + e, eK.rows(), sam.neq, 6, 1, myMat.ptr(), Bptr->ptr(), work, ierr); + delete[] work; +#else + std::vector meen; + if (sam.getElmEqns(meen,e,eK.rows())) + assemDense(eK,myMat,*Bptr,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); + else + ierr = 1; +#endif + return ierr == 0; +} + + +bool DenseMatrix::augment (const SystemMatrix& B, size_t r0, size_t c0) +{ + const SparseMatrix* sB = dynamic_cast(&B); + if (sB) return this->augment(*sB,r0,c0); + + const DenseMatrix* dB = dynamic_cast(&B); + if (dB) return this->augment(dB->myMat,r0,c0); + + return false; +} + + +bool DenseMatrix::augment (const Matrix& B, size_t r0, size_t c0) +{ + size_t newRow = r0 + B.rows(); + size_t newCol = c0 + B.cols(); + size_t newDim = newRow > newCol ? newRow : newCol; + + this->redim(newDim,newDim); + for (size_t c = 1; c <= B.cols(); c++) + for (size_t r = 1; r <= B.rows(); r++) + { + myMat(r0+r,c0+c) += B(r,c); + myMat(c0+c,r0+r) += B(r,c); + } + + return true; +} + + +bool DenseMatrix::augment (const SparseMatrix& B, size_t r0, size_t c0) +{ + const ValueMap& elem = B.getValues(); + if (elem.empty()) return false; + + size_t newRow = r0 + B.rows(); + size_t newCol = c0 + B.cols(); + size_t newDim = newRow > newCol ? newRow : newCol; + + this->redim(newDim,newDim); + for (ValueIter it = elem.begin(); it != elem.end(); it++) + { + myMat(r0+it->first.first,c0+it->first.second) += it->second; + myMat(c0+it->first.second,r0+it->first.first) += it->second; + } + + return true; +} + + +bool DenseMatrix::redim (size_t r, size_t c) +{ + if (r == myMat.rows()) + if (c == myMat.cols()) + return false; + else if (c > myMat.cols()) + { + myMat.resize(r,c); + return true; + } + + Matrix tmp(myMat); + myMat.resize(r,c); + for (size_t i = 1; i <= c && i <= tmp.cols(); i++) + myMat.fillColumn(i,tmp.getColumn(i)); + + return true; +} + + +bool DenseMatrix::add (const SystemMatrix& B, real alpha) +{ + const DenseMatrix* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + + if (myMat.rows() != Bptr->myMat.rows()) return false; + if (myMat.cols() < Bptr->myMat.cols()) return false; + + myMat.add(Bptr->myMat,alpha); + return true; +} + + +bool DenseMatrix::add (real sigma) +{ + real* v = myMat.ptr(); + size_t inc = myMat.rows()+1; + for (size_t i = 0; i < myMat.size(); i += inc) + v[i] += sigma; + return true; +} + + +bool DenseMatrix::multiply (const SystemVector& B, SystemVector& C) +{ + const StdVector* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + StdVector* Cptr = dynamic_cast(&C); + if (!Cptr) return false; + + return myMat.multiply(*Bptr,*Cptr); +} + + +bool DenseMatrix::solve (SystemVector& B, bool newLHS) +{ + const int n = myMat.rows(); + if (n < 1) return true; // No equations to solve + if (n > myMat.cols()) return false; + +#ifdef USE_CBLAS + int info = 0; + if (!ipiv) + { + ipiv = new int[n]; + dgesv_(n,1,myMat.ptr(),n,ipiv,B.getPtr(),B.dim(),info); + } + else + { + char trans = 'N'; + dgetrs_(&trans,n,1,myMat.ptr(),n,ipiv,B.getPtr(),B.dim(),info); + } + if (info == 0) return true; + + std::cerr <<"LAPACK::DGESV: "; + if (info < 0) + std::cerr <<"Invalid argument #"<< -info << std::endl; + else + std::cerr <<"Singular stiffness matrix, pivot "<< info + <<" (of total "<< n <<") is zero."<< std::endl; +#else + std::cerr <<"DGESV not available - linked without LAPack/BLAS"<< std::endl; +#endif + return false; +} + + +bool DenseMatrix::solveEig (RealArray& val, Matrix& vec, int nv) +{ + const int n = myMat.rows(); + if (n < 1 || nv < 1) return true; // No equations to solve + if (n > myMat.cols()) return false; + +#ifdef USE_CBLAS + std::cout <<" Solving dense eigenproblem using LAPACK::DSYEVX"<< std::endl; + int m, info = 0; + char jobz = 'V'; + char range = 'I'; + char uplo = 'U'; + real dummy = 0.0; + real abstol = 0.0; + // Invoke with Lwork = -1 to estimate work space size + dsyevx_(&jobz,&range,&uplo,n,myMat.ptr(),n,dummy,dummy,1,nv, + abstol,m,&val.front(),vec.ptr(),n,&dummy,-1,0,0,info); + + if (info == 0) + { + // Allocate work space + int Lwork = int(dummy); + real* work = new real[Lwork]; + int* Iwork = new int[6*n]; + val.resize(n); + vec.resize(n,nv); + // Solve the eigenproblem + dsyevx_(&jobz,&range,&uplo,n,myMat.ptr(),n,dummy,dummy,1,nv, + abstol,m,&val.front(),vec.ptr(),n,work,Lwork,Iwork+n,Iwork,info); + delete[] work; + delete[] Iwork; + val.resize(nv); + if (info == 0) return true; + } + + std::cerr <<"LAPACK::DSYEVX: "; + if (info < 0) + std::cerr <<"Invalid argument #"<< -info << std::endl; + else + std::cerr << info <<" eigenvectors failed to converge."<< std::endl; +#else + std::cerr <<"DSYEVX not available - linked without LAPack/BLAS"<< std::endl; +#endif + return false; +} + + +bool DenseMatrix::solveEig (DenseMatrix& B, RealArray& val, Matrix& vec, int nv, + real) +{ + const int n = myMat.rows(); + if (n < 1 || nv < 1) return true; // No equations to solve + if (n > myMat.cols()) return false; + +#ifdef USE_CBLAS + std::cout <<" Solving dense eigenproblem using LAPACK::DSYGVX"<< std::endl; + int m, info = 0; + char jobz = 'V'; + char range = 'I'; + char uplo = 'U'; + real dummy = 0.0; + real abstol = 0.0; + // Invoke with Lwork = -1 to estimate work space size + dsygvx_(1,&jobz,&range,&uplo,n,myMat.ptr(),n,B.myMat.ptr(),n, + dummy,dummy,1,nv,abstol,m,&val.front(),vec.ptr(),n, + &dummy,-1,0,0,info); + + if (info == 0) + { + // Allocate work space + int Lwork = int(dummy); + real* work = new real[Lwork]; + int* Iwork = new int[6*n]; + val.resize(n); + vec.resize(n,nv); + // Solve the eigenproblem + dsygvx_(1,&jobz,&range,&uplo,n,myMat.ptr(),n,B.myMat.ptr(),n, + dummy,dummy,1,nv,abstol,m,&val.front(),vec.ptr(),n, + work,Lwork,Iwork+n,Iwork,info); + delete[] work; + delete[] Iwork; + val.resize(nv); + if (info == 0) return true; + } + + std::cerr <<"LAPACK::DSYGVX: "; + if (info < 0) + std::cerr <<"Invalid argument #"<< -info << std::endl; + else if (info <= n) + std::cerr << info <<" eigenvectors failed to converge."<< std::endl; + else if (info <= 2*n) + std::cerr <<"The leading minor of order "<< info-n + <<" of matrix B is not positive definite."<< std::endl; +#else + std::cerr <<"DSYGVX not available - linked without LAPack/BLAS"<< std::endl; +#endif + return false; +} + + +bool DenseMatrix::solveEigNon (RealArray& r_val, RealArray& c_val) +{ + const int n = myMat.rows(); + if (n < 1) return true; // No equations to solve + +#ifdef USE_CBLAS + int m, info = 0; + char jobvl = 'N'; + char jobvr = 'N'; + real dummy = 0.0; + // Invoke with Lwork = -1 to estimate work space size + dgeev_(&jobvl,&jobvr,n,myMat.ptr(),n,&r_val.front(),&c_val.front(), + &dummy,1,&dummy,1,&dummy,-1,info); + + if (info == 0) + { + // Allocate work space + int Lwork = int(dummy); + real* work = new real[Lwork]; + r_val.resize(n); + c_val.resize(n); + // Solve the eigenproblem + dgeev_(&jobvl,&jobvr,n, myMat.ptr(),n,&r_val.front(),&c_val.front(), + &dummy,1,&dummy,1,work,Lwork,info); + delete[] work; + if (info == 0) return true; + } + +#else + std::cerr <<"DGEEV not available - linked without LAPack/BLAS"<< std::endl; +#endif + return false; +} diff --git a/src/LinAlg/DenseMatrix.h b/src/LinAlg/DenseMatrix.h new file mode 100644 index 00000000..f8566f22 --- /dev/null +++ b/src/LinAlg/DenseMatrix.h @@ -0,0 +1,162 @@ +// $Id: DenseMatrix.h,v 1.16 2010-12-06 09:05:46 rho Exp $ +//============================================================================== +//! +//! \file DenseMatrix.h +//! +//! \date Jan 4 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Dense system matrix representation. +//! +//============================================================================== + +#ifndef _DENSE_MATRIX_H +#define _DENSE_MATRIX_H + +#include "SystemMatrix.h" + +class SparseMatrix; + + +/*! + \brief Class for representing a dense system matrix. +*/ + +class DenseMatrix : public SystemMatrix +{ +public: + //! \brief Default constructor. + DenseMatrix(size_t m = 0, size_t n = 0) : myMat(m,n) { ipiv = 0; } + //! \brief Copy constructor. + DenseMatrix(const DenseMatrix& A); + //! \brief Special constructor taking data from a one-dimensional array. + DenseMatrix(const RealArray& data, size_t nrows = 0); + //! \brief The destructor frees the dynamically allocated arrays. + virtual ~DenseMatrix() { if (ipiv) delete[] ipiv; } + + //! \brief Returns the matrix type. + virtual Type getType() const { return DENSE; } + + //! \brief Creates a copy of the system matrix and returns a pointer to it. + virtual SystemMatrix* copy() const { return new DenseMatrix(*this); } + + //! \brief Resizes the matrix to dimension \f$r \times c\f$. + //! \details Will preserve existing matrix content within the new dimension. + bool redim(size_t r, size_t c); + + //! \brief Returns the dimension of the system matrix. + //! \param[in] idim Which direction to return the dimension in. + virtual size_t dim(int idim = 1) const; + + //! \brief Index-1 based element access. + real& operator()(size_t r, size_t c) { return myMat(r,c); } + //! \brief Index-1 based element reference. + const real& operator()(size_t r, size_t c) const { return myMat(r,c); } + + //! \brief Initializes the element assembly process. + //! \details Must be called once before the element assembly loop. + //! \param[in] sam Auxilliary data describing the FE model topology, etc. + virtual void initAssembly(const SAM& sam); + + //! \brief Initializes the matrix to zero assuming it is properly dimensioned. + virtual void init(); + + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, int e); + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \details When multi-point constraints are present, contributions from + //! these are also added into the system right-hand-side load vector. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param B The system right-hand-side load vector + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, + SystemVector& B, int e); + + //! \brief Augments a similar matrix symmetrically to the current matrix. + //! \param[in] B The matrix to be augmented + //! \param[in] r0 Row offset for the augmented matrix + //! \param[in] c0 Column offset for the augmented matrix + virtual bool augment(const SystemMatrix& B, size_t r0, size_t c0); + + //! \brief Adds a matrix with similar dimension to the current matrix. + //! \param[in] B The matrix to be added + //! \param[in] alpha Scale factor for matrix \b B + virtual bool add(const SystemMatrix& B, real alpha = 1.0); + + //! \brief Adds the diagonal matrix \f$\sigma\f$\b I to the current matrix. + virtual bool add(real sigma); + + //! \brief Performs the matrix-vector multiplication \b C = \a *this * \b B. + virtual bool multiply(const SystemVector& B, SystemVector& C); + + //! \brief Solves the linear system of equations for a given right-hand-side. + //! \param B Right-hand-side vector on input, solution vector on output + //! \param newLHS \e true if the left-hand-side matrix is updated + virtual bool solve(SystemVector& B, bool newLHS = true); + + //! \brief Solves a standard symmetric-definite eigenproblem. + //! \details The eigenproblem is assumed to be on the form + //! \b A \b x = \f$\lambda\f$ \b x where \b A ( = \a *this ) + //! is assumed to be symmetric and positive definite. + //! The eigenproblem is solved by the LAPack library subroutine \a DSYEVX. + //! \sa LAPack library documentation. + //! \param[out] eigVal Computed eigenvalues + //! \param[out] eigVec Computed eigenvectors stored column by column + //! \param[in] nev The number of eigenvalues and eigenvectors to compute + bool solveEig(RealArray& eigVal, Matrix& eigVec, int nev); + + //! \brief Solves a non-symmetric eigenproblem. + //! \details The eigenproblem is assumed to be on the form + //! \b A \b x = \f$\lambda\f$ \b x where \b A ( = \a *this ) + //! is a square non-symmetric matrix. + //! The eigenproblem is solved by the LAPack library subroutine \a DGEEV. + //! \sa LAPack library documentation. + //! \param[out] r_val Real part of the computed eigenvalues + //! \param[out] c_val Complex part of the computed eigenvalues + bool solveEigNon(RealArray& r_val, RealArray& c_val); + + //! \brief Solves a generalized symmetric-definite eigenproblem. + //! \details The eigenproblem is assumed to be on the form + //! \b A \b x = \f$\lambda\f$ \b B \b x where \b A ( = \a *this ) and \b B + //! both are assumed to be symmetric and \b B also to be positive definite. + //! The eigenproblem is solved by the LAPack library subroutine \a DSYGVX. + //! \sa LAPack library documentation. + //! \param B Symmetric and positive definite mass matrix. + //! \param[out] eigVal Computed eigenvalues + //! \param[out] eigVec Computed eigenvectors stored column by column + //! \param[in] nev The number of eigenvalues and eigenvectors to compute + //! \param[in] shift Eigenvalue shift (unused) + bool solveEig(DenseMatrix& B, RealArray& eigVal, Matrix& eigVec, int nev, + real shift = 0.0); + +protected: + //! \brief Augments a dense matrix symmetrically to the current matrix. + //! \param[in] B The matrix to be augmented + //! \param[in] r0 Row offset for the augmented matrix + //! \param[in] c0 Column offset for the augmented matrix + bool augment(const Matrix& B, size_t r0, size_t c0); + + //! \brief Augments a sparse matrix symmetrically to the current matrix. + //! \param[in] B The matrix to be augmented + //! \param[in] r0 Row offset for the augmented matrix + //! \param[in] c0 Column offset for the augmented matrix + bool augment(const SparseMatrix& B, size_t r0, size_t c0); + + //! \brief Writes the system matrix to the given output stream. + virtual std::ostream& write(std::ostream& os) const { return os << myMat; } + +private: + Matrix myMat; //!< The actual dense matrix + int* ipiv; //!< Pivot indices used in \a solve +}; + +#endif diff --git a/src/LinAlg/LinAlgInit.C b/src/LinAlg/LinAlgInit.C new file mode 100644 index 00000000..3608e7ca --- /dev/null +++ b/src/LinAlg/LinAlgInit.C @@ -0,0 +1,41 @@ +// $Id: LinAlgInit.C,v 1.2 2011-01-02 15:50:35 kmo Exp $ +//============================================================================== +//! +//! \file LinAlgInit.C +//! +//! \date May 06 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Initializer for linear algebra packages. +//! +//============================================================================== + +#include "LinAlgInit.h" +#ifdef HAS_PETSC +#include "slepceps.h" +#include "petscksp.h" +#endif + + +LinAlgInit::LinAlgInit (int argc, char** argv) +{ +#ifdef HAS_PETSC + SlepcInitialize(&argc,&argv,(char*)0,PETSC_NULL); + //PetscInitialize(&argc,&argv,(char*)0,PETSC_NULL); +#endif +#ifdef PARALLEL_PETSC + MPI_Comm_rank(PETSC_COMM_WORLD,&myPid); +#else + myPid = 0; +#endif +} + + +LinAlgInit::~LinAlgInit () +{ +#ifdef HAS_PETSC + SlepcFinalize(); + //PetscFinalize(); +#endif +} diff --git a/src/LinAlg/LinAlgInit.h b/src/LinAlg/LinAlgInit.h new file mode 100644 index 00000000..211b5732 --- /dev/null +++ b/src/LinAlg/LinAlgInit.h @@ -0,0 +1,33 @@ +// $Id: LinAlgInit.h,v 1.2 2011-01-02 15:50:35 kmo Exp $ +//============================================================================== +//! +//! \file LinAlgInit.h +//! +//! \date May 06 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Initializer for linear algebra packages. +//! +//============================================================================== + +#ifndef _LINALG_INIT_H +#define _LINALG_INIT_H + + +/*! + \brief Initializer for linear algebra packages. +*/ + +class LinAlgInit +{ +public: + //! \brief The constructor uses the command-line arguments to set up things. + LinAlgInit(int argc, char** argv); + //! \brief The destructor finalizes the linear algebra packages. + ~LinAlgInit(); + + int myPid; //!< Processor ID in parallel simulations +}; + +#endif diff --git a/src/LinAlg/LinSolParams.C b/src/LinAlg/LinSolParams.C new file mode 100644 index 00000000..d4a1de00 --- /dev/null +++ b/src/LinAlg/LinSolParams.C @@ -0,0 +1,162 @@ +// $Id: LinSolParams.C,v 1.5 2010-12-06 09:08:28 rho Exp $ +//============================================================================== +//! +//! \file LinSolParams.C +//! +//! \date Jan 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Linear solver parameters for PETSc matrices. +//! +//============================================================================== + +#include "LinSolParams.h" +#include "Utilities.h" +#include +#ifdef HAS_PETSC +#include "petscmg.h" +#endif + + +void LinSolParams::setDefault () +{ +#ifdef HAS_PETSC + // Use GMRES with ILU preconditioner as default + method = KSPGMRES; + prec = PCILU; + package = MAT_SOLVER_PETSC; + levels = 0; + overlap = 0; + + atol = 1.0e-6; + rtol = 1.0e-6; + dtol = 1.0e-6; + maxIts = 1000; +#endif +} + + +void LinSolParams::copy (const LinSolParams& spar) +{ +#ifdef HAS_PETSC + // Copy linear solver parameters + method = spar.method; + prec = spar.prec; + package = spar.package; + levels = spar.levels; + overlap = spar.overlap; + + atol = spar.atol; + rtol = spar.rtol; + dtol = spar.dtol; + maxIts = spar.maxIts; +#endif +} + + +bool LinSolParams::read (std::istream& is, int nparam) +{ + char* cline = 0; + for (int i = 0; i < nparam && (cline = utl::readLine(is)); i++) +#ifdef HAS_PETSC + if (!strncasecmp(cline,"type",4)) { + char* c = strchr(cline,'='); + for (++c; *c == ' '; c++); + int j = 0; + while (c[j] != EOF && c[j] != '\n' && c[j] != ' ' && c[j] != '\0') j++; + method.assign(c,j); + } + + else if (!strncasecmp(cline,"pc",2)) { + char* c = strchr(cline,'='); + for (++c; *c == ' '; c++); + int j = 0; + while (c[j] != EOF && c[j] != '\n' && c[j] != ' ' && c[j] != '\0') j++; + prec.assign(c,j); + } + + else if (!strncasecmp(cline,"package",7)) { + char* c = strchr(cline,'='); + for (++c; *c == ' '; c++); + int j = 0; + while (c[j] != EOF && c[j] != '\n' && c[j] != ' ' && c[j] != '\0') j++; + package.assign(c,j); + } + + else if (!strncasecmp(cline,"levels",6)) { + char* c = strchr(cline,'='); + levels = atoi(++c); + } + + else if (!strncasecmp(cline,"overlap",7)) { + char* c = strchr(cline,'='); + overlap = atoi(++c); + } + + else if (!strncasecmp(cline,"atol",4)) { + char* c = strchr(cline,'='); + atol = atof(++c); + } + + else if (!strncasecmp(cline,"rtol",4)) { + char* c = strchr(cline,'='); + rtol = atof(++c); + } + + else if (!strncasecmp(cline,"dtol",4)) { + char* c = strchr(cline,'='); + dtol = atof(++c); + } + + else if (!strncasecmp(cline,"maxits",6)) { + char* c = strchr(cline,'='); + maxIts = atoi(++c); + } + + else + { + std::cerr <<" *** LinSolParams::read: Unknown keyword: " + << cline << std::endl; + return false; + } +#else + ; +#endif + return true; +} + + +bool LinSolParams::read (const char* filename) +{ + std::ifstream fs(filename); + return this->read(fs,1000); +} + + +#ifdef HAS_PETSC +void LinSolParams::setParams(KSP& ksp) const +{ + // Set linear solver method + KSPSetType(ksp,method.c_str()); + KSPSetTolerances(ksp,rtol,atol,dtol,maxIts); + //KSPSetFromOptions(ksp); + + // Set preconditioner + PC pc; + KSPGetPC(ksp,&pc); + //PCSetType(pc,prec.c_str()); + //PCFactorSetLevels(pc,levels); + //PCMGSetLevels(pc,levels,PETSC_NULL); + //PCMGSetType(pc,PC_MG_MULTIPLICATIVE); + PCSetType(pc,prec.c_str()); + if (overlap > 0) + PCASMSetOverlap(pc,overlap); + PCFactorSetMatSolverPackage(pc,package.c_str()); + PCSetFromOptions(pc); + PCSetUp(pc); + + KSPSetFromOptions(ksp); + KSPSetUp(ksp); +} +#endif diff --git a/src/LinAlg/LinSolParams.h b/src/LinAlg/LinSolParams.h new file mode 100644 index 00000000..18febc97 --- /dev/null +++ b/src/LinAlg/LinSolParams.h @@ -0,0 +1,111 @@ +// $Id: LinSolParams.h,v 1.4 2010-12-06 09:07:51 rho Exp $ +//============================================================================== +//! +//! \file LinSolParams.h +//! +//! \date Jan 29 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Linear solver parameters for PETSc matrices. +//! \details Includes linear solver method, preconditioner +//! and convergence criteria. +//! +//============================================================================== + +#ifndef _LINSOLPARAMS_MATRIX_H +#define _LINSOLPARAMS_MATRIX_H + +#include +#ifdef HAS_PETSC +#include +#include "petscksp.h" +#endif + + +/*! + \brief Class for linear solver parameters. + \details It contains information about solver method, preconditioner + and convergence criteria. +*/ + +class LinSolParams +{ +public: + //! \brief Default constructor. + LinSolParams() { this->setDefault(); } + //! \brief Copy constructor. + LinSolParams(const LinSolParams& spar) { this->copy(spar); } + + //! \brief Set default values. + virtual void setDefault(); + + //! \brief Copy linear solver parameters. + virtual void copy(const LinSolParams& spar); + + //! \brief Read linear solver parameters from stream. + virtual bool read(std::istream& is, int nparams = 10); + + //! \brief Read linear solver parameters from file. + virtual bool read(const char* filename); + +#ifdef HAS_PETSC + //! \brief Set linear solver method + virtual void setMethod(KSPType type) { method.assign(type); } + + //! \brief Set preconditioner + virtual void setPreconditioner(PCType type) { prec.assign(type); } + + //! \brief Set linear solver package + virtual void setPackage(MatSolverPackage stype) { package.assign(stype); } + + //! \brief Set absolute convergence tolerance + virtual void setAbsTolerance(real eps) { atol = eps; } + + //! \brief Set relative convergence tolerance + virtual void setRelTolerance(real eps) { rtol = eps; } + + //! \brief Set divergence tolerance + virtual void setDivTolerance(real eps) { dtol = eps; } + + //! \brief Set maximum number of iterations + virtual void setMaxIterations(int its) { maxIts = its; } + + //! \brief Get linear solver method + virtual const char* getMethod() const { return method.c_str(); } + + //! \brief Get preconditioner + virtual const char* getPreconditioner() const { return prec.c_str(); } + + //! \brief Get linear solver package + virtual const char* getPackage() const { return package.c_str(); } + + //! \brief Get absolute convergence tolerance + virtual real getAbsTolerance() const { return atol; } + + //! \brief Get relative convergence tolerance + virtual real getRelTolerance() const { return rtol; } + + //! \brief Get divergence tolerance + virtual real getDivTolerance() const { return dtol; } + + //! \brief Get maximum number of iterations + virtual int getMaxIterations() const { return maxIts; } + + //! \brief Set linear solver parameters for KSP object + virtual void setParams(KSP& ksp) const; + +private: + std::string method; // Linear solver method + std::string prec; // Preconditioner + std::string package; // Linear software package (petsc, superlu_dist, ...) + PetscReal atol; // Absolute tolerance + PetscReal rtol; // Relative tolerance + PetscReal dtol; // Divergence tolerance + PetscInt levels; // Number of levels of fill to use + PetscInt maxIts; // Maximum number of iterations + PetscInt overlap; // Number of overlaps +#endif // HAS_PETSC +}; + +#endif diff --git a/src/LinAlg/MatVec.C b/src/LinAlg/MatVec.C new file mode 100644 index 00000000..a0d95d2f --- /dev/null +++ b/src/LinAlg/MatVec.C @@ -0,0 +1,157 @@ +// $Id: MatVec.C,v 1.6 2010-12-18 19:54:54 kmo Exp $ +//============================================================================== +//! +//! \file MatVec.C +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Index 1-based matrices and vectors for algebraic operations. +//! +//! The two transformation functions defined in this file are rewritten versions +//! of the Fortran subroutines MATTRA and VECTRA from the SAM library. +//! +//============================================================================== + +#include "MatVec.h" + +#if defined(_WIN32) +#define dgetrf_ DGETRF +#define dgetri_ DGITRI +#elif defined(_AIX) +#define dgetrf_ dgetrf +#define dgetri_ dgetri +#endif + +extern "C" { +//! \brief Computes an LU factorization of a general M-by-N matrix A. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dgetrf_(const int& M, const int& N, double* A, const int& LDA, + int* IPIV, int& INFO); +//! \brief Computes the inverse of a matrix based on its LU factorization. +//! \details This is a FORTRAN-77 subroutine in the LAPack library. +//! \sa LAPack library documentation. +void dgetri_(const int& N, double* A, const int& LDA, + int* IPIV, double* WORK, const int& LWORK, int& INFO); +} + + +// Set default values on some global variables. + +int utl::nval_per_line = 6; +double utl::zero_print_tol = 1.0e-6; + + +bool utl::transform (Matrix& A, const Matrix& T, size_t K) +{ + size_t M = A.rows(); + size_t N = T.rows(); + if (M < A.cols() || N < T.cols()) return false; + if (K < 1 || K+N-1 > M || N > 3) return false; + + real WA[3]; + size_t i, ii, j, jj, l, KN = K+N-1; + for (jj = K; jj <= M; jj++) + { + for (i = 1; i <= N; i++) + { + WA[i-1] = real(0); + for (l = 1, ii = K; l <= N; l++, ii++) + WA[i-1] += T(l,i)*A(ii,jj); + } + ii = K; + for (i = 1; i <= N; i++, ii++) + { + A(ii,jj) = WA[i-1]; + if (jj > KN) A(jj,ii) = WA[i-1]; + } + } + + for (ii = 1; ii <= KN; ii++) + { + size_t JS = ii > K ? ii-K+1 : 1; + for (j = JS; j <= N; j++) + { + WA[j-1] = real(0); + for (l = 1, jj = K; l <= N; l++, jj++) + WA[j-1] += A(ii,jj)*T(l,j); + } + jj = ii > K ? ii : K; + for (j = JS; j <= N; j++, jj++) + { + A(ii,jj) = WA[j-1]; + if (ii < K) A(jj,ii) = WA[j-1]; + } + } + + for (ii = K; ii <= KN; ii++) + for (jj = ii; jj <= KN; jj++) + A(jj,ii) = A(ii,jj); + + return true; +} + + +bool utl::transform (Vector& V, const Matrix& T, size_t K, bool transpose) +{ + size_t M = V.size(); + size_t N = T.rows(); + if (N < T.cols()) return false; + if (K < 1 || K+N-1 > M || N > 3) return false; + + real WA[3]; + size_t i, ii, j; + if (transpose) + for (i = 1; i <= N; i++) + { + WA[i-1] = real(0); + for (j = 1, ii = K; j <= N; j++, ii++) + WA[i-1] += T(j,i)*V(ii); + } + + else + for (i = 1; i <= N; i++) + { + WA[i-1] = real(0); + for (j = 1, ii = K; j <= N; j++, ii++) + WA[i-1] += T(i,j)*V(ii); + } + + for (i = 0, ii = K; i < N; i++, ii++) + V(ii) = WA[i]; + + return true; +} + + +bool utl::invert (Matrix& A) +{ + if (A.rows() <= 3) + return A.inverse() > 0.0; + +#ifdef USE_CBLAS + // Use LAPack/BLAS for larger matrices + int INFO, N = A.rows() > A.cols() ? A.rows() : A.cols(); + int* IPIV = new int[N]; + dgetrf_(N,N,A.ptr(),A.rows(),IPIV,INFO); + if (INFO != 0) + { + delete IPIV; + std::cerr <<" *** utl::invert:DGETRF: INFO = "<< INFO << std::endl; + return false; + } + double NWORK; + dgetri_(N,A.ptr(),A.rows(),IPIV,&NWORK,-1,INFO); + double* WORK = new double[int(NWORK)]; + dgetri_(N,A.ptr(),A.rows(),IPIV,WORK,int(NWORK),INFO); + delete IPIV; + delete WORK; + if (INFO == 0) return true; + std::cerr <<" *** utl::invert:DGETRI: INFO = "<< INFO << std::endl; +#else + std::cerr <<"DGETRI not available - linked without LAPack/BLAS"<< std::endl; +#endif + return false; +} diff --git a/src/LinAlg/MatVec.h b/src/LinAlg/MatVec.h new file mode 100644 index 00000000..97a41ef0 --- /dev/null +++ b/src/LinAlg/MatVec.h @@ -0,0 +1,62 @@ +// $Id: MatVec.h,v 1.9 2011-02-05 18:07:51 kmo Exp $ +//============================================================================== +//! +//! \file MatVec.h +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Index 1-based matrices and vectors for algebraic operations. +//! +//============================================================================== + +#ifndef UTL_MATVEC_H +#define UTL_MATVEC_H + +#include "matrix.h" + +//! A real-valued vector with algebraic operations +typedef utl::vector Vector; +//! A real-valued matrix with algebraic operations +typedef utl::matrix Matrix; +//! A real-valued three-dimensional matrix with algebraic operations +typedef utl::matrix3d Matrix3D; + +//! A real-valued array without algebraic operations +typedef std::vector RealArray; +//! A real-valued two-dimensional array without algebraic operations +typedef std::vector Real2DMat; +//! A real-valued three-dimensional array without algebraic operations +typedef std::vector Real3DMat; +//! An array of real-valued vectors with algebraic operations +typedef std::vector Vectors; + + +namespace utl +{ + //! \brief Congruence transformation of a symmetric matrix. + //! \details The following matrix multiplication is performed: + //! \f[ {\bf A} = {\bf T}^T{\bf A}{\bf T} \f] + //! where \b A is a full, symmetric matrix and \b T is an identity matrix + //! with the nodal sub-matrix \b Tn inserted on the diagonal. + //! \param A The matrix to be transformed + //! \param[in] Tn Nodal transformation matrix + //! \param[in] k Index telling where to insert \b Tn on the diagonal of \b T + bool transform(Matrix& A, const Matrix& Tn, size_t k); + + //! \brief Congruence transformation of a vector. + //! \details The vector \b V is pre-multiplied with the transformation matrix + //! \b T which is the identity matrix with the nodal sub-matrix \b Tn + //! inserted on the diagonal. + //! \param V The vector to be transformed + //! \param[in] Tn Nodal transformation matrix + //! \param[in] k Index telling where to insert \b Tn on the diagonal of \b T + //! \param[in] transpose If \e true, the transpose of \b Tn is used instead + bool transform(Vector& V, const Matrix& Tn, size_t k, bool transpose = false); + + //! \brief Inverts the square matrix \b A. + bool invert(Matrix& A); +}; + +#endif diff --git a/src/LinAlg/PETScMatrix.C b/src/LinAlg/PETScMatrix.C new file mode 100644 index 00000000..efdc8647 --- /dev/null +++ b/src/LinAlg/PETScMatrix.C @@ -0,0 +1,602 @@ +// $Id: PETScMatrix.C,v 1.8 2010-12-06 09:19:50 rho Exp $ +//============================================================================== +//! +//! \file PETScMatrix.C +//! +//! \date Jan 15 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Representation of the system matrix in PETSc format. +//! +//============================================================================== + +#include "PETScMatrix.h" +#ifdef HAS_PETSC +#include "SAM.h" +#include "petscmg.h" + + +PETScVector::PETScVector() +{ + VecCreate(PETSC_COMM_WORLD,&x); + VecSetFromOptions(x); +} + + +PETScVector::PETScVector(size_t n) +{ + VecCreate(PETSC_COMM_WORLD,&x); + VecSetSizes(x,n,PETSC_DECIDE); + VecSetFromOptions(x); +} + + +PETScVector::PETScVector(const real* values, size_t n) +{ + PetscScalar *x_array; + + VecCreate(PETSC_COMM_WORLD,&x); + VecSetSizes(x,n,PETSC_DECIDE); + VecSetFromOptions(x); + VecGetArray(x,&x_array); + *x_array = *values; + VecRestoreArray(x,&x_array); +} + + +PETScVector::PETScVector(const PETScVector& vec) +{ + VecDuplicate(vec.x,&x); + VecCopy(vec.x,x); +} + + +PETScVector::~PETScVector() +{ + // Deallocation of vector + VecDestroy(x); +} + + +size_t PETScVector::dim() const +{ + PetscInt size; + + size = VecGetLocalSize(x); + return size; +} + + +void PETScVector::redim(size_t n) +{ + VecSetSizes(x,n,PETSC_DECIDE); +} + + +real* PETScVector::getPtr() +{ + real* ptr = 0; + + VecGetArray(x,&ptr); + return ptr; +} + + +const real* PETScVector::getRef() const +{ + return const_cast(this)->getPtr(); +} + + +void PETScVector::restore(const real* ptr) +{ + PetscScalar* pptr = (PetscScalar*) ptr; + VecRestoreArray(x,&pptr); +} + + +void PETScVector::init(real value) +{ + VecSet(x,value); +} + + +bool PETScVector::beginAssembly() +{ + VecAssemblyBegin(x); + return true; +} + + +bool PETScVector::endAssembly() +{ + VecAssemblyEnd(x); + return true; +} + + +void PETScVector::mult(real alpha) +{ + VecScale(x,alpha); +} + + +real PETScVector::L1norm() const +{ + PetscReal val; + + VecNorm(x,NORM_1,&val); + return val; +} + + +real PETScVector::L2norm() const +{ + PetscReal val; + + VecNorm(x,NORM_2,&val); + return val; +} + + +real PETScVector::Linfnorm() const +{ + PetscReal val; + + VecNorm(x,NORM_INFINITY,&val); + return val; +} + + +PETScMatrix::PETScMatrix(const LinSolParams& spar) : solParams(spar) +{ + // Create matrix object. By default the matrix type is AIJ + A = MatCreate(PETSC_COMM_WORLD); + + // Create linear solver object. + KSPCreate(PETSC_COMM_WORLD,&ksp); + + // Create eigenvalue solver object. + //EPSCreate(PETSC_COMM_WORLD,&eps); +} + + +PETScMatrix::PETScMatrix (const PETScMatrix& B) : solParams(B.solParams) +{ + // Duplicate matrix. + A = MatDuplicate(B.A); + + // Create linear solver object. + KSPCreate(PETSC_COMM_WORLD,&ksp); + + // Create eigenvalue solver object. + //EPSCreate(PETSC_COMM_WORLD,&eps); +} + + +PETScMatrix::~PETScMatrix () +{ + // Deallocation of eigenvalue solver object. + //EPSDestroy(eps); + + // Deallocation of linear solver object. + //KSPDestroy(ksp); + + // Deallocation of matrix object. + MatDestroy(A); +} + + +static void assemPETScPara(const Matrix& eM, Mat SM, PETScVector& SV, + const std::vector& meen, const int* meqn, + const int* mpmceq, const int* mmceq, const real* ttcc) +{ + real c0; + int i, j, jp, jceq; + + // Number of degrees of freedom for element + const int nedof = meen.size(); + + // Convert meen to C array. Start column numbering at 0. + int *l2g; + l2g = new int[nedof]; + for (i = 0;i < nedof;i++) + l2g[i] = meqn[meen[i]-1]-1; + + // Cast to non-constant Matrix to modify for Dirichlet BCs + Matrix& A = const_cast(eM); + + std::vector uc, bc; + uc.resize(nedof); + bc.resize(nedof); + + bool rhsMod = false; + for (j = 1;j <= nedof;j++) { + uc[j-1] = bc[j-1] = 0.0; + jceq = mpmceq[meen[j-1]-1]; + if (jceq < 1) continue; + + jp = jceq-1; + c0 = ttcc[jp]; + + if (c0 != 0.0) { + uc[j-1] = -c0; + rhsMod = true; + } + } + + if (rhsMod) + A.multiply(uc,bc); + + // Eliminate constrained dofs from element matrix + for (j = 1;j <= nedof;j++) { + jceq = mpmceq[meen[j-1]-1]; + if (jceq < 1) continue; + + bc[j-1] = -uc[j-1]; + for (i = 1;i <= nedof;i++) + A(i,j) = A(j,i) = 0.0; + + A(j,j) = 1.0; + } + + // Add contributions to SV (righthand side) + VecSetValues(SV.getVector(),nedof,l2g,&(bc.front()),ADD_VALUES); + + // Add element stiffness matrix to global matrix + A.transpose(); + MatSetValues(SM,nedof,l2g,nedof,l2g,A.ptr(),ADD_VALUES); + A.transpose(); + + delete[] l2g; +} + + +static void assemPETSc(const Matrix& eM, Mat SM, PETScVector& SV, + const std::vector& meen, const int* meqn, + const int* mpmceq, const int* mmceq, const real* ttcc) +{ + real c0; + int i, j, ieq, jeq, ip, jp, iceq, jceq; + + // Get C array + PetscScalar* svec; + VecGetArray(SV.getVector(),&svec); + + // Number of degrees of freedom for element + const int nedof = meen.size(); + + // Convert meen to C array. Start column numbering at 0. + int *l2g; + l2g = new int[nedof]; + for (i = 0;i < nedof; i++) + l2g[i] = meen[i]-1; + + // Cast to non-constant Matrix + Matrix& A = const_cast(eM); + + // Add element stiffness matrix to global matrix + A.transpose(); + MatSetValues(SM,nedof,l2g,nedof,l2g,eM.ptr(),ADD_VALUES); + A.transpose(); + + // Add (appropriately weighted) elements corresponding to constrained + // (dependent and prescribed) dofs in eM into SM and/or SV + for (j = 1; j <= nedof; j++) { + jceq = -meen[j-1]; + if (jceq < 1) continue; + + jp = mpmceq[jceq-1]; + c0 = ttcc[jp-1]; + + // Add contributions to SV (righthand side) + if (SV.dim() > 0) + for (i = 1; i <= nedof; i++) { + ieq = meen[i-1]; + iceq = -ieq; + if (ieq > 0) + svec[ieq-1] -= c0*eM(i,j); + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) { + ieq = meqn[mmceq[ip]-1]; + svec[ieq-1] -= c0*ttcc[ip]*eM(i,j); + } + } + + // Add contributions to SM + for (jp = mpmceq[jceq-1]; jp < mpmceq[jceq]-1; jp++) { + std::cout << "jp = " << jp << std::endl; + if (mmceq[jp] > 0) { + jeq = meqn[mmceq[jp]-1]; + for (i = 1; i <= nedof; i++) { + ieq = meen[i-1]; + iceq = -ieq; + if (ieq > 0) + if (ieq == jeq) + MatSetValue(SM,ieq-1,jeq-1,(ttcc[jp]+ttcc[jp])*eM(i,j),ADD_VALUES); + + else { + MatSetValue(SM,ieq-1,jeq-1,ttcc[jp]*eM(i,j),ADD_VALUES); + MatSetValue(SM,jeq-1,ieq-1,ttcc[jp]*eM(j,i),ADD_VALUES); + } + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) { + ieq = meqn[mmceq[ip]-1]; + MatSetValue(SM,ieq-1,jeq-1,ttcc[ip]*ttcc[jp]*eM(i,j),ADD_VALUES); + } + } + } + } + + } + + // Restore vector values + VecRestoreArray(SV.getVector(),&svec); + + // Deallocate memory for l2g mapping + delete [] l2g; +} + + +static void assemPETSc(const Matrix& eM, Mat SM, const std::vector& meen, + const int* meqn, const int* mpmceq, const int* mmceq, + const real* ttcc) +{ + real c0; + int i, j, ieq, jeq, ip, jp, iceq, jceq; + + // Number of degrees of freedom for element + const int nedof = meen.size(); + + // Convert meen to C array. Start column numbering at 0. + int *l2g; + l2g = new int[nedof]; + for (i = 0;i < nedof; i++) + l2g[i] = meen[i]-1; + + // Cast to non-constant Matrix + Matrix& A = const_cast(eM); + + // Add element stiffness matrix to global matrix + A.transpose(); + MatSetValues(SM,nedof,l2g,nedof,l2g,eM.ptr(),ADD_VALUES); + A.transpose(); + + // Add (appropriately weighted) elements corresponding to constrained + // (dependent and prescribed) dofs in eM into SM + for (j = 1; j <= nedof; j++) { + jceq = -meen[j-1]; + if (jceq < 1) continue; + + jp = mpmceq[jceq-1]; + c0 = ttcc[jp-1]; + + // Add contributions to SM + for (jp = mpmceq[jceq-1]; jp < mpmceq[jceq]-1; jp++) { + std::cout << "jp = " << jp << std::endl; + if (mmceq[jp] > 0) { + jeq = meqn[mmceq[jp]-1]; + for (i = 1; i <= nedof; i++) { + ieq = meen[i-1]; + iceq = -ieq; + if (ieq > 0) { + MatSetValue(SM,ieq-1,jeq-1,ttcc[jp]*eM(i,j),ADD_VALUES); + MatSetValue(SM,jeq-1,ieq-1,ttcc[jp]*eM(j,i),ADD_VALUES); + } + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) { + ieq = meqn[mmceq[ip]-1]; + MatSetValue(SM,ieq-1,jeq-1,ttcc[ip]*ttcc[jp]*eM(i,j),ADD_VALUES); + } + } + } + } + + } + + // Deallocate memory for l2g mapping + delete [] l2g; +} + + +void PETScMatrix::initAssembly (const SAM& sam) +{ + // Get number of equations in linear system + const PetscInt neq = sam.getNoEquations(); + + // Set correct number of rows and columns for matrix. + MatSetSizes(A,neq,neq,PETSC_DECIDE,PETSC_DECIDE); + MPI_Barrier(PETSC_COMM_WORLD); + MatSetFromOptions(A); + + // Allocation of sparse pattern. +#ifdef PARALLEL_PETSC + PetscInt ifirst, ilast; + std::vector d_nnz, o_nnz; + + MatGetOwnershipRange(A,&ifirst,&ilast); + if (sam.getNoDofCouplings(ifirst,ilast,d_nnz,o_nnz)) + MatMPIAIJSetPreallocation(A,PETSC_DEFAULT,&(d_nnz[0]),PETSC_DEFAULT,&(o_nnz[0])); + else { + const PetscInt maxdofc = sam.getMaxDofCouplings(); + MatMPIAIJSetPreallocation(A,maxdofc,PETSC_NULL,maxdofc,PETSC_NULL); + } +#else + std::vector nnz; + + if (sam.getNoDofCouplings(nnz)) { + PetscInt* nnzPtr = &(nnz[0]); + MatSeqAIJSetPreallocation(A,PETSC_DEFAULT,nnzPtr); + } + else { + const PetscInt maxdofc = sam.getMaxDofCouplings(); + MatSeqAIJSetPreallocation(A,maxdofc,PETSC_NULL); + } +#endif +} + + +bool PETScMatrix::beginAssembly() +{ + // Starts parallel assembly process + MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY); + return true; +} + + +bool PETScMatrix::endAssembly() +{ + // Finalizes parallel assembly process + MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY); + return true; +} + + +void PETScMatrix::init () +{ + // Set all matrix elements to zero + MatZeroEntries(A); +} + + +bool PETScMatrix::assemble (const Matrix& eM, const SAM& sam, int e) +{ + // Get mapping "meen" between local degrees of freedom in element e + // and global degrees of freedom. + std::vector meen; + if (!sam.getElmEqns(meen,e,eM.rows())) + return false; + + // Assemble local stiffness matrix into global system. + assemPETSc(eM,A,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); + + return true; +} + + +bool PETScMatrix::assemble (const Matrix& eM, const SAM& sam, + SystemVector& B, int e) +{ + // Get mapping "meen" between local degrees of freedom in element e + // and global degrees of freedom. + std::vector meen; + if (!sam.getElmEqns(meen,e,eM.rows())) + return false; + + PETScVector* Bptr = dynamic_cast(&B); + if (!Bptr) + return false; + + // Assemble local stiffness matrix into global system. +#ifdef PARALLEL_PETSC + assemPETScPara(eM,A,*Bptr,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); +#else + assemPETSc(eM,A,*Bptr,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); +#endif + return true; +} + + +bool PETScMatrix::multiply (const SystemVector& B, SystemVector& C) +{ + const PETScVector* Bptr = dynamic_cast(&B); + PETScVector* Cptr = dynamic_cast(&C); + + if ((!Bptr) || (!Cptr)) + return false; + + MatMult(A,Bptr->getVector(),Cptr->getVector()); + return true; +} + + +bool PETScMatrix::solve (SystemVector& B, bool newLHS) +{ + PC pc; + + PETScVector* Bptr = dynamic_cast(&B); + if (!Bptr) + return false; + + Vec x; + VecDuplicate(Bptr->getVector(),&x); + VecCopy(Bptr->getVector(),x); + + if (newLHS) + KSPSetOperators(ksp,A,A,SAME_NONZERO_PATTERN); + else + KSPSetOperators(ksp,A,A,SAME_PRECONDITIONER); + solParams.setParams(ksp); + KSPSolve(ksp,x,Bptr->getVector()); + + PetscInt its; + KSPGetIterationNumber(ksp,&its); + PetscPrintf(PETSC_COMM_WORLD,"\n Iterations for %s = %D\n",solParams.getMethod(),its); + VecDestroy(x); + + return true; +} + + +bool PETScMatrix::solveEig(PETScMatrix& B, RealArray& val, + Matrix& vec, int nv, real shift, int iop) +{ + int i; + ST st; + PetscInt m, n, nconv; + PetscScalar kr, ki; + PetscScalar *xrarr; + Vec xr, xi; + + EPS eps; + EPSCreate(PETSC_COMM_WORLD,&eps); + + MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY); + MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY); + MatAssemblyBegin(B.A,MAT_FINAL_ASSEMBLY); + MatAssemblyEnd(B.A,MAT_FINAL_ASSEMBLY); + + EPSSetOperators(eps,A,B.A); + EPSSetProblemType(eps,EPS_GHEP); + EPSSetType(eps,EPSKRYLOVSCHUR); + EPSSetWhichEigenpairs(eps,EPS_SMALLEST_MAGNITUDE); + EPSGetST(eps,&st); + STSetShift(st,shift); + EPSSetDimensions(eps,nv,4*nv,PETSC_NULL); + EPSSetFromOptions(eps); + EPSSolve(eps); + EPSGetConverged(eps,&nconv); + + MatGetSize(A,&m,&n); + if (m != n) return false; + + VecCreate(PETSC_COMM_WORLD,&xr); + VecSetSizes(xr,n,PETSC_DECIDE); + VecSetFromOptions(xr); + VecDuplicate(xr,&xi); + + val.resize(nv); + vec.resize(n,nv); + for ( i = 0; i < nv; i++) { + EPSGetEigenpair(eps,i,&kr,&ki,xr,xi); + VecGetArray(xr,&xrarr); + + val[i] = kr; + vec.fillColumn(i+1,xrarr); + + VecRestoreArray(xr,&xrarr); + } + + VecDestroy(xi); + VecDestroy(xr); + + EPSDestroy(eps); + + return true; +} + +#endif // HAS_PETSC diff --git a/src/LinAlg/PETScMatrix.h b/src/LinAlg/PETScMatrix.h new file mode 100644 index 00000000..cba862e7 --- /dev/null +++ b/src/LinAlg/PETScMatrix.h @@ -0,0 +1,225 @@ +// $Id: PETScMatrix.h,v 1.6 2010-12-06 09:17:56 rho Exp $ +//============================================================================== +//! +//! \file PETScMatrix.h +//! +//! \date Jan 15 2010 +//! +//! \author Runar Holdahl / SINTEF +//! +//! \brief Representation of the system matrix in PETSc format with interface +//! to PETSc routines for assembling and solving linear equation systems. +//! +//============================================================================== + +#ifndef _PETSC_MATRIX_H +#define _PETSC_MATRIX_H + +#include "SystemMatrix.h" +#ifdef HAS_PETSC +#include "LinSolParams.h" +#include "petscksp.h" +#include "slepceps.h" +#endif + + +/*! + \brief Class for representing the system vector in PETSc format. + \details It is an interface to PETSc modules for assembling and solving + linear systems of equations. +*/ + +class PETScVector : public SystemVector +{ +public: +#ifdef HAS_PETSC + //! \brief Constructor creating an empty vector. + PETScVector(); + //! \brief Constructor creating a vector of length \a n. + PETScVector(size_t n); + //! \brief Constructor creating a vector from an array. + PETScVector(const real* values, size_t n); + //! \brief Copy constructor. + PETScVector(const PETScVector& vec); + //! \brief Destructor. + virtual ~PETScVector(); +#endif + + //! \brief Returns the vector type. + virtual Type getType() const { return PETSC; } + +#ifdef HAS_PETSC + //! \brief Returns the dimension of the system vector. + virtual size_t dim() const; + + //! \brief Sets the dimension of the system vector. + virtual void redim(size_t n); + + //! \brief Creates a copy of the system vector and returns a pointer to it. + virtual SystemVector* copy() const { return new PETScVector(*this); } + + //! \brief Access through pointer. + virtual real* getPtr(); + //! \brief Reference through pointer. + virtual const real* getRef() const; + + //! \brief Restores the vector contents from an array. + virtual void restore(const real* ptr); + + //! \brief Initializes the vector to a given scalar value. + virtual void init(real value = real(0)); + + //! \brief Begins communication step needed in parallel vector assembly. + //! \details Must be called together with endAssembly after vector assembly + //! is completed on each processor and before the linear system is solved. + bool beginAssembly(); + + //! \brief Ends communication step needed in parallel vector assembly. + //! \details Must be called together with beginAssembly after vector assembly + //! is completed on each processor and before the linear system is solved. + bool endAssembly(); + + //! \brief Multiplication with a scalar. + virtual void mult(real alpha); + + //! \brief L1-norm of vector. + virtual real L1norm() const; + + //! \brief L2-norm of vector. + virtual real L2norm() const; + + //! \brief Linfinity-norm of vector. + virtual real Linfnorm() const; + + //! \brief Return PETSc vector. + Vec& getVector() { return x; } + + //! \brief Return PETSc vector. + const Vec& getVector() const { return x; } + +private: + Vec x; //!< The actual PETSc vector. + +#else // dummy implementation when PETSc is not included + PETScVector() {} + virtual SystemVector* copy() const { return 0; } + virtual size_t dim() const { return 0; } + virtual void redim(size_t) {} + virtual real* getPtr() { return 0; } + virtual const real* getRef() const { return 0; } + virtual void init(real = real(0)) {} + virtual void mult(real) {} + virtual real L1norm() const { return real(0); } + virtual real L2norm() const { return real(0); } + virtual real Linfnorm() const { return real(0); } +#endif +}; + + +/*! + \brief Class for representing the system matrix in PETSc format. + \details It is an interface to PETSc modules for assembling and solving + linear systems of equations. +*/ + +class PETScMatrix : public SystemMatrix +{ +public: +#ifdef HAS_PETSC + //! \brief Constructor. + PETScMatrix(const LinSolParams& spar); + //! \brief Copy constructor. + PETScMatrix(const PETScMatrix& A); + //! \brief The destructor frees the dynamically allocated arrays. + virtual ~PETScMatrix(); +#endif + + //! \brief Returns the matrix type. + virtual Type getType() const { return PETSC; } + + //! \brief Returns the dimension of the system matrix. + virtual size_t dim(int = 1) const { return 0; } + +#ifdef HAS_PETSC + //! \brief Creates a copy of the system matrix and returns a pointer to it. + virtual SystemMatrix* copy() const { return new PETScMatrix(*this); } + + //! \brief Initializes the matrix to zero assuming it is properly dimensioned. + virtual void init(); + + //! \brief Begins communication step needed in parallel matrix assembly. + //! \details Must be called together with endAssembly after matrix assembly + //! is completed on each processor and before the linear system is solved. + bool beginAssembly(); + + //! \brief Ends communication step needed in parallel matrix assembly. + //! \details Must be called together with beginAssembly after matrix assembly + //! is completed on each processor and before the linear system is solved. + bool endAssembly(); + + //! \brief Initializes the element assembly process. + //! \details Must be called once before the element assembly loop. + //! The PETSc data structures are initialized and the all symbolic operations + //! that are need before the actual assembly can start are performed. + //! \param[in] sam Auxilliary data describing the FE model topology, etc. + virtual void initAssembly(const SAM& sam); + + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, int e); + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \details When multi-point constraints are present, contributions from + //! these are also added into the system right-hand-side load vector. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param B The system right-hand-side load vector + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, + SystemVector& B, int e); + + //! \brief Performs the matrix-vector multiplication \b C = \a *this * \b B. + virtual bool multiply(const SystemVector& B, SystemVector& C); + + //! \brief Solves the linear system of equations for a given right-hand-side. + //! \param B Right-hand-side vector on input, solution vector on output + virtual bool solve(SystemVector& B, bool newLHSmatrix = true); + + //! \brief Solves a generalized symmetric-definite eigenproblem. + //! \details The eigenproblem is assumed to be on the form + //! \b A \b x = \f$\lambda\f$ \b B \b x where \b A ( = \a *this ) and \b B + //! both are assumed to be symmetric and \b B also to be positive definite. + //! The eigenproblem is solved by the SLEPc library subroutine \a EPSSolve. + //! \sa SLEPc library documentation. + //! \param B Symmetric and positive definite mass matrix. + //! \param[out] eigVal Computed eigenvalues + //! \param[out] eigVec Computed eigenvectors stored column by column + //! \param[in] nev The number of eigenvalues and eigenvectors to compute + //! \param[in] shift Eigenvalue shift + //! \param[in] iop Option telling whether to factorize matrix \a A or \b B. + bool solveEig(PETScMatrix& B, RealArray& eigVal, Matrix& eigVec, int nev, + real shift = real(0), int iop = 1); + +private: + Mat A; //!< Linear system matrix + KSP ksp; //!< Linear solver + const LinSolParams& solParams; //!< Linear solver parameters + //EPS eps; //!< Eigenvalue solver + +#else // dummy implementation when PETSc is not included + PETScMatrix(const LinSolParams&) {} + virtual SystemMatrix* copy() const { return 0; } + virtual void init() {} + virtual void initAssembly(const SAM&) {} + virtual bool assemble(const Matrix&, const SAM&, int) { return false; } + virtual bool assemble(const Matrix&, const SAM&, + SystemVector&, int) { return false; } +#endif +}; + +#endif diff --git a/src/LinAlg/SAM.C b/src/LinAlg/SAM.C new file mode 100644 index 00000000..c0f01cbf --- /dev/null +++ b/src/LinAlg/SAM.C @@ -0,0 +1,731 @@ +// $Id: SAM.C,v 1.27 2011-02-05 18:07:50 kmo Exp $ +//============================================================================== +//! +//! \file SAM.C +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices. +//! +//============================================================================== + +#include "SAM.h" +#include "SystemMatrix.h" + + +#ifdef USE_F77SAM +#if defined(_WIN32) +#define elmeq_ ELMEQ +#define syseq_ SYSEQ +#define addev2_ ADDEV2 +#define expand_ EXPAND +#elif defined(_AIX) +#define elmeq_ elmeq +#define syseq_ syseq +#define addev2_ addev2 +#define expand_ expand +#endif + +extern "C" { +//! \brief Finds the matrix of element equation numbers \a MEEN for an element. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void elmeq_(const int* madof, const int* mnpc, const int* mpmceq, + const int* meqn, const int& nenod, int* meen, + int& nedof, int& neslv, int &neprd); + +//! \brief Determines the control matrix \a MEQN. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void syseq_(const int* msc, const int* mpmceq, const int* mmceq, + const int& lpu, int* mpar, int* meqn, int& ierr); + +//! \brief Adds an element vector \a eS into the system vector \a sysRHS. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void addev2_(const real* eS, const real* ttcc, const int* mpar, + const int* madof, const int* meqn, const int* mpmnpc, + const int* mmnpc, const int* mpmceq, const int* mmceq, + const int& iel, const int& nedof, const int& lpu, + real* sysRHS, int* work, int& ierr); + +//! \brief Expands and rearranges a system vector from equation to DOF-order. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void expand_(const real* solVec, const real* ttcc, + const int* mpmceq, const int* mmceq, const int* meqn, + const real& ff, const real& fs, const int& ndof, const int& neq, + real* sv); +} +#endif + + +SAM::SAM () : nnod(mpar[0]), nel(mpar[1]), ndof(mpar[2]), + nceq(mpar[6]), neq(mpar[10]), + nmmnpc(mpar[14]), nmmceq(mpar[15]) +{ + // Initialize the parameters array to zero + memset(mpar,0,sizeof(mpar)); + + // Initialize the array pointers + mpmnpc = 0; + mmnpc = 0; + madof = 0; + msc = 0; + mpmceq = 0; + mmceq = 0; + ttcc = 0; + minex = 0; + meqn = 0; +} + + +SAM::~SAM () +{ + // Deallocate all dynamically allocated arrays + if (mpmnpc) delete[] mpmnpc; + if (mmnpc) delete[] mmnpc; + if (madof) delete[] madof; + if (msc) delete[] msc; + if (mpmceq) delete[] mpmceq; + if (mmceq) delete[] mmceq; + if (ttcc) delete[] ttcc; + if (minex) delete[] minex; + if (meqn) delete[] meqn; +} + + +std::pair SAM::getNodeAndLocalDof (int idof) const +{ + for (int n = 1; n <= nnod; n++) + if (madof[n] > idof) + return std::make_pair(minex ? minex[n-1] : n, idof-madof[n-1]+1); + + return std::make_pair(0,0); +} + + +//! \brief Global stream operator printing a \a (node,localDOF) pair. + +std::ostream& operator<< (std::ostream& os, const std::pair& p) +{ + return os <<'('<< p.first <<','<< p.second <<')'; +} + + +void SAM::print (std::ostream& os) const +{ + os <<"\n\nSAM::mpar: "<< mpar[0]; + for (int i = 1; i < 30; i++) + os << (i%10 ? " " : "\n ") << mpar[i]; + os << std::endl; + + if (mmnpc && mpmnpc) + { + os <<"\n\nElement --> Nodes"; + for (int e = 0; e < nel; e++) + { + os <<'\n'<< e+1 <<":"; + for (int i = mpmnpc[e]; i < mpmnpc[e+1]; i++) + os <<' '<< (minex ? minex[mmnpc[i-1]-1] : mmnpc[i-1]); + } + os << std::endl; + } + + if (ttcc && mmceq && mpmceq) + { + os <<"\n\nConstraint Equations"; + for (int i = 0; i < nceq; i++) + { + int ip = mpmceq[i]-1; + os <<'\n'<< i+1 <<": "<< this->getNodeAndLocalDof(mmceq[ip]) <<" ="; + if (ttcc[ip] || ip+2 >= mpmceq[i+1]) os <<' '<< ttcc[ip]; + for (int jp = ip+1; jp < mpmceq[i+1]-1; jp++) + { + if (ttcc[ip] || jp > ip+1) os <<" +"; + os <<' '<< ttcc[jp] <<'*'<< this->getNodeAndLocalDof(mmceq[jp]); + } + } + os << std::endl; + } + + if (meqn && madof) + { + char code[7]; + os <<"\n\nNode --> Equations"; + for (int n = 0; n < nnod; n++) + { + int dof, i = 0; + os <<'\n'<< (minex ? minex[n] : n+1) <<":"; + for (dof = madof[n]; dof < madof[n+1]; dof++, i++) + { + os <<' '<< meqn[dof-1]; + code[i] = msc[dof-1] > 0 ? '0' + msc[dof-1] : '0'; + } + code[i] = '\0'; + os <<'\t'<< code; + } + os << std::endl; + } +} + + +bool SAM::initSystemEquations () +{ + if (!msc && ndof > 0) return false; + if (!mpmceq && nceq > 0) return false; + + // Initialize the DOF-to-equation connectivity array + int ierr = 0; + meqn = new int[ndof]; + memset(meqn,0,ndof*sizeof(int)); +#ifdef USE_F77SAM + syseq_(msc,mpmceq,mmceq,6,mpar,meqn,ierr); +#else + int i, j, idof; + int ndof1 = 0; + int ndof2 = 0; + int nspdof = 0; + int npdof = 0; + int nddof = 0; + for (i = 0; i < ndof; i++) + if (msc[i] == 0) + msc[i] = -(++nspdof); + else if (msc[i] == 1) + ndof1++; + else if (msc[i] == 2) + ndof2++; + else + ierr--; + + if (ierr < 0) + std::cerr <<"SAM::initSystemEquations: The MSC array has "<< -ierr + <<" invalid values, it should only contain 0, 1 or 2"<< std::endl; + + for (i = 1; i <= nceq; i++) + { + int ip = mpmceq[i-1]; + int jp = mpmceq[i]-1; + idof = mmceq[ip-1]; + if (idof < 1 || idof > ndof) + { + ierr--; + std::cerr <<"SAM::initSystemEquations: idof = "<< idof + <<" is out of range [1,"<< ndof <<"]"<< std::endl; + continue; + } + else if (msc[idof-1] > 0) + { + ierr--; + std::cerr <<"SAM::initSystemEquations: Invalid status code "<< msc[idof-1] + <<" for slave dof "<< idof << std::endl; + } + else if (jp == ip) + npdof++; // prescribed DOF + else if (jp > ip) + { + nddof++; // slave DOF + while (ip++ < jp) + { + int jdof = mmceq[ip-1]; + if (jdof < 1 || jdof > ndof) + ierr--; + else if (msc[jdof-1] < 1) + ierr--; + } + } + else + { + ierr--; + break; + } + meqn[idof-1] = -i; + } + + mpar[3] = ndof1; + mpar[4] = ndof2; + mpar[5] = nspdof; + mpar[7] = nspdof - nceq; + mpar[8] = npdof; + mpar[9] = nddof; + neq = ndof1 + ndof2; + + i = 1; + j = ndof1+1; + for (idof = 0; idof < ndof; idof++) + if (msc[idof] == 1) + meqn[idof] = i++; + else if (msc[idof] == 2) + meqn[idof] = j++; +#endif + + // Initialize the number of nodal DOFs + int inod, nndof; + mpar[16] = mpar[17] = madof[1]-madof[0]; + for (inod = 1; inod < nnod && madof; inod++) + if ((nndof = madof[inod+1]-madof[inod]) < mpar[16]) + mpar[16] = nndof; + else if (nndof > mpar[17]) + mpar[17] = nndof; + + if (ierr == 0) return true; + + std::cerr <<"SAM::initSystemEquations: Failure "<< ierr << std::endl; + return false; +} + + +int SAM::getMaxDofCouplings () const +{ + size_t maxdofc = 0; + + std::vector dofc; + if (this->getDofCouplings(dofc)) + for (int i = 0; i < neq; i++) + if (dofc[i].size() > maxdofc) + maxdofc = dofc[i].size(); + + return maxdofc; +} + + +bool SAM::getNoDofCouplings (IntVec& nnz) const +{ + nnz.clear(); + + // RUNAR + // Not implemented for constrained equations yet + //if (nceq > 0) return false; + + // Find the set of DOF couplings for each free DOF + std::vector dofc; + if (!this->getDofCouplings(dofc)) + return false; + + // Find total number of DOF couplings or non-zeroes in the system matrix + nnz.reserve(neq); + for (int i = 0; i < neq; i++) + nnz.push_back(dofc[i].size()); + + return true; +} + + +bool SAM::getDofCouplings (IntVec& irow, IntVec& jcol) const +{ + irow.clear(); + jcol.clear(); + + // Not implemented for constrained equations yet + if (nceq > 0) return false; + + // Find the set of DOF couplings for each free DOF + std::vector dofc; + if (!this->getDofCouplings(dofc)) + return false; + + irow.resize(neq+1); + irow[0] = 0; + + // Find total number of dof couplings or non-zeroes in the system matrix + int i; + for (i = 0; i < neq; i++) + irow[i+1] = irow[i] + dofc[i].size(); + + jcol.reserve(irow.back()); + for (i = 0; i < neq; i++) + jcol.insert(jcol.end(),dofc[i].begin(),dofc[i].end()); + + return true; +} + + +bool SAM::getDofCouplings (std::vector& dofc) const +{ + dofc.resize(neq); + for (int e = 1; e <= nel; e++) + { + IntVec meen; + if (!this->getElmEqns(meen,e)) + return false; + + for (size_t i = 0; i < meen.size(); i++) + if (meen[i] > 0) + for (size_t j = 0; j < meen.size(); j++) + if (meen[j] > 0) + dofc[meen[i]-1].insert(meen[j]); + } + + return true; +} + + +bool SAM::initForAssembly (SystemMatrix& sysK, SystemVector& sysRHS, + Vector* reactionForces) const +{ + sysK.initAssembly(*this); + return this->initForAssembly(sysRHS,reactionForces); +} + + +bool SAM::initForAssembly (SystemMatrix& sysM) const +{ + sysM.initAssembly(*this); + return neq > 0 ? true : false; +} + + +bool SAM::initForAssembly (SystemVector& sysRHS, Vector* reactionForces) const +{ + sysRHS.redim(neq); + sysRHS.init(); + if (reactionForces) + reactionForces->resize(mpar[5],true); + + return neq > 0 ? true : false; +} + + +bool SAM::assembleSystem (SystemMatrix& sysK, SystemVector& sysRHS, + const Matrix& eK, int iel, + Vector* reactionForces) const +{ + if (reactionForces) + { + Vector eS; + IntVec meen; + if (!this->getElmEqns(meen,iel,eK.rows())) + return false; + + // Add (appropriately weighted) terms corresponding to constrained + // (dependent and prescribed) DOFs in eK into reactionForces + for (size_t j = 1; j <= meen.size(); j++) + { + int jceq = -meen[j-1]; + if (jceq < 1) continue; + + int jp = mpmceq[jceq-1]; + real c0 = ttcc[jp-1]; + + for (size_t i = 1; i <= meen.size(); i++) + if (meen[i-1] <= 0) + { + eS.resize(eK.rows()); + eS(i) = -c0*eK(i,j); + } + } + + if (!eS.empty()) + this->assembleReactions(*reactionForces,eS,iel); + } + + return sysK.assemble(eK,*this,sysRHS,iel); +} + + +bool SAM::assembleSystem (SystemMatrix& sysM, + const Matrix& eM, int iel) const +{ + return sysM.assemble(eM,*this,iel); +} + + +bool SAM::assembleSystem (SystemVector& sysRHS, + const Matrix& eK, int iel, + Vector* reactionForces) const +{ + IntVec meen; + if (!this->getElmEqns(meen,iel,eK.rows())) + return false; + + // Add (appropriately weighted) terms corresponding to constrained + // (dependent and prescribed) DOFs in eK into sysRHS and reactionForces + Vector eS; + real* sysrhsPtr = sysRHS.getPtr(); + for (size_t j = 1; j <= meen.size(); j++) + { + int jceq = -meen[j-1]; + if (jceq < 1) continue; + + int jp = mpmceq[jceq-1]; + real c0 = ttcc[jp-1]; + + for (size_t i = 1; i <= meen.size(); i++) + { + int ieq = meen[i-1]; + int iceq = -ieq; + if (ieq > 0) + sysrhsPtr[ieq-1] -= c0*eK(i,j); + else if (iceq > 0) + for (int ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + sysrhsPtr[ieq-1] -= c0*ttcc[ip]*eK(i,j); + } + + if (reactionForces && iceq >= 0) + { + eS.resize(eK.rows()); + eS(i) = -c0*eK(i,j); + } + } + } + + sysRHS.restore(sysrhsPtr); + if (!eS.empty()) + this->assembleReactions(*reactionForces,eS,iel); + + return true; +} + + +bool SAM::assembleSystem (SystemVector& sysRHS, + const RealArray& eS, int iel, + Vector* reactionForces) const +{ + if (!meqn) return false; + + real* sysrhsPtr = sysRHS.getPtr(); + int ierr = 0; +#ifdef USE_F77SAM + int* work = new int[eS.size()]; + addev2_(&eS.front(), ttcc, mpar, madof, meqn, mpmnpc, mmnpc, mpmceq, mmceq, + iel, eS.size(), 6, sysrhsPtr, work, ierr); + delete[] work; +#else + IntVec meen; + if (!this->getElmEqns(meen,iel,eS.size())) + ierr = 1; + else for (size_t i = 0; i < meen.size(); i++) + { + int ieq = meen[i]; + int iceq = -ieq; + if (ieq > 0) + sysrhsPtr[ieq-1] += eS[i]; + else if (iceq > 0) + for (int ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + sysrhsPtr[ieq-1] += ttcc[ip]*eS[i]; + } + } +#endif + + sysRHS.restore(sysrhsPtr); + if (reactionForces) + this->assembleReactions(*reactionForces,eS,iel); + + return ierr == 0; +} + + +void SAM::assembleReactions (Vector& reac, const RealArray& eS, int iel) const +{ + int i, j, k, ipR; + int ip = mpmnpc[iel-1]; + int nenod = mpmnpc[iel] - ip; + for (i = k = 0; i < nenod; i++, ip++) + { + int node = mmnpc[ip-1]; + for (j = madof[node-1]; j < madof[node]; j++, k++) + if ((ipR = -msc[j-1]) > 0 && (size_t)ipR <= reac.size()) + reac(ipR) += eS[k]; + } +} + + +bool SAM::getElmEqns (IntVec& meen, int iel, int nedof) const +{ + if (iel < 1 || iel > nel) return false; + + int ip = mpmnpc[iel-1]; + int nenod = mpmnpc[iel] - ip; + int oldof = nedof; + if (nedof < 1) nedof = nenod*ndof/nnod; + +#ifdef USE_F77SAM + int neldof, neslv, neprd; + meen.resize(nedof,0); + elmeq_(madof,mmnpc+ip-1,mpmceq,meqn,nenod,&meen.front(),neldof,neslv,neprd); + if (neldof == nedof) return true; +#else + meen.clear(); + meen.reserve(nedof); + for (int i = 0; i < nenod; i++, ip++) + { + int node = mmnpc[ip-1]; + for (int j = madof[node-1]; j < madof[node]; j++) + meen.push_back(meqn[j-1]); + } + int neldof = meen.size(); + if (neldof == nedof || oldof < 0) return true; +#endif + + std::cerr <<"SAM::getElmEqns: Invalid element matrix dimension " + << nedof <<" (should have been "<< neldof <<")"<< std::endl; + return false; +} + + +bool SAM::getNodeEqns (IntVec& mnen, int inod) const +{ + if (inod < 1 || inod > nnod) return false; + + mnen.clear(); + mnen.reserve(madof[inod]-madof[inod-1]); + for (int j = madof[inod-1]; j < madof[inod]; j++) + mnen.push_back(meqn[j-1]); + + return true; +} + + +std::pair SAM::getNodeDOFs (int inod) const +{ + if (inod < 1 || inod > nnod) return std::make_pair(0,0); + + return std::make_pair(madof[inod-1],madof[inod]-1); +} + + +int SAM::getEquation (int inod, int ldof) const +{ + if (inod < 1 || inod > nnod || ldof < 1) return -1; + + int idof = madof[inod-1] + ldof-1; + if (idof >= madof[inod]) return -2; + + int ieq = meqn[idof-1]; + if (ieq < 0) ieq = meqn[mmceq[mpmceq[-ieq-1]]-1]; + return ieq; +} + + +bool SAM::expandSolution (const SystemVector& solVec, Vector& dofVec) const +{ + if (solVec.dim() < (size_t)neq) return false; + + return this->expandVector(solVec.getRef(),dofVec); +} + + +bool SAM::expandVector (const Vector& solVec, Vector& dofVec) const +{ + if (solVec.size() < (size_t)neq) return false; + + return this->expandVector(solVec.ptr(),dofVec); +} + + +bool SAM::expandVector (const real* solVec, Vector& dofVec) const +{ + if (!meqn) return false; + + dofVec.resize(ndof,true); +#ifdef USE_F77SAM + expand_(solVec, ttcc, mpmceq, mmceq, meqn, real(1), real(1), ndof, neq, + dofVec.ptr()); +#else + for (int idof = 0; idof < ndof; idof++) + { + int ieq = meqn[idof]; + int iceq = -ieq; + if (ieq > 0) + dofVec[idof] += solVec[ieq-1]; + else if (iceq > 0) + { + int ip = mpmceq[iceq-1]; + dofVec[idof] += ttcc[ip-1]; + for (; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + if (ieq > 0 && ieq <= neq) + dofVec[idof] += ttcc[ip]*solVec[ieq-1]; + } + } + } +#endif + + return true; +} + + +real SAM::dot (const Vector& x, const Vector& y) const +{ + if (mpar[16] == mpar[17]) // All nodes have the same number of DOFs + return x.dot(y); + + // Consider the nodes with mpar[17] DOFs only + int i, j, n = x.size() < y.size() ? x.size() : y.size(); + real retVal = real(0); + for (i = 0; i < nnod; i++) + if (madof[i+1] - madof[i] == mpar[17]) + for (j = madof[i]-1; j < madof[i+1] && j < n; j++) + retVal += x[j]*y[j]; + + return retVal; +} + + +real SAM::normL2 (const Vector& x) const +{ + if (x.empty()) + return real(0); + else if (mpar[16] == mpar[17]) // All nodes have the same number of DOFs + return x.norm2()/sqrt(x.size()); + + // Consider the nodes with mpar[17] DOFs only + int i, j, count = 0; + real retVal = real(0); + for (i = 0; i < nnod; i++) + if (madof[i+1] - madof[i] == mpar[17]) + for (j = madof[i]-1; j < madof[i+1] && j < (int)x.size(); j++) + { + retVal += x[j]*x[j]; + count ++; + } + + return count > 0 ? sqrt(retVal/count) : retVal; +} + + +real SAM::normInf (const Vector& x, size_t& comp) const +{ + if (x.empty() || (int)comp > mpar[17]) + return real(0); + else if (mpar[16] == mpar[17]) // All nodes have the same number of DOFs + return x.normInf(--comp,mpar[17]); + + // Consider the nodes with mpar[17] DOFs only + int i, k = comp; + real retVal = real(0); + for (i = 0; i < nnod; i++) + if (madof[i+1] - madof[i] == mpar[17]) + { + int idof = madof[i] + k-2; + if (idof < (int)x.size()) + if (fabs(x[idof]) > retVal) + { + comp = i+1; + retVal = fabs(x[idof]); + } + } + + return retVal; +} + + +real SAM::normReact (const Vector& u, const Vector& rf) const +{ + real retVal = real(0); + for (int i = 0; i < ndof; i++) + if (msc[i] < 0 && -msc[i] <= (int)rf.size()) + retVal += 0.5*u[i]*rf(-msc[i]); + + return retVal; +} diff --git a/src/LinAlg/SAM.h b/src/LinAlg/SAM.h new file mode 100644 index 00000000..cd882f21 --- /dev/null +++ b/src/LinAlg/SAM.h @@ -0,0 +1,257 @@ +// $Id: SAM.h,v 1.24 2011-02-05 18:07:51 kmo Exp $ +//============================================================================== +//! +//! \file SAM.h +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices. +//! +//============================================================================== + +#ifndef _SAM_H +#define _SAM_H + +#include "MatVec.h" +#include + +class SystemMatrix; +class SystemVector; + + +/*! + \brief This class contains data and functions for the assembly of FE matrices. + \details The names and meanings of the data members of this class are + are adopted from Kolbein Bell's pionering work on the field. + See his reports on the SAM library for a thorough elaboration. + + The class does not contain methods for initializing the data members. + That has to be done by deriving sub-classes specific to the solution methods. +*/ + +class SAM +{ +public: + //! \brief The constructor initializes an empty object. + SAM(); + //! \brief The destructor frees the dynamically allocated arrays. + virtual ~SAM(); + + //! \brief Prints out the key data to the given stream. + void print(std::ostream& os) const; + + //! \brief Returns the number of elements in the model. + int getNoElms() const { return nel; } + //! \brief Returns the number of FE nodes in the model. + int getNoNodes() const { return nnod; } + //! \brief Returns the total number of DOFs in the model. + int getNoDOFs() const { return ndof; } + //! \brief Returns the number of equations (free DOFs) in the model. + virtual int getNoEquations() const { return neq; } + + //! \brief Returns max number of dof couplings in the model. + int getMaxDofCouplings() const; + + typedef std::vector IntVec; //!< General integer vector + + //! \brief Computes number of couplings for each dof in the system matrix. + //! \param[out] nnz Number of couplings (non-zeroes) for each DOF + //! \return \e false if number of couplings is not computed, otherwise \e true + bool getNoDofCouplings(IntVec& nnz) const; + + //! \brief Interface to computation of DOF couplings for distributed matrices. + virtual bool getNoDofCouplings(int, int, IntVec&, IntVec&) const + { return false; } + + //! \brief Computes sparse structure (DOF couplings) in the system matrix. + //! \param[out] irow start index for each row in jcol + //! \param[out] jcol column indices for non-zero entries + //! \return \e false if sparse structure is not computed, otherwise \e true + bool getDofCouplings(IntVec& irow, IntVec& jcol) const; + +private: + typedef std::set IntSet; //!< General integer set + + //! \brief Finds the set of free DOFs coupled to each free DOF. + bool getDofCouplings(std::vector& dofc) const; + +public: + //! \brief Initializes the system matrices prior to the element assembly. + //! \param sysK The system left-hand-side matrix to be initialized + //! \param sysRHS The system right-hand-side load vector to be initialized + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e false if no free DOFs in the system, otherwise \e true + //! + //! \details This method must be called once before the first call to + //! \a assembleSystem for a given load case or time step. + bool initForAssembly(SystemMatrix& sysK, SystemVector& sysRHS, + Vector* reactionForces = 0) const; + + //! \brief Initializes a system matrix prior to the element assembly. + //! \param sysM The system left-hand-side matrix to be initialized + //! \return \e false if no free DOFs in the system, otherwise \e true + //! + //! \details This method must be called once before the first call to + //! \a assembleSystem for a given load case or time step. + bool initForAssembly(SystemMatrix& sysM) const; + + //! \brief Initializes the system load vector prior to the element assembly. + //! \param sysRHS The system right-hand-side load vector to be initialized + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e false if no free DOFs in the system, otherwise \e true + bool initForAssembly(SystemVector& sysRHS, Vector* reactionForces = 0) const; + + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \param sysK The left-hand-side system stiffness matrix + //! \param sysRHS The right-hand-side system load vector + //! \param[in] eK The element stiffness matrix + //! \param[in] iel Identifier for the element that \a eK belongs to + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e true on successful assembly, otherwise \e false + //! + //! \details When multi-point constraints are present, contributions from + //! these are also added into the right-hand-side system load vector. + bool assembleSystem(SystemMatrix& sysK, SystemVector& sysRHS, + const Matrix& eK, int iel = 0, + Vector* reactionForces = 0) const; + + //! \brief Adds an element matrix into the corresponding system matrix. + //! \param sysM The left-hand-side system matrix + //! \param[in] eM The element matrix + //! \param[in] iel Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + bool assembleSystem(SystemMatrix& sysM, + const Matrix& eM, int iel = 0) const; + + //! \brief Adds element stiffness contributions to the system load vector. + //! \param sysRHS The right-hand-side system load vector + //! \param[in] eK The element stiffness matrix + //! \param[in] iel Identifier for the element that \a eK belongs to + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e true on successful assembly, otherwise \e false + //! + //! \details When multi-point constraints are present, contributions from + //! these are added into the right-hand-side system load vector. + virtual bool assembleSystem(SystemVector& sysRHS, + const Matrix& eK, int iel = 0, + Vector* reactionForces = 0) const; + + //! \brief Adds an element load vector into the system load vector. + //! \param sysRHS The right-hand-side system load vector + //! \param[in] eS The element load vector + //! \param[in] iel Identifier for the element that \a eS belongs to + //! \param reactionForces Pointer to vector of nodal reaction forces + //! \return \e true on successful assembly, otherwise \e false + virtual bool assembleSystem(SystemVector& sysRHS, + const RealArray& eS, int iel = 0, + Vector* reactionForces = 0) const; + + //! \brief Finds the matrix of equation numbers for an element. + //! \param[out] meen Matrix of element equation numbers + //! \param[in] iel Identifier for the element to get the equation numbers for + //! \param[in] nedof Number of degrees of freedom in the element + //! (used for internal consistency checking, unless zero) + bool getElmEqns(IntVec& meen, int iel, int nedof = 0) const; + + //! \brief Finds the matrix of equation numbers for a node. + //! \param[out] mnen Matrix of node equation numbers + //! \param[in] inod Identifier for the node to get the equation numbers for + bool getNodeEqns(IntVec& mnen, int inod) const; + + //! \brief Returns the first and last DOFs for a node. + //! \param[in] inod Identifier for the node to get the DOF numbers for + std::pair getNodeDOFs(int inod) const; + + //! \brief Finds the equation number corresponding to a local nodal DOF. + //! \param[in] inod Identifier for the node to get the equation number for + //! \param[in] ldof Local index of the DOF within node \a inod + //! \return Equation number, or zero if the DOF is or constrained + int getEquation(int inod, int ldof) const; + + //! \brief Expands a solution vector from equation-ordering to DOF-ordering. + //! \param[in] solVec Solution vector, length = NEQ + //! \param[out] displ Displacement vector, length = NDOF = 3*NNOD + //! \return \e false if the length of \a solVec is invalid, otherwise \e true + //! + //! \details The size of the solution vector that comes out of the linear + //! equation solver equals the number of free DOFs in the system (=NEQ). + //! That is, all fixed or constrained (slave) DOFs are not present. + //! Before we can compute derived element quantities we therefore need to + //! extract the resulting displacement values also for the constrained DOFs. + virtual bool expandSolution(const SystemVector& solVec, Vector& displ) const; + + //! \brief Expands a solution vector from equation-ordering to DOF-ordering. + //! \param[in] solVec Solution vector, length = NEQ + //! \param[out] displ Displacement vector, length = NDOF = 3*NNOD + //! \return \e false if the length of \a solVec is invalid, otherwise \e true + //! + //! \details This version is typically used to expand eigenvectors. + bool expandVector(const Vector& solVec, Vector& displ) const; + + //! \brief Computes the dot-product of two vectors of length NDOF. + virtual real dot(const Vector& x, const Vector& y) const; + //! \brief Computes the l2-norm of a vector of length NDOF. + real norm2(const Vector& x) const { return sqrt(this->dot(x,x)); } + //! \brief Computes the L2-norm of a vector of length NDOF. + virtual real normL2(const Vector& x) const; + //! \brief Computes the L_infinity-norm of a vector of length NDOF. + //! \param[in] x The vector to compute the norm of + //! \param comp Local nodal DOF on input, index of the largest value on output + virtual real normInf(const Vector& x, size_t& comp) const; + //! \brief Computes energy norm contributions from nodal reaction forces. + //! \param[in] u The (incremental) nodal displacement vector + //! \param[in] rf Compressed reaction force vector + virtual real normReact(const Vector& u, const Vector& rf) const; + +protected: + //! \brief Initializes the DOF-to-equation connectivity array \a MEQN. + virtual bool initSystemEquations(); + + //! \brief Assembles reaction forces for the fixed and prescribed DOFs. + //! \param reac The vector of reaction forces + //! \param[in] eS The element load vector + //! \param[in] iel Identifier for the element that \a eS belongs to + void assembleReactions(Vector& reac, const RealArray& eS, int iel) const; + + //! \brief Expands a solution vector from equation-ordering to DOF-ordering. + //! \param[in] solVec Pointer to solution vector, length = NEQ + //! \param[out] displ Displacement vector, length = NDOF = 3*NNOD + bool expandVector(const real* solVec, Vector& displ) const; + + //! \brief Returns the internal node number and local index for a global DOF. + //! \param[in] idof Global DOF-number in the range [1,NDOF] + //! \return first = internal node number in the range [1,NNOD] + //! \return second = local DOF number in the range [1,NNDOF] + std::pair getNodeAndLocalDof(int idof) const; + + // The following parameters are pointers to specific locations in MPAR + int& nnod; //!< Number of nodes + int& nel; //!< Number of elements + int& ndof; //!< Number of DOFs + int& nceq; //!< Number of constraint equations + int& neq; //!< Number of system equations + int& nmmnpc; //!< Number of elements in MMNPC + int& nmmceq; //!< Number of elements in MMCEQ + + // The standard SAM arrays (see K. Bell's reports for detailed explanation) + int* mpmnpc; //!< Matrix of pointers to MNPCs in MMNPC + int* mmnpc; //!< Matrix of matrices of nodal point correspondances + int* madof; //!< Matrix of accumulated DOFs + int* msc; //!< Matrix of status codes + int* mpmceq; //!< Matrix of pointers to MCEQs in MMCEQ + int* mmceq; //!< Matrix of matrices of constraint equation definitions + real* ttcc; //!< Table of tables of constraint equation coefficients + int* minex; //!< Matrix of internal to external node numbers + int* meqn; //!< Matrix of equation numbers + + int mpar[50]; //!< Matrix of parameters + + friend class DenseMatrix; + friend class SPRMatrix; + friend class SparseMatrix; + friend class PETScMatrix; +}; + +#endif diff --git a/src/LinAlg/SPRMatrix.C b/src/LinAlg/SPRMatrix.C new file mode 100644 index 00000000..233ac59a --- /dev/null +++ b/src/LinAlg/SPRMatrix.C @@ -0,0 +1,405 @@ +// $Id: SPRMatrix.C,v 1.14 2010-12-06 09:21:17 rho Exp $ +//============================================================================== +//! +//! \file SPRMatrix.C +//! +//! \date Jan 4 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of the system matrix on the SPR-format. +//! +//============================================================================== + +#include "SPRMatrix.h" +#include "SAM.h" +#include + + +#if defined(_WIN32) +#define sprprp_ SPRPRP +#define sprsas_ SPRSAS +#define sprrnm_ SPRRNM +#define sprtrs_ SPRTRS +#define sprsmb_ SPRSMB +#define sprpmp_ SPRPMP +#define spradm_ SPRADM +#define sprdad_ SPRDAD +#define sprprm_ SPRPRM +#define sprsol_ SPRSOL +#define sprlax_ SPRLAX +#elif defined(_AIX) +#define sprprp_ sprprp +#define sprsas_ sprsas +#define sprrnm_ sprrnm +#define sprtrs_ sprtrs +#define sprsmb_ sprsmb +#define sprpmp_ sprpmp +#define spradm_ spradm +#define sprdad_ sprdad +#define sprprm_ sprprm +#define sprsol_ sprsol +#define sprlax_ sprlax +#endif + +extern "C" { +//! \brief Prepares the control information for the sparse assembly process. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprprp_(const int* madof, const int* minex, const int* mpmnpc, + const int* mmnpc, const int* mpmceq, const int* mmceq, + const int* msc, const int& nspar, const int& lpu, + const int* mpar, int* mspar, + const int* meqn, int* iWork, int& ierr); +//! \brief Computes the SPR-version of the connectivity array \a mmnpc. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprsas_(const int* mpar, const int* mpmnpc, const int* mmnpc, + const int* madof, const int* msc, const int* mpmceq, + const int* mmceq, const int* meqn, + int* mspar, int* msica, int* iWork, const int& lpu, int& ierr); +//! \brief Reorders the equations by means of the SPR-node partition. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprrnm_(int* mspar, int* msica, int* iWork, const int& lpu, int& ierr); +//! \brief Analyses the sparsity pattern of the system matrix. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprtrs_(int* mspar, int* msica, int* mtrees, const int* meqn, + const int& ndof, int* iWork, real* rinfo, + const int& lpu, int& ierr); +//! \brief Performs the symbolic factorization of the system matrix. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprsmb_(int* mspar, int* msica, int* mtrees, int* msifa, + const int* meqn, int* iWork, const int& lpu, int& ierr); +//! \brief Finalizes the SPR control arrays \a msica, \a mtrees and \a msifa. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprpmp_(const int* msica, const int* mtrees, const int* meqn, + const int* mpar, int* mspar, int* mvarnc); +//! \brief Assembles an element matrix \a EM into the system matrix \a SM. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void spradm_(const real* eK, const real* ttcc, const int* mpar, + const int* mspar, const int* madof, const int* meqn, + const int* mpmnpc, const int* mmnpc, const int* mpmceq, + const int* mmceq, const int* msica, const int* mtrees, + const int* msifa, const int* mvarnc, real* values, real* sysRHS, + int* work, const int& iel, const int& nedof, const int& lpu, + const int& nrhs, int& ierr); +//! \brief Adds a scalar value into the diagonal of the system matrix. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprdad_(const int* mpar, const int* mtrees, const int* msifa, + real* values, const real& sigma, const int& lpu, int& ierr); +//! \brief Performs a matrix-matrix multiplication. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprprm_(const real* A, const real* B, real* C, real* rWork, + const int* mspar, const int* mtrees, const int* msifa, + const int& m, const int& n, const int& ksa, const int& iflag, + const int& lpu, int& ierr); +//! \brief Solves the linear equation system \a Ax=b. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprsol_(const int& iop, const int* mspar, const int* mtrees, + const int* msifa, real* value, real* B, + const int& ldB, const int& nrhs, real* tol, + int* iWork, real* rWork, const int& lpu, int& ierr); +//! \brief Solves the generalized eigenproblem \a A*x=(lambda)*B*x. +//! \details This is a FORTRAN-77 subroutine in the SAM library. +//! \sa SAM library documentation. +void sprlax_(real* A, real* B, const real* tol, + const int* mparA, const int* mtreeA, const int* msifA, + const int* mparB, const int* mtreeB, const int* msifB, + const int& iop, real* val, real* vec, real* rWork, int* iWork, + const real& shift, const int& n, const int& nv, const int& maxlan, + const int& lpu, const int& ipsw, int& ierr); +} + + +SPRMatrix::SPRMatrix (const SPRMatrix& A) +{ + msica = new int[A.mpar[1]]; + msifa = new int[A.mpar[2]]; + mtrees = new int[A.mpar[35]]; + mvarnc = new int[2*A.mpar[7]]; + values = new real[A.mpar[7] + A.mpar[15]]; + + memcpy(mpar ,A.mpar ,NS*sizeof(int)); + memcpy(msica ,A.msica ,A.mpar[1]*sizeof(int)); + memcpy(msifa ,A.msifa ,A.mpar[2]*sizeof(int)); + memcpy(mtrees,A.mtrees,A.mpar[35]*sizeof(int)); + memcpy(mvarnc,A.mvarnc,2*A.mpar[7]*sizeof(int)); + memcpy(values,A.values,(A.mpar[7]+A.mpar[15])*sizeof(real)); +} + + +SPRMatrix::~SPRMatrix () +{ + if (msica) delete[] msica; + if (msifa) delete[] msifa; + if (mtrees) delete[] mtrees; + if (mvarnc) delete[] mvarnc; + if (values) delete[] values; +} + + +void SPRMatrix::initAssembly (const SAM& sam) +{ + memset(mpar,0,NS*sizeof(int)); + msica = 0; + msifa = 0; + mtrees = 0; + mvarnc = 0; + values = 0; + +#ifdef HAS_SPR + if (!sam.madof || !sam.mpmnpc) + { + std::cerr <<"SPRMatrix: SAM object is not properly initialized"<< std::endl; + return; + } + + int ierr = 0; + iWork.resize(sam.nnod + 2*sam.ndof); + int* minex = sam.minex; + if (!minex) + { + // External node numbers are not provided - generate an identity array + minex = &iWork.front(); + for (int n = 0; n < sam.nnod; n++) + minex[n] = n+1; + } + sprprp_(sam.madof, minex, sam.mpmnpc, sam.mmnpc, + sam.mpmceq, sam.mmceq, sam.msc, NS, 6, sam.mpar, + mpar, sam.meqn, &iWork.front(), ierr); + if (ierr < 0) + { + std::cerr <<"SAM::SPRPRM: Failure "<< ierr << std::endl; + return; + } + else if (sam.neq != mpar[7]) + { + std::cerr <<"SAM::SPRPRM: Internal error, NEQ = "<< sam.neq + <<" != "<< mpar[7] << std::endl; + return; + } + else if (mpar[7] < 1) + return; // No equations to solve + + // Perform symbolic assembly + std::vector itemp(mpar[34]); + sprsas_(sam.mpar, sam.mpmnpc, sam.mmnpc, sam.madof, sam.msc, sam.mpmceq, + sam.mmceq, sam.meqn, mpar, &itemp.front(), &iWork.front(), 6, ierr); + if (ierr < 0) + { + std::cerr <<"SAM::SPRSAS: Failure "<< ierr << std::endl; + return; + } + + // Reallocation of msica with the correct size + msica = new int[mpar[1]]; + memcpy(msica,&itemp.front(),mpar[1]*sizeof(int)); + + // Perform nodal reordering + iWork.resize(mpar[36]); + sprrnm_(mpar, msica, &iWork.front(), 6, ierr); + if (ierr < 0) + { + std::cerr <<"SAM::SPRRNM: Failure "<< ierr << std::endl; + return; + } + + // Analyze the sparsity pattern + mtrees = new int[mpar[35]]; + iWork.resize(mpar[37]); + real rinfo[4]; + sprtrs_(mpar, msica, mtrees, sam.meqn, sam.ndof, + &iWork.front(), rinfo, 6, ierr); + if (ierr < 0) + { + std::cerr <<"SAM::SPRTRS: Failure "<< ierr << std::endl; + return; + } + + // Perform symbolic factorization + itemp.resize(mpar[2]); + iWork.resize(3*mpar[5] + 2*mpar[7] + 1); + sprsmb_(mpar, msica, mtrees, &itemp.front(), sam.meqn, + &iWork.front(), 6, ierr); + if (ierr < 0) + { + std::cerr <<"SAM::SPRSMB: Failure "<< ierr << std::endl; + return; + } + + // Reallocation of msifa with the correct size + msifa = new int[mpar[2]]; + memcpy(msifa,&itemp.front(),mpar[2]*sizeof(int)); + + // Finalize the SPR datastructure + mvarnc = new int[2*mpar[7]]; + sprpmp_(msica, mtrees, sam.meqn, sam.mpar, mpar, mvarnc); + + // Allocate space for the matrix itself + values = new real[mpar[7] + mpar[15]]; + memset(values,0,(mpar[7] + mpar[15])*sizeof(real)); +#else + std::cerr <<"SPRMatrix: SPR solver not available"<< std::endl; +#endif +} + + +void SPRMatrix::init () +{ + if (mpar[0] < 4) return; + + mpar[0] = 4; + memset(values,0,(mpar[7] + mpar[15])*sizeof(real)); +} + + +bool SPRMatrix::assemble (const Matrix& eM, const SAM& sam, int e) +{ +#ifdef HAS_SPR + iWork.resize(mpar[38]); + int ierr; + real Bdummy; + spradm_(eM.ptr(), sam.ttcc, sam.mpar, mpar, + sam.madof, sam.meqn, sam.mpmnpc, sam.mmnpc, sam.mpmceq, sam.mmceq, + msica, mtrees, msifa, mvarnc, values, &Bdummy, &iWork.front(), + e, eM.rows(), 6, 0, ierr); + if (!ierr) return true; + + std::cerr <<"SAM::SPRADM: Failure "<< ierr << std::endl; +#endif + return false; +} + + +bool SPRMatrix::assemble (const Matrix& eM, const SAM& sam, + SystemVector& B, int e) +{ + if (B.getType() != SystemVector::STD) return false; + +#ifdef HAS_SPR + iWork.resize(mpar[38]); + int ierr; + spradm_(eM.ptr(), sam.ttcc, sam.mpar, mpar, + sam.madof, sam.meqn, sam.mpmnpc, sam.mmnpc, sam.mpmceq, sam.mmceq, + msica, mtrees, msifa, mvarnc, values, B.getPtr(), &iWork.front(), + e, eM.rows(), 6, 1, ierr); + if (!ierr) return true; + + std::cerr <<"SAM::SPRADM: Failure "<< ierr << std::endl; +#endif + return false; +} + + +bool SPRMatrix::add (const SystemMatrix& B, real alpha) +{ + const SPRMatrix* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + + if (mpar[7] != Bptr->mpar[7] || mpar[15] != Bptr->mpar[15]) return false; + + for (int i = 0; i < mpar[7]+mpar[15]; i++) + values[i] += alpha*Bptr->values[i]; + + return true; +} + + +bool SPRMatrix::add (real sigma) +{ +#ifdef HAS_SPR + int ierr; + sprdad_(mpar, mtrees, msifa, values, sigma, 6, ierr); + if (!ierr) return true; + + std::cerr <<"SAM::SPRDAD: Failure "<< ierr << std::endl; +#endif + return false; +} + + +bool SPRMatrix::multiply (const SystemVector& B, SystemVector& C) +{ + const int n = mpar[7]; + if (n < 1) return true; + + if (B.getType() != SystemVector::STD) return false; + if (C.getType() != SystemVector::STD) return false; + + C.redim(n); +#ifdef HAS_SPR + if (rWork.size() < n) rWork.resize(n); + int ierr; + sprprm_(values+n, B.getRef(), C.getPtr(), &rWork.front(), + mpar, mtrees, msifa, n, 1, 1, 1, 6, ierr); + if (!ierr) return true; + + std::cerr <<"SPRMatrix::SPRPRM: Failure "<< ierr << std::endl; +#endif + return false; +} + + +//! \brief Convenience macro. +#define MAX(a,b) (a) > (b) ? (a) : (b) + +bool SPRMatrix::solve (SystemVector& B, bool newLHS) +{ + if (mpar[7] < 1) return true; // No equations to solve + + if (B.getType() != SystemVector::STD) return false; + +#ifdef HAS_SPR + real tol[3] = { real(1.0e-12), real(0), real(0) }; + iWork.resize(MAX(mpar[12],mpar[13]+1)); + rWork.resize(MAX(mpar[16],mpar[13])); + int iop = mpar[0] < 5 ? 3 : 4; + int ierr; + sprsol_(iop, mpar, mtrees, msifa, values, B.getPtr(), + B.dim(), 1, tol, &iWork.front(), &rWork.front(), 6, ierr); + if (!ierr) return true; + + std::cerr <<"SPRMatrix::SPRSOL: Failure "<< ierr << std::endl; +#endif + return false; +} + + +bool SPRMatrix::solveEig (SPRMatrix& B, RealArray& val, Matrix& vec, int nv, + real shift, int iop) +{ + const int n = mpar[7]; + if (n < 1 || nv == 0) return true; // No equations to solve + if (n != B.mpar[7]) return false; // Incompatible matrices + +#if HAS_SPR > 1 + std::cout <<" Solving sparse eigenproblem using SAM::SPRLAX"<< std::endl; + real tol[3] = { real(1.0e-8), real(1.0e-12), real(1.0e-8) }; + int maxlan = 3*nv+12; + if (maxlan > n) maxlan = n; + int ierr = 0; + val.resize(n); + vec.resize(n,maxlan); + iWork.resize(MAX(2*maxlan+mpar[12],n)); + rWork.resize(MAX(maxlan*(maxlan+7),MAX(mpar[13],mpar[16])) + maxlan + 2*n); + sprlax_(values, B.values, tol, mpar, mtrees, msifa, B.mpar, B.mtrees, B.msifa, + iop, &val.front(), vec.ptr(), &rWork.front(), &iWork.front(), + shift, n, nv, maxlan, 6, 0, ierr); + val.resize(nv); + vec.resize(n,nv); + if (!ierr) return true; + + std::cerr <<"SAM::SPRLAX: Failure "<< ierr << std::endl; +#else + std::cerr <<"SPRMatrix: SPRLAX eigensolver not available"<< std::endl; +#endif + return false; +} diff --git a/src/LinAlg/SPRMatrix.h b/src/LinAlg/SPRMatrix.h new file mode 100644 index 00000000..246e977f --- /dev/null +++ b/src/LinAlg/SPRMatrix.h @@ -0,0 +1,121 @@ +// $Id: SPRMatrix.h,v 1.10 2010-12-06 09:20:46 rho Exp $ +//============================================================================== +//! +//! \file SPRMatrix.h +//! +//! \date Jan 4 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of the system matrix on the SPR-format with interface +//! to a Fortran module for assembling and solving linear equation systems. +//! +//============================================================================== + +#ifndef _SPR_MATRIX_H +#define _SPR_MATRIX_H + +#include "SystemMatrix.h" + +//! \brief Size of the MSPAR array. +#define NS 60 + + +/*! + \brief Class for representing the system matrix on the SPR-format. + \details It is an interface to a Fortran module for assembling and solving + linear systems of equations. +*/ + +class SPRMatrix : public SystemMatrix +{ +public: + //! \brief Default constructor. + SPRMatrix() {} + //! \brief Copy constructor. + SPRMatrix(const SPRMatrix& A); + //! \brief The destructor frees the dynamically allocated arrays. + virtual ~SPRMatrix(); + + //! \brief Returns the matrix type. + virtual Type getType() const { return SPR; } + + //! \brief Creates a copy of the system matrix and returns a pointer to it. + virtual SystemMatrix* copy() const { return new SPRMatrix(*this); } + + //! \brief Returns the dimension of the system matrix. + virtual size_t dim(int = 1) const { return mpar[7]; } + + //! \brief Initializes the element assembly process. + //! \details Must be called once before the element assembly loop. + //! The SPR data structures are initialized and the all symbolic operations + //! that are need before the actual assembly can start are performed. + //! \param[in] sam Auxilliary data describing the FE model topology, etc. + virtual void initAssembly(const SAM& sam); + + //! \brief Initializes the matrix to zero assuming it is properly dimensioned. + virtual void init(); + + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, int e); + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \details When multi-point constraints are present, contributions from + //! these are also added into the system right-hand-side load vector. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param B The system right-hand-side load vector + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, + SystemVector& B, int e); + + //! \brief Adds a matrix with similar sparsity pattern to the current matrix. + //! \param[in] B The matrix to be added + //! \param[in] alpha Scale factor for matrix \b B + virtual bool add(const SystemMatrix& B, real alpha = 1.0); + + //! \brief Adds the diagonal matrix \f$\sigma\f$\b I to the current matrix. + virtual bool add(real sigma); + + //! \brief Performs the matrix-vector multiplication \b C = \a *this * \b B. + virtual bool multiply(const SystemVector& B, SystemVector& C); + + //! \brief Solves the linear system of equations for a given right-hand-side. + //! \param B Right-hand-side vector on input, solution vector on output + //! \param newLHS \e true if the left-hand-side matrix is updated + virtual bool solve(SystemVector& B, bool newLHS = true); + + //! \brief Solves a generalized symmetric-definite eigenproblem. + //! \details The eigenproblem is assumed to be on the form + //! \b A \b x = \f$\lambda\f$ \b B \b x where \b A ( = \a *this ) and \b B + //! both are assumed to be symmetric and \b B also to be positive definite. + //! The eigenproblem is solved by the SAM library subroutine \a SPRLAN. + //! \sa SAM library documentation. + //! \param B Symmetric and positive definite mass matrix. + //! \param[out] eigVal Computed eigenvalues + //! \param[out] eigVec Computed eigenvectors stored column by column + //! \param[in] nev The number of eigenvalues and eigenvectors to compute + //! \param[in] shift Eigenvalue shift + //! \param[in] iop Option telling whether to factorize matrix \a A or \b B. + bool solveEig (SPRMatrix& B, RealArray& eigVal, Matrix& eigVec, int nev, + real shift = 0.0, int iop = 1); + +private: + int mpar[NS]; //!< Matrix of sparse PARameters + int* msica; //!< Matrix of Storage Information for CA + int* msifa; //!< Matrix of Storage Information for FA + int* mtrees; //!< Matrix of elimination assembly TREES + int* mvarnc; //!< Matrix of VARiable to Node Correspondence + real* values; //!< The actual matrix VALUES + + std::vector iWork; //!< Integer work array + std::vector rWork; //!< Real work array +}; + +#endif diff --git a/src/LinAlg/SparseMatrix.C b/src/LinAlg/SparseMatrix.C new file mode 100644 index 00000000..f82b6edb --- /dev/null +++ b/src/LinAlg/SparseMatrix.C @@ -0,0 +1,990 @@ +// $Id: SparseMatrix.C,v 1.23 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file SparseMatrix.C +//! +//! \date Jan 8 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of the system matrix on an unstructured sparse format. +//! +//============================================================================== + +#include "SparseMatrix.h" +#include "SAM.h" +#if defined(HAS_SUPERLU_MT) +#include "pdsp_defs.h" +#elif defined(HAS_SUPERLU) +#include "slu_ddefs.h" +#endif +#ifdef HAS_SAMG +#include "samg.h" +#endif +#include + +#if defined(HAS_SUPERLU_MT) +#define sluop_t superlumt_options_t +#elif defined(HAS_SUPERLU) +#define sluop_t superlu_options_t +#endif + + +/*! + \brief Data structures for the SuperLU equation solver. +*/ + +struct SuperLUdata +{ +#if defined(HAS_SUPERLU) || defined(HAS_SUPERLU_MT) + SuperMatrix A; //! The unfactored coefficient matrix + SuperMatrix L; //! The lower triangle factor + SuperMatrix U; //! The upper triangle factor + real* R; //! The row scale factors for \a A + real* C; //! The column scale factors for \a A + int* perm_r; //! Row permutation vector + int* perm_c; //! Column permutation vector + int* etree; //! The elimination tree + sluop_t* opts; //! Input options for the SuperLU driver routine +#ifdef HAS_SUPERLU_MT + equed_t equed; //! Specifies the form of equilibration that was done +#else + char equed[1]; //! Specifies the form of equilibration that was done +#endif + + //! \brief The constructor initializes the default input options. + SuperLUdata(int numThreads = 0) + { + R = C = 0; + perm_r = perm_c = etree = 0; + if (numThreads > 0) + { + opts = new sluop_t; +#ifdef HAS_SUPERLU_MT + opts->nprocs = numThreads; + opts->fact = DOFACT; + opts->trans = NOTRANS; + opts->refact = NO; + opts->panel_size = sp_ienv(1); + opts->relax = sp_ienv(2); + opts->diag_pivot_thresh = 1.0; + opts->drop_tol = 0.0; + opts->ColPerm = MMD_ATA; + opts->usepr = NO; + opts->SymmetricMode = NO; + opts->PrintStat = NO; + opts->perm_c = 0; + opts->perm_r = 0; + opts->work = 0; + opts->lwork = 0; + opts->etree = 0; + opts->colcnt_h = 0; + opts->part_super_h = 0; +#else + set_default_options(opts); + opts->SymmetricMode = YES; + opts->ColPerm = MMD_AT_PLUS_A; + opts->DiagPivotThresh = 0.001; +#endif + } + else + opts = 0; + } + + //! \brief The destructor frees the dynamically allocated data members. + ~SuperLUdata() + { + Destroy_SuperMatrix_Store(&A); + Destroy_SuperNode_Matrix(&L); + Destroy_CompCol_Matrix(&U); + if (R) delete[] R; + if (C) delete[] C; + if (perm_r) delete[] perm_r; + if (perm_c) delete[] perm_c; + if (etree) delete[] etree; + if (opts) delete opts; + } +#endif +}; + + +bool SparseMatrix::printSLUstat = false; + + +SparseMatrix::SparseMatrix (SparseSolver eqSolver, int nt) +{ + editable = true; + nrow = ncol = 0; + solver = eqSolver; + numThreads = nt; + slu = 0; +} + + +SparseMatrix::SparseMatrix (const SparseMatrix& B) +{ + editable = B.editable; + nrow = B.nrow; + ncol = B.ncol; + elem = B.elem; + IA = B.IA; + JA = B.JA; + A = B.A; + solver = B.solver; + numThreads = B.numThreads; + slu = 0; // The SuperLU data (if any) is not copied +} + + +SparseMatrix::~SparseMatrix () +{ + if (slu) delete slu; +} + + +void SparseMatrix::resize (size_t r, size_t c) +{ + editable = true; + elem.clear(); + IA.clear(); + JA.clear(); + A.clear(); + + if (r == nrow && c == ncol) return; + + nrow = r; + ncol = c > 0 ? c : r; + + if (slu) delete slu; +} + + +bool SparseMatrix::redim (size_t r, size_t c) +{ + if (!editable) return false; + + nrow = r; + ncol = c > 0 ? c : r; + for (ValueIter it = elem.begin(); it != elem.end();) + if (it->first.first > nrow || it->first.second > ncol) + { + ValueIter jt = it++; + elem.erase(jt->first); + } + else + it++; + + return true; +} + + +size_t SparseMatrix::dim (int idim) const +{ + if (idim == 1) + return nrow; + else if (idim == 2) + return ncol; + else if (idim == 3) + return nrow*ncol; + else + return this->size(); +} + + +real& SparseMatrix::operator () (size_t r, size_t c) +{ + if (r < 1 || r > nrow || c < 1 || c > ncol) + std::cerr <<"SparseMatrix::operator(): Indices (" + << r <<","<< c <<") out of range " + << nrow <<"x"<< ncol << std::endl; + else if (editable) { + IJPair index(r,c); + if (elem.find(index) == elem.end()) elem[index] = 0.0; + return elem[index]; + } + + static real anyValue = real(0); +#if INDEX_CHECK > 1 + abort(); +#endif + return anyValue; +} + + +const real& SparseMatrix::operator () (size_t r, size_t c) const +{ + if (r < 1 || r > nrow || c < 1 || c > ncol) + std::cerr <<"SparseMatrix::operator(): Indices (" + << r <<","<< c <<") out of range " + << nrow <<"x"<< ncol << std::endl; + else if (editable) { + ValueIter vit = elem.find(IJPair(r,c)); + if (vit != elem.end()) return vit->second; + } + else if (solver == SUPERLU) { + // Column-oriented format with 0-based indices + std::vector::const_iterator begin = JA.begin() + IA[c-1]; + std::vector::const_iterator end = JA.begin() + IA[c]; + std::vector::const_iterator it = std::find(begin, end, r-1); + if (it != end) return A[it - JA.begin()]; + } + else { + // Row-oriented format with 1-based indices + std::vector::const_iterator begin = JA.begin() + (IA[r-1]-1); + std::vector::const_iterator end = JA.begin() + (IA[r]-1); + std::vector::const_iterator it = std::find(begin, end, c); + if (it != end) return A[it - JA.begin()]; + } + + static const real zero = real(0); +#if INDEX_CHECK > 1 + abort(); +#endif + return zero; +} + + +std::ostream& SparseMatrix::write (std::ostream& os) const +{ + os << nrow <<' '<< ncol <<' '<< this->size(); + if (editable) + for (ValueIter it = elem.begin(); it != elem.end(); it++) + os <<'\n'<< it->first.first <<' '<< it->first.second <<" : "<< it->second; + else { + size_t i; + os <<'\n'; + for (i = 0; i < A.size(); i++) os << A[i] <<' '; + + os <<'\n'<< IA.size() <<'\n'; + for (i = 0; i < IA.size(); i++) os << IA[i] <<' '; + + os <<'\n'<< JA.size() <<'\n'; + for (i = 0; i < JA.size(); i++) os << JA[i] <<' '; + } + return os << std::endl; +} + + +void SparseMatrix::printSparsity (std::ostream& os) const +{ + if (nrow < 1 || ncol < 1) return; + + size_t r, c; + std::cout <<'\t'; + for (c = 1; c <= ncol; c++) std::cout << (c%10 ? char('0'+(c%10)) : ' '); + std::cout <<'\n'; + + for (r = 1; r <= nrow; r++) { + os << r <<'\t'; + for (c = 1; c <= ncol; c++) + if (editable) + os << (elem.find(IJPair(r,c)) == elem.end() ? '.' : 'X'); + else if (solver == SUPERLU) { + // Column-oriented format with 0-based indices + std::vector::const_iterator begin = JA.begin() + IA[c-1]; + std::vector::const_iterator end = JA.begin() + IA[c]; + os << (std::find(begin,end,r-1) == end ? '.' : 'X'); + } + else { + // Row-oriented format with 1-based indices + std::vector::const_iterator begin = JA.begin() + (IA[r-1]-1); + std::vector::const_iterator end = JA.begin() + (IA[r]-1); + os << (std::find(begin,end,c) == end ? '.' : 'X'); + } + os <<'\n'; + } + os << std::endl; +} + + +void SparseMatrix::printFull (std::ostream& os) const +{ + for (size_t r = 1; r <= nrow; r++) + for (size_t c = 1; c <= ncol; c++) + os << this->operator()(r,c) << (c < ncol ? '\t' : '\n'); + + os << std::endl; +} + + +bool SparseMatrix::augment (const SystemMatrix& B, size_t r0, size_t c0) +{ + if (!editable) return false; + + const SparseMatrix* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + if (!Bptr->editable) return false; + + if (r0+Bptr->nrow > nrow) nrow = r0 + Bptr->nrow; + if (r0+Bptr->nrow > ncol) ncol = r0 + Bptr->nrow; + if (c0+Bptr->ncol > ncol) ncol = c0 + Bptr->ncol; + if (c0+Bptr->ncol > nrow) nrow = c0 + Bptr->ncol; + + for (ValueIter it = Bptr->elem.begin(); it != Bptr->elem.end(); it++) + { + elem[std::make_pair(r0+it->first.first,c0+it->first.second)] += it->second; + elem[std::make_pair(c0+it->first.second,r0+it->first.first)] += it->second; + } + + return true; +} + + +bool SparseMatrix::truncate (real threshold) +{ + if (!editable) return false; + + ValueIter it; + real tol = real(0); + for (it = elem.begin(); it != elem.end(); it++) + if (it->first.first == it->first.second) + if (it->second > tol) + tol = it->second; + else if (it->second < -tol) + tol = -it->second; + + tol *= threshold; + size_t nnz = elem.size(); + for (it = elem.begin(); it != elem.end();) + if (it->second < tol && it->second > -tol) + { + ValueIter jt = it++; + elem.erase(jt->first); + } + else + it++; + + if (nnz > elem.size()) + std::cout <<"SparseMatrix: Truncated "<< nnz-elem.size() + <<" matrix elements smaller than "<< tol <<" to zero"<< std::endl; + return true; +} + + +bool SparseMatrix::add (const SystemMatrix& B, real alpha) +{ + if (!editable) return false; + + const SparseMatrix* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + + if (Bptr->nrow > nrow || Bptr->ncol > ncol) return false; + + if (Bptr->editable) + for (ValueIter it = Bptr->elem.begin(); it != Bptr->elem.end(); it++) + elem[it->first] += alpha*it->second; + else if (solver == SUPERLU) + // Column-oriented format with 0-based indices + for (size_t j = 1; j <= Bptr->ncol; j++) + for (int i = Bptr->IA[j-1]; i < Bptr->IA[j]; i++) + elem[IJPair(Bptr->JA[i]+1,j)] += alpha*Bptr->A[i]; + else + // Row-oriented format with 1-based indices + for (size_t i = 1; i <= Bptr->nrow; i++) + for (int j = Bptr->IA[i-1]; j < Bptr->IA[i]; j++) + elem[IJPair(i,Bptr->JA[j-1])] += alpha*Bptr->A[j-1]; + + return true; +} + + +bool SparseMatrix::add (real sigma) +{ + if (!editable) return false; + + for (size_t i = 1; i <= nrow && i <= ncol; i++) + this->operator()(i,i) += sigma; + + return true; +} + + +bool SparseMatrix::multiply (const SystemVector& B, SystemVector& C) +{ + C.resize(nrow,true); + if (B.dim() < ncol) return false; + + const StdVector* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + StdVector* Cptr = dynamic_cast(&C); + if (!Cptr) return false; + + if (editable) + for (ValueIter it = elem.begin(); it != elem.end(); it++) + (*Cptr)(it->first.first) += it->second*(*Bptr)(it->first.second); + else if (solver == SUPERLU) + // Column-oriented format with 0-based indices + for (size_t j = 1; j <= ncol; j++) + for (int i = IA[j-1]; i < IA[j]; i++) + (*Cptr)(JA[i]+1) += A[i]*(*Bptr)(j); + else + // Row-oriented format with 1-based indices + for (size_t i = 1; i <= nrow; i++) + for (int j = IA[i-1]; j < IA[i]; j++) + (*Cptr)(i) += A[j-1]*(*Bptr)(JA[j-1]); + + return true; +} + + +/*! + \brief This is a C++ version of the F77 subroutine ADDEM2 (SAM library). + \details It performs exactly the same tasks, except that \a NRHS always is 1, + and that the system matrix \a SM here is an object of the SparseMatrix class. +*/ + +static void assemSparse (const Matrix& eM, SparseMatrix& SM, Vector& SV, + const std::vector& meen, const int* meqn, + const int* mpmceq, const int* mmceq, const real* ttcc) +{ + // Add elements corresponding to free dofs in eM into SM + int i, j, ip, nedof = meen.size(); + for (j = 1; j <= nedof; j++) + { + int jeq = meen[j-1]; + if (jeq < 1) continue; + + SM(jeq,jeq) += eM(j,j); + + for (i = 1; i < j; i++) + { + int ieq = meen[i-1]; + if (ieq < 1) continue; + + SM(ieq,jeq) += eM(i,j); + SM(jeq,ieq) += eM(j,i); + } + } + + // Add (appropriately weighted) elements corresponding to constrained + // (dependent and prescribed) dofs in eM into SM and/or SV + for (j = 1; j <= nedof; j++) + { + int jceq = -meen[j-1]; + if (jceq < 1) continue; + + int jp = mpmceq[jceq-1]; + real c0 = ttcc[jp-1]; + + // Add contributions to SV (right-hand-side) + if (!SV.empty()) + for (i = 1; i <= nedof; i++) + { + int ieq = meen[i-1]; + int iceq = -ieq; + if (ieq > 0) + SV(ieq) -= c0*eM(i,j); + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + SV(ieq) -= c0*ttcc[ip]*eM(i,j); + } + } + + // Add contributions to SM + for (jp = mpmceq[jceq-1]; jp < mpmceq[jceq]-1; jp++) + if (mmceq[jp] > 0) + { + int jeq = meqn[mmceq[jp]-1]; + for (i = 1; i <= nedof; i++) + { + int ieq = meen[i-1]; + int iceq = -ieq; + if (ieq > 0) + { + SM(ieq,jeq) += ttcc[jp]*eM(i,j); + SM(jeq,ieq) += ttcc[jp]*eM(j,i); + } + else if (iceq > 0) + for (ip = mpmceq[iceq-1]; ip < mpmceq[iceq]-1; ip++) + if (mmceq[ip] > 0) + { + ieq = meqn[mmceq[ip]-1]; + SM(ieq,jeq) += ttcc[ip]*ttcc[jp]*eM(i,j); + } + } + } + } +} + + +/*! + \brief Adds a nodal vector into a non-symmetric rectangular sparse matrix. + \details The nodal values are added into the columns \a col to \a col+2. +*/ + +static void assemSparse (const RealArray& V, SparseMatrix& SM, size_t col, + const std::vector& mnen, const int* meqn, + const int* mpmceq, const int* mmceq, const real* ttcc) +{ + for (size_t d = 0; d < mnen.size(); d++, col++) + { + real vd = d < V.size() ? V[d] : V.back(); + int ieq = mnen[d]; + int ceq = -ieq; + if (ieq > 0) + SM(ieq,col) += vd; + else if (ceq > 0) + for (int ip = mpmceq[ceq-1]; ip < mpmceq[ceq]-1; ip++) + { + ieq = meqn[mmceq[ip]-1]; + SM(ieq,col) += vd; + } + } +} + + +void SparseMatrix::initAssembly (const SAM& sam) +{ + this->resize(sam.neq,sam.neq); +} + + +void SparseMatrix::init () +{ + this->resize(nrow,ncol); +} + + +bool SparseMatrix::assemble (const Matrix& eM, const SAM& sam, int e) +{ + std::vector meen; + if (!sam.getElmEqns(meen,e,eM.rows())) + return false; + + Vector dummyB; + assemSparse(eM,*this,dummyB,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); + return true; +} + + +bool SparseMatrix::assemble (const Matrix& eM, const SAM& sam, + SystemVector& B, int e) +{ + StdVector* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + + std::vector meen; + if (!sam.getElmEqns(meen,e,eM.rows())) + return false; + + assemSparse(eM,*this,*Bptr,meen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); + return true; +} + + +bool SparseMatrix::assembleCol (const RealArray& V, const SAM& sam, + int n, size_t col) +{ + if (V.empty() || col > ncol) return false; + + std::vector mnen; + if (!sam.getNodeEqns(mnen,n)) return false; + + assemSparse(V,*this,col,mnen,sam.meqn,sam.mpmceq,sam.mmceq,sam.ttcc); + return true; +} + + +// Converts an editable sparse matrix to a non-editable row-oriented storage +// format suitable for the SAMG equation solver. + +bool SparseMatrix::optimiseSAMG (bool transposed) +{ + if (!editable) return false; + + ValueMap trans; // only computed if needed + ValueIter it, begin, end; + + if (transposed) { + for (it = elem.begin(); it != elem.end(); it++) + trans[IJPair(it->first.second,it->first.first)] = it->second; + begin = trans.begin(); + end = trans.end(); + std::swap(nrow,ncol); + } + else { + begin = elem.begin(); + end = elem.end(); + } + + size_t nnz = this->size(); + A.resize(nnz); + JA.resize(nnz); + IA.resize(nrow+1,nnz+1); + + IA[0] = 1; // first row start at index 1 + size_t cur_row = 1, ix = 0; + for (it = begin; it != end; it++, ix++) { + A[ix] = it->second; // storing element value + JA[ix] = it->first.second; + while (it->first.first > cur_row) + IA[cur_row++] = ix+1; + } + + editable = false; + elem.clear(); // Erase the editable matrix elements + + // convert to row storage format required by SAMG (diagonal term first) + for (size_t r = 0; r < nrow; r++) { + int rstart = IA[r]-1; + int rend = IA[r+1]-1; + // looking for diagonal element + for (int diag_ix = rstart; diag_ix < rend; diag_ix++) + if (JA[diag_ix] == (int)(1+r)) { + // swapping (if necessary) with first element on this row + if (diag_ix > rstart) { + std::swap(A[rstart],A[diag_ix]); + std::swap(JA[rstart],JA[diag_ix]); + } + break; + } + } + + return true; +} + + +// Converts an editable sparse matrix to a non-editable column-oriented storage +// format suitable for the SuperLU equation solver. This method is based on +// the function dreadtriple() from the SuperLU package. + +bool SparseMatrix::optimiseSLU () +{ + if (!editable) return false; + + size_t nnz = this->size(); + A.resize(nnz); + JA.resize(nnz); + IA.resize(ncol+1,0); + + // Initialize the array of column pointers + ValueIter it; + for (it = elem.begin(); it != elem.end(); it++) + if (it->first.first <= nrow && it->first.second <= ncol) + IA[it->first.second-1]++; + else + return false; + + size_t j, nz; + int k, jsize = IA[0]; + for (j = 1, k = IA[0] = 0; j < ncol; j++) { + k += jsize; + jsize = IA[j]; + IA[j] = k; + } + + // Copy the triplets into the column-oriented storage + for (nz = 0, it = elem.begin(); nz < nnz; nz++, it++) { + k = IA[it->first.second-1]++; + JA[k] = it->first.first-1; + A[k] = it->second; + } + + // Reset the column pointers to the beginning of each column + for (j = ncol; j > 0; j--) + IA[j] = IA[j-1]; + IA[0] = 0; + + editable = false; + elem.clear(); // Erase the editable matrix elements + + return true; +} + + +// Invokes the linear equation solver for the given right-hand-side vector, B. + +bool SparseMatrix::solve (SystemVector& B, bool) +{ + if (this->size() < 1) return true; // No equations to solve + + StdVector* Bptr = dynamic_cast(&B); + if (!Bptr) return false; + + switch (solver) + { + case SUPERLU: return this->solveSLUx(editable,*Bptr); + case S_A_M_G: return this->solveSAMG(editable,*Bptr); + default: std::cerr <<"SparseMatrix::solve: No equation solver"<< std::endl; + } + + return false; +} + + +bool SparseMatrix::solveSLU (bool isFirstRHS, Vector& B) +{ + int ierr = ncol+1; + if (isFirstRHS) this->optimiseSLU(); + +#ifdef HAS_SUPERLU_MT + if (!slu) { + // Create a new SuperLU matrix + slu = new SuperLUdata; + slu->perm_c = new int[ncol]; + slu->perm_r = new int[nrow]; + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + } + else + { + Destroy_SuperMatrix_Store(&slu->A); + Destroy_SuperNode_Matrix(&slu->L); + Destroy_CompCol_Matrix(&slu->U); + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + } + + // Get column permutation vector perm_c[], according to permc_spec: + // permc_spec = 0: natural ordering + // permc_spec = 1: minimum degree ordering on structure of A'*A + // permc_spec = 2: minimum degree ordering on structure of A'+A + // permc_spec = 3: approximate minimum degree for unsymmetric matrices + int permc_spec = 1; + get_perm_c(permc_spec, &slu->A, slu->perm_c); + + // Create right-hand-side/solution vector + SuperMatrix Bmat; + dCreate_Dense_Matrix(&Bmat, nrow, 1, B.ptr(), nrow, + SLU_DN, SLU_D, SLU_GE); + + // Invoke the simple driver + pdgssv(numThreads, &slu->A, slu->perm_c, slu->perm_r, + &slu->L, &slu->U, &Bmat, &ierr); + + if (ierr > 0) + std::cerr <<"SuperLU_MT Failure "<< ierr << std::endl; + + Destroy_SuperMatrix_Store(&Bmat); + +#elif defined(HAS_SUPERLU) + if (!slu) { + // Create a new SuperLU matrix + slu = new SuperLUdata(1); + slu->perm_c = new int[ncol]; + slu->perm_r = new int[nrow]; + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + } + else if (isFirstRHS) + { + Destroy_SuperMatrix_Store(&slu->A); + Destroy_SuperNode_Matrix(&slu->L); + Destroy_CompCol_Matrix(&slu->U); + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + } + else + slu->opts->Fact = FACTORED; // Re-use previous factorization + + // Create right-hand-side/solution vector + SuperMatrix Bmat; + dCreate_Dense_Matrix(&Bmat, nrow, 1, B.ptr(), nrow, + SLU_DN, SLU_D, SLU_GE); + + SuperLUStat_t stat; + StatInit(&stat); + + // Invoke the simple driver + dgssv(slu->opts, &slu->A, slu->perm_c, slu->perm_r, + &slu->L, &slu->U, &Bmat, &stat, &ierr); + + if (ierr > 0) + std::cerr <<"SuperLU Failure "<< ierr << std::endl; + + if (printSLUstat) + StatPrint(&stat); + StatFree(&stat); + + Destroy_SuperMatrix_Store(&Bmat); +#else + std::cerr <<"SparseMatrix::solve: SuperLU solver not available"<< std::endl; +#endif + return ierr == 0; +} + + +bool SparseMatrix::solveSLUx (bool isFirstRHS, Vector& B) +{ + int ierr = ncol+1; + if (isFirstRHS) this->optimiseSLU(); + +#ifdef HAS_SUPERLU_MT + if (!slu) { + // Create a new SuperLU matrix + slu = new SuperLUdata(numThreads); + slu->equed = NOEQUIL; + slu->perm_c = new int[ncol]; + slu->perm_r = new int[nrow]; + slu->C = new real[ncol]; + slu->R = new real[nrow]; + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + + // Get column permutation vector perm_c[], according to permc_spec: + // permc_spec = 0: natural ordering + // permc_spec = 1: minimum degree ordering on structure of A'*A + // permc_spec = 2: minimum degree ordering on structure of A'+A + // permc_spec = 3: approximate minimum degree for unsymmetric matrices + int permc_spec = 1; + get_perm_c(permc_spec, &slu->A, slu->perm_c); + } + else if (isFirstRHS) + slu->opts->refact = YES; // Re-use previous ordering + else + slu->opts->fact = FACTORED; // Re-use previous factorization + + // Create right-hand-side vector and solution vector + Vector X(B.size()); + SuperMatrix Bmat, Xmat; + dCreate_Dense_Matrix(&Bmat, nrow, 1, B.ptr(), nrow, + SLU_DN, SLU_D, SLU_GE); + dCreate_Dense_Matrix(&Xmat, nrow, 1, X.ptr(), nrow, + SLU_DN, SLU_D, SLU_GE); + + real rpg, rcond, ferr, berr; + superlu_memusage_t mem_usage; + + // Invoke the expert driver + pdgssvx(numThreads, slu->opts, &slu->A, slu->perm_c, slu->perm_r, + &slu->equed, slu->R, slu->C, &slu->L, &slu->U, &Bmat, &Xmat, + &rpg, &rcond, &ferr, &berr, &mem_usage, &ierr); + + B.swap(X); + + if (ierr > 0) + std::cerr <<"SuperLU_MT Failure "<< ierr << std::endl; + + Destroy_SuperMatrix_Store(&Bmat); + Destroy_SuperMatrix_Store(&Xmat); + +#elif defined(HAS_SUPERLU) + if (!slu) { + // Create a new SuperLU matrix + slu = new SuperLUdata(1); + slu->perm_c = new int[ncol]; + slu->perm_r = new int[nrow]; + slu->etree = new int[ncol]; + slu->C = new real[ncol]; + slu->R = new real[nrow]; + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + } + else if (isFirstRHS) + { + Destroy_SuperMatrix_Store(&slu->A); + Destroy_SuperNode_Matrix(&slu->L); + Destroy_CompCol_Matrix(&slu->U); + dCreate_CompCol_Matrix(&slu->A, nrow, ncol, this->size(), + &A.front(), &JA.front(), &IA.front(), + SLU_NC, SLU_D, SLU_GE); + } + else + slu->opts->Fact = FACTORED; // Re-use previous factorization + + // Create right-hand-side vector and solution vector + Vector X(B.size()); + SuperMatrix Bmat, Xmat; + dCreate_Dense_Matrix(&Bmat, nrow, 1, B.ptr(), nrow, + SLU_DN, SLU_D, SLU_GE); + dCreate_Dense_Matrix(&Xmat, nrow, 1, X.ptr(), nrow, + SLU_DN, SLU_D, SLU_GE); + + void* work = 0; + int lwork = 0; + real rpg, rcond, ferr, berr; + mem_usage_t mem_usage; + + SuperLUStat_t stat; + StatInit(&stat); + + // Invoke the expert driver + dgssvx(slu->opts, &slu->A, slu->perm_c, slu->perm_r, slu->etree, slu->equed, + slu->R, slu->C, &slu->L, &slu->U, work, lwork, &Bmat, &Xmat, + &rpg, &rcond, &ferr, &berr, &mem_usage, &stat, &ierr); + + B.swap(X); + + if (ierr > 0) + std::cerr <<"SuperLU Failure "<< ierr << std::endl; + + if (printSLUstat) + StatPrint(&stat); + StatFree(&stat); + + Destroy_SuperMatrix_Store(&Bmat); + Destroy_SuperMatrix_Store(&Xmat); +#else + std::cerr <<"SparseMatrix::solve: SuperLU solver not available"<< std::endl; +#endif + return ierr == 0; +} + + +bool SparseMatrix::solveSAMG (bool isFirstRHS, Vector& B) +{ + int ierr = 1; + if (isFirstRHS) this->optimiseSAMG(); + +#ifdef HAS_SAMG + // Setting up additional parameters + int nnu = this->rows(); // number of solution components (variables) + int nna = this->size(); // number of nonzero entries in system matrix + int nsys = 1; // one unknown - scalar system + int iu = 1; // dummy - since nsys = 1; + int ndiu = 1; // dummy - since nsys = 1; + int ip = 1; // dummy - since nsys = 1 and no point-based approach used + int ndip = 1; // dummy - since nsys = 1 and no point-based approach used + int matrix = 120; // symmetric, NO zero rowsum, NO modification of matrix + int iscale = 1; // dummy - since nsys = 1; + real res_in, res_out; // output parameters + int ncyc_done; // output parameters + int nsolve = 2; // default solution strategy + // (this parameter can quickly become complicated to set) + int ifirst = 1; // no initial solution guess + real eps = -1.0e-16; // stopping criterion + int ncyc = 110200; // define the cycling and accelleration strategy + int iswitch = isFirstRHS ? 4140 : 1140; // No memory release in first run, + // assuming identical coefficent matrix in all subsequent runs + real a_cmplx = 0.0; // l + real g_cmplx = 0.0; // l specifies preallocation of memory, etc. + real p_cmplx = 0.0; // / + real w_avrge = 0.0; // / + real chktol = 0.0; // standard checking for logical correctness + // @@ set this to negative value for production run + int iout = -2; // minimal output on results and timings + int idump = -2; // printout of coarsening history + + int mode_mess = -2; + SAMG_SET_MODE_MESS(&mode_mess); + + Vector X(B.size()); + + SAMG(&nnu, &nna, &nsys, + &IA.front(), &JA.front(), &A.front(), B.ptr(), X.ptr(), + &iu, &ndiu, &ip, &ndip, &matrix, &iscale, + &res_in, &res_out, &ncyc_done, &ierr, + &nsolve, &ifirst, &eps, &ncyc, &iswitch, + &a_cmplx, &g_cmplx, &p_cmplx, &w_avrge, + &chktol, &idump, &iout); + + B.swap(X); + + if (ierr > 0) + std::cerr <<"SAMG Failure "<< ierr << std::endl; + else if (ierr < 0) + std::cerr <<"SAMG warning "<< -ierr << std::endl; +#else + std::cerr <<"SparseMatrix::solve: SAMG solver not available"<< std::endl; +#endif + return ierr <= 0; +} diff --git a/src/LinAlg/SparseMatrix.h b/src/LinAlg/SparseMatrix.h new file mode 100644 index 00000000..373f3331 --- /dev/null +++ b/src/LinAlg/SparseMatrix.h @@ -0,0 +1,216 @@ +// $Id: SparseMatrix.h,v 1.17 2011-02-04 17:02:38 kmo Exp $ +//============================================================================== +//! +//! \file SparseMatrix.h +//! +//! \date Jan 8 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of the system matrix on an unstructured sparse format. +//! +//============================================================================== + +#ifndef _SPARSE_MATRIX_H +#define _SPARSE_MATRIX_H + +#include "SystemMatrix.h" +#include +#include + +typedef std::pair IJPair; //!< 1-based matrix indices +typedef std::map ValueMap; //!< Index to matrix value mapping +typedef ValueMap::const_iterator ValueIter; //!< Iterator over matrix elements + +struct SuperLUdata; + + +/*! + \brief Class for representing a system matrix on an unstructured sparse form. + \details The sparse matrix is editable in the sense that non-zero entries may + be added at arbitrary locations. The class comes with methods for solving a + linear system of equations based on the current matrix and a given RHS-vector, + using either the commercial SAMG package or the public domain SuperLU package. +*/ + +class SparseMatrix : public SystemMatrix +{ +public: + //! \brief Available equation solvers for this matrix type. + enum SparseSolver { NONE, SUPERLU, S_A_M_G }; + + //! \brief Default constructor creating an empty matrix. + SparseMatrix(SparseSolver eqSolver = NONE, int nt = 1); + //! \brief Copy constructor. + SparseMatrix(const SparseMatrix& B); + //! \brief The destructor frees the dynamically allocated arrays. + virtual ~SparseMatrix(); + + //! \brief Returns the matrix type. + virtual Type getType() const { return solver == S_A_M_G ? SAMG : SPARSE; } + + //! \brief Creates a copy of the system matrix and returns a pointer to it. + virtual SystemMatrix* copy() const { return new SparseMatrix(*this); } + + //! \brief Resizes the matrix to dimension \f$r \times c\f$. + //! \details Will erase previous content, if any. + void resize(size_t r, size_t c = 0); + + //! \brief Resizes the matrix to dimension \f$r \times c\f$. + //! \details Will preserve existing matrix content within the new dimension. + bool redim(size_t r, size_t c); + + //! \brief Query number of matrix rows. + size_t rows() const { return nrow; } + //! \brief Query number of matrix columns. + size_t cols() const { return ncol; } + //! \brief Query total matrix size in terms of number of non-zero elements. + size_t size() const { return editable ? elem.size() : A.size(); } + + //! \brief Returns the dimension of the system matrix. + //! \param[in] idim Which direction to return the dimension in. + virtual size_t dim(int idim = 1) const; + + //! \brief Index-1 based element access. + //! \note For editable matrix only. + real& operator()(size_t r, size_t c); + //! \brief Index-1 based element reference. + const real& operator()(size_t r, size_t c) const; + + //! \brief For traversal of the non-zero elements of an editable matrix. + const ValueMap& getValues() const { return elem; } + + //! \brief Print sparsity pattern - for inspection purposes. + void printSparsity(std::ostream& os) const; + + //! \brief Print the matrix in full rectangular form. + //! \warning Not recommended for matrices of nontrivial size. + void printFull(std::ostream& os) const; + + //! \brief Initializes the element assembly process. + //! \details Must be called once before the element assembly loop. + //! \param[in] sam Auxilliary data describing the FE model topology, etc. + virtual void initAssembly(const SAM& sam); + + //! \brief Initializes the matrix to zero assuming it is properly dimensioned. + virtual void init(); + + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, int e); + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \details When multi-point constraints are present, contributions from + //! these are also added into the system right-hand-side load vector. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param B The system right-hand-side load vector + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, + SystemVector& B, int e); + + //! \brief Adds a nodal vector into columns of a non-symmetric sparse matrix. + //! \param[in] V The nodal vector + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] n Identifier for the node that \a V belongs to + //! \param[in] col Index of first column which should receive contributions + //! \return \e true on successful assembly, otherwise \e false + //! + //! \details This method can be used for rectangular matrices whose rows + //! correspond to the equation ordering og the provided \a sam object. + bool assembleCol(const RealArray& V, const SAM& sam, int n, size_t col); + + //! \brief Adds a scalar value into columns of a non-symmetric sparse matrix. + //! \param[in] val The value to add for each DOF of the specified node + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] n Identifier for the node that \a val belongs to + //! \param[in] col Index of first column which should receive contributions + //! \return \e true on successful assembly, otherwise \e false + //! + //! \details This method can be used for rectangular matrices whose rows + //! correspond to the equation ordering og the provided \a sam object. + bool assembleCol(real val, const SAM& sam, int n, size_t col) + { + return this->assembleCol(RealArray(1,val),sam,n,col); + } + + //! \brief Augments a similar matrix symmetrically to the current matrix. + //! \param[in] B The matrix to be augmented + //! \param[in] r0 Row offset for the augmented matrix + //! \param[in] c0 Column offset for the augmented matrix + virtual bool augment(const SystemMatrix& B, size_t r0, size_t c0); + + //! \brief Truncates all small matrix elements to zero. + //! \param[in] threshold Zero tolerance relative to largest diagonal element + virtual bool truncate(real threshold = real(1.0e-16)); + + //! \brief Adds a matrix with similar sparsity pattern to the current matrix. + //! \param[in] B The matrix to be added + //! \param[in] alpha Scale factor for matrix \b B + virtual bool add(const SystemMatrix& B, real alpha = real(1)); + + //! \brief Adds the diagonal matrix \f$\sigma\f$\b I to the current matrix. + virtual bool add(real sigma); + + //! \brief Performs the matrix-vector multiplication \b C = \a *this * \b B. + virtual bool multiply(const SystemVector& B, SystemVector& C); + + //! \brief Solves the linear system of equations for a given right-hand-side. + //! \param B Right-hand-side vector on input, solution vector on output + //! \param newLHS \e true if the left-hand-side matrix has been updated + virtual bool solve(SystemVector& B, bool newLHS = true); + +protected: + //! \brief Converts the matrix to an optimized row-oriented format. + //! \details The optimized format is suitable for the SAMG equation solver. + bool optimiseSAMG(bool transposed = false); + + //! \brief Converts the matrix to an optimized column-oriented format. + //! \details The optimized format is suitable for the SuperLU equation solver. + bool optimiseSLU(); + + //! \brief Invokes the SAMG equation solver for a given right-hand-side. + //! \param[in] isFirstRHS Should be \e true when the coefficient matrix is new + //! \param B Right-hand-side vector on input, solution vector on output + bool solveSAMG(bool isFirstRHS, Vector& B); + + //! \brief Invokes the SuperLU equation solver for a given right-hand-side. + //! \details This method uses the simple driver \a dgssv. + //! \param[in] isFirstRHS Should be \e true when the coefficient matrix is new + //! \param B Right-hand-side vector on input, solution vector on output + bool solveSLU(bool isFirstRHS, Vector& B); + + //! \brief Invokes the SuperLU equation solver for a given right-hand-side. + //! \details This method uses the expert driver \a dgssvx. + //! \param[in] isFirstRHS Should be \e true when the coefficient matrix is new + //! \param B Right-hand-side vector on input, solution vector on output + bool solveSLUx(bool isFirstRHS, Vector& B); + + //! \brief Writes the system matrix to the given output stream. + virtual std::ostream& write(std::ostream& os) const; + +public: + static bool printSLUstat; //!< Print solution statistics for SuperLU? + +private: + bool editable; //!< \e true during element assembly, \e false after optimized + size_t nrow; //!< Number of matrix rows + size_t ncol; //!< Number of matrix columns + + std::vector IA; //!< Identifies the beginning of each row or column + std::vector JA; //!< Specifies column/row index of each nonzero element + std::vector A; //!< Stores nonzero matrix elements + ValueMap elem; //!< Stores nonzero matrix elements with index pairs + SparseSolver solver; //!< Which equation solver to use + SuperLUdata* slu; //!< Matrix data for the SuperLU equation solver + int numThreads; //!< Number of threads to use for the SuperLU_MT solver +}; + +#endif diff --git a/src/LinAlg/SystemMatrix.C b/src/LinAlg/SystemMatrix.C new file mode 100644 index 00000000..07003116 --- /dev/null +++ b/src/LinAlg/SystemMatrix.C @@ -0,0 +1,64 @@ +// $Id: SystemMatrix.C,v 1.10 2011-02-04 17:02:38 kmo Exp $ +//============================================================================== +//! +//! \file SystemMatrix.C +//! +//! \date Jan 4 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief General representation of the system matrix on different forms. +//! +//============================================================================== + +#include "SystemMatrix.h" +#include "DenseMatrix.h" +#include "SPRMatrix.h" +#include "SparseMatrix.h" +#ifdef HAS_PETSC +#include "PETScMatrix.h" +#endif + + +SystemVector* SystemVector::create (Type vectorType) +{ + switch (vectorType) + { + case STD : return new StdVector(); +#ifdef HAS_PETSC + case PETSC : return new PETScVector(); +#endif + } + + std::cerr <<"SystemVector::create: Unsupported vector type " + << vectorType << std::endl; + return 0; +} + + +SystemMatrix* SystemMatrix::create (Type matrixType, const LinSolParams& spar) +{ +#ifdef HAS_PETSC + if (matrixType == PETSC) return new PETScMatrix(spar); +#endif + return SystemMatrix::create(matrixType); +} + + +SystemMatrix* SystemMatrix::create (Type matrixType, int num_thread_SLU) +{ + switch (matrixType) + { + case DENSE : return new DenseMatrix(); + case SPR : return new SPRMatrix(); + case SPARSE: return new SparseMatrix(SparseMatrix::SUPERLU,num_thread_SLU); + case SAMG : return new SparseMatrix(SparseMatrix::S_A_M_G); +#ifdef HAS_PETSC + case PETSC : return new PETScMatrix(LinSolParams()); +#endif + } + + std::cerr <<"SystemMatrix::create: Unsupported matrix type " + << matrixType << std::endl; + return 0; +} diff --git a/src/LinAlg/SystemMatrix.h b/src/LinAlg/SystemMatrix.h new file mode 100644 index 00000000..fa138e4a --- /dev/null +++ b/src/LinAlg/SystemMatrix.h @@ -0,0 +1,264 @@ +// $Id: SystemMatrix.h,v 1.18 2011-02-04 17:02:38 kmo Exp $ +//============================================================================== +//! +//! \file SystemMatrix.h +//! +//! \date Jan 4 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief General representation of system matrices and vectors. +//! +//============================================================================== + +#ifndef _SYSTEM_MATRIX_H +#define _SYSTEM_MATRIX_H + +#include "MatVec.h" + +class SAM; +class LinSolParams; + + +/*! + \brief Base class for representing a system vector on different formats. +*/ + +class SystemVector +{ +public: + //! \brief The available system vector formats. + enum Type { STD = 0, PETSC = 1 }; + + //! \brief Static method creating a vector of the given \a vectorType + static SystemVector* create(Type vectorType = STD); + +protected: + //! \brief Default constructor. + SystemVector() {} + +public: + //! \brief Empty destructor + virtual ~SystemVector() {} + + //! \brief Returns the vector type. + virtual Type getType() const = 0; + + //! \brief Creates a copy of the system vector and returns a pointer to it. + virtual SystemVector* copy() const = 0; + + //! \brief Returns the dimension of the system vector. + virtual size_t dim() const = 0; + + //! \brief Sets the dimension of the system vector. + virtual void redim(size_t n) = 0; + + //! \brief Resize the vector to length \a n. + virtual void resize(size_t n, bool = false) { this->redim(n); } + + //! \brief Checks if the vector is empty. + virtual bool empty() const { return this->dim() == 0; } + + //! \brief Returns the size of the system vector. + size_t size() const { return this->dim(); } + + //! \brief Access through pointer. + virtual real* getPtr() = 0; + //! \brief Reference through pointer. + virtual const real* getRef() const = 0; + + //! \brief Restores the vector contents from an array. + //! \details This method must be implemented only by subclasses for which the + //! getPtr and getRef methods do not return a pointer to the actual internal + //! memory segment containing the actual vector data. + virtual void restore(const real*) {} + + //! \brief Initializes the vector assuming it is properly dimensioned. + virtual void init(real value = real(0)) = 0; + + //! \brief Multiplication with a scalar. + virtual void mult(real alpha) = 0; + + //! \brief L1-norm of the vector. + virtual real L1norm() const = 0; + + //! \brief L2-norm of the vector. + virtual real L2norm() const = 0; + + //! \brief Linfinity-norm of the vector. + virtual real Linfnorm() const = 0; + +protected: + //! \brief Writes the system vector to the given output stream. + virtual std::ostream& write(std::ostream& os) const { return os; } + + //! \brief Global stream operator printing the vector contents. + friend std::ostream& operator<<(std::ostream& os, const SystemVector& X) + { + return X.write(os); + } +}; + + +/*! + \brief Standard system vector stored as a single continuous array. +*/ + +class StdVector : public SystemVector, public utl::vector +{ +public: + //! \brief Constructor creating an empty vector. + StdVector() {} + //! \brief Constructor creating a vector of length \a n. + StdVector(size_t n) : utl::vector(n) {} + //! \brief Constructor creating a vector from an array. + StdVector(const real* values, size_t n) : utl::vector(values,n) {} + //! \brief Overloaded copy constructor. + StdVector(const std::vector& vec) + { this->insert(this->end(),vec.begin(),vec.end()); } + + //! \brief Returns the vector type. + virtual Type getType() const { return STD; } + + //! \brief Creates a copy of the system vector and returns a pointer to it. + virtual SystemVector* copy() const { return new StdVector(*this); } + + //! \brief Checks if the vector is empty. + virtual bool empty() const { return this->std::vector::empty(); } + + //! \brief Returns the dimension of the system vector. + virtual size_t dim() const { return this->std::vector::size(); } + + //! \brief Sets the dimension of the system vector. + virtual void redim(size_t n) { this->std::vector::resize(n,real(0)); } + + //! \brief Resize the vector to length \a n. + //! \details Will erase the previous content, but only if the size changed, + //! unless \a forceClear is \e true. + virtual void resize(size_t n, bool forceClear = false) + { this->utl::vector::resize(n,forceClear); } + + //! \brief Access through pointer. + virtual real* getPtr() { return this->ptr(); } + //! \brief Reference through pointer. + virtual const real* getRef() const { return this->ptr(); } + + //! \brief Initializes the vector to a given scalar value. + virtual void init(real value = real(0)) { this->fill(value); } + + //! \brief Multiplication with a scalar. + virtual void mult(real alpha) { this->operator*=(alpha); } + + //! \brief L1-norm of the vector. + virtual real L1norm() const { return this->asum(); } + + //! \brief L2-norm of the vector. + virtual real L2norm() const { return this->norm2(); } + + //! \brief Linfinity-norm of the vector. + virtual real Linfnorm() const { size_t off = 0; return this->normInf(off); } + +protected: + //! \brief Writes the system vector to the given output stream. + virtual std::ostream& write(std::ostream& os) const + { return os << static_cast&>(*this); } +}; + + +/*! + \brief Base class for representing a system matrix on different formats. + \details The purpose of this class is to define a clean interface for the + system matrix and underlying equation solvers, such that the finite element + implementation can be performed without paying attention to the actual solver + being used. +*/ + +class SystemMatrix +{ +public: + //! \brief The available system matrix formats. + enum Type { DENSE = 0, SPR = 1, SPARSE = 2, SAMG = 3, PETSC = 4 }; + + //! \brief Static method creating a matrix of the given \a matrixType. + static SystemMatrix* create(Type matrixType, int num_thread_SLU = 1); + //! \brief Static method creating a matrix of the given \a matrixType. + static SystemMatrix* create(Type matrixType, const LinSolParams& spar); + +protected: + //! \brief Default constructor. + SystemMatrix() {} + +public: + //! \brief Empty destructor. + virtual ~SystemMatrix() {} + + //! \brief Returns the matrix type. + virtual Type getType() const = 0; + + //! \brief Creates a copy of the system matrix and returns a pointer to it. + virtual SystemMatrix* copy() const = 0; + + //! \brief Checks if the matrix is empty. + virtual bool empty() const { return this->dim(0) == 0; } + + //! \brief Returns the dimension of the system matrix. + virtual size_t dim(int idim = 1) const = 0; + + //! \brief Initializes the element assembly process. + //! \details Must be called once before the element assembly loop. + //! \param[in] sam Auxilliary data describing the FE model topology, etc. + virtual void initAssembly(const SAM& sam) = 0; + + //! \brief Initializes the matrix to zero assuming it is properly dimensioned. + virtual void init() = 0; + + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, int e) = 0; + //! \brief Adds an element stiffness matrix into the system stiffness matrix. + //! \details When multi-point constraints are present, contributions from + //! these are also added into the system right-hand-side load vector. + //! \param[in] eM The element stiffness matrix + //! \param[in] sam Auxilliary data describing the FE model topology, + //! nodal DOF status and constraint equations + //! \param B The system right-hand-side load vector + //! \param[in] e Identifier for the element that \a eM belongs to + //! \return \e true on successful assembly, otherwise \e false + virtual bool assemble(const Matrix& eM, const SAM& sam, + SystemVector& B, int e) = 0; + + //! \brief Augments a similar matrix symmetrically to the current matrix. + virtual bool augment(const SystemMatrix&, size_t, size_t) { return false; } + + //! \brief Truncates all small matrix elements to zero. + virtual bool truncate(real = real(1.0e-16)) { return false; } + + //! \brief Adds a matrix with similar structure to the current matrix. + virtual bool add(const SystemMatrix&, real = real(1)) { return false; } + + //! \brief Adds a constant diagonal matrix to the current matrix. + virtual bool add(real) { return false; } + + //! \brief Performs a matrix-vector multiplication. + virtual bool multiply(const SystemVector&, SystemVector&) { return false; } + + //! \brief Solves the linear system of equations for a given right-hand-side. + //! \param newLHS \e true if the left-hand-side matrix has been updated + virtual bool solve(SystemVector&, bool newLHS = true) { return false; } + +protected: + //! \brief Writes the system matrix to the given output stream. + virtual std::ostream& write(std::ostream& os) const { return os; } + + //! \brief Global stream operator printing the matrix contents. + friend std::ostream& operator<<(std::ostream& os, const SystemMatrix& A) + { + return A.write(os); + } +}; + +#endif diff --git a/src/LinAlg/matrix.h b/src/LinAlg/matrix.h new file mode 100644 index 00000000..e1f2a0ea --- /dev/null +++ b/src/LinAlg/matrix.h @@ -0,0 +1,1395 @@ +// $Id: matrix.h,v 1.32 2011-02-05 17:39:56 kmo Exp $ +//============================================================================== +//! +//! \file matrix.h +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Simple template classes for dense rectangular matrices and vectors. +//! \details The classes have some algebraic operators defined, such that the +//! class type \a T has to be of a numerical type, i.e., \a float or \a double. +//! The multiplication methods are implemented based on the CBLAS library if +//! the macro symbol USE_CBLAS is defined. Otherwise, inlined methods are used. +//! +//============================================================================== + +#ifndef UTL_MATRIX_H +#define UTL_MATRIX_H + +#include +#include +#include +#include +#include +#ifdef USE_MKL +#include +#elif defined(USE_CBLAS) +#include +#endif + +#ifdef INDEX_CHECK +#if INDEX_CHECK > 1 +#define ABORT_ON_INDEX_CHECK abort() +#else +#define ABORT_ON_INDEX_CHECK +#endif +#define CHECK_INDEX(label,i,n) if (i < 1 || i > n) { \ + std::cerr << label << i <<" is out of range [1,"<< n <<"]"<< std::endl; \ + ABORT_ON_INDEX_CHECK; } +#else +#define CHECK_INDEX(label,i,n) +#define ABORT_ON_INDEX_CHECK +#endif + +#ifdef SING_CHECK +#define ABORT_ON_SINGULARITY abort() +#else +#define ABORT_ON_SINGULARITY +#endif + + +namespace utl //! General utility classes and functions. +{ + /*! + \brief Sub-class of std::vector with some added algebraic operations. + \details The class type \a T has to be of a numerical type, i.e., + \a float or \a double. + */ + + template class vector : public std::vector + { + public: + //! \brief Constructor creating an empty vector. + vector() {} + //! \brief Constructor creating a vector of length \a n. + vector(size_t n) { this->resize(n); } + //! \brief Constructor creating a vector from an array. + vector(const T* values, size_t n) { this->fill(values,n); } + + //! \brief Overloaded assignment operator. + vector& operator=(const std::vector& X) + { + this->std::vector::operator=(X); + return *this; + } + + //! \brief Access through pointer. + T* ptr() { return &this->front(); } + //! \brief Reference through pointer. + const T* ptr() const { return &this->front(); } + + //! \brief Index-1 based element access. + T& operator()(size_t i) + { + CHECK_INDEX("vector::operator(): Index ",i,this->size()); + return this->operator[](i-1); + } + + //! \brief Index-1 based element reference. + const T& operator()(size_t i) const + { + CHECK_INDEX("vector::operator(): Index ",i,this->size()); + return this->operator[](i-1); + } + + //! \brief Fill the vector with a scalar value. + void fill(const T& s) { std::fill(this->begin(),this->end(),s); } + //! \brief Fill the vector with data from an array. + void fill(const T* values, size_t n = 0) + { + if (n > this->size()) this->std::vector::resize(n); + memcpy(&this->front(),values,this->size()*sizeof(T)); + } + + //! \brief Multiplication with a scalar. + vector& operator*=(const T& c); + //! \brief Division by a scalar. + vector& operator/=(const T& d) { return this->operator*=(T(1)/d); } + + //! \brief Add the given vector \b X to \a *this. + vector& operator+=(const vector& X) { return this->add(X); } + //! \brief Subtract the given vector \b X from \a *this. + vector& operator-=(const vector& X) { return this->add(X,T(-1)); } + //! \brief Add the given vector \b X scaled by \a alfa to \a *this. + vector& add(const std::vector& X, T alfa = T(1)); + + //! \brief Dot product between \a *this and another vector. + //! \param[in] v The vector to dot this vector with + //! \param[in] off1 Offset for this vector + //! \param[in] inc1 Increment for this vector + //! \param[in] off2 Offset for vector \b v + //! \param[in] inc2 Increment for vector \b v + T dot(const std::vector& v, + size_t off1 = 0, int inc1 = 1, + size_t off2 = 0, int inc2 = 1) const; + + //! \brief Return the Euclidean norm of the vector. + //! \param[in] off Index offset relative to the first vector component + //! \param[in] inc Increment in the vector component indices + T norm2(size_t off = 0, int inc = 1) const; + //! \brief Return the infinite norm of the vector. + //! \param off Index offset relative to the first vector component on input, + //! 1-based index of the largest vector component on output + //! \param[in] inc Increment in the vector component indices + T normInf(size_t& off, int inc = 1) const; + + //! \brief Return the sum of the absolute value of the vector elements. + //! \param[in] off Index offset relative to the first vector component + //! \param[in] inc Increment in the vector component indices + T asum(size_t off = 0, int inc = 1) const; + + //! \brief Return the sum of the vector elements. + //! \param[in] off Index offset relative to the first vector component + //! \param[in] inc Increment in the vector component indices + T sum(size_t off = 0, int inc = 1) const + { + T xsum = T(0); + if (inc < 1) return xsum; + + const T* v = this->ptr(); + for (size_t i = off; i < this->size(); i += inc) + xsum += v[i]; + return xsum; + } + + //! \brief Resize the vector to length \a n. + //! \details Will erase the previous content, but only if the size changed, + //! unless \a forceClear is \e true. + void resize(size_t n, bool forceClear = false) + { + if (forceClear) + { + // Erase previous content + if (n == this->size()) + this->fill(T(0)); + else + this->clear(); + } + + if (n == this->size()) return; // nothing to do + + if (!forceClear) this->clear(); + + this->std::vector::resize(n,T(0)); + } + }; + + + /*! + \brief Two-dimensional rectangular matrix with some algebraic operations. + \details This is a 2D equivalent to the \a vector class. The matrix elements + are stored column-wise in a one-dimensional array, such that its pointer + might be passed to Fortran subroutines requiring 2D arrays as arguments. + */ + + template class matrix + { + public: + //! \brief Constructor creating an empty matrix. + matrix() : nrow(0), ncol(0) {} + //! \brief Constructor creating a matrix of dimension \f$r \times c\f$. + matrix(size_t r, size_t c) : nrow(r), ncol(c), elem(r*c) {} + + //! \brief Resize the matrix to dimension \f$r \times c\f$. + //! \details Will erase the previous content, but only if both + //! the total matrix size and the number of rows in the matrix are changed. + //! It is therefore possible to add or remove a given number of columns to + //! the matrix without loosing the contents of the remaining columns. + //! If \a forceClear is \e true, the old matrix content is always erased. + void resize(size_t r, size_t c, bool forceClear = false) + { + if (forceClear) + { + // Erase previous content + if (nrow*ncol == r*c) + this->fill(T(0)); + else + { + nrow = ncol = 0; + elem.clear(); + } + } + + if (r == nrow && c == ncol) return; // nothing to do + + size_t oldNrow = nrow; + size_t oldSize = this->size(); + nrow = r; + ncol = c; + if (this->size() == oldSize) return; // no more to do, size is unchanged + + // If the number of rows is changed the previous content must be cleared + if (!forceClear && r != oldNrow) elem.clear(); + + elem.std::vector::resize(r*c,T(0)); + } + + //! \brief Query number of matrix rows. + size_t rows() const { return nrow; } + //! \brief Query number of matrix columns. + size_t cols() const { return ncol; } + //! \brief Query total matrix size. + size_t size() const { return nrow*ncol; } + //! \brief Check if the matrix is empty. + bool empty() const { return elem.empty(); } + + //! \brief Access through pointer. + T* ptr(size_t c = 0) { return &elem[nrow*c]; } + //! \brief Reference through pointer. + const T* ptr(size_t c = 0) const { return &elem[nrow*c]; } + //! \brief Type casting to a one-dimensional vector. + operator const vector&() const { return elem; } + + //! \brief Index-1 based element access. + //! \details Assuming column-wise storage as in Fortran. + T& operator()(size_t r, size_t c) + { + CHECK_INDEX("matrix::operator(): Row-index ",r,nrow); + CHECK_INDEX("matrix::operator(): Column-index ",c,ncol); + return elem[r-1+nrow*(c-1)]; + } + + //! \brief Index-1 based element reference. + //! \details Assuming column-wise storage as in Fortran. + const T& operator()(size_t r, size_t c) const + { + CHECK_INDEX("matrix::operator(): Row-index ",r,nrow); + CHECK_INDEX("matrix::operator(): Column-index ",c,ncol); + return elem[r-1+nrow*(c-1)]; + } + + //! \brief Extract a row from the matrix. + vector getRow(size_t r) const + { + CHECK_INDEX("matrix::getRow: Row-index ",r,nrow); + vector row(ncol); + for (size_t i = 0; i < ncol; i++) + row[i] = elem[r-1+nrow*i]; + return row; + } + + //! \brief Extract a column from the matrix. + vector getColumn(size_t c) const + { + CHECK_INDEX("matrix::getColumn: Column-index ",c,ncol); + vector col(nrow); + memcpy(col.ptr(),this->ptr(c-1),nrow*sizeof(T)); + return col; + } + + //! \brief Fill a column of the matrix. + void fillColumn(size_t c, const std::vector& data) + { + CHECK_INDEX("matrix::getColumn: Column-index ",c,ncol); + size_t ndata = nrow > data.size() ? data.size() : nrow; + memcpy(this->ptr(c-1),&data.front(),ndata*sizeof(T)); + } + + //! \brief Fill a column of the matrix. + void fillColumn(size_t c, const T* data) + { + CHECK_INDEX("matrix::fillColumn: Column-index ",c,ncol); + memcpy(this->ptr(c-1),data,nrow*sizeof(T)); + } + + //! \brief Fill the matrix with a scalar value. + void fill(const T& s) { std::fill(elem.begin(),elem.end(),s); } + //! \brief Fill the matrix with data from an array. + void fill(const T* values, size_t n = 0) { elem.fill(values,n); } + + //! \brief Create a diagonal matrix. + matrix& diag(const T& d, size_t dim = 0) + { + if (dim > 0) + this->resize(dim,dim,true); + else + this->resize(nrow,ncol,true); + for (size_t r = 0; r < nrow && r < ncol; r++) + elem[r+nrow*r] = d; + return *this; + } + + //! \brief Replace the current matrix by its transpose. + matrix& transpose() + { + matrix tmp(*this); + for (size_t r = 0; r < nrow; r++) + for (size_t c = 0; c < ncol; c++) + elem[c+ncol*r] = tmp.elem[r+nrow*c]; + + nrow = tmp.ncol; + ncol = tmp.nrow; + return *this; + } + +#define THIS(i,j) this->operator()(i,j) + + //! \brief Compute the determinant of a square matrix. + T det() const + { + if (ncol == 1 && nrow >= 1) + return THIS(1,1); + else if (ncol == 2 && nrow >= 2) + return THIS(1,1)*THIS(2,2) - THIS(2,1)*THIS(1,2); + else if (ncol == 3 && nrow >= 3) + return THIS(1,1)*(THIS(2,2)*THIS(3,3) - THIS(3,2)*THIS(2,3)) + - THIS(1,2)*(THIS(2,1)*THIS(3,3) - THIS(3,1)*THIS(2,3)) + + THIS(1,3)*(THIS(2,1)*THIS(3,2) - THIS(3,1)*THIS(2,2)); + else if (ncol > 0 && nrow > 0) { + std::cerr <<"matrix::det: Not available for " + << nrow <<"x"<< ncol <<" matrices"<< std::endl; + ABORT_ON_SINGULARITY; + return T(-999); + } + else + return T(0); + } + + //! \brief Compute the inverse of a square matrix. + //! \param[in] tol Division by zero tolerance + //! \return Determinant of the matrix + T inverse(const T& tol = T(0)) + { + T det = this->det(); + if (det == T(-999)) + return det; + else if (det <= tol && det >= -tol) { + std::cerr <<"matrix::inverse: Singular matrix |A|="<< det << std::endl; + ABORT_ON_SINGULARITY; + return T(0); + } + + if (ncol == 1) + THIS(1,1) = T(1) / det; + else if (ncol == 2) { + matrix B(2,2); + B(1,1) = THIS(2,2) / det; + B(2,1) = -THIS(2,1) / det; + B(1,2) = -THIS(1,2) / det; + B(2,2) = THIS(1,1) / det; + *this = B; + } + else if (ncol == 3) { + matrix B(3,3); + B(1,1) = (THIS(2,2)*THIS(3,3) - THIS(3,2)*THIS(2,3)) / det; + B(2,1) = -(THIS(2,1)*THIS(3,3) - THIS(3,1)*THIS(2,3)) / det; + B(3,1) = (THIS(2,1)*THIS(3,2) - THIS(3,1)*THIS(2,2)) / det; + B(1,2) = -(THIS(1,2)*THIS(3,3) - THIS(3,2)*THIS(1,3)) / det; + B(2,2) = (THIS(1,1)*THIS(3,3) - THIS(3,1)*THIS(1,3)) / det; + B(3,2) = -(THIS(1,1)*THIS(3,2) - THIS(3,1)*THIS(1,2)) / det; + B(1,3) = (THIS(1,2)*THIS(2,3) - THIS(2,2)*THIS(1,3)) / det; + B(2,3) = -(THIS(1,1)*THIS(2,3) - THIS(2,1)*THIS(1,3)) / det; + B(3,3) = (THIS(1,1)*THIS(2,2) - THIS(2,1)*THIS(1,2)) / det; + *this = B; + } + + return det; + } + + //! \brief Check for symmetry. + //! \param[in] tol Comparison tolerance + bool isSymmetric(const T& tol = T(0)) const + { + if (nrow != ncol) return false; + + for (size_t r = 0; r < nrow; r++) + for (size_t c = 0; c < r; c++) + { + T diff = elem[r+nrow*c] - elem[c+nrow*r]; + if (diff < -tol || diff > tol) return false; + } + + return true; + } + + //! \brief Add the given matrix \b A to \a *this. + matrix& operator+=(const matrix& A) { return this->add(A); } + //! \brief Subtract the given matrix \b A from \a *this. + matrix& operator-=(const matrix& A) { return this->add(A,T(-1)); } + //! \brief Add the given matrix \b A scaled by \a alfa to \a *this. + matrix& add(const matrix& A, T alfa = T(1)); + + //! \brief Multiplication with a scalar. + matrix& operator*=(const T& c) { return this->multiply(c); } + //! \brief Division by a scalar. + matrix& operator/=(const T& d) { return this->multiply(T(1)/d); } + //! \brief Multiplication of this matrix by a scalar \a c. + matrix& multiply(const T& c); + + /*! \brief Matrix-matrix multiplication. + \details Performs one of the following operations (\b C = \a *this): + -# \f$ {\bf C} = {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf A}^T {\bf B} \f$ + -# \f$ {\bf C} = {\bf A} {\bf B}^T \f$ + -# \f$ {\bf C} = {\bf A}^T {\bf B}^T \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A}^T {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A} {\bf B}^T \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A}^T {\bf B}^T \f$ + */ + matrix& multiply(const matrix& A, const matrix& B, + bool transA = false, bool transB = false, + bool addTo = false); + + /*! \brief Matrix-matrix multiplication. + \details Performs one of the following operations (\b C = \a *this): + -# \f$ {\bf C} = {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf A}^T {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A}^T {\bf B} \f$ + + The matrix \b B is here represented by a one-dimensional vector, + and its number of rows is assumed to match the number of columns in + \b A (or its transpose) and its number of columns is then the total + vector length divided by the number of rows. + */ + bool multiplyMat(const matrix& A, const std::vector& B, + bool transA = false, bool addTo = false); + + /*! \brief Matrix-matrix multiplication. + \details Performs one of the following operations (\b C = \a *this): + -# \f$ {\bf C} = {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf A} {\bf B}^T \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A} {\bf B}^T \f$ + + The matrix \b A is here represented by a one-dimensional vector, + and its number of columns is assumed to match the number of rows in + \b B (or its transpose) and its number of rows is then the total + vector length divided by the number of columns. + */ + bool multiplyMat(const std::vector& A, const matrix& B, + bool transB = false, bool addTo = false); + + /*! \brief Matrix-vector multiplication. + \details Performs one of the following operations (\b A = \a *this): + -# \f$ {\bf Y} = {\bf A} {\bf X} \f$ + -# \f$ {\bf Y} = {\bf A}^T {\bf X} \f$ + -# \f$ {\bf Y} = {\bf Y} + {\bf A} {\bf X} \f$ + -# \f$ {\bf Y} = {\bf Y} + {\bf A}^T {\bf X} \f$ + */ + bool multiply(const std::vector& X, std::vector& Y, + bool transA = false, bool addTo = false) const; + + //! \brief Outer product between two vectors. + bool outer_product(const std::vector& X, const std::vector& Y, + bool addTo = false) + { + if (!addTo) + this->resize(X.size(),Y.size()); + else if (X.size() != nrow || Y.size() != ncol) + { + std::cerr <<"matrix::outer_product: Incompatible matrix and vectors: A(" + << nrow <<','<< ncol <<"), X(" + << X.size() <<"), Y("<< Y.size() <<")\n" + <<" when computing A += X*Y^t" + << std::endl; + ABORT_ON_INDEX_CHECK; + return false; + } + + if (addTo) + for (size_t j = 0; j < ncol; j++) + for (size_t i = 0; i < nrow; i++) + elem[i+nrow*j] += X[i]*Y[j]; + else + for (size_t j = 0; j < ncol; j++) + for (size_t i = 0; i < nrow; i++) + elem[i+nrow*j] = X[i]*Y[j]; + + return true; + } + + private: + //! \brief Check dimension compatibility for matrix-vector multiplication. + bool compatible(const std::vector& X, const std::vector& Y, + bool transA) const + { + if ((transA ? nrow : ncol) == X.size()) return true; + + std::cerr <<"matrix::multiply: Incompatible matrices: A(" + << nrow <<','<< ncol <<"), X("<< X.size() <<")\n" + <<" when computing Y = " + << (transA ? "A^t":"A") <<" * X"<< std::endl; + ABORT_ON_INDEX_CHECK; + return false; + } + + //! \brief Check dimension compatibility for matrix-matrix multiplication. + bool compatible(const matrix& A, const matrix& B, + bool transA, bool transB, size_t& M, size_t& N, size_t& K) + { + M = transA ? A.ncol : A.nrow; + N = transB ? B.nrow : B.ncol; + K = transA ? A.nrow : A.ncol; + if (K == (transB ? B.ncol : B.nrow)) return true; + + std::cerr <<"matrix::multiply: Incompatible matrices: A(" + << A.nrow <<','<< A.ncol <<"), B(" + << B.nrow <<','<< B.ncol <<")\n" + <<" when computing C = " + << (transA ? "A^t":"A") <<" * " + << (transB ? "B^t":"B") << std::endl; + ABORT_ON_INDEX_CHECK; + return false; + } + + //! \brief Check dimension compatibility for matrix-matrix multiplication, + //! when the matrix B is represented by a one-dimensional vector. + bool compatible(const matrix& A, const std::vector& B, + bool transA, size_t& M, size_t& N, size_t& K) + { + M = transA ? A.ncol : A.nrow; + K = transA ? A.nrow : A.ncol; + N = K > 0 ? B.size()/K : 0; + if (N*K == B.size() && !B.empty()) return true; + + std::cerr <<"matrix::multiply: Incompatible matrices: A(" + << A.nrow <<','<< A.ncol <<"), B(r*c="<< B.size() <<")\n" + <<" when computing C = " + << (transA ? "A^t":"A") <<" * B"<< std::endl; + ABORT_ON_INDEX_CHECK; + return false; + } + + //! \brief Check dimension compatibility for matrix-matrix multiplication, + //! when the matrix A is represented by a one-dimensional vector. + bool compatible(const std::vector& A, const matrix& B, + bool transB, size_t& M, size_t& N, size_t& K) + { + N = transB ? B.nrow : B.ncol; + K = transB ? B.ncol : B.nrow; + M = K > 0 ? A.size() / K : 0; + if (M*K == A.size() && !A.empty()) return true; + + std::cerr <<"matrix::multiply: Incompatible matrices: A(r*c="<< A.size() + <<"), B("<< B.nrow <<","<< B.ncol <<")\n" + <<" when computing C = A * " + << (transB ? "B^t":"B") << std::endl; + ABORT_ON_INDEX_CHECK; + return false; + } + + private: + size_t nrow; //!< Number of matrix rows + size_t ncol; //!< Number of matrix columns + vector elem; //!< Actual matrix elements, stored column by column + }; + + + /*! + \brief Three-dimensional rectangular matrix with some algebraic operations. + \details This is a 3D equivalent to the \a matrix class. The matrix elements + are stored column-wise in a one-dimensional array, such that its pointer + might be passed to Fortran subroutines requiring 3D arrays as arguments. + */ + + template class matrix3d + { + public: + //! \brief Constructor creating an empty matrix. + matrix3d() { n[0] = n[1] = n[2] = 0; } + //! \brief Constructor creating a matrix of dimension + // \f$n_1 \times n_2 \times n_3\f$. + matrix3d(size_t n_1, size_t n_2, size_t n_3) + { + n[0] = n_1; n[1] = n_2; n[2] = n_3; elem.resize(n_1*n_2*n_3); + } + + //! \brief Resize the matrix to dimension \f$n_1 \times n_2 \times n_3\f$. + //! \details Will erase the previous content, but only if both + //! the total matrix size, and n_1 or n_2 in the matrix are changed. + //! It is therefore possible to add or remove a given number of elements in + //! the third dimension of the matrix without loosing the contents of the + //! first and second dimensions. + //! If \a forceClear is \e true, the old matrix content is always erased. + void resize(size_t n_1, size_t n_2, size_t n_3, bool forceClear = false) + { + if (forceClear) + { + // Erase previous content + if (n[0]*n[1]*n[2] == n_1*n_2*n_3) + this->fill(T(0)); + else + { + n[0] = n[1] = n[2] = 0; + elem.clear(); + } + } + + if (n[0] == n_1 && n[1] == n_2 && n[2] == n_3) return; // nothing to do + + size_t oldn1 = n[0]; + size_t oldn2 = n[1]; + size_t oldSize = this->size(); + n[0] = n_1; + n[1] = n_2; + n[2] = n_3; + if (this->size() == oldSize) return; // no more to do, size is unchanged + + // If the size in any of the first two dimensions are changed + // the previous content must be cleared + if (!forceClear && (n[0] != oldn1 || n[1] != oldn2)) elem.clear(); + + elem.std::vector::resize(n[0]*n[1]*n[2],T(0)); + } + + //! \brief Query dimensions. + size_t dim(short int d = 1) const { return d > 0 && d <= 3 ? n[d-1] : 0; } + //! \brief Query total matrix size. + size_t size() const { return n[0]*n[1]*n[2]; } + //! \brief Check if the matrix is empty. + bool empty() const { return elem.empty(); } + + //! \brief Type casting to a one-dimensional vector. + operator const vector&() const { return elem; } + + //! \brief Index-1 based element access. + //! \details Assuming column-wise storage as in Fortran. + T& operator()(size_t i1, size_t i2, size_t i3) + { + CHECK_INDEX("matrix3d::operator(): First index " ,i1,n[0]); + CHECK_INDEX("matrix3d::operator(): Second index ",i2,n[1]); + CHECK_INDEX("matrix3d::operator(): Third index " ,i3,n[2]); + return elem[i1-1 + n[0]*((i2-1) + n[1]*(i3-1))]; + } + + //! \brief Index-1 based element access. + //! \details Assuming column-wise storage as in Fortran. + const T& operator()(size_t i1, size_t i2, size_t i3) const + { + CHECK_INDEX("matrix3d::operator(): First index " ,i1,n[0]); + CHECK_INDEX("matrix3d::operator(): Second index ",i2,n[1]); + CHECK_INDEX("matrix3d::operator(): Third index " ,i3,n[2]); + return elem[i1-1 + n[0]*((i2-1) + n[1]*(i3-1))]; + } + + //! \brief Access through pointer. + T* ptr() { return &elem.front(); } + //! \brief Reference through pointer. + const T* ptr() const { return &elem.front(); } + + //! \brief Fill the matrix with a scalar value. + void fill(const T& s) { std::fill(elem.begin(),elem.end(),s); } + + /*! \brief Matrix-matrix multiplication. + \details Performs one of the following operations (\b C = \a *this): + -# \f$ {\bf C} = {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf A}^T {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A} {\bf B} \f$ + -# \f$ {\bf C} = {\bf C} + {\bf A}^T {\bf B} \f$ + */ + bool multiply(const matrix& A, const matrix3d& B, + bool transA = false, bool addTo = false); + + private: + //! \brief Check dimension compatibility for matrix-matrix multiplication. + bool compatible(const matrix& A, const matrix3d& B, + bool transA, size_t& M, size_t& N, size_t& K) + { + M = transA ? A.cols() : A.rows(); + N = B.n[1]*B.n[2]; + K = transA ? A.rows() : A.cols(); + if (K == B.n[0]) return true; + + std::cerr <<"matrix3d::multiply: Incompatible matrices: A(" + << A.rows() <<','<< A.cols() <<"), B(" + << B.n[0] <<','<< B.n[1] <<','<< B.n[2] <<")\n" + <<" when computing C = " + << (transA ? "A^t":"A") <<" * B"<< std::endl; + ABORT_ON_INDEX_CHECK; + return false; + } + + private: + size_t n[3]; //!< Dimension of the 3D matrix + vector elem; //!< Actual matrix elements, stored column by column + }; + + +#ifdef USE_CBLAS + //============================================================================ + //=== BLAS-implementation of the matrix/vector multiplication methods ==== + //============================================================================ + + template<> inline + vector& vector::operator*=(const float& c) + { + cblas_sscal(this->size(),c,this->ptr(),1); + return *this; + } + + template<> inline + vector& vector::operator*=(const double& c) + { + cblas_dscal(this->size(),c,this->ptr(),1); + return *this; + } + + template<> inline + float vector::dot(const std::vector& v, + size_t o1, int i1, size_t o2, int i2) const + { + int n1 = i1 > 1 || i1 < -1 ? this->size()/abs(i1) : this->size()-o1; + int n2 = i2 > 1 || i2 < -1 ? v.size()/abs(i2) : v.size()-o2; + int n = n1 < n2 ? n1 : n2; + return cblas_sdot(n,this->ptr()+o1,i1,&v.front()+o2,i2); + } + + template<> inline + double vector::dot(const std::vector& v, + size_t o1, int i1, size_t o2, int i2) const + { + int n1 = i1 > 1 || i1 < -1 ? this->size()/abs(i1) : this->size()-o1; + int n2 = i2 > 1 || i2 < -1 ? v.size()/abs(i2) : v.size()-o2; + int n = n1 < n2 ? n1 : n2; + return cblas_ddot(n,this->ptr()+o1,i1,&v.front()+o2,i2); + } + + template<> inline + float vector::norm2(size_t off, int inc) const + { + int n = inc > 1 || inc < -1 ? this->size()/abs(inc) : this->size()-off; + return cblas_snrm2(n,this->ptr()+off,inc); + } + + template<> inline + double vector::norm2(size_t off, int inc) const + { + int n = inc > 1 || inc < -1 ? this->size()/abs(inc) : this->size()-off; + return cblas_dnrm2(n,this->ptr()+off,inc); + } + + template<> inline + float vector::normInf(size_t& off, int inc) const + { + if (inc < 1) return 0.0f; + + const float* v = this->ptr() + off; + off = 1 + cblas_isamax(this->size()/inc,v,inc); + return fabsf(v[(off-1)*inc]); + } + + template<> inline + double vector::normInf(size_t& off, int inc) const + { + if (inc < 1) return 0.0; + + const double* v = this->ptr() + off; + off = 1 + cblas_idamax(this->size()/inc,v,inc); + return fabs(v[(off-1)*inc]); + } + + template<> inline + float vector::asum(size_t off, int inc) const + { + int n = inc > 1 || inc < -1 ? this->size()/abs(inc) : this->size()-off; + return cblas_sasum(n,this->ptr()+off,inc); + } + + template<> inline + double vector::asum(size_t off, int inc) const + { + int n = inc > 1 || inc < -1 ? this->size()/abs(inc) : this->size()-off; + return cblas_dasum(n,this->ptr()+off,inc); + } + + template<> inline + vector& vector::add(const std::vector& X, float alfa) + { + size_t n = this->size() < X.size() ? this->size() : X.size(); + cblas_saxpy(n,alfa,&X.front(),1,this->ptr(),1); + return *this; + } + + template<> inline + vector& vector::add(const std::vector& X, double alfa) + { + size_t n = this->size() < X.size() ? this->size() : X.size(); + cblas_daxpy(n,alfa,&X.front(),1,this->ptr(),1); + return *this; + } + + template<> inline + matrix& matrix::add(const matrix& X, float alfa) + { + size_t n = this->size() < X.size() ? this->size() : X.size(); + cblas_saxpy(n,alfa,X.ptr(),1,this->ptr(),1); + return *this; + } + + template<> inline + matrix& matrix::add(const matrix& X, double alfa) + { + size_t n = this->size() < X.size() ? this->size() : X.size(); + cblas_daxpy(n,alfa,X.ptr(),1,this->ptr(),1); + return *this; + } + + template<> inline + matrix& matrix::multiply(const float& c) + { + cblas_sscal(this->size(),c,this->ptr(),1); + return *this; + } + + template<> inline + matrix& matrix::multiply(const double& c) + { + cblas_dscal(this->size(),c,this->ptr(),1); + return *this; + } + + template<> inline + bool matrix::multiply(const std::vector& X, + std::vector& Y, + bool transA, bool addTo) const + { + if (!this->compatible(X,Y,transA)) return false; + if (!addTo) Y.resize(transA ? ncol : nrow); + + cblas_sgemv(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, + nrow, ncol, 1.0f, + this->ptr(), nrow, + &X.front(), 1, addTo ? 1.0f : 0.0f, + &Y.front(), 1); + + return true; + } + + template<> inline + bool matrix::multiply(const std::vector& X, + std::vector& Y, + bool transA, bool addTo) const + { + if (!this->compatible(X,Y,transA)) return false; + if (!addTo) Y.resize(transA ? ncol : nrow); + + cblas_dgemv(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, + nrow, ncol, 1.0, + this->ptr(), nrow, + &X.front(), 1, addTo ? 1.0 : 0.0, + &Y.front(), 1); + + return true; + } + + template<> inline + matrix& matrix::multiply(const matrix& A, + const matrix& B, + bool transA, bool transB, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,transB,M,N,K)) return *this; + if (!addTo) this->resize(M,N); + + cblas_sgemm(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, + transB ? CblasTrans : CblasNoTrans, + M, N, K, 1.0f, + A.ptr(), A.nrow, + B.ptr(), B.nrow, + addTo ? 1.0f : 0.0f, + this->ptr(), nrow); + + return *this; + } + + template<> inline + matrix& matrix::multiply(const matrix& A, + const matrix& B, + bool transA, bool transB, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,transB,M,N,K)) return *this; + if (!addTo) this->resize(M,N); + + cblas_dgemm(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, + transB ? CblasTrans : CblasNoTrans, + M, N, K, 1.0, + A.ptr(), A.nrow, + B.ptr(), B.nrow, + addTo ? 1.0 : 0.0, + this->ptr(), nrow); + + return *this; + } + + template<> inline + bool matrix::multiplyMat(const matrix& A, + const std::vector& B, + bool transA, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,M,N,K)) return false; + if (!addTo) this->resize(M,N); + + cblas_sgemm(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, CblasNoTrans, + M, N, K, 1.0f, + A.ptr(), A.nrow, + &B.front(), K, + addTo ? 1.0f : 0.0f, + this->ptr(), nrow); + + return true; + } + + template<> inline + bool matrix::multiplyMat(const matrix& A, + const std::vector& B, + bool transA, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,M,N,K)) return false; + if (!addTo) this->resize(M,N); + + cblas_dgemm(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, CblasNoTrans, + M, N, K, 1.0, + A.ptr(), A.nrow, + &B.front(), K, + addTo ? 1.0 : 0.0, + this->ptr(), nrow); + + return true; + } + + template<> inline + bool matrix::multiplyMat(const std::vector& A, + const matrix& B, + bool transB, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transB,M,N,K)) return false; + if (!addTo) this->resize(M,N); + + cblas_sgemm(CblasColMajor, + CblasNoTrans, transB ? CblasTrans : CblasNoTrans, + M, N, K, 1.0f, + &A.front(), M, + B.ptr(), B.nrow, + addTo ? 1.0f : 0.0f, + this->ptr(), nrow); + + return true; + } + + template<> inline + bool matrix::multiplyMat(const std::vector& A, + const matrix& B, + bool transB, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transB,M,N,K)) return false; + if (!addTo) this->resize(M,N); + + cblas_dgemm(CblasColMajor, + CblasNoTrans, transB ? CblasTrans : CblasNoTrans, + M, N, K, 1.0, + &A.front(), M, + B.ptr(), B.nrow, + addTo ? 1.0 : 0.0, + this->ptr(), nrow); + + return true; + } + + template<> inline + bool matrix3d::multiply(const matrix& A, + const matrix3d& B, + bool transA, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,M,N,K)) return false; + if (!addTo) this->resize(M,B.n[1],B.n[2]); + + cblas_sgemm(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, CblasNoTrans, + M, N, K, 1.0f, + A.ptr(), A.rows(), + B.ptr(), B.n[0], + addTo ? 1.0f : 0.0f, + this->ptr(), n[0]); + + return true; + } + + template<> inline + bool matrix3d::multiply(const matrix& A, + const matrix3d& B, + bool transA, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,M,N,K)) return false; + if (!addTo) this->resize(M,B.n[1],B.n[2]); + + cblas_dgemm(CblasColMajor, + transA ? CblasTrans : CblasNoTrans, CblasNoTrans, + M, N, K, 1.0, + A.ptr(), A.rows(), + B.ptr(), B.n[0], + addTo ? 1.0 : 0.0, + this->ptr(), n[0]); + + return true; + } + +#else + //============================================================================ + //=== Non-BLAS inlined implementations (slow...) ========================= + //============================================================================ + + template inline + vector& vector::operator*=(const T& c) + { + for (size_t i = 0; i < this->size(); i++) + std::vector::operator[](i) *= c; + return *this; + } + + template inline + T vector::dot(const std::vector& v, + size_t o1, int i1, size_t o2, int i2) const + { + size_t i, j; + T dotprod = T(0); + for (i = o1, j = o2; i < this->size() && j < v.size(); i += i1, j += i2) + dotprod += this->operator[](i) * v[j]; + return dotprod; + } + + template inline + T vector::norm2(size_t off, int inc) const + { + double xsum = 0.0; + if (inc < 1) return xsum; + + // Warning: This might overflow or underflow for large/small values + const T* v = this->ptr(); + for (size_t i = off; i < this->size(); i += inc) + xsum += v[i]*v[i]; + return sqrt(xsum); + } + + template inline + T vector::normInf(size_t& off, int inc) const + { + T xmax = T(0); + if (inc < 1) return xmax; + + const T* v = this->ptr(); + for (size_t i = off; i < this->size(); i += inc) + if (v[i] > xmax) + { + off = 1+i/inc; + xmax = v[i]; + } + else if (v[i] < -xmax) + { + off = 1+i/inc; + xmax = -v[i]; + } + + return xmax; + } + + template inline + T vector::asum(size_t off, int inc) const + { + T xsum = T(0); + if (inc < 1) return xsum; + + const T* v = this->ptr(); + for (size_t i = off; i < this->size(); i += inc) + xsum += v[i] < T(0) ? -v[i] : v[i]; + return xsum; + } + + template inline + vector& vector::add(const std::vector& X, T alfa) + { + T* p = this->ptr(); + const T* q = &X.front(); + for (size_t i = 0; i < this->size() && i < X.size(); i++, p++, q++) + *p += alfa*(*q); + return *this; + } + + template inline + matrix& matrix::add(const matrix& X, T alfa) + { + T* p = this->ptr(); + const T* q = X.ptr(); + for (size_t i = 0; i < this->size() && i < X.size(); i++, p++, q++) + *p += alfa*(*q); + return *this; + } + + template inline + matrix& matrix::multiply(const T& c) + { + for (size_t i = 0; i < elem.size(); i++) + elem[i] *= c; + return *this; + } + + template inline + bool matrix::multiply(const std::vector& X, std::vector& Y, + bool transA, bool addTo) const + { + if (!this->compatible(X,Y,transA)) return false; + if (!addTo) + { + Y.clear(); + Y.resize(transA ? ncol : nrow, T(0)); + } + + for (size_t i = 0; i < Y.size(); i++) + for (size_t j = 0; j < X.size(); j++) + if (transA) + Y[i] += THIS(j+1,i+1) * X[j]; + else + Y[i] += THIS(i+1,j+1) * X[j]; + + return true; + } + + template inline + matrix& matrix::multiply(const matrix& A, + const matrix& B, + bool transA, bool transB, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,transB,M,N,K)) return *this; + if (!addTo) this->resize(M,N,true); + + for (size_t i = 1; i <= M; i++) + for (size_t j = 1; j <= N; j++) + for (size_t k = 1; k <= K; k++) + if (transA && transB) + THIS(i,j) += A(k,i)*B(j,k); + else if (transA) + THIS(i,j) += A(k,i)*B(k,j); + else if (transB) + THIS(i,j) += A(i,k)*B(j,k); + else + THIS(i,j) += A(i,k)*B(k,j); + + return *this; + } + + template inline + bool matrix::multiplyMat(const matrix& A, const std::vector& B, + bool transA, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,M,N,K)) return false; + if (!addTo) this->resize(M,N,true); + + for (size_t i = 1; i <= M; i++) + for (size_t j = 1; j <= N; j++) + for (size_t k = 1; k <= K; k++) + if (transA) + THIS(i,j) += A(k,i)*B[k-1+K*(j-1)]; + else + THIS(i,j) += A(i,k)*B[k-1+K*(j-1)]; + + return true; + } + + template inline + bool matrix::multiplyMat(const std::vector& A, const matrix& B, + bool transB, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transB,M,N,K)) return false; + if (!addTo) this->resize(M,N,true); + + for (size_t i = 1; i <= M; i++) + for (size_t j = 1; j <= N; j++) + for (size_t k = 1; k <= K; k++) + if (transB) + THIS(i,j) += A[i-1+M*(k-1)]*B(j,k); + else + THIS(i,j) += A[i-1+M*(k-1)]*B(k,j); + + return true; + } + + template inline + bool matrix3d::multiply(const matrix& A, const matrix3d& B, + bool transA, bool addTo) + { + size_t M, N, K; + if (!this->compatible(A,B,transA,M,N,K)) return false; + if (!addTo) this->resize(M,B.n[1],B.n[2],true); + + for (size_t i = 1; i <= n[0]; i++) + for (size_t j = 1; j <= n[1]; j++) + for (size_t k = 1; k <= K; k++) + for (size_t l = 1; l <= n[2]; l++) + if (transA) + this->operator()(i,j,l) += A(k,i)*B(k,j,l); + else + this->operator()(i,j,l) += A(i,k)*B(k,j,l); + + return true; + } +#endif + + //============================================================================ + //=== Global operators =================================================== + //============================================================================ + + //! \brief Multiplication of a vector and a scalar. + //! \return \f$ {\bf Y} = c {\bf X} \f$ + template vector operator*(const vector& X, const T& c) + { + vector Y(X.size()); + for (size_t i = 0; i < X.size(); i++) + Y[i] = X[i]*c; + return Y; + } + + //! \brief Division of a vector by a scalar. + //! \return \f$ {\bf Y} = \frac{1}{d} {\bf X} \f$ + template vector operator/(const vector& X, const T& d) + { + vector Y(X.size()); + T c = T(1) / d; + for (size_t i = 0; i < X.size(); i++) + Y[i] = X[i]*c; + return Y; + } + + //! \brief Addition of two vectors. + //! \return \f$ {\bf Z} = {\bf X} + {\bf Y} \f$ + template vector operator+(const vector& X, const vector& Y) + { + vector Z(X.size()); + for (size_t i = 0; i < X.size() && i < Y.size(); i++) + Z[i] = X[i] + Y[i]; + return Z; + } + + //! \brief Subtraction of two vectors. + //! \return \f$ {\bf Z} = {\bf X} - {\bf Y} \f$ + template vector operator-(const vector& X, const vector& Y) + { + vector Z(X.size()); + for (size_t i = 0; i < X.size() && i < Y.size(); i++) + Z[i] = X[i] - Y[i]; + return Z; + } + + //! \brief Multiplication of a matrix and a scalar. + //! \return \f$ {\bf B} = c {\bf A} \f$ + template matrix operator*(const matrix& A, const T& c) + { + matrix B(A); + return B.multiply(c); + } + + //! \brief Multiplication of a matrix and a vector. + //! \return \f$ {\bf Y} = {\bf A} {\bf X} \f$ + template vector operator*(const matrix& A, const vector& X) + { + vector Y; + A.multiply(X,Y); + return Y; + } + + //! \brief Multiplication of two matrices. + //! \return \f$ {\bf C} = {\bf A} {\bf B} \f$ + template matrix operator*(const matrix& A, const matrix& B) + { + matrix C; + return C.multiply(A,B); + } + + extern double zero_print_tol; //!< Zero tolerance for printing numbers + extern int nval_per_line; //!< Number of values to print per line + + //! \brief Truncate a value to zero when it is less than a given threshold. + //! \details Used when printing matrices for easy comparison with other + //! matrices when they contain terms that are numerically zero, except for + //! some round-off noice. The value of the global variable \a zero_print_tol + //! is used as a tolerance in this method. + template inline T trunc(const T& v) + { + return v > T(zero_print_tol) || v < T(-zero_print_tol) ? v : T(0); + } + + //! \brief Print the vector \b X to the stream \a s. + template std::ostream& operator<<(std::ostream& s, + const vector& X) + { + if (X.size() < 1) return s; + + for (size_t i = 0; i < X.size(); i++) + s << (i%nval_per_line ? ' ':'\n') << trunc(X[i]); + + return s << std::endl; + } + + //! \brief Print the matrix \b A to the stream \a s. + //! \details If the matrix is symmetric, only the upper triangular part of + //! the matrix is printed, with the diagonal elements in the first column. + //! The global variable \a zero_print_tol is used as a tolerance when + //! checking whether the matrix is symmetric or not. + template std::ostream& operator<<(std::ostream& s, + const matrix& A) + { + if (A.rows() < 1 || A.cols() < 1) return s; + + bool symm = A.isSymmetric(zero_print_tol); + for (size_t i = 1; i <= A.rows(); i++) + { + size_t c1 = symm ? i : 1; + s <<"\nRow "<< i <<": "<< trunc(A(i,c1)); + for (size_t j = c1+1; j <= A.cols(); j++) + s <<' '<< trunc(A(i,j)); + } + + return s << std::endl; + } + + //! \brief Print the vector \b X to the stream \a s in matlab format. + template void writeMatlab(const char* label, const vector& X, + std::ostream& s = std::cout) + { + s << label <<" = ["; + for (size_t i = 1; i <= X.size(); i++) + s <<' '<< trunc(X(i)); + s <<" ];"<< std::endl; + } + + //! \brief Print the matrix \b A to the stream \a s in matlab format. + template void writeMatlab(const char* label, const matrix& A, + std::ostream& s = std::cout) + { + s << label <<" = ["; + size_t nsp = 4 + strlen(label); + for (size_t i = 1; i <= A.rows(); i++) + { + if (i > 1) + { + s <<";\n"; + for (size_t k = 0; k < nsp; k++) s <<' '; + } + for (size_t j = 1; j <= A.cols(); j++) + s <<' '<< trunc(A(i,j)); + } + s <<" ];"<< std::endl; + } + +}; + +#undef THIS +#endif diff --git a/src/LinEqSystem.C b/src/LinEqSystem.C new file mode 100644 index 00000000..9aac4902 --- /dev/null +++ b/src/LinEqSystem.C @@ -0,0 +1,26 @@ +// $Id: LinEqSystem.C,v 1.1 2009-06-29 09:55:17 kmo Exp $ +//============================================================================== +//! +//! \file LinEqSystem.C +//! +//! \date Apr 17 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Storage of a standard linear system of equations for structural FEM. +//! +//============================================================================== + +#include "LinEqSystem.h" + + +void LinEqSystem::clear() +{ + RHS.clear(); + + if (K) delete K; + if (M) delete M; + + K = 0; + M = 0; +} diff --git a/src/LinEqSystem.h b/src/LinEqSystem.h new file mode 100644 index 00000000..6c91c100 --- /dev/null +++ b/src/LinEqSystem.h @@ -0,0 +1,41 @@ +// $Id: LinEqSystem.h,v 1.4 2010-05-06 17:31:18 kmo Exp $ +//============================================================================== +//! +//! \file LinEqSystem.h +//! +//! \date Apr 17 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Storage of a standard linear system of equations for structural FEM. +//! +//============================================================================== + +#ifndef _LIN_EQ_SYSTEM_H +#define _LIN_EQ_SYSTEM_H + +#include "SystemMatrix.h" + + +/*! + \brief Class for storage of a linear system of equations. +*/ + +class LinEqSystem +{ +public: + SystemMatrix* K; //!< System stiffness matrix + SystemMatrix* M; //!< System mass matrix + StdVector RHS; //!< System right-hand-side vector + + //! \brief Default constructor. + LinEqSystem() { K = 0; M = 0; } + + //! \brief The destructor frees the dynamically allocated objects. + ~LinEqSystem() { if (K) delete K; if (M) delete M; } + + //! \brief Erases the system matrices and frees dynamically allocated storage. + void clear(); +}; + +#endif diff --git a/src/LinearEl.C b/src/LinearEl.C new file mode 100644 index 00000000..1ee138d1 --- /dev/null +++ b/src/LinearEl.C @@ -0,0 +1,1376 @@ +// $Id: LinearEl.C,v 1.32 2010-12-07 12:56:25 kmo Exp $ +//============================================================================== +//! +//! \file LinearEl.C +//! +//! \date Jan 23 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solver for linear elasticity problems using NURBS-based FEM. +//! +//============================================================================== + +#include "LinearEl.h" +#include "EigSolver.h" +#include "VolumePatch.h" +#include "ElementBlock.h" +#include "AnalyticSolutions.h" +#include "Functions.h" +#include "Tensor.h" +#include "Vec3Oper.h" +#include "SAMSpline.h" +#include "Utilities.h" +#include "VTF.h" +#include +#include +#include +#include + + +/*! + \brief Local coordinate system for a cylinder along global z-axis. +*/ + +class CylinderCS : public LocalSystem +{ +public: + //! \brief Constructor printing a message making user aware of its presense. + CylinderCS() + { + std::cout <<"\nLocal coordinate system: Cylindric"<< std::endl; + } + //! \brief Computes global-to-local transformation at the point \a X. + virtual const Tensor& getTmat(const Vec3& X) const + { + static Tensor T(3); + double r = hypot(X.x,X.y); + T(1,1) = X.x/r; + T(1,2) = X.y/r; + T(2,1) = -T(1,2); + T(2,2) = T(1,1); + T(3,3) = 1.0; + return T; + } +}; + + +LinearEl::LinearEl (const char* fileName, bool checkRHS, bool free) +{ + rCS = 0; + sam = 0; + asol = 0; + + if (fileName) + if (!this->read(fileName,checkRHS,free)) + exit(1); +} + + +LinearEl::~LinearEl () +{ + if (rCS) delete rCS; + if (sam) delete sam; + if (asol) delete asol; + + for (size_t i = 0; i < model.size(); i++) + delete model[i]; +} + + +bool LinearEl::read (const char* fileName, bool checkRHS, bool free) +{ + MPCLess::compareSlaveDofOnly = true; // to avoid multiple slave definitions + + model.clear(); + load.clear(); + + char* cline = 0; + std::cout <<"\nReading input file "<< fileName << std::endl; + std::ifstream is(fileName); + while (is.good() && (cline = utl::readLine(is))) + + if (!strncasecmp(cline,"PATCHES",7)) + { + int npatch = atoi(cline+7); + std::cout <<"\nNumber of patches: "<< npatch << std::endl; + for (int i = 0; i < npatch && (cline = utl::readLine(is)); i++) + { + VolumePatch* vp = new VolumePatch(strtok(cline," "),checkRHS); + if (vp->empty()) + delete vp; + else + model.push_back(vp); + } + if (model.size() < npatch) + { + std::cerr <<" *** LinearEl::read: Expected "<< npatch + <<" patches but could read only "<< model.size() << std::endl; + return false; + } + } + + else if (!strncasecmp(cline,"PATCHFILE",9)) + { + size_t i = 9; while (i < strlen(cline) && isspace(cline[i])) i++; + std::cout <<"\nReading data file "<< cline+i << std::endl; + std::ifstream isp(cline+i); + for (int patchNo = 1; isp.good(); patchNo++) + { + std::cout <<"Reading patch "<< patchNo << std::endl; + VolumePatch* vp = new VolumePatch(isp,checkRHS); + if (vp->empty()) + delete vp; + else + model.push_back(vp); + } + if (model.empty()) + { + std::cerr <<" *** LinearEl::read: No patches read"<< std::endl; + return false; + } + } + + else if (!strncasecmp(cline,"REFINE",6)) + { + int nref = atoi(cline+6); + std::cout <<"\nNumber of patch refinements: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int addu = atoi(strtok(NULL," ")); + int addv = atoi(strtok(NULL," ")); + int addw = atoi(strtok(NULL," ")); + if (patch == 0 || abs(patch) > model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1 + <<" "<< addu <<" "<< addv <<" "<< addw << std::endl; + if (addu > 0) model[j]->uniformRefine(0,addu); + if (addv > 0) model[j]->uniformRefine(1,addv); + if (addw > 0) model[j]->uniformRefine(2,addw); + } + } + } + + else if (!strncasecmp(cline,"RAISEORDER",10)) + { + int nref = atoi(cline+10); + std::cout <<"\nNumber of order raise: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int addu = atoi(strtok(NULL," ")); + int addv = atoi(strtok(NULL," ")); + int addw = atoi(strtok(NULL," ")); + if (patch == 0 || abs(patch) > model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRaising order of P"<< j+1 + <<" "<< addu <<" "<< addv <<" "<< addw << std::endl; + model[j]->raiseOrder(addu,addv,addw); + } + } + } + + else if (!strncasecmp(cline,"TOPOLOGYFILE",12)) + { + if (!this->createFEMmodel()) return false; + + size_t i = 12; while (i < strlen(cline) && isspace(cline[i])) i++; + std::cout <<"\nReading data file "<< cline+i << std::endl; + std::ifstream ist(cline+i); + while (cline = utl::readLine(ist)) + { + int master = atoi(strtok(cline," ")); + int mFace = atoi(strtok(NULL," "))+1; + int slave = atoi(strtok(NULL," ")); + int sFace = atoi(strtok(NULL," "))+1; + int swapd = atoi(strtok(NULL," ")); + int rev_u = atoi(strtok(NULL," ")); + int rev_v = atoi(strtok(NULL," ")); + int orient = 4*swapd+2*rev_u+rev_v; + if (master == slave || + master < 0 || master >= model.size() || + slave < 0 || slave >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch indices " + << master <<" "<< slave << std::endl; + return false; + } + std::cout <<"\tConnecting P"<< slave+1 <<" F"<< sFace + <<" to P"<< master+1 <<" F"<< mFace + <<" orient "<< orient << std::endl; + if (!model[slave]->connectPatch(sFace,*model[master],mFace,orient)) + return false; + } + } + + else if (!strncasecmp(cline,"TOPOLOGY",8)) + { + if (!this->createFEMmodel()) return false; + + int ntop = atoi(cline+8); + std::cout <<"\nNumber of patch connections: "<< ntop << std::endl; + for (int i = 0; i < ntop && (cline = utl::readLine(is)); i++) + { + int master = atoi(strtok(cline," "))-1; + int mFace = atoi(strtok(NULL," ")); + int slave = atoi(strtok(NULL," "))-1; + int sFace = atoi(strtok(NULL," ")); + int orient = (cline = strtok(NULL," ")) ? atoi(cline) : 0; + if (master == slave || + master < 0 || master >= model.size() || + slave < 0 || slave >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch indices " + << master <<" "<< slave << std::endl; + return false; + } + std::cout <<"\tConnecting P"<< slave+1 <<" F"<< sFace + <<" to P"<< master+1 <<" F"<< mFace + <<" orient "<< orient << std::endl; + if (!model[slave]->connectPatch(sFace,*model[master],mFace,orient)) + return false; + } + } + + else if (!strncasecmp(cline,"PERIODIC",8)) + { + if (!this->createFEMmodel()) return false; + + int nper = atoi(cline+8); + std::cout <<"\nNumber of periodicities: "<< nper << std::endl; + for (int i = 0; i < nper && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," "))-1; + int pfdir = atoi(strtok(NULL," ")); + if (patch < 0 || patch >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tPeriodic "<< char('H'+pfdir) <<"-direction P"<< patch+1 + << std::endl; + model[patch]->closeFaces(pfdir); + } + } + + else if (!strncasecmp(cline,"CONSTRAINTS",11)) + { + if (free) continue; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int ncon = atoi(cline+11); + std::cout <<"\nNumber of constraints: "<< ncon << std::endl; + for (int i = 0; i < ncon && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," "))-1; + int pface = atoi(strtok(NULL," ")); + int pline = pface > 10 ? pface/10 : 0; + int bcode = atoi(strtok(NULL," ")); + double pd = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + if (pface > 10) pface = pface%10; + if (patch < 0 || patch >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tConstraining P"<< patch+1; + if (pline > 0) + std::cout <<" F"<< pface <<" L"<< pline <<" at xi="<< pd + <<" with code "<< bcode; + else + { + std::cout << (pface < 0 ? " C" : " F") << abs(pface) + <<" with code "<< bcode; + if (pd != 0.0) std::cout <<" value = "<< pd; + } + std::cout << std::endl; + if (pline == 1) switch (pface) + { + case 1: model[patch]->constrainLine(-1,2,pd,bcode); break; + case 2: model[patch]->constrainLine( 1,2,pd,bcode); break; + case 3: model[patch]->constrainLine(-2,3,pd,bcode); break; + case 4: model[patch]->constrainLine( 2,3,pd,bcode); break; + case 5: model[patch]->constrainLine(-3,1,pd,bcode); break; + case 6: model[patch]->constrainLine( 3,1,pd,bcode); break; + } + else if (pline == 2) switch (pface) + { + case 1: model[patch]->constrainLine(-1,3,pd,bcode); break; + case 2: model[patch]->constrainLine( 1,3,pd,bcode); break; + case 3: model[patch]->constrainLine(-2,1,pd,bcode); break; + case 4: model[patch]->constrainLine( 2,1,pd,bcode); break; + case 5: model[patch]->constrainLine(-3,2,pd,bcode); break; + case 6: model[patch]->constrainLine( 3,2,pd,bcode); break; + } + else switch (pface) + { + case 1: model[patch]->constrainFace(-1,bcode,pd); break; + case 2: model[patch]->constrainFace( 1,bcode,pd); break; + case 3: model[patch]->constrainFace(-2,bcode,pd); break; + case 4: model[patch]->constrainFace( 2,bcode,pd); break; + case 5: model[patch]->constrainFace(-3,bcode,pd); break; + case 6: model[patch]->constrainFace( 3,bcode,pd); break; + default: + switch (-pface) + { + case 1: model[patch]->constrainCorner(-1,-1,-1,bcode,pd); break; + case 2: model[patch]->constrainCorner( 1,-1,-1,bcode,pd); break; + case 3: model[patch]->constrainCorner(-1, 1,-1,bcode,pd); break; + case 4: model[patch]->constrainCorner( 1, 1,-1,bcode,pd); break; + case 5: model[patch]->constrainCorner(-1,-1, 1,bcode,pd); break; + case 6: model[patch]->constrainCorner( 1,-1, 1,bcode,pd); break; + case 7: model[patch]->constrainCorner(-1, 1, 1,bcode,pd); break; + case 8: model[patch]->constrainCorner( 1, 1, 1,bcode,pd); break; + default: + std::cerr <<" *** LinearEl::read: Invalid face index " + << pface << std::endl; + } + } + } + } + + else if (!strncasecmp(cline,"FIXPOINTS",9)) + { + if (free) continue; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int nfix = atoi(cline+9); + std::cout <<"\nNumber of fixed points: "<< nfix << std::endl; + for (int i = 0; i < nfix && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," "))-1; + double rx = atof(strtok(NULL," ")); + double ry = atof(strtok(NULL," ")); + double rz = atof(strtok(NULL," ")); + int bcode = (cline = strtok(NULL," ")) ? atoi(cline) : 123; + if (patch < 0 || patch >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tConstraining P"<< patch+1 + <<" point at "<< rx <<" "<< ry <<" "<< rz + <<" with code "<< bcode << std::endl; + model[patch]->constrainNode(rx,ry,rz,bcode); + } + } + + else if (!strncasecmp(cline,"ANASOL",6)) + { + cline = strtok(cline+6," "); + if (!strncasecmp(cline,"HOLE",4)) + { + double a = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + asol = new Hole(a,F0,nu,true); + std::cout <<"\nAnalytical solution: Hole a="<< a <<" F0="<< F0 + <<" nu="<< nu << std::endl; + } + else if (!strncasecmp(cline,"LSHAPE",6)) + { + double a = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + double nu = atof(strtok(NULL," ")); + asol = new Lshape(a,F0,nu,true); + std::cout <<"\nAnalytical solution: Lshape a="<< a <<" F0="<< F0 + <<" nu="<< nu << std::endl; + } + else if (!strncasecmp(cline,"CANTS",5)) + { + double L = atof(strtok(NULL," ")); + double H = atof(strtok(NULL," ")); + double F0 = atof(strtok(NULL," ")); + asol = new CanTS(L,H,F0,true); + std::cout <<"\nAnalytical solution: CanTS L="<< L <<" H="<< H + <<" F0="<< F0 << std::endl; + } + else + std::cerr <<" ** LinearEl::read: Unknown analytical solution " + << cline << std::endl; + } + + else if (!strncasecmp(cline,"PRESSURE",8)) + { + if (free) continue; // Ignore all boundary conditions + + int npres = atoi(cline+8); + std::cout <<"\nNumber of pressures: "<< npres << std::endl; + for (int i = 0; i < npres && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," "))-1; + int pface = atoi(strtok(NULL," ")); + if (patch < 0 || patch >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + int pdir = -1; + double pv = 0.0; + if (asol) + std::cout <<"\tTraction on P"<< patch+1 <<" F" << pface << std::endl; + else + { + pdir = atoi(strtok(NULL," ")); + pv = atof(strtok(NULL," ")); + std::cout <<"\tPressure on P"<< patch+1 + <<" F" << pface <<" direction "<< pdir + <<": "<< pv << std::endl; + } + switch (pface) + { + case 1: load.push_back(PLoad(patch,-1,pdir,pv)); break; + case 2: load.push_back(PLoad(patch, 1,pdir,pv)); break; + case 3: load.push_back(PLoad(patch,-2,pdir,pv)); break; + case 4: load.push_back(PLoad(patch, 2,pdir,pv)); break; + case 5: load.push_back(PLoad(patch,-3,pdir,pv)); break; + case 6: load.push_back(PLoad(patch, 3,pdir,pv)); break; + default: + std::cerr <<" *** LinearEl::read: Invalid face index " + << pface << std::endl; + } + } + } + + else if (!strncasecmp(cline,"MATERIAL",8)) + { + int nmat = atoi(cline+8); + std::cout <<"\nNumber of materials: "<< nmat << std::endl; + for (int i = 0; i < nmat && (cline = utl::readLine(is)); i++) + { + int last = 0; + double E = atof(strtok(cline," ")); + double nu = atof(strtok(NULL," ")); + double rho = atof(strtok(NULL," ")); + while (cline = strtok(NULL," ")) + if (!strncasecmp(cline,"ALL",3)) + { + std::cout <<"\tMaterial for all patches: " + << E <<" "<< nu <<" "<< rho << std::endl; + for (size_t j = 0; j < model.size(); j++) + model[j]->setMaterial(E,nu,rho); + } + else + { + int patch = atoi(cline)-1; + if (patch < 0 || patch >= model.size()) + { + std::cerr <<" *** LinearEl::read: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tMaterial for P"<< patch+1 + <<": "<< E <<" "<< nu <<" "<< rho << std::endl; + model[patch]->setMaterial(E,nu,rho); + } + } + } + + else if (!strncasecmp(cline,"GRAVITY",7)) + { + gravity.x = atof(strtok(cline+7," ")); + gravity.y = atof(strtok(NULL," ")); + gravity.z = atof(strtok(NULL," ")); + std::cout <<"\nGravitation vector: "<< gravity << std::endl; + } + + else if (!strncasecmp(cline,"LOCAL_SYSTEM",12)) + { + size_t i = 12; + while (i < strlen(cline) && isspace(cline[i])) i++; + if (!strncasecmp(cline+i,"CYLINDRICZ",10)) + rCS = new CylinderCS; + else + std::cerr <<" *** LinearEl::read: Unsupported coordinate system: " + << cline+i << std::endl; + } + + else if (isalpha(cline[0])) + std::cerr <<" *** LinearEl::read: Unknown keyword: "<< cline << std::endl; + + return this->createFEMmodel(); +} + + +bool LinearEl::createFEMmodel () +{ + for (size_t i = 0; i < model.size(); i++) + if (!model[i]->generateFEMTopology()) + return false; + + return true; +} + + +bool LinearEl::preprocess (const std::vector& ignoredPatches, bool fixDup) +{ + // Erase all patches that should be ignored in the analysis + std::vector::const_iterator it; + for (it = ignoredPatches.begin(); it != ignoredPatches.end(); it++) + if (*it > 0 && *it <= model.size()) + model[*it-1]->clear(); + + // Renumber the nodes to account for overlapping nodes and erased patches + int nnod = VolumePatch::renumberNodes(model); + + if (fixDup && VolumePatch::mergeDuplNodes) + { + // Check for duplicated nodes (missing topology) + int nDupl = 0; + std::map globalNodes; + for (size_t i = 0; i < model.size(); i++) + { + std::map old2new; + for (int node = 1; node <= model[i]->getNoNodes(); node++) + { + int globNum = model[i]->getNodeID(node); + Vec3 X(model[i]->getCoord(node)); + std::map::const_iterator xit = globalNodes.find(X); + if (xit == globalNodes.end()) + globalNodes.insert(std::make_pair(X,globNum)); + else if (xit->second != globNum) + { + std::cout <<" ** Merging duplicated nodes "<< xit->second <<" and " + << globNum <<" at X="<< X << std::endl; + if (model[i]->mergeNodes(node,xit->second)) + old2new[globNum] = xit->second; + } + } + if (!old2new.empty()) + { + model[i]->renumberNodes(old2new,true); + nDupl += old2new.size(); + } + } + if (nDupl > 0) + { + std::cout <<" * "<< nDupl <<" duplicated nodes merged."<< std::endl; + // Renumber the nodes to account for the merged nodes + nnod = VolumePatch::renumberNodes(model); + } + } + + // Resolve possibly chained multi-point constraints + VolumePatch::resolveMPCchains(model); + + // Initialize data structures for the algebraic system + sam = new SAMSpline(); + return sam->init(model, VolumePatch::mergeDuplNodes ? nnod : 0); +} + + +bool LinearEl::assembleKandR (SystemMatrix::Type solver, int nGauss, Vector* R) +{ + if (!sam) return false; +#if SP_DEBUG > 1 + sam->print(std::cout); +#endif + + sys.clear(); + sys.K = SystemMatrix::create(solver); + if (!sam->initForAssembly(sys,true)) + return true; // no free dofs in system + + // Assemble the system stiffness matrix + size_t i; + bool ok = true; + for (i = 0; i < model.size() && ok; i++) + { + std::cout <<"\nAssembling stiffness matrix for P"<< i+1 << std::endl; + ok = model[i]->assembleSystem(sys,*sam,gravity,nGauss); + } + + // Assemble right-hand-side contributions from boundary tractions + for (i = 0; i < load.size() && ok; i++) + { + int j = load[i].volp; + std::cout <<"\nAssembling "<< (load[i].pdir >= 0 ? "pressure" : "traction") + <<" forces on face "<< load[i].face <<" for P"<< j+1 << std::endl; + + if (load[i].pdir >= 0) + { + // Constant surface pressure + const RealFunc* p = new ConstFunc(load[i].p); + ok = model[j]->assembleForces(sys.RHS,*sam,PressureField(p,load[i].pdir), + load[i].face,nGauss,&trac); + } + else if (asol) + // Use traction field from the analytical solution + ok = model[j]->assembleForces(sys.RHS,*sam,TractionField(*asol), + load[i].face,nGauss,&trac); + } + +#if SP_DEBUG > 3 + std::cout <<"System stiffness matrix:"<< *sys.K; + std::cout <<"System force vector:"<< sys.RHS; +#endif + + // Store the right-hand-side vector for visualization + if (ok && R) + ok = sam->expandSolution(sys.RHS,*R); + + return ok; +} + + +bool LinearEl::assembleKandM (SystemMatrix::Type solver, int nGauss) +{ + if (!sam) return false; +#if SP_DEBUG > 1 + sam->print(std::cout); +#endif + + sys.clear(); + sys.K = SystemMatrix::create(solver); + sys.M = SystemMatrix::create(solver); + if (!sam->initForAssembly(sys)) + return true; // no free dofs in system + + // Assemble the system stiffness and mass matrices + bool ok = true; + for (size_t i = 0; i < model.size() && ok; i++) + { + std::cout <<"\nAssembling stiffness/mass matrix for P"<< i+1 << std::endl; + ok = model[i]->assembleSystem(sys,*sam,Vec3(),nGauss); + } + +#if SP_DEBUG > 3 + std::cout <<"System stiffness matrix:"<< *sys.K; + std::cout <<"System mass matrix:"<< *sys.M; +#endif + return ok; +} + + +bool LinearEl::assembleKandKg (SystemMatrix::Type solver, int nGauss, + const Vector& dis) +{ + if (!sam) return false; +#if SP_DEBUG > 1 + sam->print(std::cout); +#endif + + sys.clear(); + sys.K = SystemMatrix::create(solver); + sys.M = SystemMatrix::create(solver); + if (!sam->initForAssembly(sys)) + return true; // no free dofs in system + + // Assemble the material- and geometric system stiffness matrices + bool ok = true; + Vector displ; + for (size_t i = 0; i < model.size() && ok; i++) + { + std::cout <<"\nAssembling stiffness matrices for P"<< i+1 << std::endl; + model[i]->extractSolution(dis,displ); + ok = model[i]->assembleSystem(sys,*sam,Vec3(),nGauss,displ); + } + +#if SP_DEBUG > 3 + std::cout <<"System stiffness matrix:"<< *sys.K; + std::cout <<"Geometric stiffness matrix:"<< *sys.M; +#endif + return ok; +} + + +bool LinearEl::assembleKonly (SystemMatrix::Type solver, int nGauss) +{ + if (!sam) return false; +#if SP_DEBUG > 1 + sam->print(std::cout); +#endif + + sys.clear(); + sys.K = SystemMatrix::create(solver); + if (!sam->initForAssembly(sys)) + return true; // no free dofs in system + + // Assemble the system stiffness matrix + bool ok = true; + for (size_t i = 0; i < model.size() && ok; i++) + { + std::cout <<"\nAssembling stiffness matrix for P"<< i+1 << std::endl; + ok = model[i]->assembleSystem(sys,*sam,Vec3(),nGauss); + } + +#if SP_DEBUG > 3 + std::cout <<"System stiffness matrix:"<< *sys.K; +#endif + return ok; +} + + +bool LinearEl::solve (Vector& solution) +{ + if (!sys.K) return false; + if (!sam) return false; + + // Solve the linear system of equations + std::cout <<"\nSolving the equation system ..."<< std::endl; + if (!sys.K->solve(sys.RHS)) return false; + if (!sam->expandSolution(sys.RHS,solution)) return false; + + size_t iXmax = 0; + size_t iYmax = 1; + size_t iZmax = 2; + double dXmax = solution.normInf(iXmax,3); + double dYmax = solution.normInf(iYmax,3); + double dZmax = solution.normInf(iZmax,3); + double dNorm = solution.norm2(); + + std::cout <<"\n >>> Solution summary <<<\n" + <<"\nEuclidean norm : "<< dNorm + <<"\nL2-norm : "<< dNorm/sqrt(solution.size()) + <<"\nMax X-displacement : "<< dXmax <<" node "<< iXmax + <<"\nMax Y-displacement : "<< dYmax <<" node "<< iYmax + <<"\nMax Z-displacement : "<< dZmax <<" node "<< iZmax + << std::endl; + + if (sam->getNoEquations() < 300) + { + std::cout <<"\nSolution vector:"; + for (size_t i = 0; i < solution.size(); i += 3) + std::cout <<"\nNode "<< i/3+1 <<": " + << solution[i] <<" "<< solution[i+1] <<" "<< solution[i+2]; + std::cout << std::endl; + } + + return true; +} + + +bool LinearEl::solutionNorms (Matrix& eNorm, int nGauss, const Vector& dis) +{ + Vector gNorm(asol ? 3 : 1); + eNorm.resize(gNorm.size(),sam->getNoElms(),true); + + size_t i; + bool ok = true; + Vector displ; + for (i = 0; i < model.size() && ok; i++) + { + model[i]->extractSolution(dis,displ); + ok = model[i]->solutionNorms(gNorm,eNorm,nGauss,displ,asol); + } + for (i = 1; i <= gNorm.size(); i++) + gNorm(i) = sqrt(gNorm(i)); + + std::cout <<"Energy norm |u^| = a(u^h,u^h)^0.5 : "<< gNorm(1); + if (asol) + std::cout <<"\nExact norm |u| = a(u,u)^0.5 : "<< gNorm(2) + <<"\nExact error a(e,e)^0.5, e=u-u^h : "<< gNorm(3) + <<"\nExact relative error (%) : "<< gNorm(3)/gNorm(2)*100.0; + + std::cout << std::endl; + return ok; +} + + +bool LinearEl::modes (int iop, int nev, int ncv, double shift, + std::vector& solution) +{ + if (nev < 1 || ncv <= nev) return false; + if (!sam) return false; + + Vector eigVal; + Matrix eigVec; + if (nev > sam->getNoEquations()) nev = sam->getNoEquations(); + if (ncv > sam->getNoEquations()) ncv = sam->getNoEquations(); + + // If iop < 0, try the LAPack eigensolver first (for dense matrices only). + // If iop > 0, or the LAPack solver failed or could not be used, use ARPack. + if (iop > 0 || !eig::solve(sys.K,sys.M,eigVal,eigVec,nev)) + if (!eig::solve(sys.K,sys.M,eigVal,eigVec,nev,ncv,abs(iop),shift)) + return false; + + bool freq = abs(iop) == 3 || abs(iop) == 4 || abs(iop) == 6; + + solution.resize(nev); + for (int i = 1; i <= nev; i++) + if (!sam->expandVector(eigVec.getColumn(i),solution[i-1].eigVec)) + return false; + else if (!freq) + solution[i-1].eigVal = eigVal(i); + else if (eigVal(i) < 0.0) + solution[i-1].eigVal = -sqrt(-eigVal(i))*0.5/M_PI; + else + solution[i-1].eigVal = sqrt(eigVal(i))*0.5/M_PI; + + std::cout <<"\n >>> Computed Eigenvalues <<<\n Mode\t" + << (freq ? "Frequency [Hz]" : "Eigenvalue"); + for (int j = 1; j <= nev; j++) + std::cout <<"\n "<< j <<"\t\t"<< solution[j-1].eigVal; + std::cout << std::endl; + + return true; +} + + +bool LinearEl::writeGlobalGrid (const char* inputFile, const int* n, + int nenod) const +{ + ElementBlock globalGrid(nenod), blockGrid(nenod); + + int inod, node, j, ngnod, nlnod; + IntVec nodeNums, firstEl(model.size()+1,1); + IntMat FEMbc(3); + for (size_t i = 0; i < model.size(); i++) + { + bool ok = true; + if (i == 0) + ok = model[i]->convertToElementBlock(globalGrid,n); + else + { + ok = model[i]->convertToElementBlock(blockGrid,n); + if (ok) globalGrid.merge(&blockGrid,nodeNums); + } + ngnod = globalGrid.getNoNodes(); + nlnod = i == 0 ? ngnod : blockGrid.getNoNodes(); + firstEl[i+1] += globalGrid.getNoElms(); + + Matrix field, bc(3,model[i]->getNoNodes()); + std::vector::const_iterator bit; + for (bit = model[i]->begin_BC(); bit != model[i]->end_BC(); bit++) + if (node = model[i]->getNodeIndex(bit->node)) + { + if (!bit->CX) bc(1,node) = 1.0; + if (!bit->CY) bc(2,node) = 1.0; + if (!bit->CZ) bc(3,node) = 1.0; + } + + if (ok && !model[i]->evalDisplField(field,bc,n)) + ok = false; + + for (j = 0; j < 3 && ok; j++) + FEMbc[j].resize(ngnod,0); + + for (inod = 0; inod < nlnod && ok; inod++) + { + node = i == 0 ? inod : nodeNums[inod]; + for (j = 0; j < 3; j++) + if (field(j+1,inod+1) == 1.0) + FEMbc[j][node] = 1; + } + } + + if (ngnod < nenod) return false; + + char* gridName = new char[strlen(inputFile)+5]; + strcpy(gridName,inputFile); + std::ofstream os(strcat(strtok(gridName,"."),".grid")); + std::cout <<"\nWriting global grid file "<< gridName << std::endl; + delete[] gridName; + + os << ngnod <<" "<< globalGrid.getNoElms() <<" "<< nenod <<"\n"; + inod = 0; + std::vector::const_iterator cit; + for (cit = globalGrid.begin_XYZ(); cit != globalGrid.end_XYZ(); cit++, inod++) + os << inod+1 <<" "<< *cit <<" "<< FEMbc[0][inod] <<" " + << FEMbc[1][inod] <<" "<< FEMbc[2][inod] <<"\n"; + + double E, nu, rho; + size_t ivolp = 0; + const int* p = globalGrid.getElements(); + for (int iel = 1; iel <= globalGrid.getNoElms(); iel++) + { + while (ivolp < model.size() && iel > firstEl[ivolp+1]) ivolp++; + model[ivolp]->getMaterial(E,nu,rho); + os << iel <<" "<< E <<" "<< nu <<" "<< rho; + for (j = 0; j < nenod; j++, p++) + os <<" "<< 1+(*p); + os <<"\n"; + } + + return true; +} + + +bool LinearEl::writeGlv (const char* inputFile, const Result& results, + const int* nViz, int format, bool debug) const +{ + // Open VTF-file + char* vtfName = new char[strlen(inputFile)+4]; + strcpy(vtfName,inputFile); + VTF vtf(strcat(strtok(vtfName,"."),".vtf"),format); + std::cout <<"\nWriting VTF-file "<< vtfName << std::endl; + delete[] vtfName; + + // Write geometry + int iStep = 1, nBlock = 0; + if (!this->writeGlvG(vtf,nViz)) + return false; + + // Write boundary tractions, if any + if (!this->writeGlvT(vtf,iStep,nBlock)) + return false; + + // Write Dirichlet boundary conditions + if (!this->writeGlvBC(vtf,nViz,nBlock,debug)) + return false; + + // Write load vector + if (!this->writeGlvR(vtf,results.load,nViz,iStep,nBlock)) + return false; + + // Write solution fields + if (!this->writeGlvS(vtf,results.displ,nViz,iStep,nBlock)) + return false; + + // Write eigenmodes + for (size_t i = 0; i < results.modes.size(); i++, iStep++) + if (!this->writeGlvM(vtf,results.modes[i],results.freq,nViz,iStep,nBlock)) + return false; + + // Write element norms (only when no additional vizualization points are used) + if (nViz[0] == 2 && nViz[1] == 2 && nViz[2] == 2) + if (!this->writeGlvN(vtf,results.norms,iStep,nBlock)) + return false; + + return true; +} + + +bool LinearEl::writeGlvG (VTF& vtf, const int* nViz) const +{ + char pname[16]; + for (size_t i = 0; i < model.size(); i++) + { + if (model[i]->empty()) continue; // skip empty patches + + std::cout <<"Writing geometry for patch "<< i+1 << std::endl; + ElementBlock* lvb = new ElementBlock; + if (!model[i]->convertToElementBlock(*lvb,nViz)) + return false; + + sprintf(pname,"Patch %ld",i+1); + if (!vtf.writeGrid(lvb,pname)) + return false; + } + + return true; +} + + +bool LinearEl::writeGlvT (VTF& vtf, int iStep, int& nBlock) const +{ + if (trac.empty()) + return true; + + // Write boundary tractions as discrete point vectors + std::cout <<"Writing boundary tractions" << std::endl; + if (!vtf.writeVectors(trac,++nBlock)) + return false; + + return vtf.writeVblk(nBlock,"Tractions",1,iStep); +} + + +bool LinearEl::writeGlvBC (VTF& vtf, const int* nViz, + int& nBlock, bool debug) const +{ + size_t i, j; + int node, nDupl = 0; + std::vector dID[6], nodeConn, nodeDupl; + if (debug && model.size() > 1) + { + // Generate some additional data for topology debugging + std::map globalNodes; + nodeConn.resize(1+sam->getNoNodes(),0); + nodeDupl.resize(1+sam->getNoNodes(),0); + for (i = 0; i < model.size(); i++) + for (node = 1; node <= model[i]->getNoNodes(); node++) + { + int globNum = model[i]->getNodeID(node); + nodeConn[globNum]++; + Vec3 X(model[i]->getCoord(node)); + std::map::const_iterator xit = globalNodes.find(X); + if (xit == globalNodes.end()) + globalNodes.insert(std::make_pair(X,globNum)); + else if (xit->second != globNum && nodeDupl[globNum] == 0) + { + std::cout <<" ** Duplicated nodes "<< xit->second <<" and " + << globNum <<" at X="<< X << std::endl; + nodeDupl[xit->second]++; + nodeDupl[globNum] = -xit->second; + nDupl++; + } + } + for (i = 1; i < nodeDupl.size(); i++) + if (nodeDupl[i] < 0) nodeDupl[i] = nodeDupl[-nodeDupl[i]]; + if (nDupl > 0) + std::cout <<" *** "<< nDupl <<" duplicated nodes detected."<< std::endl; + } + + Matrix field; + int geomID = 0; + for (i = 0; i < model.size(); i++) + { + if (model[i]->empty()) continue; // skip empty patches + + geomID++; + RealArray flag(debug ? 6 : 3, 0.0); + Matrix bc(debug ? 6 : 3, model[i]->getNoNodes()); + std::vector::const_iterator bit; + for (bit = model[i]->begin_BC(); bit != model[i]->end_BC(); bit++) + if (node = model[i]->getNodeIndex(bit->node)) + { + if (!bit->CX) bc(1,node) = flag[0] = 1.0; + if (!bit->CY) bc(2,node) = flag[1] = 1.0; + if (!bit->CZ) bc(3,node) = flag[2] = 1.0; + } + + if (debug) + { + for (node = 1; node <= model[i]->getNoNodes(); node++) + { + bc(6,node) = model[i]->getNodeID(node); + if (model.size() > 1) + { + bc(4,node) = nodeConn[model[i]->getNodeID(node)]; + bc(5,node) = nodeDupl[model[i]->getNodeID(node)]; + if (bc(5,node) > 0.0) flag[4] = 1.0; + } + } + if (model.size() > 1) flag[3] = 1.0; + flag[5] = 1.0; + } + else if (flag[0]+flag[1]+flag[2] == 0.0) + continue; // nothing on this patch + + std::cout <<"Writing boundary conditions for patch "<< i+1 << std::endl; + if (!model[i]->evalDisplField(field,bc,nViz)) + return false; + + // The BC fields should either be 0.0 or 1.0 + if (nViz[0] > 2 || nViz[1] > 2 || nViz[2] > 2) + for (j = 1; j <= 3; j++) + if (flag[j-1] == 1.0) + for (int n = 1; n <= field.cols(); n++) + if (field(j,n) < 0.9999) field(j,n) = 0.0; + + for (j = 0; j < field.rows(); j++) + if (flag[j] == 1.0) + if (vtf.writeNres(field.getRow(1+j),++nBlock,geomID)) + dID[j].push_back(nBlock); + else + return false; + } + + const char* label[6] = { + "fix_X", "fix_Y", "fix_Z", + "Connectivity", + "Double nodes", + "Node number" + }; + + for (j = 0; j < 6; j++) + if (!dID[j].empty()) + if (!vtf.writeSblk(dID[j],label[j],1+j)) + return false; + + return true; +} + + +bool LinearEl::writeGlvR (VTF& vtf, const Vector& load, + const int* nViz, int iStep, int& nBlock) const +{ + if (load.empty()) + return true; + + Vector lovec; + Matrix field; + int geomID = 0, idBlock = 2; + std::vector vID; + for (size_t i = 0; i < model.size(); i++) + { + if (model[i]->empty()) continue; // skip empty patches + + std::cout <<"Writing load vector for patch "<< i+1 << std::endl; + model[i]->extractSolution(load,lovec); + if (!model[i]->evalDisplField(field,lovec,nViz)) + return false; + + if (!vtf.writeVres(field,++nBlock,++geomID)) + return false; + else + vID.push_back(nBlock); + } + + return vtf.writeVblk(vID,"Load vector",idBlock,iStep); +} + + +bool LinearEl::writeGlvS (VTF& vtf, const Vector& solution, + const int* nViz, int iStep, int& nBlock) const +{ + if (solution.empty()) + return true; + + Vector displ; + Matrix field; + int j, geomID = 0, idBlock = 10; + std::vector vID, dID[3], sID[14]; + for (size_t i = 0; i < model.size(); i++) + { + if (model[i]->empty()) continue; // skip empty patches + + std::cout <<"Writing FE solution for patch "<< i+1 << std::endl; + model[i]->extractSolution(solution,displ); + if (!model[i]->evalDisplField(field,displ,nViz)) + return false; + + if (!vtf.writeVres(field,++nBlock,++geomID)) + return false; + else + vID.push_back(nBlock); + + for (j = 0; j < 3; j++) + if (!vtf.writeNres(field.getRow(1+j),++nBlock,geomID)) + return false; + else + dID[j].push_back(nBlock); + + if (!model[i]->evalStressField(field,displ,nViz,rCS)) + return false; + + for (j = 0; j < 6; j++) + if (!vtf.writeNres(field.getRow(1+j),++nBlock,geomID)) + return false; + else + sID[j].push_back(nBlock); + + VolumePatch::vonMises(field,displ); + if (!vtf.writeNres(displ,++nBlock,geomID)) + return false; + else + sID[6].push_back(nBlock); + + if (asol) + { + std::cout <<"Writing exact solution for patch "<< i+1 << std::endl; + std::vector::const_iterator cit; + const ElementBlock* grid = vtf.getBlock(geomID); + for (j = 0, cit = grid->begin_XYZ(); cit != grid->end_XYZ(); cit++) + field.fillColumn(++j,(*asol)(*cit)); + + for (j = 0; j < 6; j++) + if (!vtf.writeNres(field.getRow(1+j),++nBlock,geomID)) + return false; + else + sID[7+j].push_back(nBlock); + + VolumePatch::vonMises(field,displ); + if (!vtf.writeNres(displ,++nBlock,geomID)) + return false; + else + sID[13].push_back(nBlock); + } + } + + if (!vtf.writeDblk(vID,"Displacement",idBlock,iStep)) + return false; + + std::string label("u_x"); + for (j = 0; j < 3; j++) + if (!vtf.writeSblk(dID[j],label.c_str(),++idBlock,iStep)) + return false; + else + label[2]++; + + for (int jj = 0; jj <= (asol ? 7 : 0); jj += 7) + { + label = "s_xx"; + std::string pfx(asol ? (jj ? "Exact " : "FE ") : ""); + for (j = 0; j < 7; j++) + if (!vtf.writeSblk(sID[jj+j],(pfx+label).c_str(),++idBlock,iStep)) + return false; + else if (j < 5) + { + label[2]++; + label[3]++; + if (j == 2 || j == 3) label[2] = 'x'; + if (j == 2) label[3] = 'y'; + if (j == 4) label[3] = 'z'; + } + else + label = "von Mises stress"; + } + + return vtf.writeState(iStep,"Step %g",(double)iStep,2); +} + + +bool LinearEl::writeGlvM (VTF& vtf, const Mode& mode, bool freq, + const int* nViz, int iStep, int& nBlock) const +{ + if (mode.eigVec.empty()) + return true; + + std::cout <<"Writing eigenvector for Mode "<< iStep << std::endl; + + Vector displ; + Matrix field; + int geomID = 0, idBlock = 10; + std::vector vID; + + for (size_t i = 0; i < model.size(); i++) + { + if (model[i]->empty()) continue; // skip empty patches + if (model.size() > 1) std::cout <<"."<< std::flush; + + geomID++; + model[i]->extractSolution(mode.eigVec,displ); + if (!model[i]->evalDisplField(field,displ,nViz)) + return false; + + if (!vtf.writeVres(field,++nBlock,geomID)) + return false; + else + vID.push_back(nBlock); + } + if (model.size() > 1) std::cout << std::endl; + + if (!vtf.writeDblk(vID,"Mode Shape",idBlock,iStep)) + return false; + + return vtf.writeState(iStep, freq ? "Frequency %g" : "Eigenvalue %g", + mode.eigVal, 1); +} + + +bool LinearEl::writeGlvN (VTF& vtf, const Matrix& norms, + int iStep, int& nBlock) const +{ + if (norms.empty()) + return true; + else if (norms.rows() > 3) + return false; + + Matrix field; + int j, idBlock = 100, geomID = 0; + std::vector sID[3]; + + for (size_t i = 0; i < model.size(); i++) + { + if (model[i]->empty()) continue; // skip empty patches + + geomID++; + std::cout <<"Writing element norms for patch "<< i+1 << std::endl; + model[i]->extractElmRes(norms,field); + + for (j = 0; j < field.rows(); j++) + if (!vtf.writeEres(field.getRow(1+j),++nBlock,geomID)) + return false; + else + sID[j].push_back(nBlock); + } + + const char* label[3] = { + "a(u^h,u^h)^0.5", + "a(u,u)^0.5", + "a(e,e)^0.5, e=u-u^h" + }; + + for (j = 0; j < norms.rows(); j++) + if (!vtf.writeSblk(sID[j],label[j],++idBlock,iStep)) + return false; + + return true; +} + + +void LinearEl::dumpGeometry (const char* g2file) const +{ + std::cout <<"\nWriting updated g2-file "<< g2file << std::endl; + + std::ofstream os(g2file); + for (size_t i = 0; i < model.size() && os; i++) + model[i]->write(os); +} + + +void LinearEl::dumpSolution (const char* solfile, const Vector& solution) const +{ + std::cout <<"\nWriting solution files "<< solfile << std::endl; + + char* fileName = new char[strlen(solfile)+4]; + std::ofstream osv(strcat(strcpy(fileName,solfile),".vec")); + std::ofstream os1(strcat(strtok(fileName,"."),".u")); + std::ofstream os2(strcat(strtok(fileName,"."),".v")); + std::ofstream os3(strcat(strtok(fileName,"."),".w")); + utl::nval_per_line = 3; + + Vector displ; + for (size_t i = 0; i < model.size(); i++) + { + model[i]->extractSolution(solution,displ); + if (displ.empty()) continue; + + osv << displ; + for (size_t j = 0; j < displ.size();) + { + os1 << displ[j++] <<"\n"; + os2 << displ[j++] <<"\n"; + os3 << displ[j++] <<"\n"; + } + } + + delete[] fileName; +} + + +void LinearEl::dumpMat (const char* Kfile, const char* Mfile, + const char* Rfile) const +{ + if (Kfile && sys.K) + { + std::cout <<"\nWriting "<< Kfile << std::endl; + std::ofstream os(Kfile); + if (os) os << *sys.K; + } + + if (Mfile && sys.M) + { + std::cout <<"\nWriting "<< Mfile << std::endl; + std::ofstream os(Mfile); + if (os) os << *sys.M; + } + + if (Rfile && !sys.RHS.empty()) + { + std::cout <<"\nWriting "<< Rfile << std::endl; + std::ofstream os(Rfile); + if (os) os << sys.RHS; + } +} diff --git a/src/LinearEl.h b/src/LinearEl.h new file mode 100644 index 00000000..4b135314 --- /dev/null +++ b/src/LinearEl.h @@ -0,0 +1,244 @@ +// $Id: LinearEl.h,v 1.19 2009-10-30 12:12:40 kmo Exp $ +//============================================================================== +//! +//! \file LinearEl.h +//! +//! \date Jan 27 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solver for linear elasticity problems using NURBS-based FEM. +//! +//============================================================================== + +#ifndef _LINEAR_EL_H +#define _LINEAR_EL_H + +#include "Vec3.h" +#include "Function.h" +#include "PressureLoad.h" +#include "LinEqSystem.h" +#include + + +class VolumePatch; +class LocalSystem; +class SAMSpline; +class VTF; + + +/*! + \brief Struct for storage of data associated with one mode shape. +*/ + +struct Mode +{ + double eigVal; //!< Eigenvalue associated with this mode + Vector eigVec; //!< Eigenvector associated with this mode + // \brief Default constructor setting \a eigVal to zero. + Mode() { eigVal = 0.0; } +}; + + +/*! + \brief Result container for passing results to GLview export module. +*/ + +struct Result +{ + Vector load; //!< The external load vector + Vector displ; //!< The displacement vector due to the external load + Matrix norms; //!< Element norms + std::vector modes; //!< Mode shapes + bool freq; //!< Indicates whether eigenvalues are frequencies or not + // \brief Default constructor setting \a freq to \e false. + Result() { freq = false; } +}; + + +/*! + \brief Driver class for the NURBS-based linear elasticity solver. + \details The class incapsulates data and methods for solving linear elasticity + problems using NURBS-based finite elements. +*/ + +class LinearEl +{ +public: + //! \brief The constructor generates the model from the given input file. + //! \param[in] fileName Name of input file with model description + //! \param[in] checkRHS If \e true, ensure the model is in a right-hand system + //! \param[in] free If \e true, any specified boundary conditions are ignored + LinearEl(const char* fileName = 0, bool checkRHS = false, bool free = false); + //! \brief The destructor frees the dynamically allocated objects. + ~LinearEl(); + + //! \brief Reads model data from the specified input file \a *fileName. + bool read(const char* fileName, bool checkRHS = false, bool free = false); + + //! \brief Performs some preprocessing tasks on the finite element model. + //! \details This method should be invoked after \a readModel and before + //! any of the assembleK.... methods. + //! The main purpose of this method is to fill the SAMSpline object with data. + bool preprocess(const std::vector& ignoredPatches = std::vector(), + bool fixDup = false); + + //! \brief Administers assembly of system stiffness matrix and load vector. + //! \param[in] solver Which equation solver (matrix format) to use + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + //! \param[out] R The external load vector in DOF-order (for visualization) + bool assembleKandR(SystemMatrix::Type solver, int nGauss, Vector* R); + //! \brief Administers assembly of the system stiffness- and mass matrices. + //! \param[in] solver Which equation solver (matrix format) to use + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + bool assembleKandM(SystemMatrix::Type solver, int nGauss); + //! \brief Administers assembly of the system stiffness matrices. + //! \param[in] solver Which equation solver (matrix format) to use + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + //! \param[in] dis Solution vector in DOF-order (for geometric stiffness) + bool assembleKandKg(SystemMatrix::Type solver, int nGauss, const Vector& dis); + //! \brief Administers assembly of the system stiffness matrix. + //! \param[in] solver Which equation solver (matrix format) to use + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + bool assembleKonly(SystemMatrix::Type solver, int nGauss); + + //! \brief Solves the assembled linear system of equations for a given load. + //! \param[out] solution Solution vector, displacements at nodal points + bool solve(Vector& solution); + + //! \brief Integrates and prints out some solution norm quantities. + //! \details If an analytical solution is provided, norms of the exact + //! error in the solution is computed as well. + //! \param[out] eNorm Element-wise norm quantities + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + //! \param[in] dis Solution vector in DOF-order, displacements at nodal points + bool solutionNorms(Matrix& eNorm, int nGauss, const Vector& dis); + + //! \brief Performs a generalized eigenvalue analysis of the assembled system. + //! \param[in] iop Which eigensolver method to use + //! \param[in] nev Number of eigenvalues/vector (see ARPack documentation) + //! \param[in] ncv Number of Arnoldi vectors (see ARPack documentation) + //! \param[in] shift Eigenvalue shift + //! \param[out] solution Computed eigenvalues and associated eigenvectors + bool modes(int iop, int nev, int ncv, double shift, + std::vector& solution); + + //! \brief Writes the global grid and boundary conditions to a file. + //! \param[in] inputFile File name used to construct the grid file name from + //! \param[in] n Number of FE nodes over each knot-span + //! \param[in] nenod Number of nodes in each element to generate + bool writeGlobalGrid(const char* inputFile, const int* n, + int nenod = 8) const; + + //! \brief Writes a VTF-file with the geometry and solution fields. + //! \param[in] inputFile File name used to construct the VTF-file name from + //! \param[in] solution The solution vector(s) to output + //! \param[in] nViz Number of visualization points over a knot-span + //! \param[in] format Format of VTF-file (0=ASCII, 1=BINARY) + //! \param[in] debug If \e true, output some additional debug quantities + //! + //! \details The NURBS patches are tesselated into linear hexahedrons with + //! a fixed number of HEX8-elements within each knot-span of non-zero length. + //! The solution fields (displacements and stresses) are then evaluated at the + //! nodal points of the generated HEX8-mesh and written to the VTF-file as + //! vector and scalar result fields. + bool writeGlv(const char* inputFile, const Result& solution, + const int* nViz, int format = 0, bool debug = false) const; + + //! \brief Writes the geometry to g2-file for external viewing. + //! \param[in] g2file Filename for GoTools output file + void dumpGeometry(const char* g2file) const; + + //! \brief Writes the solution vector to files for external viewing. + //! \param[in] solfile Filename prefix + //! \param[in] solution The global solution vector in DOF-order + //! + //! \details The solution vector is written to the ASCII file \a solfile.vec + //! in the same order as the control points are ordered in the NURBS patches. + //! In addition, each component is written to files \a solfile.u, \a solfile.v + //! and \a solfile.w respectively. + void dumpSolution(const char* solfile, const Vector& solution) const; + + //! \brief Writes the system matrices to files for debugging. + //! \param[in] Kfile Filename for stiffness matrix + //! \param[in] Mfile Filename for mass matrix + //! \param[in] Rfile Filename for load vector + void dumpMat(const char* Kfile, + const char* Mfile = 0, + const char* Rfile = 0) const; + +protected: + //! \brief Creates the computational FEM model. + bool createFEMmodel(); + + //! \brief Writes the grid geometry to the VTF-file. + //! \param vtf The VTF-file object to receive the geometry data + //! \param[in] nViz Number of visualization points over each knot-span + bool writeGlvG(VTF& vtf, const int* nViz) const; + + //! \brief Writes the surface tractions for a given time step to VTF-file. + //! \param vtf The VTF-file object to receive the tractions + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvT(VTF& vtf, int iStep, int& nBlock) const; + + //! \brief Writes boundary condition codes as scalar fields to the VTF-file. + //! \param vtf The VTF-file object to receive the boundary condition data + //! \param[in] nViz Number of visualization points over each knot-span + //! \param nBlock Running result block counter + //! \param[in] debug If \e true, output some additional debug quantities + bool writeGlvBC(VTF& vtf, const int* nViz, int& nBlock, bool debug) const; + + //! \brief Writes the load vector for a given time step to VTF-file. + //! \param vtf The VTF-file object to receive the load vector + //! \param[in] load The load vector to output + //! \param[in] nViz Number of visualization points over each knot-span + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvR(VTF& vtf, const Vector& load, + const int* nViz, int iStep, int& nBlock) const; + + //! \brief Writes solution fields for a given load/time step to the VTF-file. + //! \details If an analytical solution is provided, the exact stress fields + //! are written to the VTF-file as well. + //! \param vtf The VTF-file object to receive the solution fields + //! \param[in] solution The solution vector to derive the result fields from + //! \param[in] nViz Number of visualization points over each knot-span + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvS(VTF& vtf, const Vector& solution, + const int* nViz, int iStep, int& nBlock) const; + + //! \brief Writes an eigenvector and associated eigenvalue to the VTF-file. + //! \details The eigenvalue is used as a label on the step state info + //! that is associated with the eigenvector. + //! \param vtf The VTF-file object to receive the eigenvector + //! \param[in] mode The eigenvector to output + //! \param[in] freq \e true if the eigenvalue is a frequency + //! \param[in] nViz Number of visualization points over each knot-span + //! \param[in] iMode Mode shape identifier + //! \param nBlock Running result block counter + bool writeGlvM(VTF& vtf, const Mode& mode, bool freq, + const int* nViz, int iMode, int& nBlock) const; + + //! \brief Writes element norms for a given load/time step to the VTF-file. + //! \details This method can be used only when the number of visualization + //! points over each knot-span equals 2 (that is, no additonal points). + //! \param vtf The VTF-file object to receive the element norms + //! \param[in] norms The element norms to output + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvN(VTF& vtf, const Matrix& norms, int iStep, int& nBlock) const; + +private: + std::vector model; //!< The actual NURBS/spline model + std::vector load; //!< Surface pressure loads + Vec3 gravity; //!< Gravitation vector + LocalSystem* rCS; //!< Local coordinate system for results + std::map trac; //!< Evaluated surface tractions + TensorFunc* asol; //!< Analytical stress field + SAMSpline* sam; //!< Data for FE assembly management + LinEqSystem sys; //!< The linear equation system +}; + +#endif diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000..09c03ebd --- /dev/null +++ b/src/Makefile @@ -0,0 +1,111 @@ +# $Id: Makefile,v 1.48 2011-02-08 09:06:02 kmo Exp $ +#=============================================================================== +# +# File: Makefile +# +# Created: Jan 12 2010 +# +# Author: Knut Morten Okstad / SINTEF +# +# Description: +# Makefile for the spline-based isogeometric finite element toolbox, IFEM. +# +#=============================================================================== + +# Comment in this line if PETSc and SLEPc are used +#include ${SLEPC_DIR}/conf/slepc_common_variables + +FSRC = Eig/eig_drv1.f Eig/eig_drv2.f Eig/eig_drv3.f \ + Eig/eig_drv4.f Eig/eig_drv5.f Eig/eig_drv6.f +CSRC = SIM/SIMinput.C SIM/SIMbase.C SIM/SIM3D.C SIM/SIM2D.C SIM/SIM1D.C \ + SIM/NonLinSIM.C ASM/Lagrange.C \ + ASM/AlgEqSystem.C ASM/ASMbase.C ASM/ASMmxBase.C ASM/ASMstruct.C \ + ASM/ASMs3D.C ASM/ASMs3DLag.C ASM/ASMs3DSpec.C \ + ASM/ASMs3Dmx.C ASM/ASMs3DmxLag.C \ + ASM/ASMs2D.C ASM/ASMs2DLag.C ASM/ASMs2DSpec.C \ + ASM/ASMs2Dmx.C ASM/ASMs2DmxLag.C \ + ASM/ASMs1D.C ASM/ASMs1DLag.C ASM/ASMs1DSpec.C \ + Integrands/AnalyticSolutions.C Integrands/LinearElasticity.C \ + Integrands/Elasticity.C Integrands/Poisson.C Integrands/Stokes.C \ + Integrands/StabilizedStokes.C Integrands/StabilizedNavierStokes.C \ + Integrands/NavierStokesG2.C Integrands/NavierStokesG2CN.C \ + Integrands/NavierStokesG2MP.C Integrands/NavierStokesG2GenTheta.C \ + Integrands/ChorinVelPred.C Integrands/ChorinVelPredBE.C Integrands/ChorinVelPredBDF2.C \ + Integrands/ChorinPressCorr.C Integrands/ChorinVelCorr.C \ + LinAlg/SystemMatrix.C LinAlg/DenseMatrix.C LinAlg/SPRMatrix.C \ + LinAlg/SparseMatrix.C LinAlg/PETScMatrix.C LinAlg/LinSolParams.C \ + LinAlg/LinAlgInit.C LinAlg/MatVec.C LinAlg/SAM.C \ + Eig/EigSolver.C ASM/SAMpatch.C ASM/SAMpatchPara.C ASM/GlbNorm.C \ + Utility/CoordinateMapping.C Utility/MPC.C \ + Utility/GaussQuadrature.C Utility/Legendre.C \ + Utility/ElementBlock.C Utility/VTF.C \ + Utility/Vec3Oper.C Utility/Tensor.C Utility/Function.C \ + Utility/Utilities.C Utility/Profiler.C Utility/Functions.C + +OBJS = $(CSRC:.C=.o) $(FSRC:.f=.o) +HDRS = $(CSRC:.C=.h) SIM/SIMenums.h SIM/Property.h \ + ASM/TimeDomain.h ASM/ElmMats.h ASM/ElmNorm.h \ + ASM/GlobalIntegral.h ASM/LocalIntegral.h ASM/IntegrandBase.h \ + Utility/MPCLess.h Utility/Vec3.h LinAlg/matrix.h + +LIB = libSplineFEM.a + +CXX = g++ +FC = gfortran + +GOTOPT = -IGoTools/include +VTFOPT = -DHAS_VTFAPI -IGLviewExpressWriter/include +BLASOPT = -DUSE_CBLAS +SLUOPT = -DHAS_SUPERLU -I/usr/include/superlu +#SLUOPT = -DHAS_SUPERLU_MT -ISuperLU_MT/SRC +#SAMOPT = -DHAS_SPR -DUSE_F77SAM +#SAMGOPT = -DHAS_SAMG -DSAMG_UNIX_LINUX -DSAMG_LCASE_USCORE -ISAMG +# Comment in this line if PETSc and SLEPc are used +#PETSCOPT = -DHAS_PETSC $(CC_INCLUDES) +# Comment in this line if parallel PETSc and SLEPc are used +#PETSCOPT = -DHAS_PETSC -DPARALLEL_PETSC $(CC_INCLUDES) + +#CDEBUG = -g -DINDEX_CHECK -DSP_DEBUG=3 +CDEBUG = -O2 + +#FFLAGS = -g +FFLAGS = -O3 -funroll-loops +CXXFLAGS = $(CDEBUG) -Dreal=double -DepsZ=1.0e-12 -DPROFILE_LEVEL=3 \ + -ISIM -IASM -IEig -IIntegrands -ILinAlg -IUtility \ + $(GOTOPT) $(SAMOPT) $(SAMGOPT) $(SLUOPT) $(PETSCOPT) $(BLASOPT) \ + $(VTFOPT) + +all: $(LIB) + +libs: + if [ -d ARPACK ]; cd ARPACK; make lib DIRS='$(PWD)/ARPACK/SRC $(PWD)/ARPACK/UTIL' + if [ -d SAM ]; then cd SAM/ASM; make; cd ../SPR; make; fi + if [ -d SuperLU ]; then cd SuperLU/SRC; make double; fi + if [ -d SuperLU_MT ]; then cd SuperLU_MT/SRC; make double; fi + if [ -d SAMG ]; then cd SAMG/samg; make; fi + if [ -d GLviewExpressWriter ]; then \ + cd GLviewExpressWriter/source; make install; fi + +$(LIB): $(OBJS) Makefile + $(AR) $(ARFLAGS) $@ $(OBJS) + +clean: + rm -f $(OBJS) + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile > /dev/null + mv Makefile.tmp Makefile + +depend: + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile > /dev/null + $(CXX) -M $(CXXFLAGS) $(CSRC) >> Makefile.tmp + mv Makefile Makefile.bak + mv Makefile.tmp Makefile + for dir in ASM Eig Integrands LinAlg SIM Utility; do \ + sed -i "/^[A-Za-z].*: $$dir/s/.*:/$$dir\/&/" Makefile; done + +tar: clean + cd Apps; make clean + tar cfv SplineFEM.tar README Makefile $(FSRC) $(CSRC) $(HDRS) \ + Apps/Makefile Apps/*.[Ch] + gzip SplineFEM.tar + +# DO NOT DELETE diff --git a/src/Makefile.proto b/src/Makefile.proto new file mode 100644 index 00000000..8a64ba86 --- /dev/null +++ b/src/Makefile.proto @@ -0,0 +1,69 @@ +# $Id: Makefile.proto,v 1.3 2010-03-16 08:57:47 kmo Exp $ +#=============================================================================== +# +# File: Makefile.proto +# +# Created: Oct 31 2008 +# +# Author: Knut Morten Okstad / SINTEF +# +# Description: +# Makefile for the spline-based elasticity solver (prototype), Linux platform. +# +#=============================================================================== + +CSRC = main_LinEl.C LinearEl.C VolumePatch.C LinEqSystem.C SAMSpline.C +OBJS = $(CSRC:.C=.o) + +PROGRAM = splineSolve.proto + +MYLIB = -L. -lSplineFEM +GOTOPT = -IGoTools/include +GOTLIB = -LGoTools/lib -lGoTrivariate -lGoToolsCore +BLASOPT = -DUSE_CBLAS +BLASLIB = -LARPACK -larpack -llapack -lblas +SLUOPT = -DHAS_SUPERLU -ISuperLU/SRC +SLULIB = -LSuperLU/lib -lsuperlu +SAMOPT = -DHAS_SPR -DUSE_F77SAM +SAMLIB = -LSAM -lSPR -lASM +SAMGOPT = -DHAS_SAMG -DSAMG_UNIX_LINUX -DSAMG_LCASE_USCORE -ISAMG +SAMGLIB = -LSAMG/samg -lamg_coo +VTFOPT = -DHAS_VTFAPI -IGLviewExpressWriter/include +VTFLIB = -LGLviewExpressWriter/lib -lVTFExpressAPI +LIBS = $(MYLIB) $(GOTLIB) $(SAMLIB) $(SAMGLIB) $(VTFLIB) $(SLULIB) $(BLASLIB) + +CXX = g++ + +#CDEBUG = -g -DINDEX_CHECK -DSP_DEBUG=3 +CDEBUG = -O2 + +CXXFLAGS = $(CDEBUG) -Dreal=double -IIntegrands -IEig -ILinAlg -IUtility \ + $(GOTOPT) $(BLASOPT) + +all: $(PROGRAM) + +libs: + cd ARPACK; make lib DIRS='$(PWD)/ARPACK/SRC $(PWD)/ARPACK/UTIL' + if [ -d SAM ]; then cd SAM/ASM; make; cd ../SPR; make; fi + if [ -d SuperLU ]; then cd SuperLU/SRC; make double; fi + if [ -d SAMG ]; then cd SAMG/samg; make; fi + if [ -d GLviewEpxressWriter ]; then \ + cd GLviewExpressWriter/source; make install; fi + +$(PROGRAM): $(OBJS) Makefile + $(CXX) -o $@ $(OBJS) $(LIBS) -lgfortran + +clean: + rm -f $(OBJS) + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile.proto > /dev/null + mv Makefile.tmp Makefile.proto + +depend: + sed '1,/^# DO NOT DELETE/w Makefile.tmp' < Makefile.proto > /dev/null + $(CXX) -M $(CXXFLAGS) $(MAIN) $(CSRC) >> Makefile.tmp + mv Makefile.proto Makefile.bak + mv Makefile.tmp Makefile.proto + for dir in Eig LinAlg Utility; do \ + sed -i "/^[A-Za-z].*: $$dir/s/.*:/$$dir\/&/" Makefile.proto; done + +# DO NOT DELETE diff --git a/src/PressureLoad.h b/src/PressureLoad.h new file mode 100644 index 00000000..3d26f009 --- /dev/null +++ b/src/PressureLoad.h @@ -0,0 +1,37 @@ +// $Id: PressureLoad.h,v 1.2 2009-05-05 09:25:09 kmo Exp $ +//============================================================================== +//! +//! \file PressureLoad.h +//! +//! \date Jan 27 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of a constant pressure load. +//! +//============================================================================== + +#ifndef _PRESSURELOAD_H +#define _PRESSURELOAD_H + + +/*! + \brief Struct for representing a constant pressure load. +*/ + +struct PLoad +{ + int volp; //!< Volume spline patch index [0,nvol] + int face; //!< Local face index on spline volume [-3,3] + int pdir; //!< Pressure direction [0,3] (0=normal pressure) + double p; //!< Actual pressure value + + //! \brief Constructor creating an initialized pressure instance. + //! \param[in] s Patch index + //! \param[in] f Local face index + //! \param[in] d Pressure direction + //! \param[in] v Pressure value + PLoad(int s, int f, int d, double v) : volp(s), face(f), pdir(d), p(v) {} +}; + +#endif diff --git a/src/README b/src/README new file mode 100644 index 00000000..4373cd4f --- /dev/null +++ b/src/README @@ -0,0 +1,37 @@ +$Id: README,v 1.3 2009-12-03 12:20:44 kmo Exp $ + +This is the source of the prototype spline/NURBS-based FEM solver +developed for the ICADA project at SINTEF ICT in Trondheim. + +The Makefile is for compilation on Linux. +It assumes that the libraries liblapack and libblas are installed. +In addition, the following dependencies on 3rd party libraries are present: + +Required: +GoTools - Download and install from the source code repository at + http://svn.math.sintef.no. See also the wiki documentation at + http://svn.math.sintef.no/index.php/Some_info_about_the_GoTools_and_SISL_SVN_repositories + The libraries are assumed found in the sub-folder GoTools/lib + The header files are assumed found in the sub-folder GoTools/include + The GoTools packages require that the libboost package is installed. + +ARPACK - Download and compile from http://www.caam.rice.edu/software/ARPACK + +Optional: +SAM - A few fortran subroutine from the SAM library by Kolbein Bell + can be used for the assembly of element matrices into system matrices. + In addition, the sparse solver SPR from this library can be used. + It is possible to compile without the SAM library, by commenting out + the symbols SAMOPT and SAMLIB in the Makefile. + +SuperLU - This is a public domain package for direct solution of non-symmetric + sparse equation systems. The source code is available from + http://crd.lbl.gov/~xiaoye/SuperLU. To compile without it, comment out + the symbol definitions SLUOPT and SLULIB in the Makefile + +SAMG - This is a commercial package (algebraic multigrid solver) and is + included for testing only. To compile without it, comment out the + symbol definitions SAMGOPT and SAMGLIB in the Makefile. + +GLviewExpressWriter - Ceetron's VTF API. If you don't have it, comment out the + symbol defintions VTFOPT and VTFLIB in the Makefile. diff --git a/src/SAMSpline.C b/src/SAMSpline.C new file mode 100644 index 00000000..4ad696ed --- /dev/null +++ b/src/SAMSpline.C @@ -0,0 +1,211 @@ +// $Id: SAMSpline.C,v 1.3 2009-08-18 08:49:56 kmo Exp $ +//============================================================================== +//! +//! \file SAMSpline.C +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices for SplineVolume models. +//! +//============================================================================== + +#include "SAMSpline.h" +#include "VolumePatch.h" +#include "LinEqSystem.h" +#include "MPC.h" + + +bool SAMSpline::init (const VolumePatch& patch) +{ + // Initialize some model size parameters + nnod = patch.getNoNodes(); + nel = patch.getNoElms(); + nceq = patch.getNoMPCs(); + ndof = 3*nnod; + + std::vector smod(1,(VolumePatch*)&patch); + + // Initialize the node/dof arrays (madof,msc) + initNodeDofs(smod); + + // Initialize the element connectivity arrays (mpmnpc,mmnpc) + initElementConn(smod); + + // Initialize the constraint equation arrays (mpmceq,mmceq,ttcc) + initConstraintEqs(smod); + + // Initialize the dof-to-equation connectivity array (meqn) + return initSystemEquations(); +} + + +bool SAMSpline::init (const std::vector& smod, int numNod) +{ + // Initialize some model size parameters + nnod = numNod; + for (size_t i = 0; i < smod.size(); i++) + { + if (numNod == 0) nnod += smod[i]->getNoNodes(); + nel += smod[i]->getNoElms(); + nceq += smod[i]->getNoMPCs(); + } + ndof = 3*nnod; + std::cout <<"\n\n >>> SAM model summary <<<" + <<"\nNumber of elements "<< nel + <<"\nNumber of nodes "<< nnod + <<"\nNumber of dofs "<< ndof << std::endl; + + // Initialize the node/dof arrays (madof,msc) + initNodeDofs(smod); + + // Initialize the element connectivity arrays (mpmnpc,mmnpc) + initElementConn(smod); + + // Initialize the constraint equation arrays (mpmceq,mmceq,ttcc) + initConstraintEqs(smod); + + // Initialize the dof-to-equation connectivity array (meqn) + bool status = initSystemEquations(); + std::cout <<"Number of unknowns "<< neq << std::endl; + return status; +} + + +void SAMSpline::initNodeDofs (const std::vector& smod) +{ + if (nnod < 1) return; + + // Initialize the node and dof arrays + madof = new int[nnod+1]; + msc = new int[ndof]; + + int n; madof[0] = 1; + for (n = 0; n < nnod; n++) + madof[n+1] = madof[n] + 3; + + for (n = 0; n < ndof; n++) + msc[n] = 1; + + std::vector::const_iterator bit; + for (size_t j = 0; j < smod.size(); j++) + for (bit = smod[j]->begin_BC(); bit != smod[j]->end_BC(); bit++) + { + n = bit->node; + msc[3*n-3] *= bit->CX; + msc[3*n-2] *= bit->CY; + msc[3*n-1] *= bit->CZ; + } +} + + +void SAMSpline::initElementConn (const std::vector& smod) +{ + if (nel < 1) return; + + // Find the size of the element connectivity array + size_t j; + IntMat::const_iterator eit; + for (j = 0; j < smod.size(); j++) + for (eit = smod[j]->begin_elm(); eit != smod[j]->end_elm(); eit++) + nmmnpc += eit->size(); + + // Initialize the element connectivity arrays + mpmnpc = new int[nel+1]; + mmnpc = new int[nmmnpc]; + int ip = mpmnpc[0] = 1; + for (j = 0; j < smod.size(); j++) + for (eit = smod[j]->begin_elm(); eit != smod[j]->end_elm(); eit++, ip++) + { + mpmnpc[ip] = mpmnpc[ip-1]; + for (size_t i = 0; i < eit->size(); i++) + mmnpc[(mpmnpc[ip]++)-1] = smod[j]->getNodeID(1+(*eit)[i]); + } +} + + +void SAMSpline::initConstraintEqs (const std::vector& smod) +{ + // Estimate the size of the constraint equation array + size_t j; + MPCIter cit; + for (j = 0; j < smod.size(); j++) + for (cit = smod[j]->begin_MPC(); cit != smod[j]->end_MPC(); cit++) + nmmceq += 1 + (*cit)->getNoMaster(); + + // Initialize the constraint equation arrays + mpmceq = new int[nceq+1]; + int ip = mpmceq[0] = 1; + if (nceq < 1) return; + mmceq = new int[nmmceq]; + ttcc = new real[nmmceq]; + for (j = 0; j < smod.size(); j++) + for (cit = smod[j]->begin_MPC(); cit != smod[j]->end_MPC(); cit++, ip++) + { + mpmceq[ip] = mpmceq[ip-1]; + + // Slave dof ... + int idof = madof[(*cit)->getSlave().node-1] + (*cit)->getSlave().dof - 1; + if (msc[idof-1] == 0) + { + std::cerr <<"SAM: Ignoring constraint equation for dof " + << idof <<" ("<< (*cit)->getSlave() + <<").\n This dof is already marked as FIXED."<< std::endl; + ip--; + nceq--; + continue; + } + else if (msc[idof-1] < 0) + { + std::cerr <<"SAM: Ignoring constraint equation for dof " + << idof <<" ("<< (*cit)->getSlave() + <<").\n This dof is already marked as SLAVE."<< std::endl; + ip--; + nceq--; + continue; + } + + int ipslv = (mpmceq[ip]++) - 1; + mmceq[ipslv] = idof; + ttcc[ipslv] = (*cit)->getSlave().coeff; + msc[idof-1] = -ip; + + // Master dofs ... + for (size_t i = 0; i < (*cit)->getNoMaster(); i++) + { + idof = madof[(*cit)->getMaster(i).node-1] + (*cit)->getMaster(i).dof-1; + if (msc[idof-1] > 0) + { + int ipmst = (mpmceq[ip]++) - 1; + mmceq[ipmst] = idof; + ttcc[ipmst] = (*cit)->getMaster(i).coeff; + } + else if (msc[idof-1] < 0) + { + // Master dof is constrained (unresolved chaining) + std::cerr <<"SAM: Chained MPCs detected" + <<", slave "<< (*cit)->getSlave() + <<", master "<< (*cit)->getMaster(i) + <<" (ignored)."<< std::endl; + mpmceq[ip] = mpmceq[ip-1]; + ip--; + nceq--; + break; + } + } + } + + // Reset the negative values in msc before calling SYSEQ + for (ip = 0; ip < ndof; ip++) + if (msc[ip] < 0) msc[ip] = 0; +} + + +bool SAMSpline::initForAssembly (LinEqSystem& sys, bool withRHS) const +{ + if (sys.K) sys.K->initAssembly(*this); + if (sys.M) sys.M->initAssembly(*this); + if (withRHS) sys.RHS.resize(neq,true); + return neq > 0 ? true : false; +} diff --git a/src/SAMSpline.h b/src/SAMSpline.h new file mode 100644 index 00000000..6f441a4d --- /dev/null +++ b/src/SAMSpline.h @@ -0,0 +1,63 @@ +// $Id: SAMSpline.h,v 1.2 2009-05-27 12:52:34 kmo Exp $ +//============================================================================== +//! +//! \file SAMSpline.h +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Assembly of FE matrices into system matrices for SplineVolume models. +//! +//============================================================================== + +#ifndef _SAM_SPLINE_H +#define _SAM_SPLINE_H + +#include "SAM.h" + +class VolumePatch; +class LinEqSystem; + + +/*! + \brief This is a sub-class of SAM with added functionality for spline models. + \details It contains some additional functions for initializing the SAM-data + for an FE model based on SplineVolume patches. +*/ + +class SAMSpline : public SAM +{ +public: + //! \brief Default constructor which initializes an empty SAMSpline object. + SAMSpline() : SAM() {} + //! \brief Empty destructor. + virtual ~SAMSpline() {} + + //! \brief Allocates the dynamic arrays and populates them with data + //! for a single-patch spline model. + bool init(const VolumePatch& model); + //! \brief Allocates the dynamic arrays and populates them with data + //! for a multi-patch spline model. + bool init(const std::vector& model, int numNod = 0); + + //! \brief Performs the necessary initialization of the system matrices + //! prior to the element assembly. + //! \details This method must be called once before the first call to + //! \a assembleSystem for a given load case or time step. + //! \param sys The system left-hand-side matrices and right-hand-side vector + //! \param withRHS If \a true, initialize the right-hand-side vector too + //! \return \e false if no free DOFs in the system, otherwise \e true + bool initForAssembly(LinEqSystem& sys, bool withRHS = false) const; + +protected: + //! \brief Initializes the nodal arrays \a MINEX, \a MADOF and \a MSC. + void initNodeDofs(const std::vector& p); + //! \brief Initializes the element topology arrays \a MPMNPC and \a MMNPC. + void initElementConn(const std::vector& p); + //! \brief Initializes the multi-point constraint arrays + //! \a MPMCEQ, \a MMCEQ and \a TTCC. + void initConstraintEqs(const std::vector& p); +}; + +#endif diff --git a/src/SIM/NonLinSIM.C b/src/SIM/NonLinSIM.C new file mode 100644 index 00000000..3dc9bd0e --- /dev/null +++ b/src/SIM/NonLinSIM.C @@ -0,0 +1,452 @@ +// $Id: NonLinSIM.C,v 1.28 2011-02-05 18:27:02 kmo Exp $ +//============================================================================== +//! +//! \file NonLinSIM.C +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Nonlinear solution driver for isogeometric FEM simulators. +//! +//============================================================================== + +#include "NonLinSIM.h" +#include "SIMbase.h" +#include "Profiler.h" +#include "Utilities.h" +#include + + +NonLinSIM::NonLinSIM (SIMbase* sim) +{ + model = sim; + nBlock = 0; + +#if SP_DEBUG > 2 + msgLevel = 100; // prints the linear solution vector if size < 100 +#else + msgLevel = 1; // prints the convergence history only +#endif + + // Default solution parameters + maxit = 20; + nupdat = 20; + startTime = 0.0; + stopTime = 1.0; + convTol = 1.0e-6; + divgLim = 1.0; + eta = 0.0; +} + + +NonLinSIM::~NonLinSIM () +{ + if (model) delete model; +} + + +bool NonLinSIM::parse (char* keyWord, std::istream& is) +{ + if (!strncasecmp(keyWord,"TIME_STEPPING",13)) + { + double dt; + tInc.clear(); + tInc.reserve(5); + std::istringstream cline(utl::readLine(is)); + + cline >> startTime >> stopTime >> dt; + if (cline.fail() || cline.bad()) return false; + while (!cline.fail() && !cline.bad()) + { + tInc.push_back(dt); + cline >> dt; + } + } + else if (!strncasecmp(keyWord,"NONLINEAR_SOLVER",16)) + { + std::istringstream cline(utl::readLine(is)); + cline >> maxit >> convTol; + if (cline.fail() || cline.bad()) return false; + + double tmp; + cline >> tmp; + if (!cline.fail() && !cline.bad()) + divgLim = tmp; + + int itmp; + cline >> itmp; + if (!cline.fail() && !cline.bad()) + nupdat = itmp; + else + nupdat = maxit; + + cline >> tmp; + if (!cline.fail() && !cline.bad()) + eta = tmp; + } + else + return model->parse(keyWord,is); + + return true; +} + + +void NonLinSIM::init (SolvePrm& param) +{ + param.startTime = startTime; + param.stopTime = stopTime; + param.tInc = tInc; + param.time.t = startTime; + param.time.dt = tInc.empty() ? stopTime-startTime : tInc.front(); + param.maxit = maxit; + param.nupdat = nupdat; + param.convTol = convTol; + param.divgLim = divgLim; + param.eta = eta; + + // TODO perhaps set initial conditions for time-dependent problems here + size_t nSols = model->getNoSolutions(); + if (nSols < 2) nSols = 2; + solution.resize(nSols); + for (size_t n = 0; n < nSols; n++) + solution[n].resize(model->getNoDOFs(),true); +} + + +bool NonLinSIM::advanceStep (SolvePrm& param) +{ + // Update solution vectors between time steps + for (int n = solution.size()-1; n > 0; n--) + solution[n] = solution[n-1]; + + return param.increment(); +} + + +bool NonLinSIM::SolvePrm::multiSteps () const +{ + if (tInc.empty()) return false; + + const double epsT = 1.0e-6; + return (startTime+(1.0+epsT)*tInc.front() < stopTime); +} + + +bool NonLinSIM::SolvePrm::increment () +{ + const double epsT = 1.0e-6; + + if (++step <= (int)tInc.size()) + time.dt = tInc[step-1]; + + if (time.t+time.dt*epsT >= stopTime) + return false; + + time.t += time.dt; + + if (time.t > stopTime) + { + // Adjust the size of the last time step + time.dt += stopTime - time.t; + time.t = stopTime; + } + + return true; +} + + +bool NonLinSIM::solveStep (SolvePrm& param, SIM::SolutionMode mode) +{ + PROFILE1("NonLinSIM::solveStep"); + + if (msgLevel >= 0 && myPid == 0) + std::cout <<"\n step="<< param.step + <<" time="<< param.time.t << std::endl; + + param.iter = 0; + param.alpha = 1.0; + + if (!model->updateDirichlet(param.time.t,&solution.front())) + return false; + + if (!model->setMode(mode)) + return false; + + bool newTangent = true; + if (!model->assembleSystem(param.time,solution,newTangent)) + return false; + + if (!model->extractLoadVec(residual)) + return false; + + if (!model->solveSystem(linsol,msgLevel-1)) + return false; + + while (param.iter <= param.maxit) + switch (this->checkConvergence(param)) + { + case CONVERGED: + if (!this->updateConfiguration(param)) + return false; + + model->setMode(SIM::RECOVERY); + return this->solutionNorms(param.time); + + case DIVERGED: + return false; + + default: + param.iter++; + if (!this->updateConfiguration(param)) + return false; + + if (param.iter == 1) + if (!model->updateDirichlet()) + return false; + + if (param.iter > param.nupdat) + { + newTangent = false; + model->setMode(SIM::RHS_ONLY); + } + else + model->setMode(mode); + + if (!model->assembleSystem(param.time,solution,newTangent)) + return false; + + if (!model->extractLoadVec(residual)) + return false; + + if (!model->solveSystem(linsol,msgLevel-1)) + return false; + + if (!this->lineSearch(param)) + return false; + } + + return false; +} + + +bool NonLinSIM::lineSearch (SolvePrm& param) +{ + if (param.eta <= 0.0) return true; // No line search + + double s0 = residual.dot(linsol); + double smin = fabs(s0); + double cmin = 1.0; + double ck = 1.0; + double cp = 1.0; + if (myPid == 0) + std::cout <<"\t0: ck="<< ck <<" sk="<< s0 << std::endl; + + param.alpha = 1.0; + for (int iter = 1; iter <= 10; iter++) + { + if (!this->updateConfiguration(param)) + return false; + + if (!model->assembleSystem(param.time,solution,false)) + return false; + + if (!model->extractLoadVec(residual)) + return false; + + double sk = residual.dot(linsol); + if (myPid == 0) + std::cout <<"\t"<< iter <<": ck="<< ck <<" sk="<< sk << std::endl; + + if (fabs(sk) < param.eta*fabs(s0)) + { + param.alpha = 0.0; + return true; + } + else if (fabs(sk) < smin) + { + smin = fabs(sk); + cmin = ck; + } + + cp = ck; + ck *= s0/(s0-sk); + if (ck > 1.5) + ck = 1.5; + else if (ck < 0.5) + ck = 0.5; + + if (fabs(ck-cp) < 0.5*param.eta*fabs(ck+cp)) + { + param.alpha = 0.0; + return true; + } + + param.alpha = ck - param.alpha; + } + + param.alpha = cmin - param.alpha; + return true; +} + + +NonLinSIM::ConvStatus NonLinSIM::checkConvergence (SolvePrm& param) +{ + static double prevNorm = 0.0; + static int nIncrease = 0; + + double norm, enorm, resNorm, linsolNorm; + model->iterationNorms(linsol,residual,enorm,resNorm,linsolNorm); + + if (param.iter == 0) + { + if (enorm > param.refNorm) + param.refNorm = enorm; + norm = prevNorm = 1.0; + nIncrease = 0; + } + else + norm = fabs(enorm)/param.refNorm; + + if (msgLevel > 0 && myPid == 0 && !solution.empty()) + { + // Print convergence history + std::ios::fmtflags oldFlags = std::cout.flags(std::ios::scientific); + std::streamsize oldPrec = std::cout.precision(3); + std::cout <<" iter="<< param.iter + <<" conv="<< norm + <<" enen="<< enorm + <<" resn="<< resNorm + <<" incn="<< linsolNorm << std::endl; + std::cout.flags(oldFlags); + std::cout.precision(oldPrec); + } + + // Check for convergence + if (norm < param.convTol) + return CONVERGED; + + // Check for divergence (increasing norm in three consequtive iterations) + if (norm <= prevNorm) + nIncrease = 0; + else if (++nIncrease > 1 || norm > param.divgLim) + return DIVERGED; + + prevNorm = norm; + return NONE; +} + + +bool NonLinSIM::updateConfiguration (SolvePrm& param) +{ + if (solution.empty()) return false; + + if (solution.front().empty()) + solution.front() = linsol; + else if (param.alpha != 0.0) + solution.front().add(linsol,param.alpha); + + return true; +} + + +bool NonLinSIM::solutionNorms (const TimeDomain& time, const char* compName) +{ + if (msgLevel < 0 || solution.empty()) return true; + + const int nsd = model->getNoSpaceDim(); + + size_t iMax[nsd]; + double dMax[nsd]; + double normL2 = model->solutionNorms(solution.front(),dMax,iMax); + + if (!model->solutionNorms(time,solution,eNorm,gNorm)) + gNorm.clear(); + + if (myPid == 0) + { + std::cout <<" Primary solution summary: L2-norm : "<< normL2; + char D = 'X'; + for (int d = 0; d < nsd; d++, D++) + std::cout <<"\n Max "<< D <<'-'<< compName + <<" : "<< dMax[d] <<" node "<< iMax[d]; + if (gNorm.size() > 0) + { + std::cout <<"\n Energy norm: |u^h| = a(u^h,u^h)^0.5 : "<< gNorm(1); + int oldPrec = std::cout.precision(10); + std::cout <<"\t a(u^h,u^h) = "<< gNorm(1)*gNorm(1); + std::cout.precision(oldPrec); + } + if (gNorm.size() > 1 && gNorm(2) != 0.0) + { + std::cout <<"\n External energy: ((f,u^h)+(t,u^h))^0.5 : "<< gNorm(2); + int oldPrec = std::cout.precision(10); + std::cout <<"\t(f,u)+(t,u) = "<< gNorm(2)*gNorm(2); + std::cout.precision(oldPrec); + } + std::cout << std::endl; + } + + return true; +} + + +bool NonLinSIM::saveModel (char* fileName, int format, int* nViz) +{ + PROFILE1("NonLinSIM::saveModel"); + + nBlock = 0; // initialize the result block counter + + // Write VTF-file with model geometry + if (!model->writeGlv(fileName,nViz,format)) + return false; + + // Write Dirichlet boundary conditions + return model->writeGlvBC(nViz,nBlock); +} + + +bool NonLinSIM::saveStep (int iStep, double time, int* nViz, bool psolOnly) +{ + PROFILE1("NonLinSIM::saveStep"); + + // Negative iStep means we are saving the initial state only + if (!model->setMode(iStep < 0 ? SIM::INIT : SIM::RECOVERY)) + return false; + else if (iStep < 0) + iStep = -iStep; + + // Write boundary tractions, if any + if (!psolOnly) + if (!model->writeGlvT(iStep,nBlock)) + return false; + + // Write residual force vector, but only when no extra visualization points + if (!psolOnly && nViz[0] == 2 && nViz[1] <= 2 && nViz[2] <= 2) + if (!model->writeGlvV(residual,"Residual forces",nViz,iStep,nBlock)) + return false; + + // Write solution fields + if (!solution.empty()) + if (!model->writeGlvS(solution.front(),nViz,iStep,nBlock,psolOnly)) + return false; + + // Write element norms (only when no additional visualization points are used) + if (!psolOnly && nViz[0] == 2 && nViz[1] <= 2 && nViz[2] <= 2) + if (!model->writeGlvN(eNorm,iStep,nBlock)) + return false; + + // Write time/load step information + return model->writeGlvStep(iStep,time); +} + + +void NonLinSIM::dumpStep (int iStep, double time, std::ostream& os, + bool withID) const +{ + if (withID) + os <<"\n\n# Dump of primary solution at Step "<< iStep + <<", Time="<< time <<"\n"; + + model->dumpPrimSol(solution.front(),os,withID); +} diff --git a/src/SIM/NonLinSIM.h b/src/SIM/NonLinSIM.h new file mode 100644 index 00000000..3bd26500 --- /dev/null +++ b/src/SIM/NonLinSIM.h @@ -0,0 +1,147 @@ +// $Id: NonLinSIM.h,v 1.15 2011-01-28 15:28:54 kmo Exp $ +//============================================================================== +//! +//! \file NonLinSIM.h +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Nonlinear solution driver for isogeometric FEM simulators. +//! +//============================================================================== + +#ifndef _NONLIN_SIM_H +#define _NONLIN_SIM_H + +#include "SIMinput.h" +#include "SIMenums.h" +#include "TimeDomain.h" +#include "MatVec.h" + +class SIMbase; + + +/*! + \brief Nonlinear solution driver for isogeometric FEM simulators. + \details This class contains data and methods for computing the nonlinear + solution to a FE problem based on splines/NURBS basis functions, through + Newton-Raphson iterations. +*/ + +class NonLinSIM : public SIMinput +{ +public: + //! \brief The constructor initialized default solution parameters. + //! \param sim Pointer to the spline FE model + NonLinSIM(SIMbase* sim = 0); + //! \brief The destructor frees the dynamically allocated FE model object. + virtual ~NonLinSIM(); + + /*! + \brief A struct for nonlinear solution parameters. + */ + struct SolvePrm + { + int step; //!< Load/time step counter + int& iter; //!< Iteration counter + int maxit; //!< Maximum number of iterations + int nupdat; //!< Number of iterations with updated tangent + double startTime; //!< Start (pseudo)time of simulation + double stopTime; //!< Stop (pseudo)time of simulation + RealArray tInc; //!< Time (or pseudo time) increment size(s) + double convTol; //!< Relative convergence tolerance + double divgLim; //!< Relative divergence limit + double refNorm; //!< Reference energy norm used in convergence checks + double alpha; //!< Iteration acceleration parameter (line search) + double eta; //!< Line searce tolerance + TimeDomain time; //!< Time domain data + + //! \brief The constructor initializes the counters to zero. + SolvePrm() : iter(time.it) { step = maxit = 0; refNorm = alpha = 1.0; } + //! \brief Returns \e true if the simulation consists of several load steps. + bool multiSteps() const; + //! \brief Increments the time/load step. + //! \return \e true, if we have reached the end of the simulation. + bool increment(); + }; + + //! \brief Initializes solution parameters object with values read from file. + virtual void init(SolvePrm& param); + //! \brief Advances the time/load step one step forward. + virtual bool advanceStep(SolvePrm& param); + + //! \brief Opens a new VTF-file and writes the model geometry to it. + //! \param[in] fileName File name used to construct the VTF-file name from + //! \param[in] format Format of VTF-file (0=ASCII, 1=BINARY) + //! \param[in] nViz Number of visualization points over a knot-span + bool saveModel(char* fileName, int format, int* nViz); + + //! \brief Solves the nonlinear equations by Newton-Raphson iterations. + //! \param param Solution algorithm parameters + //! \param[in] mode Solution mode to use for this step + bool solveStep(SolvePrm& param, SIM::SolutionMode mode = SIM::STATIC); + + //! \brief Computes and prints some solution norm quantities. + //! \param[in] time Parameters for nonlinear/time-dependent simulations. + //! \param[in] compName Solution name to be used in the norm output + bool solutionNorms(const TimeDomain& time, + const char* compName = "displacement"); + + //! \brief Saves the converged results to VTF file of a given load/time step. + //! \param[in] iStep Load/time step identifier + //! \param[in] time Current time/load parameter + //! \param[in] nViz Number of visualization points over each knot-span + //! \param[in] psolOnly If \e true, skip secondary solution field output + bool saveStep(int iStep, double time, int* nViz, bool psolOnly = false); + + //! \brief Dumps the primary solution to given stream for inspection. + //! \param[in] iStep Load/time step identifier + //! \param[in] time Current time/load parameter + //! \param[in] os The output streeam to write to. + //! \param[in] withID If \e true, write node ID and coordinates too + void dumpStep(int iStep, double time, std::ostream& os, + bool withID = true) const; + +protected: + //! \brief Convergence status enum. + enum ConvStatus { NONE, CONVERGED, DIVERGED }; + + //! \brief Perform line search to accelerate convergence. + virtual bool lineSearch(SolvePrm& param); + //! \brief Checks whether the nonlinear iterations have converged or diverged. + virtual ConvStatus checkConvergence(SolvePrm& param); + //! \brief Updates configuration variables (solution vector) in an iteration. + virtual bool updateConfiguration(SolvePrm& param); + + //! \brief Parses a data section from an input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + +private: + SIMbase* model; //!< The isogeometric FE model + +protected: + Vector linsol; //!< Linear solution vector + Vector residual; //!< Residual force vector + std::vector solution; //!< Total solution vectors + +private: + // Nonlinear solution algorithm parameters + double startTime; //!< Start time of the simulation + double stopTime; //!< Stop time of the simulation + RealArray tInc; //!< Time increment size(s) + double convTol; //!< Relative convergence tolerance + double divgLim; //!< Relative divergence limit + double eta; //!< Line search tolerance + int maxit; //!< Maximum number of iterations in a time/load step + int nupdat; //!< Number of iterations with updated tangent + + // Post-processing attributes + int nBlock; //!< Running VTF result block counter + Vector gNorm; //!< Global norms + Matrix eNorm; //!< Element norms +}; + +#endif diff --git a/src/SIM/Property.h b/src/SIM/Property.h new file mode 100644 index 00000000..bde520e6 --- /dev/null +++ b/src/SIM/Property.h @@ -0,0 +1,52 @@ +// $Id: Property.h,v 1.4 2010-01-29 13:07:14 kmo Exp $ +//============================================================================== +//! +//! \file Property.h +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of a distributed physical property. +//! +//============================================================================== + +#ifndef _PROPERTY_H +#define _PROPERTY_H + +#include + + +/*! + \brief Struct for representing a distributed physical property. +*/ + +struct Property +{ + //! \brief The available property types. + enum Type + { + UNDEFINED, + MATERIAL, + DIRICHLET, + DIRICHLET_INHOM, + NEUMANN + }; + + Type pcode; //!< Physical property code + size_t pindx; //!< Physical property index + size_t patch; //!< Patch index [0,nPatch> + char lindx; //!< Local entity index which is assigned the property + char ldim; //!< Local entity dimension flag [0,3] + + //! \brief Default constructor. + Property() : pcode(UNDEFINED), pindx(0), patch(0), ldim(0), lindx(0) {} + + //! \brief Constructor creating an initialized property instance. + Property(Type t, size_t px, size_t p, char ld, char lx = 0) : + pcode(t), pindx(px), patch(p), ldim(ld), lindx(lx) {} +}; + +typedef std::vector PropertyVec; //!< Vector of properties + +#endif diff --git a/src/SIM/SIM1D.C b/src/SIM/SIM1D.C new file mode 100644 index 00000000..ea625fac --- /dev/null +++ b/src/SIM/SIM1D.C @@ -0,0 +1,391 @@ +// $Id: SIM1D.C,v 1.4 2011-01-05 12:54:55 kmo Exp $ +//============================================================================== +//! +//! \file SIM1D.C +//! +//! \date Feb 04 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 1D NURBS-based FEM analysis. +//! +//============================================================================== + +#include "SIM1D.h" +#include "ASMs1D.h" +#include "ASMs1DLag.h" +#include "ASMs1DSpec.h" +#include "Functions.h" +#include "Utilities.h" +#include +#include +#include +#include + + +SIM1D::SIM1D (unsigned char n_f) +{ + nf = n_f; +} + + +bool SIM1D::parse (char* keyWord, std::istream& is) +{ + char* cline = 0; + if (!strncasecmp(keyWord,"PATCHES",7)) + { + ASMbase* pch = 0; + int npatch = atoi(keyWord+7); + std::cout <<"\nNumber of patches: "<< npatch << std::endl; + for (int i = 0; i < npatch && (cline = utl::readLine(is)); i++) + { + switch (discretization) { + case Lagrange: + pch = new ASMs1DLag(strtok(cline," "),1,nf); + break; + case Spectral: + pch = new ASMs1DSpec(strtok(cline," "),1,nf); + break; + default: + pch = new ASMs1D(strtok(cline," "),1,nf); + } + if (pch->empty()) + delete pch; + else + myModel.push_back(pch); + } + + if (myModel.size() < npatch) + { + std::cerr <<" *** SIM1D::parse: Expected "<< npatch + <<" patches but could read only "<< myModel.size() + << std::endl; + return false; + } + } + + else if (!strncasecmp(keyWord,"PATCHFILE",9)) + { + size_t i = 9; while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isp(keyWord+i); + + ASMbase* pch = 0; + for (int patchNo = 1; isp.good(); patchNo++) + { + std::cout <<"Reading patch "<< patchNo << std::endl; + switch (discretization) { + case Lagrange: + pch = new ASMs1DLag(isp,1,nf); + break; + case Spectral: + pch = new ASMs1DSpec(isp,1,nf); + break; + default: + pch = new ASMs1D(isp,1,nf); + } + if (pch->empty()) + delete pch; + else + myModel.push_back(pch); + } + + if (myModel.empty()) + { + std::cerr <<" *** SIM1D::parse: No patches read"<< std::endl; + return false; + } + } + + else if (!strncasecmp(keyWord,"PROPERTYFILE",12)) + { + bool oneBasedIdx = keyWord[12] == '1'; + size_t i = (oneBasedIdx || keyWord[12] == '0') ? 13 : 12; + while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isp(keyWord+i); + while (isp.good()) + { + Property p; + int ldim, lindx = 0; + isp >> p.pindx >> p.patch >> ldim; + if (ldim < 1) isp >> lindx; + + if (!oneBasedIdx) + { + // We always require the item indices to be 1-based + ++p.patch; + ++lindx; + } + + p.ldim = ldim; + p.lindx = lindx; + if (isp.good()) + myProps.push_back(p); + } + } + + else if (!strncasecmp(keyWord,"DIRICHLET",9)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + + int ndir = atoi(keyWord+9); + std::cout <<"\nNumber of Dirichlet properties: "<< ndir << std::endl; + for (int i = 0; i < ndir && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + double d = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + std::cout <<"\tDirichlet code "<< code <<": "; + if (d == 0.0) + { + this->setPropertyType(code,Property::DIRICHLET); + std::cout <<"(fixed)"; + } + else + { + this->setPropertyType(code,Property::DIRICHLET_INHOM); + if (cline = strtok(NULL," ")) + myScalars[code] = const_cast(utl::parseRealFunc(cline,d)); + else + { + std::cout << d; + myScalars[code] = new ConstFunc(d); + } + } + std::cout << std::endl; + } + } + + // The remaining keywords are retained for backward compatibility with the + // prototype version. They enable direct specification of topology and + // properties as well as uniform refinement without using the GPM module. + + else if (!strncasecmp(keyWord,"REFINE",6)) + { + int nref = atoi(keyWord+6); + std::cout <<"\nNumber of patch refinements: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + bool uniform = !strchr(cline,'.'); + int patch = atoi(strtok(cline," ")); + if (patch == 0 || abs(patch) > myModel.size()) + { + std::cerr <<" *** SIM1D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + if (uniform) + { + int addu = atoi(strtok(NULL," ")); + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1 <<" "<< addu << std::endl; + static_cast(myModel[j])->uniformRefine(addu); + } + } + else + { + RealArray xi; + while (cline = strtok(NULL," ")) + xi.push_back(atof(cline)); + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1; + for (size_t i = 0; i < xi.size(); i++) + std::cout <<" "<< xi[i]; + std::cout << std::endl; + static_cast(myModel[j])->refine(xi); + } + } + } + } + + else if (!strncasecmp(keyWord,"RAISEORDER",10)) + { + int nref = atoi(keyWord+10); + std::cout <<"\nNumber of order raise: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int addu = atoi(strtok(NULL," ")); + if (patch == 0 || abs(patch) > myModel.size()) + { + std::cerr <<" *** SIM1D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRaising order of P"<< j+1 + <<" "<< addu << std::endl; + static_cast(myModel[j])->raiseOrder(addu); + } + } + } + + else if (!strncasecmp(keyWord,"TOPOLOGY",8)) + { + if (!this->createFEMmodel()) return false; + + int ntop = atoi(keyWord+8); + std::cout <<"\nNumber of patch connections: "<< ntop << std::endl; + for (int i = 0; i < ntop && (cline = utl::readLine(is)); i++) + { + int master = atoi(strtok(cline," ")); + int mVert = atoi(strtok(NULL," ")); + int slave = atoi(strtok(NULL," ")); + int sVert = atoi(strtok(NULL," ")); + if (master == slave || + master < 1 || master > myModel.size() || + slave < 1 || slave > myModel.size()) + { + std::cerr <<" *** SIM1D::parse: Invalid patch indices " + << master <<" "<< slave << std::endl; + return false; + } + std::cout <<"\tConnecting P"<< slave <<" V"<< sVert + <<" to P"<< master <<" E"<< mVert << std::endl; + ASMs1D* spch = static_cast(myModel[slave-1]); + ASMs1D* mpch = static_cast(myModel[master-1]); + if (!spch->connectPatch(sVert,*mpch,mVert)) + return false; + } + } + + else if (!strncasecmp(keyWord,"PERIODIC",8)) + { + if (!this->createFEMmodel()) return false; + + int nper = atoi(keyWord+8); + std::cout <<"\nNumber of periodicities: "<< nper << std::endl; + for (int i = 0; i < nper && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + if (patch < 1 || patch > myModel.size()) + { + std::cerr <<" *** SIM1D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tPeriodic P" << patch << std::endl; + static_cast(myModel[patch-1])->closeEnds(); + } + } + + else if (!strncasecmp(keyWord,"CONSTRAINTS",11)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int ncon = atoi(keyWord+11); + std::cout <<"\nNumber of constraints: "<< ncon << std::endl; + for (int i = 0; i < ncon && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int pvert = atoi(strtok(NULL," ")); + int bcode = atoi(strtok(NULL," ")); + double pd = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + if (pd == 0.0) + { + if (!this->addConstraint(patch,pvert,0,bcode%1000)) + return false; + } + else + { + int code = 1000 + bcode; + while (myScalars.find(code) != myScalars.end()) + code += 1000; + + if (!this->addConstraint(patch,pvert,0,bcode%1000,code)) + return false; + + if (cline = strtok(NULL," ")) + myScalars[code] = const_cast(utl::parseRealFunc(cline,pd)); + else + myScalars[code] = new ConstFunc(pd); + } + std::cout << std::endl; + } + } + + else if (!strncasecmp(keyWord,"FIXPOINTS",9)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int nfix = atoi(keyWord+9); + std::cout <<"\nNumber of fixed points: "<< nfix << std::endl; + for (int i = 0; i < nfix && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + double rx = atof(strtok(NULL," ")); + int bcode = (cline = strtok(NULL," ")) ? atoi(cline) : 123; + if (patch < 1 || patch > myModel.size()) + { + std::cerr <<" *** SIM1D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tConstraining P"<< patch + <<" point at "<< rx <<" with code "<< bcode << std::endl; + static_cast(myModel[patch-1])->constrainNode(rx,bcode); + } + } + else + return this->SIMbase::parse(keyWord,is); + + return true; +} + + +/*! + \brief Local-scope convenience function for error message generation. +*/ + +static bool constrError (const char* lab, int idx) +{ + std::cerr <<" *** SIM1D::addConstraint: Invalid "<< lab << idx << std::endl; + return false; +} + + +bool SIM1D::addConstraint (int patch, int lndx, int, int dirs, int code) +{ + if (patch < 1 || patch > myModel.size()) + return constrError("patch index ",patch); + + std::cout <<"\tConstraining P"<< patch + << " V" << lndx <<" in direction(s) "<< dirs; + if (code) std::cout <<" code = "<< code <<" "; + + ASMs1D* pch = static_cast(myModel[patch-1]); + switch (lndx) // Vertex constraints + { + case 1: pch->constrainNode(0.0,code); break; + case 2: pch->constrainNode(1.0,code); break; + default: std::cout << std::endl; + return constrError("vertex index ",lndx); + } + + return true; +} + + +void SIM1D::setQuadratureRule (size_t ng) +{ + for (size_t i = 0; i < myModel.size(); i++) + if (!myModel.empty()) + static_cast(myModel[i])->setGauss(ng); +} diff --git a/src/SIM/SIM1D.h b/src/SIM/SIM1D.h new file mode 100644 index 00000000..bfde1781 --- /dev/null +++ b/src/SIM/SIM1D.h @@ -0,0 +1,56 @@ +// $Id: SIM1D.h,v 1.1 2010-09-02 15:28:01 kmo Exp $ +//============================================================================== +//! +//! \file SIM1D.h +//! +//! \date Feb 04 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 1D NURBS-based FEM analysis. +//! +//============================================================================== + +#ifndef _SIM_1D_H +#define _SIM_1D_H + +#include "SIMbase.h" + + +/*! + \brief Driver class for 1D NURBS-based FEM solver. +*/ + +class SIM1D : public SIMbase +{ +public: + //! \brief Default constructor. + //! \param[in] n_f Number of components in the primary solution field + SIM1D(unsigned char n_f = 1); + //! \brief Empty destructor. + virtual ~SIM1D() {} + + //! \brief Defines the spatial numerical integration scheme to use. + //! \param[in] ng Number of Gauss points in each parameter direction + virtual void setQuadratureRule(size_t ng); + +protected: + //! \brief Parses a data section from an input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + + //! \brief Preprocesses a user-defined Dirichlet boundary property. + //! \param[in] patch 1-based index of the patch to receive the property + //! \param[in] lndx Local index of the boundary item to receive the property + //! \param[in] ldim Dimension of the boundary item to receive the property + //! \param[in] dirs Which local DOFs to constrain + //! \param[in] code In-homegeneous Dirichlet condition property code + virtual bool addConstraint(int patch, int lndx, int ldim, + int dirs, int code = 0); + +protected: + unsigned char nf; //!< Number of scalar fields +}; + +#endif diff --git a/src/SIM/SIM2D.C b/src/SIM/SIM2D.C new file mode 100644 index 00000000..8aa3f2e7 --- /dev/null +++ b/src/SIM/SIM2D.C @@ -0,0 +1,487 @@ +// $Id: SIM2D.C,v 1.17 2011-01-05 12:54:56 kmo Exp $ +//============================================================================== +//! +//! \file SIM2D.C +//! +//! \date Feb 04 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 2D NURBS-based FEM analysis. +//! +//============================================================================== + +#include "SIM2D.h" +#include "ASMs2Dmx.h" +#include "ASMs2DmxLag.h" +#include "ASMs2DSpec.h" +#include "Functions.h" +#include "Utilities.h" +#include +#include +#include +#include + + +SIM2D::SIM2D (unsigned char n1, unsigned char n2) +{ + nf[0] = n1; + nf[1] = n2; +} + + +bool SIM2D::parse (char* keyWord, std::istream& is) +{ + char* cline = 0; + if (!strncasecmp(keyWord,"PATCHES",7)) + { + ASMbase* pch = 0; + int npatch = atoi(keyWord+7); + std::cout <<"\nNumber of patches: "<< npatch << std::endl; + for (int i = 0; i < npatch && (cline = utl::readLine(is)); i++) + { + cline = strtok(cline," "); + switch (discretization) { + case Lagrange: + if (nf[1] > 0) + pch = new ASMs2DmxLag(cline,2,nf[0],nf[1]); + else + pch = new ASMs2DLag(cline,2,nf[0]); + break; + case Spectral: + pch = new ASMs2DSpec(cline,2,nf[0]); + break; + default: + if (nf[1] > 0) + pch = new ASMs2Dmx(cline,2,nf[0],nf[1]); + else + pch = new ASMs2D(cline,2,nf[0]); + } + if (pch->empty() || this->getLocalPatchIndex(i+1) < 1) + delete pch; + else + myModel.push_back(pch); + } + + if (myModel.size() < npatch) + { + std::cerr <<" *** SIM2D::parse: Expected "<< npatch + <<" patches but could read only "<< myModel.size() + << std::endl; + return false; + } + } + + else if (!strncasecmp(keyWord,"PATCHFILE",9)) + { + size_t i = 9; while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isp(keyWord+i); + + ASMbase* pch = 0; + for (int patchNo = 1; isp.good(); patchNo++) + { + std::cout <<"Reading patch "<< patchNo << std::endl; + switch (discretization) { + case Lagrange: + if (nf[1] > 0) + pch = new ASMs2DmxLag(isp,2,nf[0],nf[1]); + else + pch = new ASMs2DLag(isp,2,nf[0]); + break; + case Spectral: + pch = new ASMs2DSpec(isp,2,nf[0]); + break; + default: + if (nf[1] > 0) + pch = new ASMs2Dmx(isp,2,nf[0],nf[1]); + else + pch = new ASMs2D(isp,2,nf[0]); + } + if (pch->empty() || this->getLocalPatchIndex(patchNo) < 1) + delete pch; + else + myModel.push_back(pch); + } + + if (myModel.empty()) + { + std::cerr <<" *** SIM2D::parse: No patches read"<< std::endl; + return false; + } + } + + else if (!strncasecmp(keyWord,"NODEFILE",8)) + { + if (!this->createFEMmodel()) return false; + + bool oneBasedIdx = keyWord[8] == '1'; + size_t i = (oneBasedIdx || keyWord[8] == '0') ? 9 : 8; + while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isn(keyWord+i); + while (isn.good()) + { + int patch = 0; + isn >> patch; + if (!oneBasedIdx) ++patch; + int pid = this->getLocalPatchIndex(patch); + if (pid < 0) return false; + + ASMs2D::BlockNodes n; + for (i = 0; i < 4 && isn.good(); i++) + isn >> n.ibnod[i]; + for (i = 0; i < 4 && isn.good(); i++) + isn >> n.edges[i].icnod >> n.edges[i].incr; + isn >> n.iinod; + + if (!oneBasedIdx) + { + // We always require the node numbers to be 1-based + for (i = 0; i < 4; i++) ++n.ibnod[i]; + for (i = 0; i < 4; i++) ++n.edges[i].icnod; + ++n.iinod; + } + + if (isn.good() && pid > 0) + if (!static_cast(myModel[pid-1])->assignNodeNumbers(n)) + { + std::cerr <<" *** SIM2D::parse: Failed to assign node numbers" + <<" for patch "<< patch << std::endl; + return false; + } + } + } + + else if (!strncasecmp(keyWord,"PROPERTYFILE",12)) + { + bool oneBasedIdx = keyWord[12] == '1'; + size_t i = (oneBasedIdx || keyWord[12] == '0') ? 13 : 12; + while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isp(keyWord+i); + while (isp.good()) + { + Property p; + int ldim, lindx = 0; + isp >> p.pindx >> p.patch >> ldim; + if (ldim < 2) isp >> lindx; + + if (!oneBasedIdx) + { + // We always require the item indices to be 1-based + ++p.patch; + ++lindx; + } + + p.ldim = ldim; + p.lindx = lindx; + p.patch = this->getLocalPatchIndex(p.patch); + if (p.patch > 0 && isp.good()) + myProps.push_back(p); + } + } + + else if (!strncasecmp(keyWord,"DIRICHLET",9)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + + int ndir = atoi(keyWord+9); + std::cout <<"\nNumber of Dirichlet properties: "<< ndir << std::endl; + for (int i = 0; i < ndir && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + double d = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + std::cout <<"\tDirichlet code "<< code <<": "; + if (d == 0.0) + { + this->setPropertyType(code,Property::DIRICHLET); + std::cout <<"(fixed)"; + } + else + { + this->setPropertyType(code,Property::DIRICHLET_INHOM); + if (cline = strtok(NULL," ")) + myScalars[code] = const_cast(utl::parseRealFunc(cline,d)); + else + { + std::cout << d; + myScalars[code] = new ConstFunc(d); + } + } + std::cout << std::endl; + } + } + + // The remaining keywords are retained for backward compatibility with the + // prototype version. They enable direct specification of topology and + // properties as well as grid refinement without using the GPM module. + + else if (!strncasecmp(keyWord,"REFINE",6)) + { + int nref = atoi(keyWord+6); + std::cout <<"\nNumber of patch refinements: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + bool uniform = !strchr(cline,'.'); + int patch = atoi(strtok(cline," ")); + if (patch == 0 || abs(patch) > myModel.size()) + { + std::cerr <<" *** SIM2D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + if (uniform) + { + int addu = atoi(strtok(NULL," ")); + int addv = atoi(strtok(NULL," ")); + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1 + <<" "<< addu <<" "<< addv << std::endl; + ASMs2D* pch = static_cast(myModel[j]); + pch->uniformRefine(0,addu); + pch->uniformRefine(1,addv); + } + } + else + { + int dir = atoi(strtok(NULL," ")); + RealArray xi; + while (cline = strtok(NULL," ")) + xi.push_back(atof(cline)); + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1 <<" dir="<< dir; + for (size_t i = 0; i < xi.size(); i++) + std::cout <<" "<< xi[i]; + std::cout << std::endl; + static_cast(myModel[j])->refine(dir-1,xi); + } + } + } + } + + else if (!strncasecmp(keyWord,"RAISEORDER",10)) + { + int nref = atoi(keyWord+10); + std::cout <<"\nNumber of order raise: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int addu = atoi(strtok(NULL," ")); + int addv = atoi(strtok(NULL," ")); + if (patch == 0 || abs(patch) > myModel.size()) + { + std::cerr <<" *** SIM2D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRaising order of P"<< j+1 + <<" "<< addu <<" "<< addv << std::endl; + static_cast(myModel[j])->raiseOrder(addu,addv); + } + } + } + + else if (!strncasecmp(keyWord,"TOPOLOGY",8)) + { + if (!this->createFEMmodel()) return false; + + int ntop = atoi(keyWord+8); + std::cout <<"\nNumber of patch connections: "<< ntop << std::endl; + for (int i = 0; i < ntop && (cline = utl::readLine(is)); i++) + { + int master = atoi(strtok(cline," ")); + int mEdge = atoi(strtok(NULL," ")); + int slave = atoi(strtok(NULL," ")); + int sEdge = atoi(strtok(NULL," ")); + bool rever = (cline = strtok(NULL," ")) ? cline[0] == 'R' : false; + if (master == slave || + master < 1 || master > myModel.size() || + slave < 1 || slave > myModel.size()) + { + std::cerr <<" *** SIM2D::parse: Invalid patch indices " + << master <<" "<< slave << std::endl; + return false; + } + std::cout <<"\tConnecting P"<< slave <<" E"<< sEdge + <<" to P"<< master <<" E"<< mEdge + <<" reversed? "<< rever << std::endl; + ASMs2D* spch = static_cast(myModel[slave-1]); + ASMs2D* mpch = static_cast(myModel[master-1]); + if (!spch->connectPatch(sEdge,*mpch,mEdge,rever)) + return false; + } + } + + else if (!strncasecmp(keyWord,"PERIODIC",8)) + { + if (!this->createFEMmodel()) return false; + + int nper = atoi(keyWord+8); + std::cout <<"\nNumber of periodicities: "<< nper << std::endl; + for (int i = 0; i < nper && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int pedir = atoi(strtok(NULL," ")); + if (patch < 1 || patch > myModel.size()) + { + std::cerr <<" *** SIM2D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tPeriodic "<< char('H'+pedir) <<"-direction P"<< patch + << std::endl; + static_cast(myModel[patch-1])->closeEdges(pedir); + } + } + + else if (!strncasecmp(keyWord,"CONSTRAINTS",11)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int ncon = atoi(keyWord+11); + std::cout <<"\nNumber of constraints: "<< ncon << std::endl; + for (int i = 0; i < ncon && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int pedge = atoi(strtok(NULL," ")); + int bcode = atoi(strtok(NULL," ")); + double pd = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + + patch = this->getLocalPatchIndex(patch); + if (patch < 1) continue; + + int ldim = pedge >= 0 ? 1 : 0; + if (pd == 0.0) + { + if (!this->addConstraint(patch,abs(pedge),ldim,bcode%1000)) + return false; + } + else + { + int code = 1000 + bcode; + while (myScalars.find(code) != myScalars.end()) + code += 1000; + + if (!this->addConstraint(patch,abs(pedge),ldim,bcode%1000,code)) + return false; + + if (cline = strtok(NULL," ")) + myScalars[code] = const_cast(utl::parseRealFunc(cline,pd)); + else + myScalars[code] = new ConstFunc(pd); + } + std::cout << std::endl; + } + } + + else if (!strncasecmp(keyWord,"FIXPOINTS",9)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int nfix = atoi(keyWord+9); + std::cout <<"\nNumber of fixed points: "<< nfix << std::endl; + for (int i = 0; i < nfix && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + double rx = atof(strtok(NULL," ")); + double ry = atof(strtok(NULL," ")); + int bcode = (cline = strtok(NULL," ")) ? atoi(cline) : 123; + + int pid = this->getLocalPatchIndex(patch); + if (pid < 1) continue; + + std::cout <<"\tConstraining P"<< patch + <<" point at "<< rx <<" "<< ry + <<" with code "<< bcode << std::endl; + static_cast(myModel[pid-1])->constrainNode(rx,ry,bcode); + } + } + + else + return this->SIMbase::parse(keyWord,is); + + return true; +} + + +/*! + \brief Local-scope convenience function for error message generation. +*/ + +static bool constrError (const char* lab, int idx) +{ + std::cerr <<" *** SIM2D::addConstraint: Invalid "<< lab << idx << std::endl; + return false; +} + + +bool SIM2D::addConstraint (int patch, int lndx, int ldim, int dirs, int code) +{ + if (patch < 1 || patch > myModel.size()) + return constrError("patch index ",patch); + + std::cout <<"\tConstraining P"<< patch + << (ldim == 0 ? " V" : " E") << lndx <<" in direction(s) "<< dirs; + if (code) std::cout <<" code = "<< code <<" "; + + ASMs2D* pch = static_cast(myModel[patch-1]); + switch (ldim) + { + case 0: // Vertex constraints + switch (lndx) + { + case 1: pch->constrainCorner(-1,-1,dirs,code); break; + case 2: pch->constrainCorner( 1,-1,dirs,code); break; + case 3: pch->constrainCorner(-1, 1,dirs,code); break; + case 4: pch->constrainCorner( 1, 1,dirs,code); break; + default: std::cout << std::endl; + return constrError("vertex index ",lndx); + } + break; + + case 1: // Edge constraints + switch (lndx) + { + case 1: pch->constrainEdge(-1,dirs,code); break; + case 2: pch->constrainEdge( 1,dirs,code); break; + case 3: pch->constrainEdge(-2,dirs,code); break; + case 4: pch->constrainEdge( 2,dirs,code); break; + default: std::cout << std::endl; + return constrError("edge index ",lndx); + } + break; + + default: + std::cout << std::endl; + return constrError("local dimension switch ",ldim); + } + + return true; +} + + +void SIM2D::setQuadratureRule (size_t ng) +{ + for (size_t i = 0; i < myModel.size(); i++) + if (!myModel.empty()) + static_cast(myModel[i])->setGauss(ng); +} diff --git a/src/SIM/SIM2D.h b/src/SIM/SIM2D.h new file mode 100644 index 00000000..60ef7653 --- /dev/null +++ b/src/SIM/SIM2D.h @@ -0,0 +1,59 @@ +// $Id: SIM2D.h,v 1.7 2010-12-29 19:00:36 kmo Exp $ +//============================================================================== +//! +//! \file SIM2D.h +//! +//! \date Feb 04 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 2D NURBS-based FEM analysis. +//! +//============================================================================== + +#ifndef _SIM_2D_H +#define _SIM_2D_H + +#include "SIMbase.h" + + +/*! + \brief Driver class for 2D NURBS-based FEM solver. + \details The class implements the parse method of the parent class, and can + be used for any 2D continuum or shell problem. +*/ + +class SIM2D : public SIMbase +{ +public: + //! \brief Default constructor. + //! \param[in] n1 Dimension of the primary solution field + //! \param[in] n2 Dimension of the second solution field (mixed method) + SIM2D(unsigned char n1 = 2, unsigned char n2 = 0); + //! \brief Empty destructor. + virtual ~SIM2D() {} + + //! \brief Defines the spatial numerical integration scheme to use. + //! \param[in] ng Number of Gauss points in each parameter direction + virtual void setQuadratureRule(size_t ng); + +protected: + //! \brief Parses a data section from an input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + + //! \brief Preprocesses a user-defined Dirichlet boundary property. + //! \param[in] patch 1-based index of the patch to receive the property + //! \param[in] lndx Local index of the boundary item to receive the property + //! \param[in] ldim Dimension of the boundary item to receive the property + //! \param[in] dirs Which local DOFs to constrain + //! \param[in] code In-homegeneous Dirichlet condition property code + virtual bool addConstraint(int patch, int lndx, int ldim, + int dirs, int code = 0); + +protected: + unsigned char nf[2]; //!< Number of scalar fields +}; + +#endif diff --git a/src/SIM/SIM3D.C b/src/SIM/SIM3D.C new file mode 100644 index 00000000..50bfd078 --- /dev/null +++ b/src/SIM/SIM3D.C @@ -0,0 +1,599 @@ +// $Id: SIM3D.C,v 1.22 2011-01-05 12:54:56 kmo Exp $ +//============================================================================== +//! +//! \file SIM3D.C +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 3D NURBS-based FEM analysis. +//! +//============================================================================== + +#include "SIM3D.h" +#include "ASMs3Dmx.h" +#include "ASMs3DmxLag.h" +#include "ASMs3DSpec.h" +#include "Functions.h" +#include "Utilities.h" +#include +#include +#include +#include + + +SIM3D::SIM3D (bool checkRHS, unsigned char n1, unsigned char n2) +{ + nf[0] = n1; + nf[1] = n2; + checkRHSys = checkRHS; +} + + +bool SIM3D::parse (char* keyWord, std::istream& is) +{ + char* cline = 0; + if (!strncasecmp(keyWord,"PATCHES",7)) + { + ASMbase* pch = 0; + int npatch = atoi(keyWord+7); + std::cout <<"\nNumber of patches: "<< npatch << std::endl; + for (int i = 0; i < npatch && (cline = utl::readLine(is)); i++) + { + cline = strtok(cline," "); + switch (discretization) { + case Lagrange: + if (nf[1] > 0) + pch = new ASMs3DmxLag(cline,checkRHSys,nf[0],nf[1]); + else + pch = new ASMs3DLag(cline,checkRHSys,nf[0]); + break; + case Spectral: + pch = new ASMs3DSpec(cline,checkRHSys,nf[0]); + break; + default: + if (nf[1] > 0) + pch = new ASMs3Dmx(cline,checkRHSys,nf[0],nf[1]); + else + pch = new ASMs3D(cline,checkRHSys,nf[0]); + } + if (pch->empty() || this->getLocalPatchIndex(i+1) < 1) + delete pch; + else + myModel.push_back(pch); + } + + if (myModel.size() < npatch) + { + std::cerr <<" *** SIM3D::parse: Expected "<< npatch + <<" patches but could read only "<< myModel.size() + << std::endl; + return false; + } + } + + else if (!strncasecmp(keyWord,"PATCHFILE",9)) + { + size_t i = 9; while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isp(keyWord+i); + + ASMbase* pch = 0; + for (int patchNo = 1; isp.good(); patchNo++) + { + std::cout <<"Reading patch "<< patchNo << std::endl; + switch (discretization) { + case Lagrange: + if (nf[1] > 0) + pch = new ASMs3DmxLag(isp,checkRHSys,nf[0],nf[1]); + else + pch = new ASMs3DLag(isp,checkRHSys,nf[0]); + break; + case Spectral: + pch = new ASMs3DSpec(isp,checkRHSys,nf[0]); + break; + default: + if (nf[1] > 0) + pch = new ASMs3Dmx(isp,checkRHSys,nf[0],nf[1]); + else + pch = new ASMs3D(isp,checkRHSys,nf[0]); + } + if (pch->empty() || this->getLocalPatchIndex(patchNo) < 1) + delete pch; + else + myModel.push_back(pch); + } + + if (myModel.empty()) + { + std::cerr <<" *** SIM3D::parse: No patches read"<< std::endl; + return false; + } + } + + else if (!strncasecmp(keyWord,"NODEFILE",8)) + { + if (!this->createFEMmodel()) return false; + + bool oneBasedIdx = keyWord[8] == '1'; + size_t i = (oneBasedIdx || keyWord[8] == '0') ? 9 : 8; + while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isn(keyWord+i); + while (isn.good()) + { + int patch = 0; + isn >> patch; + if (!oneBasedIdx) ++patch; + int pid = this->getLocalPatchIndex(patch); + if (pid < 0) return false; + + ASMs3D::BlockNodes n; + for (i = 0; i < 8 && isn.good(); i++) + isn >> n.ibnod[i]; + for (i = 0; i < 12 && isn.good(); i++) + isn >> n.edges[i].icnod >> n.edges[i].incr; + for (i = 0; i < 6 && isn.good(); i++) + isn >> n.faces[i].isnod >> n.faces[i].incrI >> n.faces[i].incrJ; + isn >> n.iinod; + + if (!oneBasedIdx) + { + // We always require the node numbers to be 1-based + for (i = 0; i < 8; i++) ++n.ibnod[i]; + for (i = 0; i < 12; i++) ++n.edges[i].icnod; + for (i = 0; i < 6; i++) ++n.faces[i].isnod; + ++n.iinod; + } + + if (isn.good() && pid > 0) + if (!static_cast(myModel[pid-1])->assignNodeNumbers(n)) + { + std::cerr <<" *** SIM3D::parse: Failed to assign node numbers" + <<" for patch "<< patch << std::endl; + return false; + } + } + } + + else if (!strncasecmp(keyWord,"PROPERTYFILE",12)) + { + bool oneBasedIdx = keyWord[12] == '1'; + size_t i = (oneBasedIdx || keyWord[12] == '0') ? 13 : 12; + while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream isp(keyWord+i); + while (isp.good()) + { + Property p; + int ldim, lindx = 0; + isp >> p.pindx >> p.patch >> ldim; + if (ldim < 3) isp >> lindx; + + if (!oneBasedIdx) + { + // We always require the item indices to be 1-based + ++p.patch; + ++lindx; + } + + p.ldim = ldim; + p.lindx = lindx; + p.patch = this->getLocalPatchIndex(p.patch); + if (p.patch > 0 && isp.good()) + myProps.push_back(p); + } + } + + else if (!strncasecmp(keyWord,"DIRICHLET",9)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + + int ndir = atoi(keyWord+9); + std::cout <<"\nNumber of Dirichlet properties: "<< ndir << std::endl; + for (int i = 0; i < ndir && (cline = utl::readLine(is)); i++) + { + int code = atoi(strtok(cline," ")); + double d = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + std::cout <<"\tDirichlet code "<< code <<": "; + if (d == 0.0) + { + this->setPropertyType(code,Property::DIRICHLET); + std::cout <<"(fixed)"; + } + else + { + this->setPropertyType(code,Property::DIRICHLET_INHOM); + if (cline = strtok(NULL," ")) + myScalars[code] = const_cast(utl::parseRealFunc(cline,d)); + else + { + std::cout << d; + myScalars[code] = new ConstFunc(d); + } + } + std::cout << std::endl; + } + } + + // The remaining keywords are retained for backward compatibility with the + // prototype version. They enable direct specification of topology and + // properties as well as grid refinement without using the GPM module. + + else if (!strncasecmp(keyWord,"REFINE",6)) + { + int nref = atoi(keyWord+6); + std::cout <<"\nNumber of patch refinements: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + bool uniform = !strchr(cline,'.'); + int patch = atoi(strtok(cline," ")); + if (patch == 0 || abs(patch) > myModel.size()) + { + std::cerr <<" *** SIM3D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + if (uniform) + { + int addu = atoi(strtok(NULL," ")); + int addv = atoi(strtok(NULL," ")); + int addw = atoi(strtok(NULL," ")); + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1 + <<" "<< addu <<" "<< addv <<" "<< addw << std::endl; + ASMs3D* pch = static_cast(myModel[j]); + pch->uniformRefine(0,addu); + pch->uniformRefine(1,addv); + pch->uniformRefine(2,addw); + } + } + else + { + int dir = atoi(strtok(NULL," ")); + RealArray xi; + while (cline = strtok(NULL," ")) + xi.push_back(atof(cline)); + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRefining P"<< j+1 <<" dir="<< dir; + for (size_t i = 0; i < xi.size(); i++) + std::cout <<" "<< xi[i]; + std::cout << std::endl; + static_cast(myModel[j])->refine(dir-1,xi); + } + } + } + } + + else if (!strncasecmp(keyWord,"RAISEORDER",10)) + { + int nref = atoi(keyWord+10); + std::cout <<"\nNumber of order raise: "<< nref << std::endl; + for (int i = 0; i < nref && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int addu = atoi(strtok(NULL," ")); + int addv = atoi(strtok(NULL," ")); + int addw = atoi(strtok(NULL," ")); + if (patch == 0 || abs(patch) > myModel.size()) + { + std::cerr <<" *** SIM3D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + int ipatch = patch-1; + if (patch < 0) + { + ipatch = 0; + patch = -patch; + } + for (int j = ipatch; j < patch; j++) + { + std::cout <<"\tRaising order of P"<< j+1 + <<" "<< addu <<" "<< addv <<" "<< addw << std::endl; + static_cast(myModel[j])->raiseOrder(addu,addv,addw); + } + } + } + + else if (!strncasecmp(keyWord,"TOPOLOGYFILE",12)) + { + if (!this->createFEMmodel()) return false; + + size_t i = 12; while (i < strlen(keyWord) && isspace(keyWord[i])) i++; + std::cout <<"\nReading data file "<< keyWord+i << std::endl; + std::ifstream ist(keyWord+i); + while (cline = utl::readLine(ist)) + { + int master = atoi(strtok(cline," "))+1; + int mFace = atoi(strtok(NULL," "))+1; + int slave = atoi(strtok(NULL," "))+1; + int sFace = atoi(strtok(NULL," "))+1; + int swapd = atoi(strtok(NULL," ")); + int rev_u = atoi(strtok(NULL," ")); + int rev_v = atoi(strtok(NULL," ")); + int orient = 4*swapd+2*rev_u+rev_v; + if (master == slave || + master < 1 || master > myModel.size() || + slave < 1 || slave > myModel.size()) + { + std::cerr <<" *** SIM3D::parse: Invalid patch indices " + << master <<" "<< slave << std::endl; + return false; + } + std::cout <<"\tConnecting P"<< slave <<" F"<< sFace + <<" to P"<< master <<" F"<< mFace + <<" orient "<< orient << std::endl; + ASMs3D* spch = static_cast(myModel[slave-1]); + ASMs3D* mpch = static_cast(myModel[master-1]); + if (!spch->connectPatch(sFace,*mpch,mFace,orient)) + return false; + } + } + + else if (!strncasecmp(keyWord,"TOPOLOGY",8)) + { + if (!this->createFEMmodel()) return false; + + int ntop = atoi(keyWord+8); + std::cout <<"\nNumber of patch connections: "<< ntop << std::endl; + for (int i = 0; i < ntop && (cline = utl::readLine(is)); i++) + { + int master = atoi(strtok(cline," ")); + int mFace = atoi(strtok(NULL," ")); + int slave = atoi(strtok(NULL," ")); + int sFace = atoi(strtok(NULL," ")); + int orient = (cline = strtok(NULL," ")) ? atoi(cline) : 0; + if (master == slave || + master < 1 || master > myModel.size() || + slave < 1 || slave > myModel.size()) + { + std::cerr <<" *** SIM3D::parse: Invalid patch indices " + << master <<" "<< slave << std::endl; + return false; + } + std::cout <<"\tConnecting P"<< slave <<" F"<< sFace + <<" to P"<< master <<" F"<< mFace + <<" orient "<< orient << std::endl; + ASMs3D* spch = static_cast(myModel[slave-1]); + ASMs3D* mpch = static_cast(myModel[master-1]); + if (!spch->connectPatch(sFace,*mpch,mFace,orient)) + return false; + } + } + + else if (!strncasecmp(keyWord,"PERIODIC",8)) + { + if (!this->createFEMmodel()) return false; + + int nper = atoi(keyWord+8); + std::cout <<"\nNumber of periodicities: "<< nper << std::endl; + for (int i = 0; i < nper && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int pfdir = atoi(strtok(NULL," ")); + if (patch < 1 || patch > myModel.size()) + { + std::cerr <<" *** SIM3D::parse: Invalid patch index " + << patch << std::endl; + return false; + } + std::cout <<"\tPeriodic "<< char('H'+pfdir) <<"-direction P"<< patch + << std::endl; + static_cast(myModel[patch-1])->closeFaces(pfdir); + } + } + + else if (!strncasecmp(keyWord,"CONSTRAINTS",11)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int ncon = atoi(keyWord+11); + std::cout <<"\nNumber of constraints: "<< ncon << std::endl; + for (int i = 0; i < ncon && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + int pface = atoi(strtok(NULL," ")); + int bcode = atoi(strtok(NULL," ")); + double pd = (cline = strtok(NULL," ")) ? atof(cline) : 0.0; + + patch = this->getLocalPatchIndex(patch); + if (patch < 1) continue; + + if (pface > 10) + { + if (!this->addConstraint(patch,pface%10,pface/10,pd,bcode)) + return false; + } + else if (pd == 0.0) + { + int ldim = pface < 0 ? 0 : 2; + if (!this->addConstraint(patch,abs(pface),ldim,bcode%1000)) + return false; + } + else + { + int ldim = pface < 0 ? 0 : 2; + int code = 1000 + bcode; + while (myScalars.find(code) != myScalars.end()) + code += 1000; + + if (!this->addConstraint(patch,abs(pface),ldim,bcode%1000,code)) + return false; + + if (cline = strtok(NULL," ")) + myScalars[code] = const_cast(utl::parseRealFunc(cline,pd)); + else + myScalars[code] = new ConstFunc(pd); + } + if (pface < 10) std::cout << std::endl; + } + } + + else if (!strncasecmp(keyWord,"FIXPOINTS",9)) + { + if (ignoreDirichlet) return true; // Ignore all boundary conditions + if (!this->createFEMmodel()) return false; + + int nfix = atoi(keyWord+9); + std::cout <<"\nNumber of fixed points: "<< nfix << std::endl; + for (int i = 0; i < nfix && (cline = utl::readLine(is)); i++) + { + int patch = atoi(strtok(cline," ")); + double rx = atof(strtok(NULL," ")); + double ry = atof(strtok(NULL," ")); + double rz = atof(strtok(NULL," ")); + int bcode = (cline = strtok(NULL," ")) ? atoi(cline) : 123; + + int pid = this->getLocalPatchIndex(patch); + if (pid < 1) continue; + + std::cout <<"\tConstraining P"<< patch + <<" point at "<< rx <<" "<< ry <<" "<< rz + <<" with code "<< bcode << std::endl; + static_cast(myModel[pid-1])->constrainNode(rx,ry,rz,bcode); + } + } + + else + return this->SIMbase::parse(keyWord,is); + + return true; +} + + +/*! + \brief Local-scope convenience function for error message generation. +*/ + +static bool constrError (const char* lab, int idx) +{ + std::cerr <<" *** SIM3D::addConstraint: Invalid "<< lab << idx << std::endl; + return false; +} + + +bool SIM3D::addConstraint (int patch, int lndx, int ldim, int dirs, int code) +{ + if (patch < 1 || patch > myModel.size()) + return constrError("patch index ",patch); + + std::cout <<"\tConstraining P"<< patch + << (ldim == 0 ? " V" : (ldim == 1 ? " E": " F")) << lndx + <<" in direction(s) "<< dirs; + if (code) std::cout <<" code = "<< code <<" "; + + ASMs3D* pch = static_cast(myModel[patch-1]); + switch (ldim) + { + case 0: // Vertex constraints + switch (lndx) + { + case 1: pch->constrainCorner(-1,-1,-1,dirs,code); break; + case 2: pch->constrainCorner( 1,-1,-1,dirs,code); break; + case 3: pch->constrainCorner(-1, 1,-1,dirs,code); break; + case 4: pch->constrainCorner( 1, 1,-1,dirs,code); break; + case 5: pch->constrainCorner(-1,-1, 1,dirs,code); break; + case 6: pch->constrainCorner( 1,-1, 1,dirs,code); break; + case 7: pch->constrainCorner(-1, 1, 1,dirs,code); break; + case 8: pch->constrainCorner( 1, 1, 1,dirs,code); break; + default: std::cout << std::endl; + return constrError("vertex index ",lndx); + } + break; + + case 1: // Edge constraints + if (lndx > 0 && lndx <= 12) + pch->constrainEdge(lndx,dirs,code); + else + { + std::cout << std::endl; + return constrError("edge index ",lndx); + } + break; + + case 2: + switch (lndx) + { + case 1: pch->constrainFace(-1,dirs,code); break; + case 2: pch->constrainFace( 1,dirs,code); break; + case 3: pch->constrainFace(-2,dirs,code); break; + case 4: pch->constrainFace( 2,dirs,code); break; + case 5: pch->constrainFace(-3,dirs,code); break; + case 6: pch->constrainFace( 3,dirs,code); break; + default: std::cout << std::endl; + return constrError("face index ",lndx); + } + break; + + default: + std::cout << std::endl; + return constrError("local dimension switch ",ldim); + } + + return true; +} + + +bool SIM3D::addConstraint (int patch, int lndx, int line, double xi, int dirs) +{ + if (patch < 1 || patch > myModel.size()) + return constrError("patch index ",patch); + + std::cout <<"\tConstraining P"<< patch + <<" F"<< lndx <<" L"<< line <<" at xi="<< xi + <<" in direction(s) "<< dirs << std::endl; + + ASMs3D* pch = static_cast(myModel[patch-1]); + switch (line) + { + case 1: // Face line constraints in local I-direction + switch (lndx) + { + case 1: pch->constrainLine(-1,2,xi,dirs); break; + case 2: pch->constrainLine( 1,2,xi,dirs); break; + case 3: pch->constrainLine(-2,3,xi,dirs); break; + case 4: pch->constrainLine( 2,3,xi,dirs); break; + case 5: pch->constrainLine(-3,1,xi,dirs); break; + case 6: pch->constrainLine( 3,1,xi,dirs); break; + default: return constrError("face index ",lndx); + } + break; + + case 2: // Face line constraints in local J-direction + switch (lndx) + { + case 1: pch->constrainLine(-1,3,xi,dirs); break; + case 2: pch->constrainLine( 1,3,xi,dirs); break; + case 3: pch->constrainLine(-2,1,xi,dirs); break; + case 4: pch->constrainLine( 2,1,xi,dirs); break; + case 5: pch->constrainLine(-3,2,xi,dirs); break; + case 6: pch->constrainLine( 3,2,xi,dirs); break; + default: return constrError("face index ",lndx); + } + break; + + default: + return constrError("face line index ",line); + } + + return true; +} + + +void SIM3D::setQuadratureRule (size_t ng) +{ + for (size_t i = 0; i < myModel.size(); i++) + if (!myModel.empty()) + static_cast(myModel[i])->setGauss(ng); +} diff --git a/src/SIM/SIM3D.h b/src/SIM/SIM3D.h new file mode 100644 index 00000000..f9d8fedd --- /dev/null +++ b/src/SIM/SIM3D.h @@ -0,0 +1,70 @@ +// $Id: SIM3D.h,v 1.11 2010-12-29 19:00:36 kmo Exp $ +//============================================================================== +//! +//! \file SIM3D.h +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Solution driver for 3D NURBS-based FEM analysis. +//! +//============================================================================== + +#ifndef _SIM_3D_H +#define _SIM_3D_H + +#include "SIMbase.h" + + +/*! + \brief Driver class for 3D NURBS-based FEM solver. + \details The class implements the parse method of the parent class, and can + be used for any 3D continuum problem. +*/ + +class SIM3D : public SIMbase +{ +public: + //! \brief Default constructor. + //! \param[in] check If \e true, ensure the model is in a right-hand system + //! \param[in] n1 Dimension of the primary solution field + //! \param[in] n2 Dimension of the second solution field (mixed method) + SIM3D(bool check = false, unsigned char n1 = 3, unsigned char n2 = 0); + //! \brief Empty destructor. + virtual ~SIM3D() {} + + //! \brief Defines the spatial numerical integration scheme to use. + //! \param[in] ng Number of Gauss points in each parameter direction + virtual void setQuadratureRule(size_t ng); + +protected: + //! \brief Parses a data section from an input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + + //! \brief Preprocesses a user-defined Dirichlet boundary property. + //! \param[in] patch 1-based index of the patch to receive the property + //! \param[in] lndx Local index of the boundary item to receive the property + //! \param[in] ldim Dimension of the boundary item to receive the property + //! \param[in] dirs Which local DOFs to constrain + //! \param[in] code In-homegeneous Dirichlet condition property code + virtual bool addConstraint(int patch, int lndx, int ldim, + int dirs, int code = 0); + + //! \brief Constrains a parametric line on a boundary face. + //! \param[in] patch 1-based index of the patch to receive the property + //! \param[in] lndx Local index of the boundary face to receive the property + //! \param[in] line Local direction of the line on the face (1=I, 2=J) + //! \param[in] xi Relative coordinate [0,1] defining the line placement + //! \param[in] dirs Which local DOFs to constrain + bool addConstraint(int patch, int lndx, int line, double xi, int dirs); + +protected: + unsigned char nf[2]; //!< Number of scalar fields + + bool checkRHSys; //!< Check if all patches are in a right-hand system +}; + +#endif diff --git a/src/SIM/SIMbase.C b/src/SIM/SIMbase.C new file mode 100644 index 00000000..446b4756 --- /dev/null +++ b/src/SIM/SIMbase.C @@ -0,0 +1,1247 @@ +// $Id: SIMbase.C,v 1.49 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file SIMbase.C +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for NURBS-based FEM simulators. +//! +//============================================================================== + +#include "SIMbase.h" +#include "ASMbase.h" +#include "PETScMatrix.h" +#ifdef PARALLEL_PETSC +#include "SAMpatchPara.h" +#include "petscksp.h" +#else +#include "SAMpatch.h" +#endif +#include "IntegrandBase.h" +#include "AlgEqSystem.h" +#include "LinSolParams.h" +#include "GlbNorm.h" +#include "ElmNorm.h" +#include "Tensor.h" +#include "Vec3.h" +#include "Vec3Oper.h" +#include "EigSolver.h" +#include "Profiler.h" +#include "ElementBlock.h" +#include "VTF.h" +#include "Utilities.h" +#include +#include + + +SIMbase::Discretization SIMbase::discretization = SIMbase::Spline; +bool SIMbase::ignoreDirichlet = false; +int SIMbase::num_threads_SLU = 1; + + +SIMbase::SIMbase () +{ + myProblem = 0; + myVtf = 0; + mySam = 0; + myEqSys = 0; + mySolParams = 0; + nGlPatches = 0; + + MPCLess::compareSlaveDofOnly = true; // to avoid multiple slave definitions + +#ifdef PARALLEL_PETSC + // In parallel simulations, we need to retain all DOFs in the equation system. + // The fixed DOFs (if any) will receive a homogeneous constraint instead. + ASMbase::fixHomogeneousDirichlet = false; +#endif +} + + +SIMbase::~SIMbase () +{ +#ifdef SP_DEBUG + std::cout <<"\nEntering SIMbase destructor"<< std::endl; +#endif + + if (myProblem) delete myProblem; + if (myVtf) delete myVtf; + if (myEqSys) delete myEqSys; + if (mySam) delete mySam; + if (mySolParams) delete mySolParams; + + for (FEModelVec::iterator i1 = myModel.begin(); i1 != myModel.end(); i1++) + delete *i1; + for (SclFuncMap::iterator i2 = myScalars.begin(); i2 != myScalars.end(); i2++) + delete i2->second; + for (VecFuncMap::iterator i3 = myVectors.begin(); i3 != myVectors.end(); i3++) + delete i3->second; + for (TracFuncMap::iterator i4 = myTracs.begin(); i4 != myTracs.end(); i4++) + delete i4->second; + +#ifdef SP_DEBUG + std::cout <<"Leaving SIMbase destructor"<< std::endl; +#endif +} + + +bool SIMbase::parse (char* keyWord, std::istream& is) +{ + if (!strncasecmp(keyWord,"LINEARSOLVER",12)) + { + if (!mySolParams) mySolParams = new LinSolParams(); + mySolParams->read(is,atoi(keyWord+12)); + } + else if (!strncasecmp(keyWord,"PARTITIONING",12)) + { + int nproc = atoi(keyWord+12); + if (myPid == 0) + std::cout <<"\nNumber of partitions: "<< nproc << std::endl; + + char* cline = 0; + nGlPatches = 0; + for (int i = 0; i < nproc && (cline = utl::readLine(is));i++) + { + int proc = atoi(strtok(cline," ")); + int first = atoi(strtok(NULL," ")); + int last = atoi(strtok(NULL," ")); + + if (last > nGlPatches) nGlPatches = last; + + if (proc == myPid && last >= first) + { + myPatches.reserve(last-first+1); + for (int j = first; j <= last; j++) + myPatches.push_back(j); + } + } + + if (myPatches.empty()) + { + std::cerr <<" *** SIMbase::parse: No partitioning input for processor: " + << myPid << std::endl; + return false; + } + } + else if (isalpha(keyWord[0])) + std::cerr <<" *** SIMbase::parse: Unknown keyword: "<< keyWord << std::endl; + + return true; +} + + +bool SIMbase::createFEMmodel () +{ + for (size_t i = 0; i < myModel.size(); i++) + if (!myModel[i]->generateFEMTopology()) + return false; + + if (nGlPatches == 0 && nProc == 1) + nGlPatches = myModel.size(); + + return true; +} + + +int SIMbase::getLocalPatchIndex (int patchNo) const +{ + if (patchNo < 1 || (patchNo > nGlPatches && nGlPatches > 0)) + { + std::cerr <<" *** SIMbase::getLocalPatchIndex: Patch number "<< patchNo + <<" out of range [1,"<< nGlPatches <<"]"<< std::endl; + return -1; + } + else if (myPatches.empty() || nProc == 1) + return patchNo; + + for (size_t i = 0; i < myPatches.size(); i++) + if (myPatches[i] == patchNo) + return 1+i; + + return 0; +} + + +bool SIMbase::preprocess (const std::vector& ignoredPatches, bool fixDup) +{ + if (!this->createFEMmodel()) return false; + + // Erase all patches that should be ignored in the analysis + std::vector::const_iterator it; + for (it = ignoredPatches.begin(); it != ignoredPatches.end(); it++) + if (*it > 0 && (size_t)*it <= myModel.size()) + myModel[*it-1]->clear(); + + // If material properties are specified for at least one patch, assign the + // property code 999999 to all patches with no material property code yet + std::vector pchWthMat; + for (PropertyVec::const_iterator p = myProps.begin(); p != myProps.end(); p++) + if (p->pcode == Property::MATERIAL && !myModel[p->patch-1]->empty()) + pchWthMat.push_back(p->patch-1); + + if (!pchWthMat.empty()) + for (size_t i = 0; i < myModel.size(); i++) + if (!myModel[i]->empty()) + if (std::find(pchWthMat.begin(),pchWthMat.end(),i) == pchWthMat.end()) + myProps.push_back(Property(Property::MATERIAL,999999,i+1, + myModel[i]->getNoSpaceDim())); + + // Renumber the nodes to account for overlapping nodes and erased patches +#ifdef PARALLEL_PETSC + std::vector l2gn; + int nnod = ASMbase::renumberNodes(myModel,&l2gn); +#else + int nnod = ASMbase::renumberNodes(myModel); +#endif + + if (fixDup) + { + // Check for duplicated nodes (missing topology) + int nDupl = 0; + std::map globalNodes; + for (size_t i = 0; i < myModel.size(); i++) + { + std::map old2new; + std::cout <<" * Checking Patch "<< i+1 << std::endl; + for (size_t node = 1; node <= myModel[i]->getNoNodes(); node++) + { + int globNum = myModel[i]->getNodeID(node); + Vec3 X(myModel[i]->getCoord(node)); + std::map::const_iterator xit = globalNodes.find(X); + if (xit == globalNodes.end()) + globalNodes.insert(std::make_pair(X,globNum)); + else if (xit->second != globNum) + { + std::cout <<" ** Merging duplicated nodes "<< xit->second <<" and " + << globNum <<" at X="<< X << std::endl; + if (myModel[i]->mergeNodes(node,xit->second)) + old2new[globNum] = xit->second; + } + } + if (!old2new.empty()) + { + myModel[i]->renumberNodes(old2new,true); + nDupl += old2new.size(); + } + } + if (nDupl > 0) + { + std::cout <<" * "<< nDupl <<" duplicated nodes merged."<< std::endl; + // Renumber the nodes to account for the merged nodes + nnod = ASMbase::renumberNodes(myModel); + } + } + + // Process the specified Dirichlet boundary conditions + std::cout <<"\nResolving Dirichlet boundary conditions"<< std::endl; + + bool ok = true; + for (PropertyVec::const_iterator p = myProps.begin(); p != myProps.end(); p++) + switch (p->pcode) { + + case Property::DIRICHLET: + if (this->addConstraint(p->patch,p->lindx,p->ldim,p->pindx)) + std::cout << std::endl; + else + ok = false; + break; + + case Property::DIRICHLET_INHOM: + if (this->addConstraint(p->patch,p->lindx,p->ldim,p->pindx%1000,p->pindx)) + std::cout << std::endl; + else + ok = false; + break; + + default: + break; + } + + // Set initial values for the inhomogeneous diriclet conditions, if any + this->initDirichlet(); + + // Resolve possibly chained multi-point constraints + ASMbase::resolveMPCchains(myModel); + + // Initialize data structures for the algebraic system +#ifdef PARALLEL_PETSC + if (nProc > 1) + mySam = new SAMpatchPara(l2gn); + else +#endif + mySam = new SAMpatch(); + + return mySam->init(myModel,nnod) && ok; +} + + +void SIMbase::setPropertyType (int code, Property::Type ptype) +{ + if (code < 0) + std::cerr <<" ** SIMbase::setPropertyType: Negative property code " + << code <<" (ignored)"<< std::endl; + else for (size_t j = 0; j < myProps.size(); j++) + if (myProps[j].pindx == (size_t)code && + myProps[j].pcode == Property::UNDEFINED) + myProps[j].pcode = ptype; +} + + +bool SIMbase::initSystem (SystemMatrix::Type mType, size_t nMats, size_t nVec) +{ + if (!mySam) return false; +#if SP_DEBUG > 1 + mySam->print(std::cout); + std::string heading("\n\nNodal coordinates for Patch 1"); + size_t i, j = heading.size()-1; + for (i = 0; i < myModel.size() && i < 9; i++, heading[j]++) + myModel[i]->printNodes(std::cout,heading.c_str()); +#endif + + myEqSys = new AlgEqSystem(*mySam); + myEqSys->init(mType,mySolParams,nMats,nVec,num_threads_SLU); + myEqSys->initAssembly(); + return true; +} + + +bool SIMbase::setAssociatedRHS (size_t iMat, size_t iVec) +{ + if (!myEqSys) return false; + + return myEqSys->setAssociatedVector(iMat,iVec); +} + + +bool SIMbase::setMode (int mode) +{ + if (!myProblem) return false; + + myProblem->setMode((SIM::SolutionMode)mode); + + return true; +} + + +void SIMbase::printProblem (std::ostream& os) const +{ + if (!myProblem) return; + + std::cout <<"\nProblem definition:"<< std::endl; + myProblem->print(os); + +#if SP_DEBUG > 1 + std::cout <<"\nProperty mapping:"; + for (PropertyVec::const_iterator i = myProps.begin(); i != myProps.end(); i++) + std::cout <<"\n"<< i->pcode <<" "<< i->pindx <<" "<< i->patch + << (int)i->lindx <<" "<< (int)i->ldim; + std::cout << std::endl; +#endif +} + + +size_t SIMbase::getNoSpaceDim () const +{ + return myModel.empty() ? 0 : myModel.front()->getNoSpaceDim(); +} + + +size_t SIMbase::getNoDOFs () const +{ + return mySam ? mySam->getNoDOFs() : 0; +} + + +size_t SIMbase::getNoNodes (bool unique) const +{ + size_t nnod = 0; + if (unique && mySam) + nnod = mySam->getNoNodes(); + else for (size_t i = 0; i < myModel.size(); i++) + nnod += myModel[i]->getNoNodes(); + + return nnod; +} + + +size_t SIMbase::getNoSolutions () const +{ + return myProblem ? myProblem->getNoSolutions() : 0; +} + + +bool SIMbase::initDirichlet (double time) +{ + Vector dummy; + return this->updateDirichlet(time,&dummy); +} + + +bool SIMbase::updateDirichlet (double time, const Vector* prevSol) +{ + if (prevSol) + for (size_t i = 0; i < myModel.size(); i++) + if (!myModel[i]->updateDirichlet(myScalars,time)) + return false; + + if (mySam) + return mySam->updateConstraintEqs(myModel,prevSol); + else + return true; +} + + +bool SIMbase::assembleSystem (const TimeDomain& time, const Vectors& prevSol, + bool newLHSmatrix) +{ + PROFILE1("Element assembly"); + + myEqSys->init(newLHSmatrix); + bool ok = true; + + // Loop over the different material regions, integrating interior + // coefficient matrix terms for the patch associated with each material + size_t i, j = 0, lp = 0; + for (i = 0; i < myProps.size() && ok; i++) + if (myProps[i].pcode == Property::MATERIAL) + if ((j = myProps[i].patch) < 1 || j > myModel.size()) + { + std::cerr <<" *** SIMbase::assembleSystem: Patch index "<< j + <<" out of range [1,"<< myModel.size() <<"]"<< std::endl; + ok = false; + } + else if (this->initMaterial(myProps[i].pindx)) + { + if (msgLevel > 1) + std::cout <<"\nAssembling interior matrix terms for P"<< j + << std::endl; + this->extractPatchSolution(myModel[j-1],prevSol); + ok = myModel[j-1]->integrate(*myProblem,*myEqSys,time); + lp = j; + } + else + ok = false; + + if (j == 0 && ok) + // All patches are referring to the same material, and we assume it has + // been initialized during input processing (thus no initMaterial call here) + for (i = 0; i < myModel.size() && ok; i++) + { + if (msgLevel > 1) + std::cout <<"\nAssembling interior matrix terms for P"<< i+1 + << std::endl; + this->extractPatchSolution(myModel[i],prevSol); + ok = myModel[i]->integrate(*myProblem,*myEqSys,time); + lp = i+1; + } + + // Assemble right-hand-side contributions from the Neumann boundary conditions + if (myEqSys->getVector()) + for (i = 0; i < myProps.size() && ok; i++) + if (myProps[i].pcode == Property::NEUMANN) + if ((j = myProps[i].patch) < 1 || j > myModel.size()) + { + std::cerr <<" *** SIMbase::assembleSystem: Patch index "<< j + <<" out of range [1,"<< myModel.size() <<"]"<< std::endl; + ok = false; + } + + else if (myProps[i].ldim+1 == myModel[j-1]->getNoSpaceDim()) + if (this->initNeumann(myProps[i].pindx)) + { + int bIndex = myProps[i].lindx; + if (msgLevel > 1) + std::cout <<"\nAssembling Neumann matrix terms for boundary " + << bIndex <<" on P"<< j << std::endl; + if (j != lp) this->extractPatchSolution(myModel[j-1],prevSol); + ok = myModel[j-1]->integrate(*myProblem,bIndex,*myEqSys,time); + lp = j; + } + else + ok = false; + + else if (myProps[i].ldim+2 == myModel[j-1]->getNoSpaceDim()) + if (this->initNeumann(myProps[i].pindx)) + { + int bIndex = myProps[i].lindx; + if (msgLevel > 1) + std::cout <<"\nAssembling Neumann matrix terms for edge " + << bIndex <<" on P"<< j << std::endl; + if (j != lp) this->extractPatchSolution(myModel[j-1],prevSol); + ok = myModel[j-1]->integrateEdge(*myProblem,bIndex,*myEqSys,time); + lp = j; + } + else + ok = false; + + SystemMatrix* A = myEqSys->getMatrix(); + SystemVector* b = myEqSys->getVector(); + if (A && b && A->getType() == SystemMatrix::PETSC) + { +#ifdef HAS_PETSC + // Communication of matrix and vector assembly (for PETSc only) + if (!static_cast(A)->beginAssembly()) return false; + if (!static_cast(A)->endAssembly()) return false; + if (!static_cast(b)->beginAssembly()) return false; + if (!static_cast(b)->endAssembly()) return false; +#else + ok = false; +#endif + } + +#if SP_DEBUG > 3 + if (A && newLHSmatrix) std::cout <<"\nSystem coefficient matrix:"<< *A; + if (b) std::cout <<"\nSystem right-hand-side vector:"<< *b; +#endif + + return ok; +} + + +bool SIMbase::extractLoadVec (Vector& loadVec) const +{ + SystemVector* b = myEqSys->getVector(); + if (!b) return false; + + // Expand load vector from equation ordering to DOF-ordering + return mySam->expandSolution(*b,loadVec); +} + + +void SIMbase::extractPatchSolution (const ASMbase* patch, const Vectors& sol) +{ + for (size_t i = 0; i < sol.size() && i < myProblem->getNoSolutions(); i++) + if (!sol[i].empty()) + patch->extractNodeVec(sol[i],myProblem->getSolution(i)); +} + + +bool SIMbase::solveSystem (Vector& solution, int printSol, + const char* compName, bool newLHS) +{ + SystemMatrix* A = myEqSys->getMatrix(); + SystemVector* b = myEqSys->getVector(); + if (!A) std::cerr <<" *** SIMbase::solveSystem: No LHS matrix"<< std::endl; + if (!b) std::cerr <<" *** SIMbase::solveSystem: No RHS vector"<< std::endl; + if (!A || !b) return false; + + // Solve the linear system of equations + if (msgLevel > 1) + std::cout <<"\nSolving the equation system ..."<< std::endl; + { + PROFILE1("Equation solving"); + if (!A->solve(*b,newLHS)) return false; + } + + // Expand solution vector from equation ordering to DOF-ordering + if (!mySam->expandSolution(*b,solution)) return false; + if (printSol < 1) return true; + + // Compute and print solution norms + const int nsd = this->getNoSpaceDim(); + size_t iMax[nsd]; + double dMax[nsd]; + double dNorm = this->solutionNorms(solution,dMax,iMax); + + if (myPid == 0) + { + std::cout <<"\n >>> Solution summary <<<\n" + <<"\nL2-norm : "<< dNorm; + char D = 'X'; + for (int d = 0; d < nsd; d++, D++) + std::cout <<"\nMax "<< D <<'-'<< compName <<" : " + << dMax[d] <<" node "<< iMax[d]; + std::cout << std::endl; + } + + // Print entire solution vector if it is small enough + if (mySam->getNoEquations() < printSol) + { + std::cout <<"\nSolution vector:"; + for (int inod = 1; inod <= mySam->getNoNodes(); inod++) + { + std::cout <<"\nNode "<< inod <<":"; + std::pair dofs = mySam->getNodeDOFs(inod); + for (int d = dofs.first-1; d < dofs.second; d++) + std::cout <<" "<< solution[d]; + } + std::cout << std::endl; + } + + return true; +} + + +void SIMbase::iterationNorms (const Vector& u, const Vector& r, + double& eNorm, double& rNorm, double& dNorm) const +{ + eNorm = mySam->dot(r,u); + rNorm = mySam->norm2(r); + dNorm = mySam->norm2(u); +} + + +double SIMbase::solutionNorms (const Vector& x, double* inf, size_t* ind) const +{ + for (size_t d = 0; d < this->getNoSpaceDim(); d++) + { + ind[d] = d+1; + inf[d] = mySam->normInf(x,ind[d]); + } + + return mySam->normL2(x); +} + + +bool SIMbase::solutionNorms (const TimeDomain& time, const Vectors& psol, + Matrix& eNorm, Vector& gNorm) +{ + NormBase* norm = myProblem->getNormIntegrand(this->getAnaSol()); + if (!norm) norm = myProblem->getNormIntegrandScal(this->getScalarSol()); + if (!norm) + { + std::cerr <<" *** SIMbase::solutionNorms: No integrand."<< std::endl; + return false; + } + + PROFILE1("Norm integration"); + + const Vector& primsol = psol.front(); + + size_t nNorms = norm->getNoFields(); + size_t i, nel = mySam->getNoElms(); + eNorm.resize(nNorms,nel,true); + gNorm.resize(nNorms,true); + + // Initialize norm integral classes + GlbNorm globalNorm(gNorm,GlbNorm::SQRT); + LintegralVec elementNorms; + elementNorms.reserve(nel); + for (i = 0; i < nel; i++) + elementNorms.push_back(new ElmNorm(eNorm.ptr(i))); + + // Loop over the different material regions, integrating solution norm terms + // for the patch domain associated with each material + bool ok = true; + size_t j = 0, lp = 0; + for (i = 0; i < myProps.size() && ok; i++) + if (myProps[i].pcode == Property::MATERIAL) + if ((j = myProps[i].patch) < 1 || j > myModel.size()) + ok = false; + else if (this->initMaterial(myProps[i].pindx)) + { + myModel[j-1]->extractNodeVec(primsol,myProblem->getSolution()); + ok = myModel[j-1]->integrate(*norm,globalNorm,time,elementNorms); + lp = j; + } + else + ok = false; + + if (j == 0 && ok) + // All patches are referring to the same material, and we assume it has + // been initialized during input processing (thus no initMaterial call here) + for (i = 0; i < myModel.size() && ok; i++) + { + myModel[i]->extractNodeVec(primsol,myProblem->getSolution()); + ok = myModel[i]->integrate(*norm,globalNorm,time,elementNorms); + lp = j; + } + + // Integrate norm contributions due to Neumann boundary conditions, if any. + // Note: Currently, only the global norms are considered here. + // The corresponding element-level norms are not stored. This is mainly + // because the current design only allows one loop over the elements + // in the element-norm calculations. Consider rivising this later. + if (norm->hasBoundaryTerms()) + for (i = 0; i < myProps.size() && ok; i++) + if (myProps[i].pcode == Property::NEUMANN) + if ((j = myProps[i].patch) < 1 || j > myModel.size()) + ok = false; + + else if (myProps[i].ldim+1 == myModel[j-1]->getNoSpaceDim()) + if (this->initNeumann(myProps[i].pindx)) + { + if (j != lp) + myModel[j-1]->extractNodeVec(primsol,myProblem->getSolution()); + ok = myModel[j-1]->integrate(*norm,myProps[i].lindx, + globalNorm,time); + lp = j; + } + else + ok = false; + + else if (myProps[i].ldim+2 == myModel[j-1]->getNoSpaceDim()) + if (this->initNeumann(myProps[i].pindx)) + { + if (j != lp) + myModel[j-1]->extractNodeVec(primsol,myProblem->getSolution()); + ok = myModel[j-1]->integrateEdge(*norm,myProps[i].lindx, + globalNorm,time); + lp = j; + } + else + ok = false; + + // Add norm contributions due to inhomogeneous Dirichlet boundary conditions + // (if any), that is, the path integral of the total solution vector + // times the reaction forces at the prescribed DOFs + const Vector* reactionForces = myEqSys->getReactions(); + if (reactionForces && norm->indExt() > 0) + if (psol.size() > 1) + { + static double extEnergy = 0.0; + static Vector prevForces(reactionForces->size()); + extEnergy += mySam->normReact(psol[0]-psol[1],*reactionForces+prevForces); + gNorm(norm->indExt()) += extEnergy; + prevForces = *reactionForces; + } + else + gNorm(norm->indExt()) += mySam->normReact(psol.front(),*reactionForces); + +#ifdef PARALLEL_PETSC + if (nProc > 1) + { + double* tmp = new double[nNorms]; + MPI_Allreduce(gNorm.ptr(),tmp,nNorms,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD); + memcpy(gNorm.ptr(),tmp,nNorms*sizeof(double)); + delete[] tmp; + } +#endif + + delete norm; + for (i = 0; i < nel; i++) + delete elementNorms[i]; + + return ok; +} + + +bool SIMbase::systemModes (std::vector& solution, + int nev, int ncv, int iop, double shift, + size_t iA, size_t iB) +{ + if (nev < 1 || ncv <= nev) return false; + + PROFILE1("Eigenvalue analysis"); + + Vector eigVal; + Matrix eigVec; + if (nev > mySam->getNoEquations()) nev = mySam->getNoEquations(); + if (ncv > mySam->getNoEquations()) ncv = mySam->getNoEquations(); + + // Solve the eigenvalue problem + std::cout <<"\nSolving the eigenvalue problem ..."<< std::endl; + SystemMatrix* A = myEqSys->getMatrix(iA); + SystemMatrix* B = myEqSys->getMatrix(iB); + bool ok = eig::solve(A,B,eigVal,eigVec,nev,ncv,iop,shift); + // To interface SLEPC another interface is used + //bool ok = eig::solve(A,B,eigVal,eigVec,nev); + + // Expand eigenvectors to DOF-ordering and print out eigenvalues + bool freq = iop == 3 || iop == 4 || iop == 6; + std::cout <<"\n >>> Computed Eigenvalues <<<\n Mode\t" + << (freq ? "Frequency [Hz]" : "Eigenvalue"); + solution.resize(nev); + for (int i = 1; i <= nev && ok; i++) + { + solution[i-1].eigNo = i; + if (!mySam->expandVector(eigVec.getColumn(i),solution[i-1].eigVec)) + ok = false; + else if (!freq) + solution[i-1].eigVal = eigVal(i); + else if (eigVal(i) < 0.0) + solution[i-1].eigVal = -sqrt(-eigVal(i))*0.5/M_PI; + else + solution[i-1].eigVal = sqrt( eigVal(i))*0.5/M_PI; + + std::cout <<"\n "<< i <<"\t\t"<< solution[i-1].eigVal; + } + std::cout << std::endl; + return ok; +} + + +bool SIMbase::writeGlv (const char* inpFile, const int* nViz, int format) +{ + if (myVtf) return false; + + // Open a new VTF-file + char* vtfName = new char[strlen(inpFile)+10]; + strtok(strcpy(vtfName,inpFile),"."); + if (nProc > 1) + sprintf(vtfName+strlen(vtfName),"_p%04d.vtf",myPid); + else + strcat(vtfName,".vtf"); + + std::cout <<"\nWriting VTF-file "<< vtfName << std::endl; + myVtf = new VTF(vtfName,format); + delete[] vtfName; + + // Convert and write model geometry + char pname[16]; + for (size_t i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + if (msgLevel > 0) + std::cout <<"Writing geometry for patch "<< i+1 << std::endl; + size_t nd = myModel[i]->getNoParamDim(); + ElementBlock* lvb = new ElementBlock(nd == 3 ? 8 : (nd == 2 ? 4 : 2)); + if (!myModel[i]->tesselate(*lvb,nViz)) + return false; + + sprintf(pname,"Patch %ld",i+1); + if (!myVtf->writeGrid(lvb,pname)) + return false; + } + + return true; +} + + +bool SIMbase::writeGlvBC (const int* nViz, int& nBlock) const +{ + if (!myVtf) return false; + + Matrix field; + size_t i, j; + int node, geomID = 0; + std::vector dID[3]; + + for (i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + geomID++; + size_t nbc = myModel.front()->getNoFields(1); + RealArray flag(3,0.0); + Matrix bc(nbc,myModel[i]->getNoNodes()); + ASMbase::BCVec::const_iterator bit; + for (bit = myModel[i]->begin_BC(); bit != myModel[i]->end_BC(); bit++) + if ((node = myModel[i]->getNodeIndex(bit->node))) + { + if (!bit->CX && nbc > 0) bc(1,node) = flag[0] = 1.0; + if (!bit->CY && nbc > 1) bc(2,node) = flag[1] = 1.0; + if (!bit->CZ && nbc > 2) bc(3,node) = flag[2] = 1.0; + } + + if (flag[0]+flag[1]+flag[2] == 0.0) + continue; // nothing on this patch + + if (msgLevel > 1) + std::cout <<"Writing boundary conditions for patch "<< i+1 << std::endl; + if (!myModel[i]->evalSolution(field,bc,nViz)) + return false; + + // The BC fields should either be 0.0 or 1.0 + if (nViz[0] > 2 || nViz[1] > 2 || nViz[2] > 2) + for (j = 1; j <= 3; j++) + if (flag[j-1] == 1.0) + for (size_t n = 1; n <= field.cols(); n++) + if (field(j,n) < 0.9999) field(j,n) = 0.0; + + for (j = 0; j < 3; j++) + if (flag[j] == 1.0) + if (myVtf->writeNres(field.getRow(1+j),++nBlock,geomID)) + dID[j].push_back(nBlock); + else + return false; + } + + const char* label[3] = { + "fix_X", "fix_Y", "fix_Z" + }; + + for (j = 0; j < 3; j++) + if (!dID[j].empty()) + if (!myVtf->writeSblk(dID[j],label[j],1+j)) + return false; + + return true; +} + + +bool SIMbase::writeGlvT (int iStep, int& nBlock) const +{ + if (myProblem->hasTractionValues()) + { + if (msgLevel > 1) + std::cout <<"Writing boundary tractions"<< std::endl; + return myProblem->writeGlvT(myVtf,iStep,nBlock); + } + + return true; +} + + +bool SIMbase::writeGlvV (const Vector& vec, const char* fieldName, + const int* nViz, int iStep, int& nBlock) const +{ + if (vec.empty()) + return true; + else if (!myVtf) + return false; + + Matrix field; + Vector lovec; + int geomID = 0, idBlock = 2; + std::vector vID; + + for (size_t i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + if (msgLevel > 1) + std::cout <<"Writing vector field for patch "<< i+1 << std::endl; + myModel[i]->extractNodeVec(vec,lovec); + if (!myModel[i]->evalSolution(field,lovec,nViz)) + return false; + + if (!myVtf->writeVres(field,++nBlock,++geomID,this->getNoSpaceDim())) + return false; + else + vID.push_back(nBlock); + } + + return myVtf->writeVblk(vID,fieldName,idBlock,iStep); +} + + +bool SIMbase::writeGlvS (const Vector& psol, + const int* nViz, int iStep, int& nBlock, bool psolOnly) +{ + if (psol.empty()) + return true; + else if (!myVtf) + return false; + + Matrix field; + size_t i, j; + int geomID = 0, idBlock = 10; + std::vector vID, dID[14], sID[14]; + bool haveAsol = false; + bool scalarEq = myModel.empty() ? false : myModel.front()->getNoFields() == 1; + if (scalarEq) { + if (this->getScalarSol()) + haveAsol = true; + } + else + if (this->getAnaSol()) + haveAsol = true; + + for (i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + if (msgLevel > 1) + std::cout <<"Writing FE solution for patch "<< i+1 << std::endl; + myModel[i]->extractNodeVec(psol,myProblem->getSolution()); + if (!myModel[i]->evalSolution(field,myProblem->getSolution(),nViz)) + return false; + + if (scalarEq) + { + if (!myVtf->writeNres(field.getRow(1),++nBlock,++geomID)) + return false; + else + dID[0].push_back(nBlock); + } + else + { + if (!myVtf->writeVres(field,++nBlock,++geomID,this->getNoSpaceDim())) + return false; + else + vID.push_back(nBlock); + + for (j = 0; j < field.rows() && j < 3; j++) + if (!myVtf->writeNres(field.getRow(1+j),++nBlock,geomID)) + return false; + else + dID[j].push_back(nBlock); + } + + if (psolOnly) continue; // skip secondary solution + + LocalSystem::patch = i; + if (!myModel[i]->evalSolution(field,*myProblem,nViz)) + return false; + + if (scalarEq) + if (!myVtf->writeVres(field,++nBlock,geomID)) + return false; + else + vID.push_back(nBlock); + + size_t k = 0; + for (j = 1; j <= field.rows() && k < 14; j++) + if (!myVtf->writeNres(field.getRow(j),++nBlock,geomID)) + return false; + else + sID[k++].push_back(nBlock); + + if (haveAsol) + { + if (msgLevel > 1) + std::cout <<"Writing exact solution for patch "<< i+1 << std::endl; + const ElementBlock* grid = myVtf->getBlock(geomID); + std::vector::const_iterator cit = grid->begin_XYZ(); + Vector solPt; field.fill(0.0); + bool ok = true; + for (j = 1; cit != grid->end_XYZ() && ok; j++, cit++) + { + if (scalarEq) + ok = myProblem->evalSolScal(solPt,*this->getScalarSol(),*cit); + else + ok = myProblem->evalSol(solPt,*this->getAnaSol(),*cit); + if (ok) + field.fillColumn(j,solPt); + } + for (j = 1; j <= field.rows() && k < 14 && ok; j++) + if (!myVtf->writeNres(field.getRow(j),++nBlock,geomID)) + return false; + else + sID[k++].push_back(nBlock); + } + } + + if (scalarEq) + { + if (!myVtf->writeSblk(dID[0],"u",++idBlock,iStep)) + return false; + + if (psolOnly) return true; + + if (!myVtf->writeVblk(vID,"Flux",idBlock,iStep)) + return false; + } + else + { + if (!myVtf->writeDblk(vID,"Solution",idBlock,iStep)) + return false; + + std::string label("u_x"); + for (j = 0; j < 3 && !dID[j].empty(); j++) + if (!myVtf->writeSblk(dID[j],label.c_str(),++idBlock,iStep)) + return false; + else + label[2]++; + } + + if (psolOnly) return true; + + size_t nf = myProblem->getNoFields(); + for (j = 0; j < nf && !sID[j].empty(); j++) + if (!myVtf->writeSblk(sID[j],myProblem->getFieldLabel(j,haveAsol?"FE":0), + ++idBlock,iStep)) return false; + + if (haveAsol) + for (j = 0; j < nf && !sID[nf+j].empty(); j++) + if (!myVtf->writeSblk(sID[nf+j],myProblem->getFieldLabel(j,"Exact"), + ++idBlock,iStep)) return false; + + return true; +} + + +bool SIMbase::writeGlvStep (int iStep, double value, int itype) +{ + if (itype == 0) + return myVtf->writeState(iStep,"Time %g",value,itype); + else + return myVtf->writeState(iStep,"Step %g",value,itype); +} + + +bool SIMbase::writeGlvM (const Mode& mode, bool freq, + const int* nViz, int& nBlock) +{ + if (mode.eigVec.empty()) + return true; + else if (!myVtf) + return false; + + if (msgLevel > 1) + std::cout <<"Writing eigenvector for Mode "<< mode.eigNo << std::endl; + + Vector displ; + Matrix field; + int geomID = 0, idBlock = 10; + std::vector vID; + + for (size_t i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + if (myModel.size() > 1 && msgLevel > 1) std::cout <<"."<< std::flush; + + geomID++; + myModel[i]->extractNodeVec(mode.eigVec,displ); + if (!myModel[i]->evalSolution(field,displ,nViz)) + return false; + + if (!myVtf->writeVres(field,++nBlock,geomID)) + return false; + else + vID.push_back(nBlock); + } + if (myModel.size() > 1 && msgLevel > 1) std::cout << std::endl; + + if (!myVtf->writeDblk(vID,"Mode Shape",idBlock,mode.eigNo)) + return false; + + return myVtf->writeState(mode.eigNo, freq ? "Frequency %g" : "Eigenvalue %g", + mode.eigVal, 1); +} + + +bool SIMbase::writeGlvN (const Matrix& norms, int iStep, int& nBlock) +{ + if (norms.empty()) + return true; + else if (!myVtf) + return false; + + Matrix field; + int idBlock = 100, geomID = 0; + std::vector sID[3]; + + size_t i, j, k; + for (i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + geomID++; + if (msgLevel > 1) + std::cout <<"Writing element norms for patch "<< i+1 << std::endl; + myModel[i]->extractElmRes(norms,field); + + for (j = k = 0; j < field.rows() && j < 4; j++) + if (field.rows()%2 || j != 1) // Skip the external norms (always zero) + if (!myVtf->writeEres(field.getRow(1+j),++nBlock,geomID)) + return false; + else + sID[k++].push_back(nBlock); + } + + const char* label[3] = { + "a(u^h,u^h)^0.5", + "a(u,u)^0.5", + "a(e,e)^0.5, e=u-u^h" + }; + + for (j = 0; j < 3; j++) + if (!sID[j].empty()) + if (!myVtf->writeSblk(sID[j],label[j],++idBlock,iStep)) + return false; + + return true; +} + + +void SIMbase::closeGlv () +{ + if (myVtf) delete myVtf; + myVtf = 0; +} + + +void SIMbase::dumpPrimSol (const Vector& psol, std::ostream& os, + bool withID) const +{ + if (psol.empty()) return; + + size_t i, j, ip; + unsigned char k, n; + for (i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + Vector patchSol; + myModel[i]->extractNodeVec(psol,patchSol); + + if (withID) + { + if (myModel.size() > 1) + os <<"\n# Patch: "<< i+1; + os <<"\n# inod/gnod\tNodal Coordinates\tSolution\n"; + } + for (ip = 0, j = 1; j <= myModel[i]->getNoNodes(); j++) + { + if ((n = myModel[i]->getNodalDOFs(j)) == 0) + continue; + else if (withID) + os << j <<' '<< myModel[i]->getNodeID(j) + <<"\t\t"<< myModel[i]->getCoord(j) <<"\t\t"; + os << patchSol[ip++]; + for (k = 1; k < n; k++) + os <<' '<< patchSol[ip++]; + os <<'\n'; + } + } + + os.flush(); +} + + +bool SIMbase::dumpGeometry (std::ostream& os) const +{ + bool retVal = true; + for (size_t i = 0; i < myModel.size() && retVal; i++) + if (!myModel[i]->empty()) + retVal = myModel[i]->write(os); + + return retVal; +} + + +bool SIMbase::dumpSolution (const Vector& psol, std::ostream& os) const +{ + if (psol.empty()) + return true; + + Matrix field; + size_t i, j, k; + const int nsd = this->getNoSpaceDim(); + + for (i = 0; i < myModel.size(); i++) + { + if (myModel[i]->empty()) continue; // skip empty patches + + if (myModel.size() > 1) + os <<"\n# Patch: "<< i+1; + + // Extract and write primary solution + Vector& patchSol = myProblem->getSolution(); + myModel[i]->extractNodeVec(psol,patchSol); + for (int d = 1; d <= nsd; d++) + { + os <<"# FE u_"<< char('w'+d); + for (j = 1; j <= myModel[i]->getNoNodes(); j++) + { + std::pair dofs = mySam->getNodeDOFs(j); + int idof = dofs.first+d-1; + if (idof <= dofs.second) + os <<"\n"<< patchSol[idof-1]; + } + os << std::endl; + } + + // Evaluate secondary solution variables + LocalSystem::patch = i; + if (!myModel[i]->evalSolution(field,*myProblem,0)) + return false; + + // Write the secondary solution + for (j = 1; j <= field.rows(); j++) + { + os <<"# "<< myProblem->getFieldLabel(j-1,"FE"); + for (k = 1; k <= field.cols(); k++) + os <<"\n"<< field(j,k); + os << std::endl; + } + } + + return true; +} diff --git a/src/SIM/SIMbase.h b/src/SIM/SIMbase.h new file mode 100644 index 00000000..8e4c4ba8 --- /dev/null +++ b/src/SIM/SIMbase.h @@ -0,0 +1,374 @@ +// $Id: SIMbase.h,v 1.33 2011-02-08 09:32:18 kmo Exp $ +//============================================================================== +//! +//! \file SIMbase.h +//! +//! \date Dec 08 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for NURBS-based FEM simulators. +//! +//============================================================================== + +#ifndef _SIM_BASE_H +#define _SIM_BASE_H + +#include "SIMinput.h" +#include "SystemMatrix.h" +#include "TimeDomain.h" +#include "Property.h" +#include "Function.h" +#include + +class ASMbase; +class Integrand; +class VTF; +class SAMpatch; +class AlgEqSystem; +class LinSolParams; + + +/*! + \brief Struct for storage of data associated with one mode shape. +*/ + +struct Mode +{ + int eigNo; //!< Eigenvalue identifier + double eigVal; //!< Eigenvalue associated with this mode + Vector eigVec; //!< Eigenvector associated with this mode + // \brief Default constructor setting \a eigNo and \a eigVal to zero. + Mode() { eigNo = 0; eigVal = 0.0; } +}; + + +/*! + \brief Base class for NURBS-based FEM simulators. + \details This class incapsulates data and methods need for solving + partial differential equations using NURBS-based finite elements. + It only contains the problem-independent data and methods. + Sub-classes are derived with additional info regarding the problem to solve. +*/ + +class SIMbase : public SIMinput +{ +protected: + //! \brief The constructor initializes the pointers to dynamic data members. + SIMbase(); + +public: + //! \brief The destructor frees the dynamically allocated objects. + virtual ~SIMbase(); + + + // Model input and pre-processing methods + // ====================================== + + //! \brief Parses a data section from an input stream. + //! \param[in] keyWord Keyword of current data section to read + //! \param is The file stream to read from + virtual bool parse(char* keyWord, std::istream& is); + + //! \brief Performs some pre-processing tasks on the FE model. + //! \param[in] ignoredPatches Indices of patches to ignore in the analysis + //! \param[in] fixDup Merge duplicated FE nodes on patch interfaces? + bool preprocess(const std::vector& ignoredPatches = std::vector(), + bool fixDup = false); + + //! \brief Allocates the system matrices of the FE problem to be solved. + //! \param[in] mType The matrix format to use + //! \param[in] nMats Number of system matrices + //! \param[in] nVec Number of system right-hand-side vectors + bool initSystem(SystemMatrix::Type mType, size_t nMats, size_t nVec); + + //! \brief Associates a system vector to a system matrix. + //! \sa AlgEqSystem::setAssociatedVector + //! \param[in] iMat Index of a coefficient matrix + //! \param[in] iVec Index of the system vector to associate with the matrix + bool setAssociatedRHS(size_t iMat, size_t iVec); + + //! \brief Defines the solution mode before the element assembly is started. + //! \param[in] mode The solution mode to use + bool setMode(int mode); + + //! \brief Defines the spatial numerical integration scheme to use. + virtual void setQuadratureRule(size_t) {} + + //! \brief Prints out problem-specific data to the given stream. + void printProblem(std::ostream& os) const; + + //! \brief Returns the number of spatial dimensions in the model. + size_t getNoSpaceDim() const; + //! \brief Returns the model size in terms of number of DOFs. + size_t getNoDOFs() const; + //! \brief Returns the model size in terms of number of (unique) nodes. + size_t getNoNodes(bool unique = false) const; + //! \brief Returns the number of solution vectors. + size_t getNoSolutions() const; + + //! \brief Initializes time-dependent in-homogeneous Dirichlet coefficients. + //! \param[in] time Current time + bool initDirichlet(double time = 0.0); + + //! \brief Updates the time-dependent in-homogeneous Dirichlet coefficients. + //! \param[in] time Current time + //! \param[in] prevSol Pointer to previous primary solution in DOF-order + //! + //! \details If \a prevSol is null, the Dirichlet coefficients are set to + //! zero (used when doing equilibrium iterations on a fixed time level). + //! If \a prevSol points to an empty vector, the coefficients are set to the + //! current (updated) values of the functions defining the Dirichlet condition + //! (used for the initial time step), otherwise they are set to the difference + //! between the new values from the Dirichlet functions, and the previous + //! values stored in the provided \a prevSol vector. + bool updateDirichlet(double time = 0.0, const Vector* prevSol = 0); + + + // Computational methods + // ===================== + + //! \brief Administers assembly of the linear equation system. + //! \param[in] time Parameters for nonlinear/time-dependent simulations + //! \param[in] pSol Previous primary solution vectors in DOF-order + //! \param[in] newLHSmatrix If \e false, only integrate the RHS vector + bool assembleSystem(const TimeDomain& time, const Vectors& pSol = Vectors(), + bool newLHSmatrix = true); + + //! \brief Administers assembly of the linear equation system. + //! \param[in] pSol Previous primary solution vectors in DOF-order + //! + //! \details Use this version for linear/stationary problems only. + bool assembleSystem(const Vectors& pSol = Vectors()) + { return this->assembleSystem(TimeDomain(),pSol); } + + //! \brief Extracts the assembled load vector for inspection/visualization. + //! \param[out] loadVec Global load vector in DOF-order + bool extractLoadVec(Vector& loadVec) const; + + //! \brief Solves the assembled linear system of equations for a given load. + //! \param[out] solution Global primary solution vector + //! \param[in] printSol Print solution if its size is less than \a printSol + //! \param[in] compName Solution name to be used in norm output + //! \param[in] newLHS If \e false, reuse the LHS-matrix from previous call. + virtual bool solveSystem(Vector& solution, int printSol = 0, + const char* compName = "displacement", + bool newLHS = true); + + //! \brief Evaluates some iteration norms for convergence assessment. + //! \param[in] x Global primary solution vector + //! \param[in] r Global residual vector associated with the solution vector + //! \param[out] eNorm Energy norm of solution increment + //! \param[out] rNorm Residual norm of solution increment + //! \param[out] dNorm Displacement norm of solution increment + void iterationNorms(const Vector& x, const Vector& r, + double& eNorm, double& rNorm, double& dNorm) const; + + //! \brief Evaluates some norms of the primary solution vector + //! \param[in] x Global primary solution vector + //! \param[out] inf Infinity norms in each spatial direction + //! \param[out] ind Global index of the node corresponding to the inf-value + //! \return L2-norm of the solution vector + double solutionNorms(const Vector& x, double* inf, size_t* ind) const; + + //! \brief Integrates some solution norm quantities. + //! \details If an analytical solution is provided, norms of the exact + //! error in the solution are computed as well. + //! \param[in] time Parameters for nonlinear/time-dependent simulations. + //! \param[in] psol Global primary solution vector + //! \param[out] eNorm Element-wise norm quantities + //! \param[out] gNorm Global norm quantities + bool solutionNorms(const TimeDomain& time, const Vectors& psol, + Matrix& eNorm, Vector& gNorm); + + //! \brief Integrates some solution norm quantities. + //! \details If an analytical solution is provided, norms of the exact + //! error in the solution are computed as well. + //! \param[in] psol Global primary solution vector + //! \param[out] eNorm Element-wise norm quantities + //! \param[out] gNorm Global norm quantities + //! + //! \details Use this version for linear/stationary problems only. + bool solutionNorms(const Vectors& psol, Matrix& eNorm, Vector& gNorm) + { return this->solutionNorms(TimeDomain(),psol,eNorm,gNorm); } + + //! \brief Performs a generalized eigenvalue analysis of the assembled system. + //! \param[in] iop Which eigensolver method to use + //! \param[in] nev Number of eigenvalues/vector (see ARPack documentation) + //! \param[in] ncv Number of Arnoldi vectors (see ARPack documentation) + //! \param[in] shift Eigenvalue shift + //! \param[out] solution Computed eigenvalues and associated eigenvectors + //! \param[in] iA Index of system matrix \b A in \a myEqSys->A + //! \param[in] iB Index of system matrix \b B in \a myEqSys->A + bool systemModes(std::vector& solution, + int nev, int ncv, int iop, double shift, + size_t iA = 0, size_t iB = 1); + + + // Post-processing methods + // ======================= + + //! \brief Opens a new VTF-file and writes the model geometry to it. + //! \param[in] inpFile File name used to construct the VTF-file name from + //! \param[in] nViz Number of visualization points over a knot-span + //! \param[in] format Format of VTF-file (0=ASCII, 1=BINARY) + //! + //! \details The spline patches are tesselated into linear finite elements + //! with a fixed number of elements within each knot-span of non-zero length. + //! The solution fields are then evaluated at the nodal points of the + //! generated FE mesh and written to the VTF-file as vector and scalar fields + //! by the other \a writeGlv* methods. + bool writeGlv(const char* inpFile, const int* nViz, int format); + + //! \brief Writes boundary conditions as scalar fields to the VTF-file. + //! \param[in] nViz Number of visualization points over each knot-span + //! \param nBlock Running result block counter + bool writeGlvBC(const int* nViz, int& nBlock) const; + + //! \brief Writes boundary tractions for a given time step to the VTF-file. + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvT(int iStep, int& nBlock) const; + + //! \brief Writes a vector field for a given load/time step to the VTF-file. + //! \param[in] vec The vector field to output (nodal values in DOF-order) + //! \param[in] fieldName Name identifying the vector field + //! \param[in] nViz Number of visualization points over each knot-span + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + virtual bool writeGlvV(const Vector& vec, const char* fieldName, + const int* nViz, int iStep, int& nBlock) const; + + //! \brief Writes solution fields for a given load/time step to the VTF-file. + //! \details If an analytical solution is provided, the exact stress fields + //! are written to the VTF-file as well. + //! \param[in] psol Global primary solution vector + //! \param[in] nViz Number of visualization points over each knot-span + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + //! \param[in] psolOnly If \e true, skip secondary solution field evaluation + virtual bool writeGlvS(const Vector& psol, const int* nViz, int iStep, + int& nBlock, bool psolOnly = false); + + //! \brief Writes a mode shape and associated eigenvalue to the VTF-file. + //! \details The eigenvalue is used as a label on the step state info + //! that is associated with the eigenvector. + //! \param[in] mode The mode shape eigenvector to output + //! \param[in] freq \e true if the eigenvalue is a frequency + //! \param[in] nViz Number of visualization points over each knot-span + //! \param nBlock Running result block counter + bool writeGlvM(const Mode& mode, bool freq, const int* nViz, int& nBlock); + + //! \brief Writes element norms for a given load/time step to the VTF-file. + //! \details This method can be used only when the number of visualization + //! points over each knot-span equals 2 (that is, no additonal points). + //! \param[in] norms The element norms to output + //! \param[in] iStep Load/time step identifier + //! \param nBlock Running result block counter + bool writeGlvN(const Matrix& norms, int iStep, int& nBlock); + + //! \brief Writes time/load step info to the VTF-file. + //! \param[in] iStep Load/time step identifier + //! \param[in] value Time or load parameter of the step + //! \param[in] itype Type indentifier of the step + bool writeGlvStep(int iStep, double value = 0.0, int itype = 0); + + //! \brief Closes the current VTF-file. + void closeGlv(); + + //! \brief Dumps the (possibly refined) geometry in g2-format. + //! \param os Output stream to write the geometry data to + bool dumpGeometry(std::ostream& os) const; + //! \brief Dumps the entire solution in ASCII format. + //! \param[in] psol Primary solution vector to derive other quantities from + //! \param os Output stream to write the solution data to + bool dumpSolution(const Vector& psol, std::ostream& os) const; + //! \brief Dumps the primary solution in ASCII format for inspection. + //! \param[in] psol Primary solution vector + //! \param os Output stream to write the solution data to + //! \param[in] withID If \e true, write node ID and coordinates too + void dumpPrimSol(const Vector& psol, std::ostream& os, + bool withID = true) const; + +protected: + //! \brief Defines the type of a property set. + //! \param[in] code The property code to be associated with the property type + //! \param[in] ptype The property type to be associated with the given code + void setPropertyType(int code, Property::Type ptype); + + //! \brief Preprocesses a user-defined Dirichlet boundary property. + //! \param[in] patch 1-based index of the patch to receive the property + //! \param[in] lndx Local index of the boundary item to receive the property + //! \param[in] ldim Dimension of the boundary item to receive the property + //! \param[in] dirs Which local DOFs to constrain + //! \param[in] code In-homegeneous Dirichlet condition property code + virtual bool addConstraint(int patch, int lndx, int ldim, + int dirs, int code = 0) = 0; + + //! \brief Creates the computational FEM model from the spline patches. + bool createFEMmodel(); + + //! \brief Returns the local patch index for the given global patch number. + //! \details For serial applications this is an identity mapping only, whereas + //! for parallel applications the local (1-based) patch index on the current + //! processor is returned. If \a patchNo is out of range, -1 is returned. + //! If \a patchNo is not on current processor, 0 is returned. + int getLocalPatchIndex(int patchNo) const; + + //! \brief Initializes material properties for integration of interior terms. + virtual bool initMaterial(size_t) { return true; } + + //! \brief Initializes for integration of Neumann terms for a given property. + virtual bool initNeumann(size_t) { return true; } + + //! \brief Returns the analytical solution field, if any. + virtual TensorFunc* getAnaSol() const { return 0; } + + //! \brief Returns the scalar analytical solution field, if any. + virtual VecFunc* getScalarSol() const { return 0; } + + //! \brief Extract local solution vector(s) for a given patch. + //! \param[in] patch Pointer to the patch to extract solution vectors for + //! \param[in] sol Global primary solution vectors in DOF-order + void extractPatchSolution(const ASMbase* patch, const Vectors& sol); + +public: + //! \brief Enum defining the available discretization methods. + enum Discretization { Spline, Lagrange, Spectral }; + + static Discretization discretization; //!< Spatial discretization option + + static bool ignoreDirichlet; //!< Set to \e true for free vibration analysis + + static int num_threads_SLU; //!< Number of threads for SuperLU_MT + +protected: + //! \brief Spline patch container + typedef std::vector FEModelVec; + //! \brief Scalar field container + typedef std::map SclFuncMap; + //! \brief Vector field container + typedef std::map VecFuncMap; + //! \brief Traction field container + typedef std::map TracFuncMap; + + // Model attributes + FEModelVec myModel; //!< The actual NURBS/spline model + PropertyVec myProps; //!< Physical property mapping + SclFuncMap myScalars; //!< Scalar property fields + VecFuncMap myVectors; //!< Vector property fields + TracFuncMap myTracs; //!< Traction property fields + Integrand* myProblem; //!< Problem-specific data and methods + VTF* myVtf; //!< VTF-file for result visualization + + // Parallel computing attributes + int nGlPatches; //!< Number of global patches + std::vector myPatches; //!< Global patch numbers for current processor + + // Equation solver attributes + AlgEqSystem* myEqSys; //!< The actual linear equation system + SAMpatch* mySam; //!< Auxiliary data for FE assembly management + LinSolParams* mySolParams; //!< Input parameters for PETSc +}; + +#endif diff --git a/src/SIM/SIMenums.h b/src/SIM/SIMenums.h new file mode 100644 index 00000000..6d7b9c8f --- /dev/null +++ b/src/SIM/SIMenums.h @@ -0,0 +1,45 @@ +// $Id: SIMenums.h,v 1.3 2010-12-18 20:03:54 kmo Exp $ +//============================================================================== +//! +//! \file SIMenums.h +//! +//! \date Jul 8 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Various enums for simulation scope. +//! +//============================================================================== + +#ifndef _SIM_ENUMS_H +#define _SIM_ENUMS_H + + +namespace SIM //! Simulation scope +{ + //! \brief Enum defining various solution formulations that may occur. + enum Formulation + { + NONE = 0, + LINEAR = 1, + NONLINEAR = 2, + LAPLACE = 10, + STRESS = 11 + }; + + //! \brief Enum defining the various solution modes that may occur. + enum SolutionMode + { + INIT, + STATIC, + DYNAMIC, + VIBRATION, + BUCKLING, + STIFF_ONLY, + MASS_ONLY, + RHS_ONLY, + RECOVERY + }; +}; + +#endif diff --git a/src/SIM/SIMinput.C b/src/SIM/SIMinput.C new file mode 100644 index 00000000..db7958f3 --- /dev/null +++ b/src/SIM/SIMinput.C @@ -0,0 +1,52 @@ +// $Id: SIMinput.C,v 1.3 2010-10-10 11:20:54 kmo Exp $ +//============================================================================== +//! +//! \file SIMinput.C +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for simulators with input parsing functionality. +//! +//============================================================================== + +#include "SIMinput.h" +#include "Utilities.h" +#ifdef PARALLEL_PETSC +#include "petscsys.h" +#endif +#include + + +int SIMinput::msgLevel = 2; + + +SIMinput::SIMinput () +{ +#ifdef PARALLEL_PETSC + MPI_Comm_rank(PETSC_COMM_WORLD,&myPid); + MPI_Comm_size(PETSC_COMM_WORLD,&nProc); +#else + myPid = 0; + nProc = 1; +#endif +} + + +bool SIMinput::read (const char* fileName) +{ + char* keyWord = 0; + std::cout <<"\nReading input file "<< fileName << std::endl; + std::ifstream is(fileName); + while (is.good() && (keyWord = utl::readLine(is))) + if (!this->parse(keyWord,is)) + { + std::cerr <<" *** SIMinput::read: Failure occured while parsing \"" + << keyWord <<"\""<< std::endl; + return false; + } + + std::cout <<"\nReading input file succeeded."<< std::endl; + return true; +} diff --git a/src/SIM/SIMinput.h b/src/SIM/SIMinput.h new file mode 100644 index 00000000..ea72a202 --- /dev/null +++ b/src/SIM/SIMinput.h @@ -0,0 +1,45 @@ +// $Id: SIMinput.h,v 1.3 2010-10-10 11:20:54 kmo Exp $ +//============================================================================== +//! +//! \file SIMinput.h +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Base class for simulators with input parsing functionality. +//! +//============================================================================== + +#ifndef _SIM_INPUT_H +#define _SIM_INPUT_H + +#include + + +/*! + \brief Base class for NURBS-based FEM simulators with input file parsing. +*/ + +class SIMinput +{ +protected: + //! \brief The default constructor initializes \a myPid and \a nProc. + SIMinput(); + //! \brief Empty destructor. + virtual ~SIMinput() {} + +public: + //! \brief Reads model data from the specified input file \a *fileName. + bool read(const char* fileName); + //! \brief Parses a data section from an input stream. + virtual bool parse(char* keyWord, std::istream& is) = 0; + + static int msgLevel; //!< Controls the amount of console output during solving + +protected: + int myPid; //!< Processor ID in parallel simulations + int nProc; //!< Number of processors in parallel simulations +}; + +#endif diff --git a/src/SplineFEM.dox b/src/SplineFEM.dox new file mode 100644 index 00000000..d8383696 --- /dev/null +++ b/src/SplineFEM.dox @@ -0,0 +1,50 @@ +// $Id: SplineFEM.dox,v 1.3 2009-09-15 07:12:54 kmo Exp $ +//============================================================================== +//! +//! \file SplineFEM.dox +//! +//! \date Apr 20 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief File containing the Main Page of the spline FEM documentation. +//! +//============================================================================== + +/*! \mainpage The prototype Spline FEM elasticity solver + +\author Knut Morten Okstad, Sintef ICT, Trondheim + +\section intro Introduction + +SplineFEM is the prototype FE elasticity solver developed in the ICADA project. +The purpose of this code is to try out different implementations and the learn +about the isogeometric analysis technology itself. +The code can solve static elasticity problems for a varity of load conditions, +such as normal and component pressure loads and gravity load. +It can also solve for the natural eigenfrequencies and associated mode shapes, +as well as performing linearized buckling analysis. +The program writes the results to a standard VTF-file, by first converting the +geometry into a mesh of standard HEX8 elements, and then mapping the results +onto that mesh. + +\section overview Overview of the source code documentation + +The top-level driver is organized in the class LinearEl. This class has methods +for reading model data from an input file, to assemble the algebraic system of +equations resulting from the finite element discretization, and to solve that +system. It also contains a method for writing a VTF-file with results. +These methods are invoked from the main program, which is found in main_LinEl.C. + +The core of the finite element implementation is contained in the class +VolumePatch, which has methods for evaluating the element matrices involved and +assembling them into the system matrices. There is typically one VolumePatch +object for each spline patch in the model. + +The actual NURBS evaluation is performed through the GoTools library, which is +not part of the current package. In addition, this code depends on the ARPACK, +LAPACK and BLAS libraries, the VTFAPI library, and portions of the SAM library +(ASM and optionally SPR). Optionally, the SAMG algebraic multi-grid solver and +the SuperLU direct solver (public domain http://crd.lbl.gov/~xiaoye/SuperLU) +may be included. +*/ diff --git a/src/Utility/CoordinateMapping.C b/src/Utility/CoordinateMapping.C new file mode 100644 index 00000000..92ec41f3 --- /dev/null +++ b/src/Utility/CoordinateMapping.C @@ -0,0 +1,151 @@ +// $Id: CoordinateMapping.C,v 1.4 2010-10-14 20:01:55 kmo Exp $ +//============================================================================== +//! +//! \file CoordinateMapping.C +//! +//! \date May 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Utilities for coordinate mapping transformations. +//! +//============================================================================== + +#include "CoordinateMapping.h" +#include "Vec3.h" +#include "Profiler.h" + +#ifndef epsZ +//! \brief Zero tolerance for the Jacobian determinant. +#define epsZ 1.0e-16 +#endif + + +real utl::Jacobian (matrix& J, matrix& dNdX, + const matrix& X, const matrix& dNdu, + bool computeGradient) +{ + // Compute the Jacobian matrix, J = [dXdu] + J.multiply(X,dNdu); // J = X * dNdu + + // Compute the Jacobian determinant and inverse + real detJ = J.inverse(epsZ); + + // Compute the first order derivatives of the basis function, w.r.t. X + if (detJ != real(0) && computeGradient) + dNdX.multiply(dNdu,J); // dNdX = dNdu * J^-1 + + return detJ; +} + + +real utl::Jacobian (matrix& J, Vec3& t, matrix& dNdX, + const matrix& X, const matrix& dNdu, + size_t tangent) +{ + // Compute the Jacobian matrix, J = [dXdu] + J.multiply(X,dNdu); // J = X * dNdu + + // Extract the tangent vector + t = J.getColumn(tangent); + + // Compute the Jacobian determinant and inverse + real detJ = J.inverse(epsZ); + + // Compute the first order derivatives of the basis function, w.r.t. X + if (detJ != real(0)) + dNdX.multiply(dNdu,J); // dNdX = dNdu * J^-1 + + // Return the curve dilation (dS) in the tangent direction, vt + return t.normalize(); +} + + +real utl::Jacobian (matrix& J, Vec3& n, matrix& dNdX, + const matrix& X, const matrix& dNdu, + size_t t1, size_t t2) +{ + // Compute the Jacobian matrix, J = [dXdu] + J.multiply(X,dNdu); // J = X * dNdu + + real dS; + if (J.cols() == 2) + { + // Compute the face normal + Vec3 v1(J.getColumn(t1)); + Vec3 v2(J.getColumn(t2)); + Vec3 v3(v1,v2); // v3 = v1 x v2 + // Compute the curve dilation (dS) and the in-plane edge normal (n) + dS = v2.normalize(); // dA = |v2| + v3.normalize(); + n.cross(v2,v3); // n = v2 x v3 / (|v2|*|v3|) + } + else + { + // Compute the face normal (n) and surface dilation (dS) + n.cross(J.getColumn(t1),J.getColumn(t2)); // n = t1 x t2 + dS = n.normalize(); // dS = |n| + } + + // Compute the Jacobian inverse + if (J.inverse(epsZ) == real(0)) + return real(0); + + // Compute the first order derivatives of the basis function, w.r.t. X + dNdX.multiply(dNdu,J); // dNdX = dNdu * J^-1 + + return dS; +} + + +bool utl::Hessian (matrix3d& H, matrix3d& d2NdX2, + const matrix& Ji, const matrix& X, + const matrix3d& d2Ndu2, const matrix& dNdu, + bool computeGradient) +{ + PROFILE4("utl::Hessian"); + + // Compute the Hessian matrix, H = [d2Xdu2] + if (!H.multiply(X,d2Ndu2)) // H = X * d2Ndu2 + return false; + else if (!computeGradient) + return true; + + // Check that the matrix dimensions are compatible + size_t nsd = X.rows(); + size_t nnod = X.cols(); + if (Ji.rows() != nsd || Ji.cols() != nsd) + { + std::cerr <<"Hessian: Invalid dimension on Jacobian inverse, Ji(" + << Ji.rows() <<","<< Ji.cols() <<"), nsd="<< nsd << std::endl; + return false; + } + else if (dNdu.rows() != nnod || dNdu.cols() != nsd) + { + std::cerr <<"Hessian: Invalid dimension on basis function matrix, dNdu(" + << dNdu.rows() <<","<< dNdu.cols() <<"), nnod="<< nnod + <<", nsd="<< nsd << std::endl; + return false; + } + + // Compute the second order derivatives of the basis functions, w.r.t. X + d2NdX2.resize(nnod,nsd,nsd,true); + size_t i1, i2, i3, i4, i5, i6; + for (size_t n = 1; n <= nnod; n++) + for (i1 = 1; i1 <= nsd; i1++) + for (i2 = 1; i2 <= nsd; i2++) + { + real& v = d2NdX2(n,i1,i2); + for (i3 = 1; i3 <= nsd; i3++) + for (i4 = 1; i4 <= nsd; i4++) + { + real Ji31x42 = Ji(i3,i1)*Ji(i4,i2); + v += d2Ndu2(n,i3,i4)*Ji31x42; + for (i5 = 1; i5 <= nsd; i5++) + for (i6 = 1; i6 <= nsd; i6++) + v -= dNdu(n,i5)*H(i6,i3,i4)*Ji31x42*Ji(i5,i6); + } + } + + return true; +} diff --git a/src/Utility/CoordinateMapping.h b/src/Utility/CoordinateMapping.h new file mode 100644 index 00000000..204c1a34 --- /dev/null +++ b/src/Utility/CoordinateMapping.h @@ -0,0 +1,75 @@ +// $Id: CoordinateMapping.h,v 1.2 2010-10-14 20:01:55 kmo Exp $ +//============================================================================== +//! +//! \file CoordinateMapping.h +//! +//! \date May 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Utilities for coordinate mapping transformations. +//! +//============================================================================== + +#ifndef _COORDINATE_MAPPING_H +#define _COORDINATE_MAPPING_H + +#include "matrix.h" + +class Vec3; + + +namespace utl +{ + //! \brief Set up the Jacobian matrix of the coordinate mapping. + //! \param[out] J The inverse of the Jacobian matrix + //! \param[out] dNdX First order derivatives of basis functions, w.r.t. X + //! \param[in] X Matrix of element nodal coordinates + //! \param[in] dNdu First order derivatives of basis functions + //! \param[in] computeGradient If \e false, skip calculation of \a dNdX + //! \return The Jacobian determinant + real Jacobian(matrix& J, matrix& dNdX, + const matrix& X, const matrix& dNdu, + bool computeGradient = true); + + //! \brief Set up the Jacobian matrix of the coordinate mapping along an edge. + //! \param[out] J The inverse of the Jacobian matrix + //! \param[out] t Unit tangent vector along the edge + //! \param[out] dNdX 1st order derivatives of basis functions, w.r.t. X + //! \param[in] X Matrix of element nodal coordinates + //! \param[in] dNdu First order derivatives of basis functions + //! \param[in] tangent Parametric tangent direction along the edge + //! \return The curve dilation of the edge + real Jacobian(matrix& J, Vec3& t, matrix& dNdX, + const matrix& X, const matrix& dNdu, + size_t tangent); + + //! \brief Set up the Jacobian matrix of the coordinate mapping on a boundary. + //! \param[out] J The inverse of the Jacobian matrix + //! \param[out] n Outward-directed unit normal vector on the boundary + //! \param[out] dNdX 1st order derivatives of basis functions, w.r.t. X + //! \param[in] X Matrix of element nodal coordinates + //! \param[in] dNdu First order derivatives of basis functions + //! \param[in] t1 First parametric tangent direction of the boundary + //! \param[in] t2 Second parametric tangent direction of the boundary + //! \return The surface/curve dilation of the boundary + real Jacobian(matrix& J, Vec3& n, matrix& dNdX, + const matrix& X, const matrix& dNdu, + size_t t1, size_t t2); + + //! \brief Set up the Hessian matrix of the coordinate mapping. + //! \param[out] H The Hessian matrix + //! \param[out] d2NdX2 Second order derivatives of basis functions, w.r.t. X + //! \param[in] Ji The inverse of the Jacobian matrix + //! \param[in] X Matrix of element nodal coordinates + //! \param[in] d2Ndu2 Second order derivatives of basis functions + //! \param[in] dNdu First order derivatives of basis functions + //! \param[in] computeGradient If \e false, skip calculation of \a d2NdX2 + //! \return \e false if matrix dimensions are incompatible, otherwise \e true + bool Hessian(matrix3d& H, matrix3d& d2NdX2, + const matrix& Ji, const matrix& X, + const matrix3d& d2Ndu2, const matrix& dNdu, + bool computeGradient = true); +}; + +#endif diff --git a/src/Utility/ElementBlock.C b/src/Utility/ElementBlock.C new file mode 100644 index 00000000..9c7d93b2 --- /dev/null +++ b/src/Utility/ElementBlock.C @@ -0,0 +1,82 @@ +// $Id: ElementBlock.C,v 1.4 2010-09-05 11:29:18 kmo Exp $ +//============================================================================== +//! +//! \file ElementBlock.C +//! +//! \date Jun 03 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Storage of a standard FE grid block of uniform element type. +//! +//============================================================================== + +#include "ElementBlock.h" +#include "Vec3Oper.h" +#include + + +ElementBlock::ElementBlock (size_t nenod) +{ + if (nenod != 2 && nenod != 4 && nenod != 9 && nenod != 8 && nenod != 27) + { + std::cout <<"ElementBlock: Invalid number of element nodes "<< nenod + <<" reset to 8"<< std::endl; + nenod = 8; + } + nen = nenod; +} + + +void ElementBlock::resize (size_t nI, size_t nJ, size_t nK) +{ + coord.resize(nI*nJ*nK); + if (nen == 2 && nJ < 2 && nK < 2) + MMNPC.resize(2*(nI-1)); + else if (nen == 4 && nK < 2) + MMNPC.resize(4*(nI-1)*(nJ-1)); + else if (nen == 9 && nK < 2) + MMNPC.resize(9*(nI-1)*(nJ-1)/4); + else if (nen == 8) + MMNPC.resize(8*(nI-1)*(nJ-1)*(nK-1)); + else if (nen == 27) + MMNPC.resize(27*(nI-1)*(nJ-1)*(nK-1)/8); +} + + +bool ElementBlock::setCoor (size_t i, real x, real y, real z) +{ + if (i >= coord.size()) return false; + + coord[i] = Vec3(x,y,z); + return true; +} + + +bool ElementBlock::setNode (size_t i, int nodeNumb) +{ + if (i >= MMNPC.size()) return false; + + MMNPC[i] = nodeNumb; + return true; +} + + +void ElementBlock::merge (const ElementBlock* other, std::vector& nodeNums) +{ + nodeNums.resize(other->coord.size()); + + size_t i; + std::vector::const_iterator cit; + for (i = 0; i < nodeNums.size(); i++) + if ((cit = find(coord.begin(),coord.end(),other->coord[i])) == coord.end()) + { + nodeNums[i] = coord.size(); + coord.push_back(other->coord[i]); + } + else + nodeNums[i] = cit - coord.begin(); + + for (i = 0; i < other->MMNPC.size(); i++) + MMNPC.push_back(nodeNums[other->MMNPC[i]]); +} diff --git a/src/Utility/ElementBlock.h b/src/Utility/ElementBlock.h new file mode 100644 index 00000000..8805b2a2 --- /dev/null +++ b/src/Utility/ElementBlock.h @@ -0,0 +1,66 @@ +// $Id: ElementBlock.h,v 1.2 2010-03-10 13:59:02 kmo Exp $ +//============================================================================== +//! +//! \file ElementBlock.h +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Storage of a standard FE grid block. +//! +//============================================================================== + +#ifndef _ELEMENTBLOCK_H +#define _ELEMENTBLOCK_H + +#include "Vec3.h" +#include + +/*! + \brief Class for storage of a standard FE grid block. +*/ + +class ElementBlock +{ +public: + //! The constructor defines the number of nodes per element \a nenod. + ElementBlock(size_t nenod = 8); + + //! \brief Reallocates the internal arrays to fit a structured element block. + //! \param[in] nI Number of element in I-direction + //! \param[in] nJ Number of element in J-direction + //! \param[in] nK Number of element in K-direction + void resize(size_t nI, size_t nJ = 1, size_t nK = 1); + + //! \brief Defines the coordinates of node \a i + bool setCoor(size_t i, real x, real y, real z); + + //! \brief Defines the global number of element node \a i + bool setNode(size_t i, int nodeNumb); + + //! \brief Returns the total number of nodes in the block. + size_t getNoNodes() const { return coord.size(); } + //! \brief Returns the total number of elements in the block. + size_t getNoElms() const { return MMNPC.size()/nen; } + //! \brief Returns the number of nodes per element. + size_t getNoElmNodes() const { return nen; } + + //! \brief Merges another element block into this one. + void merge(const ElementBlock* other, std::vector& nodeNums); + + //! \brief Returns the beginning of the coord array. + std::vector::const_iterator begin_XYZ() const { return coord.begin(); } + //! \brief Returns the end of the coord array. + std::vector::const_iterator end_XYZ() const { return coord.end(); } + + //! \brief Returns a pointer to the element connectivity array. + const int* getElements() const { return &MMNPC.front(); } + +private: + std::vector coord; //!< Vector of nodal coordinates + std::vector MMNPC; //!< Matrix of Matrices of Nodal Point Correspondance + size_t nen; //!< Number of Element Nodes +}; + +#endif diff --git a/src/Utility/Function.C b/src/Utility/Function.C new file mode 100644 index 00000000..1c3612fa --- /dev/null +++ b/src/Utility/Function.C @@ -0,0 +1,46 @@ +// $Id: Function.C,v 1.6 2010-10-14 19:10:55 kmo Exp $ +//============================================================================== +//! +//! \file Function.C +//! +//! \date Jul 1 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief General functions with arbitrary argument and value type. +//! +//============================================================================== + +#include "Function.h" +#include "Tensor.h" +#include "Vec3.h" +#include "Vec3Oper.h" + + +bool PressureField::isNormalPressure () const +{ + return pdir < 1 || pdir > 3; +} + + +Vec3 PressureField::evaluate (const Vec3& x, const Vec3& n) const +{ + const RealFunc& p = *pressure; + + if (pdir < 1) // normal pressure + return p(x) * n; + + Vec3 t; // pressure acting in global pdir direction + t[(pdir-1)%3] = p(x); + + if (pdir > 3) // normal pressure in global pdir direction + t = (t*n) * n; + + return t; +} + + +Vec3 TractionField::evaluate (const Vec3& x, const Vec3& n) const +{ + return stress(x) * n; +} diff --git a/src/Utility/Function.h b/src/Utility/Function.h new file mode 100644 index 00000000..6709f452 --- /dev/null +++ b/src/Utility/Function.h @@ -0,0 +1,150 @@ +// $Id: Function.h,v 1.6 2010-09-16 10:19:08 kmo Exp $ +//============================================================================== +//! +//! \file Function.h +//! +//! \date Jul 1 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief General functions with arbitrary argument and value type. +//! +//============================================================================== + +#ifndef UTL_FUNCTION_H +#define UTL_FUNCTION_H + +#include + + +namespace utl +{ + /*! + \brief Base class for unary function of arbitrary result and argument type. + */ + + template + class Function : public std::unary_function + { + protected: + //! \brief Empty constructor. + Function() {} + public: + //! \brief Empty destructor. + virtual ~Function() {} + + protected: + //! \brief Evaluates the function for the argument \a x. + virtual Result evaluate(const Arg& x) const = 0; + + public: + //! \brief Operator returning the function value for the given argument. + Result operator()(const Arg& x) const { return this->evaluate(x); } + }; + + + /*! + \brief Base class for binary function of arbitrary result and argument type. + \details The two arguments have to be of the same type. + */ + + template + class Function2 : public std::binary_function + { + protected: + //! \brief Empty constructor. + Function2() {} + public: + //! \brief Empty destructor. + virtual ~Function2() {} + + protected: + //! \brief Evaluates the function for the arguments \a x and \a y. + virtual Result evaluate(const Arg& x, const Arg& y) const = 0; + + public: + //! \brief Operator returning the function value for the given arguments. + Result operator()(const Arg& x, const Arg& y) const + { return this->evaluate(x,y); } + }; +} + + +class Vec3; +class SymmTensor; + +//! \brief Scalar-valued unary function of a scalar value. +typedef utl::Function ScalarFunc; + +//! \brief Scalar-valued unary function of a spatial point. +typedef utl::Function RealFunc; + +//! \brief Vector-valued unary function of a spatial point. +typedef utl::Function VecFunc; + +//! \brief Tensor-valued unary function of a spatial point. +typedef utl::Function TensorFunc; + + +/*! + \brief Vector-valued binary function of a spatial point and normal vector. +*/ + +class TractionFunc : public utl::Function2 +{ +public: + //! \brief Returns whether the traction is always normal to the face or not. + virtual bool isNormalPressure() const { return false; } +}; + + +/*! + \brief Traction field based on a given pressure function. +*/ + +class PressureField : public TractionFunc +{ + const RealFunc* pressure; //!< Scalar field to derive the traction field from + char pdir; //!< The global pressure direction (0...3) + +public: + //! \brief Constructor initializing a constant pressure field. + //! \param[in] p The constant pressure value + //! \param[in] dir The global direction the pressure is acting in + PressureField(real p, int dir = 0); + //! \brief Constructor initializing the scalar pressure field function. + //! \param[in] p The scalar field defining the spatial pressure distribution + //! \param[in] dir The global direction the pressure is acting in + PressureField(const RealFunc* p, int dir = 0) : pressure(p), pdir(dir) {} + //! \brief The destructor frees the scalar field function. + virtual ~PressureField() { delete pressure; } + + //! \brief Returns whether the traction is always normal to the face or not. + virtual bool isNormalPressure() const; + +protected: + //! \brief Evaluates the traction at point \a x and surface normal \a n. + virtual Vec3 evaluate(const Vec3& x, const Vec3& n) const; +}; + + +/*! + \brief Traction field based on a given stress tensor function. +*/ + +class TractionField : public TractionFunc +{ + const TensorFunc& stress; //!< The tensor field to derive the traction from + +public: + //! \brief Constructor initializing the tensor function reference. + TractionField(const TensorFunc& field) : stress(field) {} + //! \brief Empty destructor. + virtual ~TractionField() {} + +protected: + //! \brief Evaluates the traction at point \a x and surface normal \a n. + virtual Vec3 evaluate(const Vec3& x, const Vec3& n) const; +}; + +#endif diff --git a/src/Utility/Functions.C b/src/Utility/Functions.C new file mode 100644 index 00000000..782ca148 --- /dev/null +++ b/src/Utility/Functions.C @@ -0,0 +1,181 @@ +// $Id: Functions.C,v 1.7 2011-01-05 12:45:14 kmo Exp $ +//============================================================================== +//! +//! \file Functions.C +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Specific function implementations. +//! +//============================================================================== + +#include "Functions.h" +#include "Vec3.h" +#include +#include + + +PressureField::PressureField (real p, int dir) : pdir(dir) +{ + pressure = new ConstFunc(p); +} + + +real ConstTimeFunc::evaluate (const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + return (*tfunc)(X ? X->t : real(0)); +} + + +real SpaceTimeFunc::evaluate (const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + return (*sfunc)(x) * (*tfunc)(X ? X->t : real(0)); +} + + +real LinearTinitFunc::evaluate (const Vec3& x) const +{ + const Vec4* X = dynamic_cast(&x); + if (X && X->t < Tinit) + return value*X->t/Tinit; + else + return value; +} + + +real LinearXFunc::evaluate (const Vec3& X) const +{ + return a*X.x + b; +} + + +real LinearYFunc::evaluate (const Vec3& X) const +{ + return a*X.y + b; +} + + +real LinearZFunc::evaluate (const Vec3& X) const +{ + return a*X.z + b; +} + + +real LinearRotZFunc::evaluate (const Vec3& _X) const +{ + // Always return zero if the argument has no time component + const Vec4* X = dynamic_cast(&_X); + if (!X) return real(0); + + real x = X->x - x0; + real y = X->y - y0; + real c = cos(A*X->t); + real s = sin(A*X->t); + return rX ? x*c-y*s-x : x*s+y*c-y; +} + + +real StepXFunc::evaluate (const Vec3& X) const +{ + return X.x < x0 || X.x > x1 ? 0.0 : fv; +} + + + +real StepXYFunc::evaluate (const Vec3& X) const +{ + return X.x < x0 || X.x > x1 || X.y < y0 || X.y > y1 ? 0.0 : fv; +} + + +const RealFunc* utl::parseRealFunc (char* cline, real A) +{ + // Check for spatial variation + int linear = 0; + if (cline) + if (strcmp(cline,"X") == 0) + linear = 1; + else if (strcmp(cline,"Y") == 0) + linear = 2; + else if (strcmp(cline,"Z") == 0) + linear = 3; + else if (strcmp(cline,"XrotZ") == 0) + linear = 4; + else if (strcmp(cline,"YrotZ") == 0) + linear = 5; + else if (strcmp(cline,"Tinit") == 0) + linear = 6; + else if (strcmp(cline,"StepX") == 0) + linear = 7; + else if (strcmp(cline,"StepXY") == 0) + linear = 8; + + real C = A; + const RealFunc* f = 0; + if (linear && (cline = strtok(NULL," "))) + { + C = real(1); + std::cout <<"("<< A <<"*"; + if (linear < 4) + std::cout << char('W' + linear) <<" + "<< cline <<")"; + else if (linear < 6) + std::cout << char('W' + linear-3) <<"RotZ("<< cline <<"))"; + switch (linear) { + case 1: + f = new LinearXFunc(A,atof(cline)); + break; + case 2: + f = new LinearYFunc(A,atof(cline)); + break; + case 3: + f = new LinearZFunc(A,atof(cline)); + break; + case 4: + f = new LinearRotZFunc(true,A,atof(cline),atof(strtok(NULL," "))); + break; + case 5: + f = new LinearRotZFunc(false,A,atof(cline),atof(strtok(NULL," "))); + break; + case 6: + std::cout <<"RampT("<< cline <<"))"; + f = new LinearTinitFunc(A,atof(cline)); + break; + case 7: + std::cout <<"StepX("<< cline <<"))"; + f = new StepXFunc(A,atof(cline),atof(strtok(NULL," "))); + break; + case 8: + std::cout <<"StepXY("<< cline <<"))"; + f = new StepXYFunc(A,atof(cline),atof(strtok(NULL," "))); + break; + } + cline = strtok(NULL," "); + } + else + std::cout << C; + + // Check for time variation + if (!cline) return f; + + const ScalarFunc* s = 0; + double freq = atof(cline); + if (cline = strtok(NULL," ")) + { + std::cout <<" * sin("<< freq <<"*t + "<< cline <<")"; + s = new SineFunc(C,freq,atof(cline)); + } + else + { + std::cout <<" * "<< freq <<"*t"; + s = new LinearFunc(C*freq); + } + + if (f) + return new SpaceTimeFunc(f,s); + else + return new ConstTimeFunc(s); +} diff --git a/src/Utility/Functions.h b/src/Utility/Functions.h new file mode 100644 index 00000000..66cac126 --- /dev/null +++ b/src/Utility/Functions.h @@ -0,0 +1,279 @@ +// $Id: Functions.h,v 1.6 2011-01-05 12:45:14 kmo Exp $ +//============================================================================== +//! +//! \file Functions.h +//! +//! \date Jun 1 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Specific function implementations. +//! +//============================================================================== + +#ifndef _FUNCTIONS_H +#define _FUNCTIONS_H + +#include "Function.h" +#include + + +/*! + \brief A linear scalar function. +*/ + +class LinearFunc : public ScalarFunc +{ + real scale; //!< Scaling factor + +public: + //! \brief Constructor initializing the function parameter. + LinearFunc(real s = real(1)) { scale = s; } + +protected: + //! \brief Evaluates the scalar function. + virtual real evaluate(const real& x) const { return scale*x; } +}; + + +/*! + \brief A sinusoidal scalar function. +*/ + +class SineFunc : public ScalarFunc +{ + real scale; //!< Amplitude of the sine function + real freq; //!< Angular frequency of the sine function + real phase; //!< Phase shift of the sine function + +public: + //! \brief Constructor initializing the function parameters. + SineFunc(real s = real(1), real f = real(1), real p = real(0)) + { scale = s; freq = f; phase = p; } + +protected: + //! \brief Evaluates the scalar function. + virtual real evaluate(const real& x) const { return scale*sin(freq*x+phase); } +}; + + +/*! + \brief A scalar function, constant in space and time. +*/ + +class ConstFunc : public RealFunc +{ + real fval; //!< The function value + +public: + //! \brief Constructor initializing the function value. + ConstFunc(real v) { fval = v; } + +protected: + //! \brief Evaluates the constant function. + virtual real evaluate(const Vec3&) const { return fval; } +}; + + +/*! + \brief A scalar function, constant in space but varying in time. +*/ + +class ConstTimeFunc : public RealFunc +{ + const ScalarFunc* tfunc; //!< The time dependent function value + +public: + //! \brief Constructor initializing the function value. + ConstTimeFunc(const ScalarFunc* f) : tfunc(f) {} + //! \brief The destructor frees the time function. + virtual ~ConstTimeFunc() { delete tfunc; } + +protected: + //! \brief Evaluates the time-varying function. + virtual real evaluate(const Vec3& x) const; +}; + + +/*! + \brief A scalar function, varying in space and time. + \details The function value is defined as a product between one + space-dependent component and one time-dependent component. +*/ + +class SpaceTimeFunc : public RealFunc +{ + const RealFunc* sfunc; //!< The space-dependent term + const ScalarFunc* tfunc; //!< The time-dependent term + +public: + //! \brief Constructor initializing the function terms. + SpaceTimeFunc(const RealFunc* s, const ScalarFunc* t) : sfunc(s), tfunc(t) {} + //! \brief The destructor frees the space and time functions. + virtual ~SpaceTimeFunc() { delete sfunc; delete tfunc; } + +protected: + //! \brief Evaluates the space-time function. + virtual real evaluate(const Vec3& x) const; +}; + + +/*! + \brief A scalar function, linear in \a t up to \a Tinit. +*/ + +class LinearTinitFunc : public RealFunc +{ + real value; //!< Max function value + real Tinit; //!< Function is linear from 0 to t = Tinit + +public: + //! \brief Constructor initializing the function parameters. + LinearTinitFunc(real value_, real Tinit_) { value = value_; Tinit = Tinit_; } + +protected: + //! \brief Evaluates the linear function. + virtual real evaluate(const Vec3& x) const; +}; + + +/*! + \brief A scalar function, linear in \a x. +*/ + +class LinearXFunc : public RealFunc +{ + real a; //!< The function derivative + real b; //!< The function value at \a x = 0 + +public: + //! \brief Constructor initializing the function parameters. + LinearXFunc(real A, real B = real(0)) { a = A; b = B; } + +protected: + //! \brief Evaluates the linear function. + virtual real evaluate(const Vec3& X) const; +}; + + +/*! + \brief A scalar function, linear in \a y. +*/ + +class LinearYFunc : public RealFunc +{ + real a; //!< The function derivative + real b; //!< The function value at \a y = 0 + +public: + //! \brief Constructor initializing the function parameters. + LinearYFunc(real A, real B = real(0)) { a = A; b = B; } + +protected: + //! \brief Evaluates the linear function. + virtual real evaluate(const Vec3& X) const; +}; + + +/*! + \brief A scalar function, linear in \a z. +*/ + +class LinearZFunc : public RealFunc +{ + real a; //!< The function derivative + real b; //!< The function value at \a z = 0 + +public: + //! \brief Constructor initializing the function parameters. + LinearZFunc(real A, real B = real(0)) { a = A; b = B; } + +protected: + //! \brief Evaluates the linear function. + virtual real evaluate(const Vec3& X) const; +}; + + +/*! + \brief A scalar function, defining a linear rotation about the global Z-axis. + \details The time component of the function argument multiplied with the + function parameter \a A, is interpreted as the angle of rotation (in radians) + about the Z-axis passing through the point \a x0, \a y0. + The function then returns the translation in either \a x or \a y direction + (depending on the \a retX argument to the constructor) of the global point + { \a X.x, \a X.y } corresponding to this rotation. + \note If the function is passed a Vec3 object as argument (and not a Vec4), + it will always return zero. +*/ + +class LinearRotZFunc : public RealFunc +{ + bool rX; //!< Flag telling whether to return the X (true) or Y component + real A; //!< Magnitude of the rotation + real x0; //!< Global x-coordinate of rotation centre + real y0; //!< Global y-coordinate of rotation centre + +public: + //! \brief Constructor initializing the function parameters. + LinearRotZFunc(bool retX, real a, real x_0 = real(0), real y_0 = real(0)) + { rX = retX; A = a; x0 = x_0; y0 = y_0; } + +protected: + //! \brief Evaluates the rotation function. + virtual real evaluate(const Vec3& X) const; +}; + + +/*! + \brief A scalar function, step in \a x. +*/ + +class StepXFunc : public RealFunc +{ + real fv; //!< The non-zero function value + real x0; //!< Function is zero for \a x < \a x0 + real x1; //!< Function is zero for \a x > \a x1 + +public: + //! \brief Constructor initializing the function parameters. + StepXFunc(real v, real X0 = real(0), real X1 = real(1)) + { fv = v; x0 = X0; x1 = X1; } + +protected: + //! \brief Evaluates the linear function. + virtual real evaluate(const Vec3& X) const; +}; + + +/*! + \brief A scalar function, step in \a x and \a y. +*/ + +class StepXYFunc : public RealFunc +{ + real fv; //!< The non-zero function value + real x0; //!< Function is zero for \a x < \a x0 + real y0; //!< Function is zero for \a y < \a y0 + real x1; //!< Function is zero for \a x > \a x1 + real y1; //!< Function is zero for \a y > \a y1 + +public: + //! \brief Constructor initializing the function parameters. + StepXYFunc(real v, + real X1 = real(1), real Y1 = real(1), + real X0 = real(-1), real Y0 = real(-1)) + { fv = v; x0 = X0; y0 = Y0; x1 = X1; y1 = Y1; } + +protected: + //! \brief Evaluates the linear function. + virtual real evaluate(const Vec3& X) const; +}; + + +namespace utl +{ + //! \brief Creates a real function by parsing data from a character string. + const RealFunc* parseRealFunc(char* cline, real A = real(0)); +} + +#endif diff --git a/src/Utility/GaussQuadrature.C b/src/Utility/GaussQuadrature.C new file mode 100644 index 00000000..cb099c89 --- /dev/null +++ b/src/Utility/GaussQuadrature.C @@ -0,0 +1,207 @@ +// $Id: GaussQuadrature.C,v 1.1 2009-12-03 15:37:06 kmo Exp $ +//============================================================================== +//! +//! \file GaussQuadrature.C +//! +//! \date Oct 27 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Gaussian quadrature rules in one dimension. +//! +//============================================================================== + +#include "GaussQuadrature.h" +#include + + +//! \brief 1-point rule. +static double G1[2][1] = { + // coord + 0.0, + // weight + 2.0 +}; + +//! \brief 2-point rule. +static double G2[2][2] = { + // coord + -0.5773502691896257, + 0.5773502691896257, + // weight + 1.0 , + 1.0 +}; + +//! \brief 3-point rule. +static double G3[2][3] = { + // coord + -0.7745966692414834, + 0.0 , + 0.7745966692414834, + // weight + 0.5555555555555556, + 0.8888888888888889, + 0.5555555555555556 +}; + +//! \brief 4-point rule. +static double G4[2][4] = { + // coord + -0.86113631159405257524, + -0.33998104358485626481, + 0.33998104358485626481, + 0.86113631159405257524, + // weight + 0.34785484513745385736, + 0.65214515486254614264, + 0.65214515486254614264, + 0.34785484513745385736 +}; + +//! \brief 5-point rule. +static double G5[2][5] = { + // coord + -0.90617984593866399282, + -0.53846931010568309105, + 0.0 , + 0.53846931010568309105, + 0.90617984593866399282, + // weight + 0.23692688505618908749, + 0.47862867049936646808, + 0.56888888888888888888, + 0.47862867049936646808, + 0.23692688505618908749, +}; + +//! \brief 6-point rule. +static double G6[2][6] = { + // coord + -0.9324695142031520, + -0.6612093864662645, + -0.2386191860831969, + 0.2386191860831969, + 0.6612093864662645, + 0.9324695142031520, + // weight + 0.1713244923791703, + 0.3607615730481386, + 0.4679139345726911, + 0.4679139345726911, + 0.3607615730481386, + 0.1713244923791703, +}; + +//! \brief 7-point rule. +static double G7[2][7] = { + // coord + -0.9491079123427585, + -0.7415311855993944, + -0.4058451513773972, + 0.0 , + 0.4058451513773972, + 0.7415311855993944, + 0.9491079123427585, + // weight + 0.1294849661688697, + 0.2797053914892767, + 0.3818300505051189, + 0.4179591836734694, + 0.3818300505051189, + 0.2797053914892767, + 0.1294849661688697, +}; + +//! \brief 8-point rule. +static double G8[2][8] = { + // coord + -0.9602898564975362, + -0.7966664774136267, + -0.5255324099163290, + -0.1834346424956498, + 0.1834346424956498, + 0.5255324099163290, + 0.7966664774136267, + 0.9602898564975362, + // weight + 0.1012285362903763, + 0.2223810344533745, + 0.3137066458778873, + 0.3626837833783620, + 0.3626837833783620, + 0.3137066458778873, + 0.2223810344533745, + 0.1012285362903763, +}; + +//! \brief 9-point rule. +static double G9[2][9] = { + // coord + -0.9681602395076261, + -0.8360311073266358, + -0.6133714327005904, + -0.3242534234038089, + 0.0, + 0.3242534234038089, + 0.6133714327005904, + 0.8360311073266358, + 0.9681602395076261, + // weight + 0.0812743883615744, + 0.1806481606948574, + 0.2606106964029354, + 0.3123470770400029, + 0.3302393550012598, + 0.3123470770400028, + 0.2606106964029355, + 0.1806481606948574, + 0.0812743883615744, +}; + +//! \brief 10-point rule. +static double G10[2][10] = { + // coord + -0.97390653, + -0.86506337, + -0.67940957, + -0.43339539, + -0.14887434, + 0.14887434, + 0.43339539, + 0.67940957, + 0.86506337, + 0.97390653, + // weight + 0.06667134, + 0.14945135, + 0.21908636, + 0.26926672, + 0.29552422, + 0.29552422, + 0.26926672, + 0.21908636, + 0.14945135, + 0.06667134, +}; + + +const double* GaussQuadrature::getGauss(int n, int i) +{ + switch (n) { + case 1: return G1[i]; + case 2: return G2[i]; + case 3: return G3[i]; + case 4: return G4[i]; + case 5: return G5[i]; + case 6: return G6[i]; + case 7: return G7[i]; + case 8: return G8[i]; + case 9: return G9[i]; + case 10: return G10[i]; + } + + std::cerr <<"GaussQuadrature: "<< n <<"-point rule is not available" + << std::endl; + return 0; +} diff --git a/src/Utility/GaussQuadrature.h b/src/Utility/GaussQuadrature.h new file mode 100644 index 00000000..da54edbb --- /dev/null +++ b/src/Utility/GaussQuadrature.h @@ -0,0 +1,39 @@ +// $Id: GaussQuadrature.h,v 1.1 2009-12-03 15:37:07 kmo Exp $ +//============================================================================== +//! +//! \file GaussQuadrature.h +//! +//! \date Oct 27 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Gaussian quadrature rules in one dimension. +//! +//============================================================================== + +#ifndef _GAUSS_QUADRATURE_H +#define _GAUSS_QUADRATURE_H + + +/*! + \brief Gaussian quadrature rules in one dimension. +*/ + +class GaussQuadrature +{ +public: + //! \brief Get gauss point coordinates in the domain [-1,1]. + //! \param[in] n Number of gauss points + static const double* getCoord (int n) { return getGauss(n,0); } + //! \brief Get gauss point weights. + //! \param[in] n Number of gauss points + static const double* getWeight(int n) { return getGauss(n,1); } + +private: + //! \brief Returns the gauss point coordinates or weights. + //! \param[in] n Number of gauss points + //! \param[in] i Option telling what to return (0=coordinates, 1=weights) + static const double* getGauss(int n, int i); +}; + +#endif diff --git a/src/Utility/Legendre.C b/src/Utility/Legendre.C new file mode 100644 index 00000000..54017deb --- /dev/null +++ b/src/Utility/Legendre.C @@ -0,0 +1,189 @@ +// $Id: Legendre.C,v 1.2 2010-10-05 06:47:34 kmo Exp $ +//============================================================================== +//! +//! \file Legendre.C +//! +//! \date Mar 19 2009 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Various utility methods for Spectral elements. +//! +//============================================================================== + +#include "Legendre.h" +#include "DenseMatrix.h" + + +bool Legendre::GL (RealArray& weights, RealArray& points, int n) +{ + points.resize(n); + weights.resize(n); + if (n < 2) return false; + + DenseMatrix A(n,n); + A(1,2) = real(1); + + int i; + if (n > 2) + { + for (i = 2; i < n; i++) + { + A(i,i-1) = real(i-1)/real(2*i-1); + A(i,i+1) = real(i) /real(2*i-1); + } + A(n,n-1) = real(n-1)/real(2*n-1); + } + + Vector eig_complex, evalpoints; + if (!A.solveEigNon(evalpoints,eig_complex)) return false; + std::sort(evalpoints.begin(),evalpoints.end()); + + points = evalpoints; + + real L; + for (i = 0; i < n; i++) + if (!LegendreDerEval(n,evalpoints[i],L)) + return false; + else + weights[i] = real(2)/((real(1)-evalpoints[i]*evalpoints[i])*L*L); + + return true; +} + + + +bool Legendre::GLL (Vector& weights, Vector& points, int n) +{ + weights.resize(n); + points.resize(n); + + points(1) = -real(1); + points(n) = real(1); + + int i; + real L, Ld; + + if (n < 3) + { + for (i = 1; i <= n; i++) + if (!LegendreEval(n-1,points(i),L)) + return false; + else + weights(i) = real(2)/((n-1)*n*L*L); + + return true; + } + + RealArray xw, xg; + if (!GL(xw,xg,n-1)) return false; + + const real tol = 1.0e-8; + + for (i = 2; i < n; i++) + { + points(i) = (xg[i-2] + xg[i-1])/real(2); + real ptemp, res = 1; + while (res > tol) + { + ptemp = points(i); + if (!LegendreEval(n-1,ptemp,L)) return false; + if (!LegendreDerEval(n-1,ptemp,Ld)) return false; + points(i) = ptemp + ((real(1)-ptemp*ptemp)*Ld)/((n-1)*n*L); + res = fabs(points(i) - ptemp); + } + } + + for (i = 1; i <= n; i++) + if (!LegendreEval(n-1,points(i),L)) + return false; + else + weights(i) = real(2)/((n-1)*n*L*L); + + return true; +} + + +bool Legendre::LegendreEval (int n, real x, real& retval) +{ + if (n < 1) + { + std::cerr <<" *** Legendre::LegendreEval: Polynomial index "<< n + <<" out of range: n < 1"<< std::endl; + return false; + } + + if (x < -real(1) || x > real(1)) + { + std::cerr <<" *** Legendre::LegendreEval: Evaluation point "<< x + <<" out of range: [-1,1]"<< std::endl; + return false; + } + + Vector val(n+1); + val(1) = real(1); + val(2) = x; + for (int i = 2; i <= n; i++) + val(i+1) = real(2*i-1)/real(i)*x*val(i) - real(i-1)/real(i)*val(i-1); + + retval = val(n+1); + return true; +} + + +bool Legendre::LegendreDerEval (int n, real x, real& retval) +{ + if (n < 1) + { + std::cerr <<" *** Legendre::LegendreDerEval: Polynomial index "<< n + <<" out of range: n < 1"<< std::endl; + return false; + } + + if (x < -real(1) || x > real(1)) + { + std::cerr <<" *** Legendre::LegendreDerEval: Evaluation point "<< x + <<" out of range: [-1,1]"<< std::endl; + return false; + } + + if (x == real(1) || x == -real(1)) + { + retval = pow(x,n-1)*real(n)*real(n+1)/real(2); + return true; + } + + Vector val(n+1); + val(1) = real(1); + val(2) = x; + for (int i = 2; i <= n; i++) + val(i+1) = real(2*i-1)/real(i)*x*val(i) - real(i-1)/real(i)*val(i-1); + + retval = real(n)/(real(1)-x*x)*val(n) - real(n)*x/(real(1)-x*x)*val(n+1); + return true; +} + + +bool Legendre::basisDerivatives (int n, Matrix& D) +{ + D.resize(n,n); + + Vector w,p; + if (!GLL(w,p,n)) return false; + + real l1,l2; + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) + if (i == j) + D(i,j) = real(0); + else + { + if (!LegendreEval(n-1,p(i),l1)) return false; + if (!LegendreEval(n-1,p(j),l2)) return false; + D(i,j) = l1/(l2*(p(i)-p(j))); + } + + D(1,1) = -real(n)*real(n-1)/real(4); + D(n,n) = -D(1,1); + return true; +} diff --git a/src/Utility/Legendre.h b/src/Utility/Legendre.h new file mode 100644 index 00000000..33835bf9 --- /dev/null +++ b/src/Utility/Legendre.h @@ -0,0 +1,53 @@ +// $Id: Legendre.h,v 1.2 2010-10-05 06:47:34 kmo Exp $ +//============================================================================== +//! +//! \file Legendre.h +//! +//! \date Mar 19 2009 +//! +//! \author Einar Christensen / SINTEF +//! +//! \brief Various utility methods for Spectral elements. +//! +//============================================================================== + +#ifndef _LEGENDRE_H +#define _LEGENDRE_H + +#include "MatVec.h" + + +namespace Legendre +{ + //! \brief Get Gauss-Legendre points and weights in the domain [-1,1]. + //! \param[out] weights Computed Gauss-Legendre weight + //! \param[out] points Computed Gauss-Legendre points + //! \param[in] n Number of Gauss points + bool GL(RealArray& weights, RealArray& points, int n); + + //! \brief Get Gauss-Lobatto-Legendre points and weights in the domain [-1,1]. + //! \param[out] weights Computed Gauss-Legendre weight + //! \param[out] points Computed Gauss-Legendre points + //! \param[in] n Number of Gauss points + bool GLL(Vector& weights, Vector& points, int n); + + //! \brief Evaluates the \a n-th Legendre polynomial. + //! \param[in] n Polynomial degree + //! \param[in] x Evaluation point + //! \param[out] retval Polynomial value at point \a x + bool LegendreEval(int n, real x, real& retval); + + //! \brief Evaluates the first derivative of the \a n-th Legendre polynomial. + //! \param[in] n Polynomial degree + //! \param[in] x Evaluation point + //! \param[out] retval Value of the first derivative at point \a x + bool LegendreDerEval(int n, real x, real& retval); + + //! \brief Evaluates first derivatives of the \a n one-dimensional + //! Lagrange interpolation polynomials through \a n GLL-points. + //! \param[in] n Number of GLL points\polynomials + //! \param[out] der Evaluated values + bool basisDerivatives(int n, Matrix& der); +}; + +#endif diff --git a/src/Utility/MPC.C b/src/Utility/MPC.C new file mode 100644 index 00000000..9637c5b1 --- /dev/null +++ b/src/Utility/MPC.C @@ -0,0 +1,94 @@ +// $Id: MPC.C,v 1.2 2010-10-14 19:10:56 kmo Exp $ +//============================================================================== +//! +//! \file MPC.C +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of multi-point constraint (MPC) equations. +//! +//============================================================================== + +#include "MPC.h" +#include "MPCLess.h" +#include "Utilities.h" + + +bool MPCLess::compareSlaveDofOnly = false; + + +//! \brief Global operator for comparing two MPC::DOF objects. + +bool operator< (const MPC::DOF& lhs, const MPC::DOF& rhs) +{ + if (lhs.node < rhs.node) return true; + if (lhs.node > rhs.node) return false; + if (lhs.dof < rhs.dof) return true; + if (lhs.dof > rhs.dof) return false; + + if (MPCLess::compareSlaveDofOnly) + return false; // ignore coefficient differences, if any + else + return lhs.coeff < rhs.coeff ? true : false; +} + + +bool MPCLess::operator() (const MPC* lhs, const MPC* rhs) const +{ + if (!rhs) return false; + if (!lhs) return true; + + if (lhs->getSlave() < rhs->getSlave()) return true; + if (rhs->getSlave() < lhs->getSlave()) return false; + + if (compareSlaveDofOnly) return false; // ignore master differences, if any + + size_t lMaster = lhs->getNoMaster(); + size_t rMaster = rhs->getNoMaster(); + for (size_t i = 0; i < lMaster && i < rMaster; i++) + { + if (lhs->getMaster(i) < rhs->getMaster(i)) return true; + if (rhs->getMaster(i) < lhs->getMaster(i)) return false; + } + + return lMaster < rMaster ? true : false; +} + + +int MPC::renumberNodes (const std::map& old2new, bool msg) +{ + int invalid = utl::renumber(slave.node,old2new,msg) ? 0 : 1; + for (size_t i = 0; i < master.size(); i++) + if (!utl::renumber(master[i].node,old2new,msg)) + invalid++; + + return invalid; +} + + +//! \brief Global stream operator printing a constraint equation. + +std::ostream& operator<< (std::ostream& s, const MPC& mpc) +{ + s <<"Slave "<< mpc.slave.node <<","<< mpc.slave.dof; + if (mpc.slave.coeff != real(0)) + s <<" = "<< mpc.slave.coeff; + else if (mpc.master.empty()) + return s <<" = 0"<< std::endl; + else + s <<" = "; + + for (size_t i = 0; i < mpc.master.size(); i++) + { + if (i == 0 && mpc.slave.coeff == real(0)) + s << mpc.master[i].coeff; + else if (mpc.master[i].coeff >= real(0)) + s <<" + "<< mpc.master[i].coeff; + else + s <<" - "<< -mpc.master[i].coeff; + s <<"*("<< mpc.master[i].node <<","<< mpc.master[i].dof <<")"; + } + return s << std::endl; +} diff --git a/src/Utility/MPC.h b/src/Utility/MPC.h new file mode 100644 index 00000000..cc591b89 --- /dev/null +++ b/src/Utility/MPC.h @@ -0,0 +1,153 @@ +// $Id: MPC.h,v 1.4 2010-10-14 19:10:56 kmo Exp $ +//============================================================================== +//! +//! \file MPC.h +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of multi-point constraint (MPC) equations. +//! +//============================================================================== + +#ifndef _MPC_H +#define _MPC_H + +#include +#include +#include + + +/*! + \brief A class for representing a general multi-point constraint equation. + + \details A multi-point constraint (MPC) equation is used to introduce + additional coupling between the degrees of freedom (DOF) in a FE grid. + + An MPC equation is in general a linear coupling of one (slave) dof to a set + of master dofs, and may be expressed as + + \f[ s = c_0 + c_1 m_1 + c_2 m_2 + \ldots + c_n m_n \f] + + where +
    +
  • \f$s\f$ denotes the slave dof +
  • \f$c_0\f$ is the value of the slave dof when all master dofs are zero, + or when there are no masters in the equation (prescribed dof). +
  • \f$m_i\f$ is a master dof +
  • \f$c_i, i > 0\f$ is a constant scaling coefficient + associated with the \a i'th master +
  • \a n denotes the total number of master dofs in the equation +
+ When \a n = 0, the above equation represents a fixed (\f$c_0=0\f$) or + prescribed (\f$c_0\ne0\f$) dof. + + One or more of the master dofs may also be a slave in another constraint + equation (chained constrains). In this case the two equations are combined + to eliminate the master dof that is constrained. This is done while + preprocessing the model by the Grid::resolveMPCchains function. +*/ + +class MPC +{ +public: + + /*! + \brief A struct for representing one term (DOF number and associated + coefficient) in a MPC equation. + */ + struct DOF + { + //! \brief Default constructor with no arguments. + DOF() + { + node = dof = 0; + coeff = real(0); + }; + + //! \brief Convenience constructor creating a valid DOF object. + //! \param[in] n Node number (1...NNOD) + //! \param[in] d The local DOF number (1...3) + //! \param[in] c Associated coefficient or constrained value + DOF(int n, int d, real c = real(0)) + { + node = n; + dof = d; + coeff = c; + }; + + //! \brief Global stream operator printing a DOF instance. + friend std::ostream& operator<<(std::ostream& s, const DOF& dof) + { + return s <<"u_"<< char('w'+dof.dof) <<" in node "<< dof.node; + } + + int node; //!< Node number identifying this DOF + int dof; //!< Local DOF number within \a node + real coeff; //!< The constrained value, or master DOF scaling coefficient + }; + + //! \brief Constructor creating a constraint for a specified slave DOF + //! with no master DOFs. + //! \param[in] n The node number of the slave DOF (1...NNOD) + //! \param[in] d The local DOF number of the slave DOF (1...3) + //! \param[in] c The actual value that this slave DOF is constrained to + //! when there are no master DOFs, or all master DOFs are zero + MPC(int n, int d, real c = real(0)) { iceq = -1; slave = DOF(n,d,c); } + + //! \brief Adds a master DOF to the constraint equation. + //! \param[in] n The node number of the master DOF (1...NNOD) + //! \param[in] d The local DOF number of the master DOF (1...3) + //! \param[in] c The coefficient that this master should be scaled with + //! \param[in] tol Tolerance for comparison with zero, + //! if the coefficient \a c is zero, the master DOF is not added + void addMaster(int n, int d, real c = real(1), real tol = real(1.0e-8)) + { + if (c < -tol || c > tol) master.push_back(DOF(n,d,c)); + } + + //! \brief Updates the coefficient of the \a pos'th master DOF. + void updateMaster(size_t pos, real c) + { + if (pos < master.size()) + master[pos].coeff = c; + } + + //! \brief Removes the \a pos'th master DOF from the constraint equation. + void removeMaster(size_t pos) + { + if (pos < master.size()) + master.erase(master.begin()+pos); + } + + //! \brief Renumbers all node numbers in the constraint equation. + //! \param[in] old2new Old-to-new node number mapping + //! \param[in] msg If \e true, give error message for each invalid node number + //! \return Number of invalid node numbers detected + int renumberNodes(const std::map& old2new, bool msg = false); + + //! \brief Increments the \a c0 coefficient by a given \a offset. + void addOffset(real offset) { slave.coeff += offset; } + + //! \brief Assigns a new \a c0 coefficient to the constraint equation. + void setSlaveCoeff(real c0) { slave.coeff = c0; } + + //! \brief Returns a reference to the slave DOF. + const DOF& getSlave() const { return slave; } + //! \brief Returns a reference to the \a i'th master DOF. + const DOF& getMaster(size_t i) const { return master[i]; } + + //! \brief Returns the number of master DOFs. + size_t getNoMaster() const { return master.size(); } + + //! \brief Global stream operator printing a constraint equation. + friend std::ostream& operator<<(std::ostream& s, const MPC& mpc); + + int iceq; //!< Global constraint equation identifier +private: + DOF slave; //!< The slave DOF of this constraint equation + std::vector master; //!< The master DOFs of this constraint equation +}; + +#endif diff --git a/src/Utility/MPCLess.h b/src/Utility/MPCLess.h new file mode 100644 index 00000000..19f2f050 --- /dev/null +++ b/src/Utility/MPCLess.h @@ -0,0 +1,35 @@ +// $Id: MPCLess.h,v 1.1 2009-12-03 15:37:07 kmo Exp $ +//============================================================================== +//! +//! \file MPCLess.h +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Comparison of two \a MPC objects in a \a set object. +//! +//============================================================================== + +#ifndef _MPC_LESS_H +#define _MPC_LESS_H + +class MPC; + + +/*! + \brief Functor used to sort a set of MPC pointers +*/ + +class MPCLess +{ +public: + //! \brief Comparison operator used when inserting an MPC-pointer into a + //! \a set object. + bool operator()(const MPC* lhs, const MPC* rhs) const; + //! \brief Indicates whether only the slave dof number should affect sorting. + //! \details The default is to also compare the associated coefficients. + static bool compareSlaveDofOnly; +}; + +#endif diff --git a/src/Utility/Profiler.C b/src/Utility/Profiler.C new file mode 100644 index 00000000..4bcc5774 --- /dev/null +++ b/src/Utility/Profiler.C @@ -0,0 +1,187 @@ +// $Id: Profiler.C,v 1.8 2011-01-05 12:43:10 kmo Exp $ +//============================================================================== +//! +//! \file Profiler.C +//! +//! \date May 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Simple profiling of computational tasks. +//! +//============================================================================== + +#include "Profiler.h" +#ifdef PARALLEL_PETSC +#include "petscsys.h" +#endif +#include + + +Profiler* utl::profiler = 0; + + +Profiler::Profiler (const std::string& name) : myName(name), nRunners(0) +{ + this->start("Total"); + + allCPU = allWall = 0.0; + + // Update pointer to current profiler (it should only be one at any time) + if (utl::profiler) delete utl::profiler; + utl::profiler = this; +} + + +Profiler::~Profiler () +{ + this->stop("Total"); + this->report(std::cout); +} + + +//! \brief Returns the current wall time in seconds and resolution in microsec. + +static double WallTime () +{ + timeval tmpTime; + gettimeofday(&tmpTime,NULL); + return tmpTime.tv_sec + tmpTime.tv_usec/1.0e6; +} + + +void Profiler::start (const std::string& funcName) +{ + Profile& p = myTimers[funcName]; + if (p.running) return; + + p.running = true; + nRunners++; + p.nCalls++; + p.startWall = WallTime(); + p.startCPU = clock(); +} + + +void Profiler::stop (const std::string& funcName) +{ + clock_t stopCPU = clock(); + double stopWall = WallTime(); + + std::map::iterator it = myTimers.find(funcName); + if (it == myTimers.end()) + std::cerr <<" *** No matching timer for "<< funcName << std::endl; + else if (it->second.running) + { + // Accumulate consumed CPU and wall time by this task + Profile& p = it->second; + double deltaCPU = double(stopCPU - p.startCPU)/double(CLOCKS_PER_SEC); + double deltaWall = stopWall - p.startWall; + p.running = false; + p.totalCPU += deltaCPU; + p.totalWall += deltaWall; + if (--nRunners == 1) + { + // This is a "main" task, accumulate the total time for all main tasks + allCPU += deltaCPU; + allWall += deltaWall; + } + } +} + + +static bool use_ms = false; //!< Print mean times in microseconds? + + +//! \brief Global stream operator printing a Profile instance. + +std::ostream& operator<<(std::ostream& os, const Profiler::Profile& p) +{ + // First the CPU time + os.width(10); + os << p.totalCPU; + if (p.nCalls > 1) + { + os.width(9); + os << (use_ms ? 1000.0 : 1.0)*p.totalCPU/p.nCalls <<" |"; + } + else + os <<" |"; + + // Then the wall time and the number of invokations (if more than one) + os.width(10); + os << p.totalWall; + if (p.nCalls > 1) + { + os.width(9); + os << (use_ms ? 1000.0 : 1.0)*p.totalWall/p.nCalls <<" |"; + os.width(6); + os << p.nCalls; + } + else + os <<" |"; + + return os <<'\n'; +} + + +void Profiler::report (std::ostream& os) const +{ + if (myTimers.empty()) return; + + use_ms = true; // Print mean times in microseconds by default + std::map::const_iterator it; + for (it = myTimers.begin(); it != myTimers.end(); it++) + { + // Make sure the task has stopped profiling (in case of exceptions) + if (it->second.running) const_cast(this)->stop(it->first); + if (it->second.nCalls > 1) + if (it->second.totalWall/it->second.nCalls >= 100.0) + use_ms = false; // Print mean times in seconds + } + + // Find the time for "other" tasks, i.e., the difference between + // the measured total time and the sum of all the measured tasks + Profile other; + std::map::const_iterator tit = myTimers.find("Total"); + if (tit != myTimers.end()) + { + if (!tit->second.haveTime()) return; // Nothing to report, run in zero time + other.totalCPU = tit->second.totalCPU - allCPU; + other.totalWall = tit->second.totalWall - allWall; + } + + int myId = -1; +#ifdef PARALLEL_PETSC + MPI_Comm_rank(PETSC_COMM_WORLD,&myId); +#endif + + // Print a table with timing results, all tasks with zero time are ommitted + const char* Ms = (use_ms ? "Mean(ms)" : "Mean(s) "); + os <<"\n===============================================================" + <<"\n=== Profiling results for "<< myName; + if (myId >= 0) os <<" on processor "<< myId; + os <<"\n=================================================================" + <<"\n | CPU time | Wall time |" + <<"\nTask | Total(s) "<second.haveTime()) + if (it->first.size() >= 22) + os << it->first.substr(0,22) <<'|'<< it->second; + else + os << it->first << std::string(22-it->first.size(),' ') + <<'|'<< it->second; + + // Finally, print the "other" and "total" times + if (other.haveTime()) + os <<"Other |"<< other; + if (tit != myTimers.end()) + os <<"----------------------+--------------------+--------------------+------" + <<"\nTotal time |"<< tit->second; + os <<"=======================================================================" + << std::endl; +} diff --git a/src/Utility/Profiler.h b/src/Utility/Profiler.h new file mode 100644 index 00000000..da55f9ef --- /dev/null +++ b/src/Utility/Profiler.h @@ -0,0 +1,142 @@ +// $Id: Profiler.h,v 1.3 2010-07-04 10:15:30 kmo Exp $ +//============================================================================== +//! +//! \file Profiler.h +//! +//! \date May 18 2010 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Simple profiling of computational tasks. +//! +//============================================================================== + +#ifndef _PROFILER_H +#define _PROFILER_H + +#include +#include +#include + + +/*! + \brief Simple class for profiling of computational tasks. + + \details The profiler measures the CPU time and the wall clock time for + computational tasks identified be the user. Each task may be invoked an + arbitrary number of times, and the average time consumption is then also + recorded along with the number of invokations. + + The profiling results are printed in a nicely formatted table when the + profiler object goes out of scope, typically at the end of the program. +*/ + +class Profiler +{ +public: + //! \brief The constructor initializes the profiler object. + //! \param[in] name Program name to be printed in the profiling report header. + //! + //! \details The constructor also updates the global static pointer + //! utl::profiler to point to \a *this, deleting any already pointed-to + //! object first. This means, only one Profiler object can exist at any time. + Profiler(const std::string& name); + //! \brief The destructor prints the profiling report to the console. + ~Profiler(); + + //! \brief Starts profiling of task \a funcName and increments \a nRunners. + void start(const std::string& funcName); + //! \brief Stops profiling of task \a funcName and decrements \a nRunners. + void stop(const std::string& funcName); + + //! \brief Prints a profiling report for all tasks that have been measured. + void report(std::ostream& os) const; + //! \brief Clears the profiler. + void clear() { myTimers.clear(); allCPU = allWall = 0.0; nRunners = 0; } + +private: + //! \brief Stores profiling data for one computational task. + struct Profile + { + clock_t startCPU; //!< The last starting CPU time of this task + double startWall; //!< The last starting wall clock time of this task + double totalCPU; //!< Total CPU time consumed by this task so far + double totalWall; //!< Total wall clock time consumed by this task so far + size_t nCalls; //!< Number of invokations of this task + bool running; //!< Flag indicating if this task is currently running + + //! \brief The constructor initializes the total times to zero. + Profile() : nCalls(0), running(false) { totalCPU = totalWall = 0.0; } + //! \brief Checks if this profile item have any timing to report. + bool haveTime() const { return totalCPU >= 0.005 || totalWall >= 0.005; } + }; + + //! \brief Global stream operator printing a Profile instance. + friend std::ostream& operator<<(std::ostream& os, const Profile& p); + + std::string myName; //!< Name of this profiler + + std::map myTimers; //!< The task profiles with names + + double allCPU; //!< Accumulated CPU time from all "main" tasks + double allWall; //!< Accumulated wall clock time of all "main" tasks + + //! \details When \a nRunners is 1, we are only measuring the total time. + //! When \a nRunners is 2, we are also measuring a "main" task, which total + //! time is subtracted from the measured total time to find the reminder of + //! all tasks that are not being measured. When \a nRunners is 3 or larger, + //! the measured task is already included in another "main" task, + //! and therefore its time is not included when calculating the "other" times. + size_t nRunners; //!< Number of tasks currently running +}; + + +namespace utl +{ + extern Profiler* profiler; //!< Pointer to the one and only profiler object. + + //! \brief Convenience class to profile the local scope. + class prof + { + const char* name; //!< Name tag on the local scope to profile + public: + //! \brief The constructor starts the profiling of the named task. + prof(const char* tag) : name(tag) { if (profiler) profiler->start(name); } + //! \brief The destructor stops the profiling. + ~prof() { if (profiler) profiler->stop(name); } + }; +} + + +//! \brief Macro to add profiling of the local scope. +#define PROFILE(label) utl::prof _prof(label) + +#if PROFILE_LEVEL >= 1 +#define PROFILE1(label) PROFILE(label) +#else +//! \brief Macro to add level 1 profiling of the local scope. +#define PROFILE1(label) +#endif + +#if PROFILE_LEVEL >= 2 +#define PROFILE2(label) PROFILE(label) +#else +//! \brief Macro to add level 2 profiling of the local scope. +#define PROFILE2(label) +#endif + +#if PROFILE_LEVEL >= 3 +#define PROFILE3(label) PROFILE(label) +#else +//! \brief Macro to add level 3 profiling of the local scope. +#define PROFILE3(label) +#endif + +#if PROFILE_LEVEL >= 4 +#define PROFILE4(label) PROFILE(label) +#else +//! \brief Macro to add level 4 profiling of the local scope. +#define PROFILE4(label) +#endif + +#endif diff --git a/src/Utility/Tensor.C b/src/Utility/Tensor.C new file mode 100644 index 00000000..23d32296 --- /dev/null +++ b/src/Utility/Tensor.C @@ -0,0 +1,539 @@ +// $Id: Tensor.C,v 1.11 2010-12-20 13:52:42 kmo Exp $ +//============================================================================== +//! +//! \file Tensor.C +//! +//! \date Dec 17 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of second-order tensors with some basic operations. +//! +//============================================================================== + +#include "Tensor.h" +#include "Vec3.h" + + +std::ostream& Tensor::print (std::ostream& os) const +{ + switch (n) { + case 1: + return os << v[0] << std::endl; + case 2: + return os << v[0] <<' '<< v[2] <<'\n' + << v[1] <<' '<< v[3] << std::endl; + case 3: + return os << v[0] <<' '<< v[3] <<' '<< v[6] <<'\n' + << v[1] <<' '<< v[4] <<' '<< v[7] <<'\n' + << v[2] <<' '<< v[5] <<' '<< v[8] << std::endl; + } + + return os; +} + + +Tensor::Tensor (const std::vector& t1, const std::vector& t2) : n(3) +{ + Vec3 v1(t1), v2(t2), v3; + + v1.normalize(); + v3.cross(v1,v2).normalize(); + v2.cross(v3,v1); + + v.resize(9); + for (t_ind i = 0; i < 3; i++) + { + v[ i] = v1[i]; + v[3+i] = v2[i]; + v[6+i] = v3[i]; + } +} + + +Tensor& Tensor::operator= (const Tensor& T) +{ + if (v.size() == T.v.size()) + std::copy(T.v.begin(),T.v.end(),v.begin()); + else + { + // Handle different dimensions and/or symmetry + t_ind ndim = T.n; + if (n > ndim) + this->zero(); + else + ndim = n; + for (t_ind i = 1; i <= ndim; i++) + for (t_ind j = (this->symmetric() ? i : 1); j <= n; j++) + v[this->index(i,j)] = T(i,j); + } + + return *this; +} + + +Tensor& Tensor::operator= (const std::vector& val) +{ + if (val.size() == v.size()) + std::copy(val.begin(),val.end(),v.begin()); + else if (val.size() > v.size()) + std::copy(val.begin(),val.begin()+v.size(),v.begin()); + else + { + std::copy(val.begin(),val.end(),v.begin()); + std::fill(v.begin()+val.size(),v.end(),real(0)); + } + + return *this; +} + + +Tensor& Tensor::operator= (real val) +{ + this->zero(); + + t_ind i, j, inc = this->symmetric() ? 1 : n+1; + for (i = j = 0; i < n; i++, j += inc) + v[j] = val; + + return *this; +} + + +Tensor& Tensor::operator+= (const Tensor& T) +{ + if (v.size() == T.v.size()) + for (t_ind i = 0; i < v.size(); i++) + v[i] += T.v[i]; + else + std::cerr <<"Tensor::operator+(const Tensor&): " + <<"Not implemented for tensors of different size."<< std::endl; + + return *this; +} + + +Tensor& Tensor::operator+= (real val) +{ + t_ind i, j, inc = this->symmetric() ? 1 : n+1; + for (i = j = 0; i < n; i++, j += inc) + v[j] += val; + + return *this; +} + + +Tensor& Tensor::operator*= (real val) +{ + for (t_ind i = 0; i < v.size(); i++) + v[i] *= val; + + return *this; +} + + +bool Tensor::isZero (real tol) const +{ + for (t_ind i = 0; i < v.size(); i++) + if (v[i] > tol || -v[i] > tol) + return false; + + return true; +} + + +Tensor& Tensor::transpose () +{ + switch (n) { + case 2: + std::swap(v[1],v[2]); + break; + case 3: + std::swap(v[1],v[3]); + std::swap(v[2],v[6]); + std::swap(v[5],v[7]); + } + + return *this; +} + + +real Tensor::trace () const +{ + if (n == 3) + return v[0] + v[4] + v[8]; + else if (n == 2) + return v[0] + v[3]; + else if (n == 1) + return v[0]; + + return real(0); +} + + +real Tensor::det () const +{ + if (n == 3) + return v[0]*(v[4]*v[8] - v[5]*v[7]) + - v[3]*(v[1]*v[8] - v[2]*v[7]) + + v[6]*(v[1]*v[5] - v[2]*v[4]); + else if (n == 2) + return v[0]*v[3] - v[1]*v[2]; + else if (n == 1) + return v[0]; + + return real(0); +} + + +real Tensor::inverse (real tol) +{ + real det = this->det(); + if (det <= tol && det >= -tol) + { + std::cerr <<"Tensor::inverse: Singular tensor |T|="<< det << std::endl; + return real(0); + } + + if (n == 3) + { + real T11 = v[0]; real T12 = v[3]; real T13 = v[6]; + real T21 = v[1]; real T22 = v[4]; real T23 = v[7]; + real T31 = v[2]; real T32 = v[5]; real T33 = v[8]; + v[0] = (T22*T33 - T32*T23) / det; + v[1] = -(T21*T33 - T31*T23) / det; + v[2] = (T21*T32 - T31*T22) / det; + v[3] = -(T12*T33 - T32*T13) / det; + v[4] = (T11*T33 - T31*T13) / det; + v[5] = -(T11*T32 - T31*T12) / det; + v[6] = (T12*T23 - T22*T13) / det; + v[7] = -(T11*T23 - T21*T13) / det; + v[8] = (T11*T22 - T21*T12) / det; + } + else if (n == 2) + { + real T11 = v[0]; real T12 = v[2]; + real T21 = v[1]; real T22 = v[3]; + v[0] = T22 / det; + v[1] = -T21 / det; + v[2] = -T12 / det; + v[3] = T11 / det; + } + else if (n == 1) + v[0] = real(1) / det; + + return det; +} + + +/*! + \brief Multiplication between a Tensor and a point vector. +*/ + +Vec3 operator* (const Tensor& T, const Vec3& v) +{ + switch (T.n) { + case 1: + return Vec3(T(1,1)*v.x, v.y, v.z); + case 2: + return Vec3(T(1,1)*v.x + T(1,2)*v.y, + T(2,1)*v.x + T(2,2)*v.y, v.z); + case 3: + return Vec3(T(1,1)*v.x + T(1,2)*v.y + T(1,3)*v.z, + T(2,1)*v.x + T(2,2)*v.y + T(2,3)*v.z, + T(3,1)*v.x + T(3,2)*v.y + T(3,3)*v.z); + } + return v; +} + + +/*! + \brief Multiplication between a point vector and transpose of a Tensor. +*/ + +Vec3 operator* (const Vec3& v, const Tensor& T) +{ + switch (T.n) { + case 1: + return Vec3(T(1,1)*v.x, v.y, v.z); + case 2: + return Vec3(T(1,1)*v.x + T(2,1)*v.y, + T(1,2)*v.x + T(2,2)*v.y, v.z); + case 3: + return Vec3(T(1,1)*v.x + T(2,1)*v.y + T(3,1)*v.z, + T(1,2)*v.x + T(2,2)*v.y + T(3,2)*v.z, + T(1,3)*v.x + T(2,3)*v.y + T(3,3)*v.z); + } + return v; +} + + +std::ostream& SymmTensor::print (std::ostream& os) const +{ + switch (n) { + case 1: + return os << v[0] << std::endl; + case 2: + return os << v[0] <<'\n' + << v[2] <<' '<< v[1] << std::endl; + case 3: + return os << v[0] <<'\n' + << v[3] <<' '<< v[1] <<'\n' + << v[5] <<' '<< v[4] <<' '<< v[2] << std::endl; + } + + return os; +} + + +bool SymmTensor::redim (const t_ind dim) +{ + if (n == dim) return false; + + const_cast(n) = dim; + v.resize(n*(n+1)/2,real(0)); + return true; +} + + +SymmTensor::SymmTensor (const std::vector& vec) : Tensor(0) +{ + if (vec.size() > 5) + this->redim(3); + else if (vec.size() > 2) + this->redim(2); + else if (vec.size() > 0) + this->redim(1); + else + this->redim(0); + + std::copy(vec.begin(),vec.begin()+v.size(),v.begin()); +} + + +/*! + Perform the triple matrix product \f[{\bf A} = {\bf T}^T{\bf A T}\f] + where \b A = \a *this +*/ + +SymmTensor& SymmTensor::transform (const Tensor& T) +{ + if (T.symmetric() || T.dim() != n) return *this; + + real S11, S12, S13, S21, S22, S23, S31, S32, S33; + switch (n) { + case 2: + S11 = T(1,1)*v[0] + T(1,2)*v[2]; + S12 = T(1,1)*v[2] + T(1,2)*v[1]; + S21 = T(2,1)*v[0] + T(2,2)*v[2]; + S22 = T(2,1)*v[2] + T(2,2)*v[1]; + + v[0] = S11*T(1,1) + S12*T(1,2); + v[1] = S21*T(2,1) + S22*T(2,2); + v[2] = S11*T(2,1) + S12*T(2,2); + break; + + case 3: + S11 = T(1,1)*v[0] + T(1,2)*v[3] + T(1,3)*v[5]; + S12 = T(1,1)*v[3] + T(1,2)*v[1] + T(1,3)*v[4]; + S13 = T(1,1)*v[5] + T(1,2)*v[4] + T(1,3)*v[2]; + S21 = T(2,1)*v[0] + T(2,2)*v[3] + T(2,3)*v[5]; + S22 = T(2,1)*v[3] + T(2,2)*v[1] + T(2,3)*v[4]; + S23 = T(2,1)*v[5] + T(2,2)*v[4] + T(2,3)*v[2]; + S31 = T(3,1)*v[0] + T(3,2)*v[3] + T(3,3)*v[5]; + S32 = T(3,1)*v[3] + T(3,2)*v[1] + T(3,3)*v[4]; + S33 = T(3,1)*v[5] + T(3,2)*v[4] + T(3,3)*v[2]; + + v[0] = S11*T(1,1) + S12*T(1,2) + S13*T(1,3); + v[1] = S21*T(2,1) + S22*T(2,2) + S23*T(2,3); + v[2] = S31*T(3,1) + S32*T(3,2) + S33*T(3,3); + v[3] = S11*T(2,1) + S12*T(2,2) + S13*T(2,3); + v[4] = S21*T(3,1) + S22*T(3,2) + S23*T(3,3); + v[5] = S31*T(1,1) + S32*T(1,2) + S33*T(1,3); + } + + return *this; +} + + +real SymmTensor::trace () const +{ + if (n == 3) + return v[0] + v[1] + v[2]; + else if (n == 2) + return v[0] + v[1]; + else if (n == 1) + return v[0]; + + return real(0); +} + + +real SymmTensor::det () const +{ + if (n == 3) + return v[0]*(v[1]*v[2] - v[4]*v[4]) + - v[3]*(v[3]*v[2] - v[5]*v[4]) + + v[5]*(v[3]*v[4] - v[5]*v[1]); + else if (n == 2) + return (v[0]-v[2])*v[1]; + else if (n == 1) + return v[0]; + + return real(0); +} + + +real SymmTensor::inverse (real tol) +{ + real det = this->det(); + if (det <= tol && det >= -tol) + { + std::cerr <<"SymmTensor::inverse: Singular tensor |T|="<< det << std::endl; + return real(0); + } + + if (n == 3) + { + real T11 = v[0]; + real T21 = v[3]; real T22 = v[1]; + real T31 = v[5]; real T32 = v[4]; real T33 = v[2]; + v[0] = (T22*T33 - T32*T32) / det; + v[1] = (T11*T33 - T31*T31) / det; + v[2] = (T11*T22 - T21*T21) / det; + v[3] = -(T21*T33 - T31*T32) / det; + v[4] = -(T11*T32 - T31*T21) / det; + v[5] = (T21*T32 - T31*T22) / det; + } + else if (n == 2) + { + real T11 = v[0]; + real T21 = v[2]; real T22 = v[1]; + v[0] = T22 / det; + v[1] = T11 / det; + v[2] = -T21 / det; + } + else if (n == 1) + v[0] = real(1) / det; + + return det; +} + + +SymmTensor& SymmTensor::rightCauchyGreen (const Tensor& F) +{ + this->redim(F.dim()); + + switch (n) { + case 2: + v[0] = F(1,1)*F(1,1) + F(2,1)*F(2,1); + v[1] = F(1,2)*F(1,2) + F(2,2)*F(2,2); + v[2] = F(1,1)*F(1,2) + F(2,1)*F(2,2); + break; + + case 3: + v[0] = F(1,1)*F(1,1) + F(2,1)*F(2,1) + F(3,1)*F(3,1); + v[1] = F(1,2)*F(1,2) + F(2,2)*F(2,2) + F(3,2)*F(3,2); + v[2] = F(1,3)*F(1,3) + F(2,3)*F(2,3) + F(3,3)*F(3,3); + v[3] = F(1,1)*F(1,2) + F(2,1)*F(2,2) + F(3,1)*F(3,2); + v[4] = F(1,2)*F(1,3) + F(2,2)*F(2,3) + F(3,2)*F(3,3); + v[5] = F(1,1)*F(1,3) + F(2,1)*F(2,3) + F(3,1)*F(3,3); + } + + return *this; +} + + +real SymmTensor::vonMises () const +{ + switch (n) { + case 1: + return v[0]; + case 2: + return sqrt(v[0]*v[0] + v[1]*v[1] - v[0]*v[1] + 2.0*v[2]*v[2]); + case 3: + return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2] - + v[0]*v[1] - v[1]*v[2] - v[2]*v[0] + + 3.0*(v[3]*v[3] + v[4]*v[4] + v[5]*v[5])); + } + + return real(0); +} + + +/*! + \brief Multiplication between a scalar and a symmetric tensor. +*/ + +SymmTensor operator* (real a, const SymmTensor& T) +{ + SymmTensor S(T.dim()); + + for (Tensor::t_ind i = 0; i < T.v.size(); i++) + S.v[i] = a*T.v[i]; + + return S; +} + + +/*! + \brief Adding a scaled unit tensor to a symmetric tensor. +*/ + +SymmTensor operator+ (const SymmTensor& T, real a) +{ + SymmTensor S(T); + + for (Tensor::t_ind i = 0; i < S.n; i++) + S.v[i] += a; + + return S; +} + + +/*! + \brief Subtracting a scaled unit tensor from a symmetric tensor. +*/ + +SymmTensor operator- (const SymmTensor& T, real a) +{ + SymmTensor S(T); + + for (Tensor::t_ind i = 0; i < S.n; i++) + S.v[i] -= a; + + return S; +} + + +SymmTensor4::SymmTensor4 (const std::vector& x, t_ind nsd) : v(x), n(nsd) +{ + m = 0; + if (n == 3) + m = 6; + else if (n == 2) + m = 3; + else + std::cerr <<" *** Invalid fourth-order tensor, dim="<< n << std::endl; + + if (v.size() < m*m) + std::cerr <<" *** Invalid fourth-order tensor," + <<" matrix represention too small, size="<< v.size() << std::endl; + + ptr = (real*)&v.front(); +} + + +const real& SymmTensor4::operator() (t_ind i, t_ind j, t_ind k, t_ind l) const +{ + return v[index(i,j)*m+index(k,l)]; +} + + +real& SymmTensor4::operator() (t_ind i, t_ind j, t_ind k, t_ind l) +{ + return ptr[index(i,j)*m+index(k,l)]; +} + + +int LocalSystem::patch = 0; diff --git a/src/Utility/Tensor.h b/src/Utility/Tensor.h new file mode 100644 index 00000000..12dafa74 --- /dev/null +++ b/src/Utility/Tensor.h @@ -0,0 +1,249 @@ +// $Id: Tensor.h,v 1.9 2010-11-18 11:22:10 kmo Exp $ +//============================================================================== +//! +//! \file Tensor.h +//! +//! \date Dec 17 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of second-order tensors with some basic operations. +//! +//============================================================================== + +#ifndef _TENSOR_H +#define _TENSOR_H + +#include +#include + +class Vec3; + + +/*! + \brief Simple class for representing a non-symmetric second-order tensor. +*/ + +class Tensor +{ +protected: + typedef unsigned short int t_ind; //!< Tensor index type + + const t_ind n; //!< Number of spatial dimensions for the tensor + std::vector v; //!< The actual tensor component values + + //! \brief Return a 0-based array index for the given tensor indices. + //! \details Assuming column-wise storage for non-symmetric tensors. + virtual t_ind index(t_ind i, t_ind j) const { return i-1 + n*(j-1); } + + //! \brief Print out the tensor to an output stream. + virtual std::ostream& print(std::ostream& os) const; + +public: + //! \brief Constructor creating a zero tensor. + Tensor(const t_ind nsd) : n(nsd) { v.resize(n*n,real(0)); } + //! \brief Constructor creating a transformation from two tangent vectors. + Tensor(const std::vector& t1, const std::vector& t2); + + //! \brief Set to 0-tensor. + void zero() { std::fill(v.begin(),v.end(),real(0)); } + + //! \brief Type casting to a one-dimensional vector, for referencing. + operator const std::vector&() const { return v; } + //! \brief Type casting to a one-dimensional vector, for assignment. + operator std::vector&() { return v; } + + //! \brief Reference through a pointer. + const real* ptr() const { return &v.front(); } + + //! \brief Index-1 based component reference. + const real& operator()(t_ind i, t_ind j) const { return v[this->index(i,j)]; } + //! \brief Index-1 based component access. + real& operator()(t_ind i, t_ind j) { return v[this->index(i,j)]; } + + //! \brief Assignment operator. + Tensor& operator=(const Tensor& T); + //! \brief Overloaded assignment operator. + Tensor& operator=(const std::vector& val); + //! \brief Overloaded assignment operator. + Tensor& operator=(real val); + + //! \brief Incrementation operator. + Tensor& operator+=(const Tensor& T); + //! \brief Incrementation operator. + Tensor& operator+=(real val); + + //! \brief Scaling operator. + Tensor& operator*=(real val); + + //! \brief Return the dimension of this tensor. + t_ind dim() const { return n; } + + //! \brief Query whether this tensor is symmetric or not. + bool symmetric() const { return v.size() == n*(n+1)/2; } + + //! brief Query whether this tensor is zero within the given tolerance. + bool isZero(real tol = real(1.0e-6)) const; + + //! \brief Transpose the tensor. + virtual Tensor& transpose(); + + //! \brief Compute the trace of the tensor. + virtual real trace() const; + + //! \brief Compute the determinant of the tensor. + virtual real det() const; + + //! \brief Invert the tensor. + //! \param[in] tol Division by zero tolerance + //! \return Determinant of the tensor + virtual real inverse(real tol = real(0)); + + // Global operators + + //! \brief Multiplication between a tensor and a point vector. + friend Vec3 operator*(const Tensor& T, const Vec3& v); + //! \brief Multiplication between a point vector and transpose of a tensor. + friend Vec3 operator*(const Vec3& v, const Tensor& T); + + //! \brief Output stream operator. + friend std::ostream& operator<<(std::ostream& os, const Tensor& T) + { + return T.print(os); + } +}; + + +/*! + \brief Simple class for representing a symmetric second-order tensor. +*/ + +class SymmTensor : public Tensor +{ + //! \brief Sets the number of spatial dimensions for the tensor to \a newDim. + //! \return \e true if the dimension was changed, otherwise \e false + //! + //! \details This method is private because the tensor dimension is not + //! supposed to be changed by the application. It is only for internal use. + bool redim(const t_ind newDim); + +protected: + //! \brief Return a 0-based array index for the given tensor indices. + //! \details Symmetric tensors are assumed stored with the following order: + //! s11, s22, s33, s12, s23, s13. + virtual t_ind index(t_ind i, t_ind j) const + { + if (i == j) + return i-1; // diagonal term + else if (n == 2) + return 2; // off-diagonal term (2D) + + if (i == j+1 || i+2 == j) std::swap(i,j); + return i+2; // upper triangular term (3D) + } + + //! \brief Print out the lower triangle of the tensor to an output stream. + virtual std::ostream& print(std::ostream& os) const; + +public: + //! \brief Constructor creating a zero tensor. + SymmTensor(const t_ind nsd) : Tensor(nsd) { v.resize(n*(n+1)/2,real(0)); } + //! \brief Constructor creating a symmetric tensor from a vector. + SymmTensor(const std::vector& vec); + + //! \brief Transpose the symmetric tensor (do nothing). + virtual Tensor& transpose() { return *this; } + + //! \brief Compute the trace of the symmetric tensor. + virtual real trace() const; + + //! \brief Compute the determinant of the symmetric tensor. + virtual real det() const; + + //! \brief Invert the symmetric tensor. + //! \param[in] tol Division by zero tolerance + //! \return Determinant of the tensor + virtual real inverse(real tol = real(0)); + + //! \brief Congruence transformation of a symmetric tensor. + SymmTensor& transform(const Tensor& T); + + //! \brief Construct the right Cauchy-Green tensor from a deformation tensor. + SymmTensor& rightCauchyGreen(const Tensor& F); + + //! \brief Return the von Mises value of the symmetric tensor. + real vonMises() const; + + // Global operators + + //! \brief Multiplication between a scalar and a symmetric tensor. + friend SymmTensor operator*(real a, const SymmTensor& T); + + //! \brief Adding a scaled unit tensor to a symmetric tensor. + friend SymmTensor operator+(const SymmTensor& T, real a); + //! \brief Subtracting a scaled unit tensor from a symmetric tensor. + friend SymmTensor operator-(const SymmTensor& T, real a); +}; + + +/*! + \brief Simple class for representing a symmetric fourth-order tensor. +*/ + +class SymmTensor4 +{ + typedef unsigned short int t_ind; //!< Tensor index type + + t_ind n; //!< Number of spatial dimensions for the tensor + t_ind m; //!< Dimension of the matrix representation + const std::vector& v; //!< The actual tensor component values + real* ptr; //!< Non-const pointer to tensor component values + + //! \brief Return a 0-based array index for the given row and column indices. + //! \details Symmetric second-order tensors in 3D are assumed stored with the + //! following order in a one-dimensional array: s11, s22, s33, s12, s23, s13. + inline t_ind index(t_ind i, t_ind j) const + { + if (i == j) + return i-1; // diagonal term + else if (n == 2) + return 2; // off-diagonal term (2D) + + if (i == j+1 || i+2 == j) std::swap(i,j); + return i+2; // upper triangular term (3D) + } + +public: + //! \brief The constructor creates a tensor from a vector of components. + //! \details The provided vector is assumed to contain the components of the + //! matrix representation of the tensor, stored in a one-dimensional array. + SymmTensor4(const std::vector& x, t_ind nsd = 3); + + //! \brief Index-1 based component reference. + const real& operator()(t_ind i, t_ind j, t_ind d, t_ind l) const; + //! \brief Index-1 based component access. + real& operator()(t_ind i, t_ind j, t_ind k, t_ind l); +}; + + +/*! + \brief Abstract interface to problem-specific local coordinate systems. +*/ + +class LocalSystem +{ +protected: + //! \brief Protected default constructor since this is an interface class. + LocalSystem() {} + +public: + //! \brief Empty default destructor. + virtual ~LocalSystem() {} + + //! \brief Computes the global-to-local transformation at the point \a X. + virtual const Tensor& getTmat(const Vec3& X) const = 0; + + static int patch; //!< Counter used to establish multi-patch local systems +}; + +#endif diff --git a/src/Utility/Utilities.C b/src/Utility/Utilities.C new file mode 100644 index 00000000..7ae502ee --- /dev/null +++ b/src/Utility/Utilities.C @@ -0,0 +1,188 @@ +// $Id: Utilities.C,v 1.9 2011-01-07 08:55:54 kmo Exp $ +//============================================================================== +//! +//! \file Utilities.C +//! +//! \date May 29 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Various utility methods. +//! +//============================================================================== + +#include "Utilities.h" +#include + + +char* utl::readLine (std::istream& is) +{ + static char buf[1024]; + for (size_t n = 0; is.good(); n = strlen(buf)) + { + for (size_t i = 0; i < n; i++) + if (!isspace(buf[i])) + if (buf[i] == '#') + break; // comment line - skip + else + return buf+i; // first non-blank character + + is.getline(buf,sizeof(buf)); + } + + return 0; +} + + +bool utl::ignoreComments (std::istream& is) +{ + int c = ' '; + while (isspace(c)) + c = is.get(); + + while (c == '#') + { + is.ignore(256,'\n'); + c = is.get(); + } + is.putback(c); + + return is.good(); +} + + +bool utl::renumber (int& num, int& runner, std::map& old2new) +{ + std::map::iterator it = old2new.find(num); + if (it == old2new.end()) + it = old2new.insert(std::make_pair(num,++runner)).first; + + if (num == it->second) return false; + + num = it->second; + return true; +} + + +bool utl::renumber (int& num, const std::map& old2new, bool msg) +{ + std::map::const_iterator it = old2new.find(num); + if (it == old2new.end()) + { + if (msg) + std::cerr <<" *** utl::renumber: Old value "<< num + <<" does not exist in old2new mapping"<< std::endl; + return false; + } + + num = it->second; + return true; +} + + +int utl::gather (const std::vector& index, size_t nr, + const std::vector& in, std::vector& out, + size_t offset_in) +{ + int outside = 0; + out.resize(nr*index.size()); + const real* data = &in.front() + offset_in; + real* outVec = &out.front(); + for (size_t i = 0; i < index.size(); i++, outVec += nr) + if (index[i] >= 0 && offset_in+nr*index[i] < in.size()) + memcpy(outVec,data+nr*index[i],nr*sizeof(real)); + else + outside++; + + return outside; +} + + +int utl::gather (const std::vector& index, size_t nr, + const utl::vector& in, utl::matrix& out, + size_t offset_in) +{ + int outside = 0; + out.resize(nr,index.size()); + const real* data = &in.front() + offset_in; + for (size_t i = 0; i < index.size(); i++) + if (index[i] >= 0 && offset_in+nr*index[i] < in.size()) + out.fillColumn(1+i,data+nr*index[i]); + else + outside++; + + return outside; +} + + +int utl::gather (const std::vector& index, size_t ir, size_t nr, + const std::vector& in, std::vector& out, + size_t offset_in, int shift_idx) +{ + if (ir >= nr) return index.size(); + + int outside = 0; + out.resize(index.size()); + offset_in += ir; + for (size_t i = 0; i < index.size(); i++) + { + int ip = offset_in + nr*(index[i]-shift_idx); + if (ip >= offset_in && ip < in.size()) + out[i] = in[ip]; + else + outside++; + } + + return outside; +} + + +size_t utl::Pascal (int p, unsigned short int nsd) +{ + size_t nM = 1; + for (int q = 1; q <= p; q++) + for (int i = q; i >= 0; i--) + if (nsd == 2) + nM++; + else for (int j = q; j >= 0; j--) + if (i+j <= q) nM++; + + return nM; +} + + +void utl::Pascal (int p, real x, real y, std::vector& phi) +{ + phi.clear(); + phi.reserve(Pascal(p,2)); + phi.push_back(real(1)); + for (int q = 1; q <= p; q++) + for (int i = q; i >= 0; i--) + { + int k, j = q-i; + real a = real(1); + for (k = 0; k < i; k++) a *= x; + for (k = 0; k < j; k++) a *= y; + phi.push_back(a); + } +} + + +void utl::Pascal (int p, real x, real y, real z, std::vector& phi) +{ + phi.clear(); + phi.reserve(Pascal(p,3)); + phi.push_back(real(1)); + for (int q = 1; q <= p; q++) + for (int i = q; i >= 0; i--) + for (int j = q; j >= 0; j--) + if (i+j <= q) + { + int l, k = q-i-j; + real a = real(1); + for (l = 0; l < i; l++) a *= x; + for (l = 0; l < j; l++) a *= y; + for (l = 0; l < k; l++) a *= z; + phi.push_back(a); + } +} diff --git a/src/Utility/Utilities.h b/src/Utility/Utilities.h new file mode 100644 index 00000000..d4dfb424 --- /dev/null +++ b/src/Utility/Utilities.h @@ -0,0 +1,94 @@ +// $Id: Utilities.h,v 1.7 2010-12-30 15:11:34 kmo Exp $ +//============================================================================== +//! +//! \file Utilities.h +//! +//! \date May 29 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Various utility methods. +//! +//============================================================================== + +#ifndef _UTILITIES_H +#define _UTILITIES_H + +#include "matrix.h" +#include +#include +#include + + +namespace utl +{ + //! \brief Reads one line, ignoring comment lines and leading blanks. + //! \details The data read is kept in an internal static buffer. + //! \param is File stream to read from + //! \returns Pointer to the static buffer containg the data read. + char* readLine(std::istream& is); + //! \brief Ignores comment lines and blank lines from an input stream. + //! \param is File stream to read from + bool ignoreComments(std::istream& is); + + //! \brief Transforms the integer value \a num into a unique range. + //! \details This method is invoked on a series of (non-unique) values. + //! When a value that has not been encountered before is detected, + //! it is transformed into \a runner+1 and \a runner is incremented. + //! \param num The integer value to transform, updated value on output + //! \param runner The last new value assigned + //! \param old2new Mapping from old values to new values + //! return \e true if the value of \a num changed + bool renumber(int& num, int& runner, std::map& old2new); + + //! \brief Transforms the integer value \a num according to a given mapping. + //! \details In this method the \a old2new mapping is not updated. + //! \param num The integer value to transform, updated value on output + //! \param[in] old2new Mapping from old values to new values + //! \param[in] msg If \e true, give error message if given value not found + //! \return \a true if \a num was found in the \a old2new mapping + bool renumber(int& num, const std::map& old2new, bool msg = false); + + //! \brief Compresses the rows of a 2D array based on given scatter indices. + //! \param[in] index Scatter indices of the columns that should be retained + //! \param[in] nr Number of rows in the 2D array + //! \param[in] in The input array stored column-wise in a 1D array + //! \param[out] out The output array stored column-wise in a 1D array + //! \param[in] offset_in Optional start offset for the \a in vector + int gather(const std::vector& index, size_t nr, + const std::vector& in, std::vector& out, + size_t offset_in = 0); + + //! \brief Compresses the rows of a 2D array based on given scatter indices. + //! \param[in] index Scatter indices of the columns that should be retained + //! \param[in] nr Number of rows in the 2D array + //! \param[in] in The input array stored column-wise in a 1D array + //! \param[out] out The output array stored as a 2D matrix + //! \param[in] offset_in Optional start offset for the \a in vector + int gather(const std::vector& index, size_t nr, + const utl::vector& in, utl::matrix& out, + size_t offset_in = 0); + + //! \brief Compresses a row of a 2D array based on given scatter indices. + //! \param[in] index Scatter indices of the columns that should be retained + //! \param[in] ir Index of the row to compress + //! \param[in] nr Number of rows in the 2D array + //! \param[in] in The input array stored column-wise in a 1D array + //! \param[out] out The output array stored column-wise in a 1D array + //! \param[in] offset_in Optional start offset for the \a in vector + //! \param[in] shift_idx Optional constant shift in the scatter indices + int gather(const std::vector& index, size_t ir, size_t nr, + const std::vector& in, std::vector& out, + size_t offset_in = 0, int shift_idx = 0); + + //! \brief Returns the number of monomials in Pascal's triangle. + //! \param[in] p Polynomial order (>= 0) + //! \param[in] nsd Number of spatial dimensions (2 or 3) + size_t Pascal(int p, unsigned short int nsd); + //! \brief Evaluates the monomials of Pascal's triangle in 2D for order \a p. + void Pascal(int p, real x, real y, std::vector& phi); + //! \brief Evaluates the monomials of Pascal's triangle in 3D for order \a p. + void Pascal(int p, real x, real y, real z, std::vector& phi); +}; + +#endif diff --git a/src/Utility/VTF.C b/src/Utility/VTF.C new file mode 100644 index 00000000..723ad0e5 --- /dev/null +++ b/src/Utility/VTF.C @@ -0,0 +1,483 @@ +// $Id: VTF.C,v 1.10 2010-12-31 13:14:06 kmo Exp $ +//============================================================================== +//! +//! \file VTF.C +//! +//! \date Dec 1 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Output of FE model and results to VTF file. +//! +//============================================================================== + +#include "VTF.h" +#if HAS_VTFAPI == 1 +#include "VTFAPI.h" +#include "VTOAPIPropertyIDs.h" +#endif +#include "ElementBlock.h" +#include + + +real VTF::vecOffset[3] = { 0.0, 0.0, 0.0 }; + + +VTF::VTF (const char* filename, int type) +{ +#ifdef HAS_VTFAPI + myState = 0; + + // Create the VTF file object + myFile = new VTFAFile(); + + // Enable debug info to stderr/console + myFile->SetOutputDebugError(1); + + if (!VTFA_FAILURE(myFile->CreateVTFFile(filename,type > 0))) + return; + + delete myFile; + showError("Error creating VTF file"); +#else + showError("VTF export is not available in this version"); +#endif + myFile = 0; +} + + +VTF::~VTF () +{ + std::vector geomID(myBlocks.size()); + for (size_t i = 0; i < myBlocks.size(); i++) + { + geomID[i] = i+1; + delete myBlocks[i]; + } + + if (!writeGeometry(&geomID.front(),geomID.size())) + showError("Error writing geometry"); + +#ifdef HAS_VTFAPI + if (!myFile) return; + + size_t i; + for (i = 0; i < myDBlock.size(); i++) + if (myDBlock[i]) + { + if (VTFA_FAILURE(myFile->WriteBlock(myDBlock[i]))) + showError("Error writing Displacement Block"); + delete myDBlock[i]; + } + for (i = 0; i < myVBlock.size(); i++) + if (myVBlock[i]) + { + if (VTFA_FAILURE(myFile->WriteBlock(myVBlock[i]))) + showError("Error writing Vector Block"); + delete myVBlock[i]; + } + + for (i = 0; i < mySBlock.size(); i++) + if (mySBlock[i]) + { + if (VTFA_FAILURE(myFile->WriteBlock(mySBlock[i]))) + showError("Error writing Scalar Block"); + delete mySBlock[i]; + } + + if (myState) + { + if (VTFA_FAILURE(myFile->WriteBlock(myState))) + showError("Error writing state info block"); + delete myState; + } + + if (VTFA_FAILURE(myFile->CloseFile())) + showError("Error closing VTF file"); + + delete myFile; +#endif +} + + +bool VTF::writeGrid (const ElementBlock* block, const char* partname) +{ + if (!myFile) return true; + + myBlocks.push_back(block); + int geomID = myBlocks.size(); + + if (!writeNodes(geomID)) + return showError("Error writing node block",geomID); + + if (!writeElements(partname,geomID,geomID)) + return showError("Error writing element block",geomID); + + return true; +} + + +bool VTF::writeVres (const std::vector& nodeResult, + int idBlock, int geomID, size_t nvc) +{ + if (!myFile) return true; + + const size_t nnod = myBlocks[geomID-1]->getNoNodes(); + const size_t nres = nodeResult.size(); + const size_t ncmp = nres/(nnod > 0 ? nnod : 1); + if (nres != ncmp*nnod) + return showError("Invalid size of result array",nres); + else if (nvc < 1) + nvc = ncmp; + +#ifdef HAS_VTFAPI + // Cast to float + float* resVec = new float[3*nnod]; + if (nres == 3*nnod && nvc == 3) + for (size_t i = 0; i < nres; i++) + resVec[i] = nodeResult[i]; + else + for (size_t i = 0; i < nnod; i++) + for (size_t j = 0; j < 3; j++) + resVec[3*i+j] = j < nvc ? nodeResult[ncmp*i+j] : 0.0f; + + VTFAResultBlock dBlock(idBlock,VTFA_DIM_VECTOR,VTFA_RESMAP_NODE,0); + + if (VTFA_FAILURE(dBlock.SetResults3D(resVec,nnod))) + return showError("Error defining result block",idBlock); + + delete[] resVec; + dBlock.SetMapToBlockID(geomID); + if (VTFA_FAILURE(myFile->WriteBlock(&dBlock))) + return showError("Error writing result block",idBlock); +#endif + + return true; +} + + +bool VTF::writeEres (const std::vector& elementResult, + int idBlock, int geomID) +{ + if (!myFile) return true; + if (geomID < 1 || geomID > myBlocks.size()) return false; + + const size_t nres = elementResult.size(); + if (nres > myBlocks[geomID-1]->getNoElms()) + return showError("Invalid size of result array",nres); + else if (nres < myBlocks[geomID-1]->getNoElms()) + showError("Warning: Fewer element results that anticipated",nres); + +#ifdef HAS_VTFAPI + // Cast to float + float* resVec = new float[nres]; + for (size_t i = 0; i < nres; i++) + resVec[i] = elementResult[i]; + + VTFAResultBlock dBlock(idBlock,VTFA_DIM_SCALAR,VTFA_RESMAP_ELEMENT,0); + + if (VTFA_FAILURE(dBlock.SetResults1D(resVec,nres))) + return showError("Error defining result block",idBlock); + + delete[] resVec; + dBlock.SetMapToBlockID(geomID); + if (VTFA_FAILURE(myFile->WriteBlock(&dBlock))) + return showError("Error writing result block",idBlock); +#endif + + return true; +} + + +bool VTF::writeNres (const std::vector& nodalResult, + int idBlock, int geomID) +{ + if (!myFile) return true; + if (geomID < 1 || geomID > myBlocks.size()) return false; + + const size_t nres = nodalResult.size(); + if (nres != myBlocks[geomID-1]->getNoNodes()) + return showError("Invalid size of result array",nres); + +#ifdef HAS_VTFAPI + // Cast to float + float* resVec = new float[nres]; + for (size_t i = 0; i < nres; i++) + resVec[i] = nodalResult[i]; + + VTFAResultBlock dBlock(idBlock,VTFA_DIM_SCALAR,VTFA_RESMAP_NODE,0); + + if (VTFA_FAILURE(dBlock.SetResults1D(resVec,nres))) + return showError("Error defining result block",idBlock); + + delete[] resVec; + dBlock.SetMapToBlockID(geomID); + if (VTFA_FAILURE(myFile->WriteBlock(&dBlock))) + return showError("Error writing result block",idBlock); +#endif + + return true; +} + + +bool VTF::writeVectors (const std::map& pntResult, int idBlock) +{ +#ifdef HAS_VTFAPI + bool writePoints = false; + static int geomID = 0; + if (geomID == 0) + { + // The big assumption here is that we have only one call to writeVectors + // per time step, and that all subsequent calls are with the same points + myBlocks.push_back(new ElementBlock()); + geomID = myBlocks.size(); + writePoints = true; + } + + VTFANodeBlock nBlock(geomID,0); + VTFAResultBlock rBlock(idBlock,VTFA_DIM_VECTOR,VTFA_RESMAP_NODE,0); + + size_t i = 0, np = pntResult.size(); + if (writePoints && VTFA_FAILURE(nBlock.SetNumNodes(np))) + return showError("Error defining node block",geomID); + else if (VTFA_FAILURE(rBlock.SetNumResults(np))) + return showError("Error defining result block",idBlock); + else + rBlock.SetMapToBlockID(geomID); + + int* mnpc = writePoints ? new int[np] : 0; + std::map::const_iterator cit; + for (cit = pntResult.begin(); cit != pntResult.end(); cit++, i++) + if (writePoints && VTFA_FAILURE(nBlock.AddNode(vecOffset[0]+cit->first.x, + vecOffset[1]+cit->first.y, + vecOffset[2]+cit->first.z))) + return showError("Error adding node to block",geomID); + else if (VTFA_FAILURE(rBlock.AddResult(cit->second.x, + cit->second.y, + cit->second.z))) + return showError("Error adding result to block",idBlock); + else if (writePoints) + mnpc[i] = i; + + if (writePoints) + { + // We must define an element block (with point elements) also, + // otherwise GLview does not visualize the vectors + VTFAElementBlock eBlock(geomID,0,0); + eBlock.SetPartID(geomID); + eBlock.SetNodeBlockID(geomID); + if (VTFA_FAILURE(eBlock.AddElements(VTFA_POINTS,mnpc,np))) + return showError("Error defining element block",geomID); + delete[] mnpc; + + if (VTFA_FAILURE(myFile->WriteBlock(&nBlock))) + return showError("Error writing node block",geomID); + else if (VTFA_FAILURE(myFile->WriteBlock(&eBlock))) + return showError("Error writing element block",geomID); + } + if (VTFA_FAILURE(myFile->WriteBlock(&rBlock))) + return showError("Error writing result block",idBlock); +#endif + + return true; +} + + +bool VTF::writeDblk (const std::vector& dBlockIDs, const char* resultName, + int idBlock, int iStep) +{ +#if HAS_VTFAPI == 1 + if (myDBlock.size() < idBlock) myDBlock.resize(idBlock,0); + + if (!myDBlock[--idBlock]) + { + myDBlock[idBlock] = new VTFADisplacementBlock(idBlock+1); + if (resultName) myDBlock[idBlock]->SetName(resultName); + myDBlock[idBlock]->SetRelativeDisplacementResults(1); + } + if (VTFA_FAILURE(myDBlock[idBlock]->SetResultBlocks(&dBlockIDs.front(), + dBlockIDs.size(),iStep))) + return showError("Error defining displacement block",idBlock); +#endif + + return true; +} + + +bool VTF::writeVblk (int vBlockID, const char* resultName, + int idBlock, int iStep) +{ +#if HAS_VTFAPI == 1 + if (myVBlock.size() < idBlock) myVBlock.resize(idBlock,0); + + if (!myVBlock[--idBlock]) + { + myVBlock[idBlock] = new VTFAVectorBlock(idBlock+1); + if (resultName) myVBlock[idBlock]->SetName(resultName); + } + if (VTFA_FAILURE(myVBlock[idBlock]->SetResultBlocks(&vBlockID,1,iStep))) + return showError("Error defining vector block",idBlock); +#endif + + return true; +} + + +bool VTF::writeVblk (const std::vector& vBlockIDs, const char* resultName, + int idBlock, int iStep) +{ +#if HAS_VTFAPI == 1 + if (myVBlock.size() < idBlock) myVBlock.resize(idBlock,0); + + if (!myVBlock[--idBlock]) + { + myVBlock[idBlock] = new VTFAVectorBlock(idBlock+1); + if (resultName) myVBlock[idBlock]->SetName(resultName); + } + if (VTFA_FAILURE(myVBlock[idBlock]->SetResultBlocks(&vBlockIDs.front(), + vBlockIDs.size(),iStep))) + return showError("Error defining vector block",idBlock); +#endif + + return true; +} + + +bool VTF::writeSblk (int sBlockID, const char* resultName, + int idBlock, int iStep) +{ +#if HAS_VTFAPI == 1 + if (mySBlock.size() < idBlock) mySBlock.resize(idBlock,0); + + if (!mySBlock[--idBlock]) + { + mySBlock[idBlock] = new VTFAScalarBlock(idBlock+1); + if (resultName) mySBlock[idBlock]->SetName(resultName); + } + if (VTFA_FAILURE(mySBlock[idBlock]->SetResultBlocks(&sBlockID,1,iStep))) + return showError("Error defining scalar block",idBlock); +#endif + + return true; +} + + +bool VTF::writeSblk (const std::vector& sBlockIDs, const char* resultName, + int idBlock, int iStep) +{ +#if HAS_VTFAPI == 1 + if (mySBlock.size() < idBlock) mySBlock.resize(idBlock,0); + + if (!mySBlock[--idBlock]) + { + mySBlock[idBlock] = new VTFAScalarBlock(idBlock+1); + if (resultName) mySBlock[idBlock]->SetName(resultName); + } + if (VTFA_FAILURE(mySBlock[idBlock]->SetResultBlocks(&sBlockIDs.front(), + sBlockIDs.size(),iStep))) + return showError("Error defining scalar block",idBlock); +#endif + + return true; +} + + +bool VTF::writeState (int iStep, const char* fmt, real refValue, int refType) +{ +#ifdef HAS_VTFAPI + if (!myState) myState = new VTFAStateInfoBlock(); + + char stepName[32]; + sprintf(stepName,fmt,refValue); + if (VTFA_FAILURE(myState->SetStepData(iStep,stepName,refValue,refType))) + return showError("Error defining state info block"); +#endif + + return true; +} + + +bool VTF::writeGeometry (const int* pGeometryParts, int iNumParts) +{ + bool ok = true; + +#ifdef HAS_VTFAPI + VTFAGeometryBlock geoBlock; + + if (VTFA_FAILURE(geoBlock.SetGeometryElementBlocks(pGeometryParts,iNumParts))) + ok = false; + + if (VTFA_FAILURE(myFile->WriteBlock(&geoBlock))) + ok = false; +#endif + + return ok; +} + + +bool VTF::writeNodes (int iBlockID) +{ + bool ok = true; + +#ifdef HAS_VTFAPI + VTFANodeBlock nBlock(iBlockID,0); + + const ElementBlock* grid = myBlocks.back(); + if (VTFA_FAILURE(nBlock.SetNumNodes(grid->getNoNodes()))) + ok = false; + + std::vector::const_iterator cit; + for (cit = grid->begin_XYZ(); cit != grid->end_XYZ() && ok; cit++) + if (VTFA_FAILURE(nBlock.AddNode(cit->x, cit->y, cit->z))) ok = false; + + if (VTFA_FAILURE(myFile->WriteBlock(&nBlock))) + ok = false; +#endif + + return ok; +} + + +bool VTF::writeElements (const char* partName, int iBlockID, int iNodeBlockID) +{ + bool ok = true; + +#ifdef HAS_VTFAPI + VTFAElementBlock eBlock(iBlockID,0,0); + + const ElementBlock* grid = myBlocks.back(); + const int* mnpc = grid->getElements(); + int nel = grid->getNoElms(); + switch (grid->getNoElmNodes()) { + case 2: + ok = VTFA_SUCCESS(eBlock.AddElements(VTFA_BEAMS,mnpc,nel)); + break; + case 4: + ok = VTFA_SUCCESS(eBlock.AddElements(VTFA_QUADS,mnpc,nel)); + break; + case 8: + ok = VTFA_SUCCESS(eBlock.AddElements(VTFA_HEXAHEDRONS,mnpc,nel)); + break; + default: + ok = false; + } + + eBlock.SetPartID(iBlockID); + eBlock.SetPartName(partName); + eBlock.SetNodeBlockID(iNodeBlockID); + + if (VTFA_FAILURE(myFile->WriteBlock(&eBlock))) + ok = false; +#endif + + return ok; +} + + +bool VTF::showError (const char* msg, int ID) +{ + std::cerr <<"VTF: "<< msg; + if (ID) std::cerr <<" "<< ID; + std::cerr << std::endl; + return false; +} diff --git a/src/Utility/VTF.h b/src/Utility/VTF.h new file mode 100644 index 00000000..8da1c0f1 --- /dev/null +++ b/src/Utility/VTF.h @@ -0,0 +1,157 @@ +// $Id: VTF.h,v 1.4 2010-12-31 13:14:06 kmo Exp $ +//============================================================================== +//! +//! \file VTF.h +//! +//! \date Dec 1 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Output of FE model and results to VTF file. +//! +//============================================================================== + +#ifndef _VTF_H +#define _VTF_H + +#include +#include + +struct ElementBlock; +class Vec3; +class VTFAFile; +class VTFAStateInfoBlock; +class VTFADisplacementBlock; +class VTFAVectorBlock; +class VTFAScalarBlock; + + +/*! + \brief Class for output of FE model and results to VTF file. +*/ + +class VTF +{ +public: + //! \brief Constructor which opens a new VTF-file. + //! \param[in] filename Name of the VTF-file + //! \param[in] type File type (0 = ASCII, 1 = BINARY) + VTF(const char* filename, int type = 0); + //! \brief The destructor finalizes and closes the VTF-file. + ~VTF(); + + //! \brief Writes the FE geometry to the VTF-file. + //! \param[in] g The FE grid that all results written are referred to + //! \param[in] partname Name of the geometry being written + bool writeGrid(const ElementBlock* g, const char* partname); + + //! \brief Writes a block of scalar nodal results to the VTF-file. + //! \param[in] nodeResult Vector of nodal results, + //! length must equal the number of nodes in the geometry block + //! \param[in] idBlock Result block identifier + //! \param[in] gID Geometry block identifier + bool writeNres(const std::vector& nodeResult, + int idBlock = 1, int gID = 1); + //! \brief Writes a block of scalar element results to the VTF-file. + //! \param[in] elmResult Vector of element results (one per element) + //! length must equal the number of elements in the geometry block + //! \param[in] idBlock Result block identifier + //! \param[in] gID Geometry block identifier + bool writeEres(const std::vector& elmResult, + int idBlock = 1, int gID = 1); + //! \brief Writes a block of vector nodal results to the VTF-file. + //! \param[in] nodeResult Vector of nodal results, length must be equal the + //! number of nodes in the geometry block times 1...5 + //! \param[in] idBlock Result block identifier + //! \param[in] gID Geometry block identifier + //! \param[in] nvc Number of components per node in \a nodeResult + bool writeVres(const std::vector& nodeResult, + int idBlock = 1, int gID = 1, size_t nvc = 0); + //! \brief Writes a block of point vector results to the VTF-file. + //! \details This method creates a separate geometry block consisting of the + //! attack points of the result vectors, since they are independent of the + //! FE geometry created by the \a writeGrid method. + //! \param[in] pntResult A set of result vectors with associated attack points + //! \param[in] idBlock Vector block identifier + bool writeVectors(const std::map& pntResult, int idBlock = 1); + + //! \brief Writes a scalar block definition to the VTF-file. + //! \param[in] sBlockID The result block that makes up this scalar block + //! \param[in] resultName Name of the result quantity + //! \param[in] idBlock Scalar block identifier + //! \param[in] iStep Load/Time step identifier + bool writeSblk(int sBlockID, + const char* resultName = 0, int idBlock = 1, int iStep = 1); + //! \brief Writes a scalar block definition to the VTF-file. + //! \param[in] sBlockIDs All result blocks that make up this scalar block + //! \param[in] resultName Name of the result quantity + //! \param[in] idBlock Scalar block identifier + //! \param[in] iStep Load/Time step identifier + bool writeSblk(const std::vector& sBlockIDs, + const char* resultName = 0, int idBlock = 1, int iStep = 1); + //! \brief Writes a vector block definition to the VTF-file. + //! \param[in] vBlockID The result block that makes up this vector block + //! \param[in] resultName Name of the result quantity + //! \param[in] idBlock Vector block identifier + //! \param[in] iStep Load/Time step identifier + bool writeVblk(int vBlockID, + const char* resultName = 0, int idBlock = 1, int iStep = 1); + //! \brief Writes a vector block definition to the VTF-file. + //! \param[in] vBlockIDs All result blocks that make up this vector block + //! \param[in] resultName Name of the result quantity + //! \param[in] idBlock Vector block identifier + //! \param[in] iStep Load/Time step identifier + bool writeVblk(const std::vector& vBlockIDs, + const char* resultName = 0, int idBlock = 1, int iStep = 1); + //! \brief Writes a displacement block definition to the VTF-file. + //! \param[in] dBlockIDs All result blocks that make up the displacement block + //! \param[in] resultName Name of the result quantity + //! \param[in] idBlock Displacement block identifier + //! \param[in] iStep Load/Time step identifier + bool writeDblk(const std::vector& dBlockIDs, + const char* resultName = 0, int idBlock = 1, int iStep = 1); + + //! \brief Writes a state info block to the VTF-file. + //! \param[in] iStep Load/Time step identifier + //! \param[in] fmt Format string for step name + //! \param[in] refValue Reference value for the step (time, frequency, etc.) + //! \param[in] refType Reference value type (0=Time, 1=Frequency, 2=Load case) + bool writeState(int iStep, const char* fmt, real refValue, int refType = 0); + + //! \brief Returns the pointer to a geometry block. + const ElementBlock* getBlock(int geomID) const { return myBlocks[geomID-1]; } + +private: + //! \brief Writes a geometry block to the VTF-file. + //! \param[in] parts Array of geometry part indices + //! \param[in] nPart Number of geometry parts + bool writeGeometry(const int* parts, int nPart); + //! \brief Writes a node block to the VTF-file. + //! \details The coordinates of the last added element block are written. + //! \param[in] blockID Node block identifier + bool writeNodes(int blockID); + //! \brief Writes an element block to the VTF-file. + //! \details The element topology of the last added element block is written. + //! \param[in] partName Name used to identify the part in GLview + //! \param[in] blockID Element block identifier + //! \param[in] nodesID Node block which the written element block refers to + bool writeElements(const char* partName, int blockID, int nodesID); + //! \brief Prints an error message to \a std::cerr. + //! \param[in] msg The message to print + //! \param[in] ID If non-zero, the value is appended to the printed message + //! \return \e false (always) + static bool showError(const char* msg, int ID = 0); + +public: + static real vecOffset[3]; //!< Optional offset for vector attack points + +private: + VTFAFile* myFile; //!< Pointer to the actual VTF-file being written + VTFAStateInfoBlock* myState; //!< The state info block for this file + std::vector myDBlock; //!< Displacement blocks + std::vector myVBlock; //!< Vector field blocks + std::vector mySBlock; //!< Scalar field blocks + std::vector myBlocks; //!< The FE geometry +}; + +#endif diff --git a/src/Utility/Vec3.h b/src/Utility/Vec3.h new file mode 100644 index 00000000..3d438cec --- /dev/null +++ b/src/Utility/Vec3.h @@ -0,0 +1,212 @@ +// $Id: Vec3.h,v 1.9 2010-12-20 13:54:56 kmo Exp $ +//============================================================================== +//! +//! \file Vec3.h +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of a point in 3D space with some basic operations. +//! +//============================================================================== + +#ifndef _VEC3_H +#define _VEC3_H + +#include +#include +#include + + +/*! + \brief Simple class for representing a point in 3D space. +*/ + +class Vec3 +{ + real v[3]; //!< The actual point vector + +public: + real& x; //!< Reference to X-component + real& y; //!< Reference to Y-component + real& z; //!< Reference to Z-component + + //! \brief Default constructor creating a point at origin. + Vec3() : x(v[0]), y(v[1]), z(v[2]) { x = y = z = 0.0; } + //! \brief Constructor creating a point at the specified location. + Vec3(real X, real Y, real Z) : x(v[0]), y(v[1]), z(v[2]) + { x = X; y = Y; z = Z; } + //! \brief Constructor creating a point from the given \a std::vector. + Vec3(const std::vector& X) : x(v[0]), y(v[1]), z(v[2]) + { for (size_t i = 0; i < 3; i++) v[i] = i < X.size() ? X[i] : 0.0; } + //! \brief Copy constructor. + Vec3(const Vec3& X) : x(v[0]), y(v[1]), z(v[2]) + { x = X.x; y = X.y; z = X.z; } + //! \brief Constructor creating a cross product of two other vectors. + Vec3(const Vec3& X, const Vec3& Y) : x(v[0]), y(v[1]), z(v[2]) + { this->cross(X,Y); } + + //! \brief Assignment operator. + Vec3& operator=(const Vec3& X) { x = X.x; y = X.y; z = X.z; return *this; } + //! \brief Overloaded assignment operator. + Vec3& operator=(real val) { x = y = z = val; return *this; } + + //! \brief Indexing operator for component reference. + const real& operator[](int i) const { return v[i]; } + //! \brief Indexing operator for component assignment. + real& operator[](int i) { return v[i]; } + + //! \brief Reference through a pointer. + const real* ptr() const { return v; } + + //! \brief Multiplication with a scalar. + Vec3& operator*=(real c) { x *= c; y *= c; z *= c; return *this; } + //! \brief Division by a scalar. + Vec3& operator/=(real d) { x /= d; y /= d; z /= d; return *this; } + + //! \brief Add the given vector \b X to \a *this. + Vec3& operator+=(const Vec3& X) + { + x += X.x; + y += X.y; + z += X.z; + return *this; + } + + //! \brief Subtract the given vector \b X from \a *this. + Vec3& operator-=(const Vec3& X) + { + x -= X.x; + y -= X.y; + z -= X.z; + return *this; + } + + //! \brief Return the length of the vector. + real length() const { return sqrt(x*x+y*y+z*z); } + + //! \brief Normalize the vector and return its length. + real normalize(double tol = 1.0e-16) + { + register double len = sqrt(x*x+y*y+z*z); + if (len <= tol) return len; + x /= len; y /= len; z /= len; + return len; + } + + //! \brief Cross product between two vectors. + Vec3& cross(const Vec3& a, const Vec3& b) + { + x = a.y*b.z - a.z*b.y; + y = a.z*b.x - a.x*b.z; + z = a.x*b.y - a.y*b.x; + return *this; + } + + //! \brief Equality check between two vectors. + bool equal(const Vec3& a, double tol = 1.0e-6) const + { + if (fabs(x-a.x) <= tol) + if (fabs(y-a.y) <= tol) + if (fabs(z-a.z) <= tol) + return true; + + return false; + } + + //! \brief Check if the vector is zero with the given tolerance. + bool isZero(double tol = 1.0e-6) const + { + if (fabs(x) <= tol) + if (fabs(y) <= tol) + if (fabs(z) <= tol) + return true; + + return false; + } + + //! \brief Check if one vector is less than the other. + //! \details First we compare the z-coordinates. Only if the z-coordinates are + //! equal, we compare the y-coordinates, and if they are equal too, + //! we finally compare the x-coordinates. + bool lessThan(const Vec3& a, double tol = 1.0e-6) const + { + for (int i = 2; i >= 0; i--) + if (v[i]+tol < a.v[i]) + return true; + else if (v[i] > a.v[i]+tol) + return false; + + return false; + } + + //! \brief Print out a vector. + virtual std::ostream& print(std::ostream& os, double tol = 1.0e-12) const + { + if (fabs(x) > tol) + os << x; + else + os <<"0"; + + if (fabs(y) > tol) + os <<" "<< y; + else + os <<" 0"; + + if (fabs(z) > tol) + os <<" "<< z; + else + os <<" 0"; + + return os; + } + + static double comparisonTolerance; //!< Coordinate comparison tolerance +}; + + +/*! + \brief Simple class for representing a point in 3D space and time. +*/ + +class Vec4 : public Vec3 +{ +public: + real t; //!< The time coordinate + + //! \brief Default constructor creating a point at origin. + Vec4() { t = 0.0; } + //! \brief Constructor creating a point at the specified location. + Vec4(real X, real Y, real Z, real T = 0.0) : Vec3(X,Y,Z) { t = T; } + //! \brief Constructor creating a point at the specified location. + Vec4(const Vec3& X, real T = 0.0) : Vec3(X) { t = T; } + //! \brief Constructor creating a point from the given \a std::vector. + Vec4(const std::vector& X) : Vec3(X) { t = X.size() > 3 ? X[3] : 0.0; } + //! \brief Copy constructor. + Vec4(const Vec4& X) : Vec3(X) { t = X.t; } + + //! \brief Assignment operator. + Vec4& operator=(const Vec4& X) + { x = X.x; y = X.y; z = X.z; t = X.t; return *this; } + //! \brief Overloaded assignment operator. + Vec4& operator=(real val) { x = y = z = val; return *this; } + + //! \brief Assignment method. + Vec4& assign(const Vec3& X) + { + x = X.x; y = X.y; z = X.z; + const Vec4* x4 = dynamic_cast(&X); + if (x4) t = x4->t; + return *this; + } + + //! \brief Print out a vector. + virtual std::ostream& print(std::ostream& os, double tol = 1.0e-12) const + { + this->Vec3::print(os,tol); + return os <<" "<< t; + } +}; + +#endif diff --git a/src/Utility/Vec3Oper.C b/src/Utility/Vec3Oper.C new file mode 100644 index 00000000..9326941f --- /dev/null +++ b/src/Utility/Vec3Oper.C @@ -0,0 +1,95 @@ +// $Id: Vec3Oper.C,v 1.2 2010-02-09 09:13:53 kmo Exp $ +//============================================================================== +//! +//! \file Vec3Oper.C +//! +//! \date Dec 19 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Global algebraic operations involving Vec3 objects. +//! +//============================================================================== + +#include "Vec3Oper.h" +#include "Vec3.h" + + +double Vec3::comparisonTolerance = 1.0e-4; + + +/*! + \brief Multiplication of a matrix and a vector. + \return \f$ {\bf y} = {\bf A} {\bf x} \f$ + + Special version for computation of 3D point vectors. + The number of rows in \b A must be (at least) 3. + Extra rows (if any) in \b A are ignored. +*/ + +Vec3 operator* (const utl::matrix& A, const std::vector& x) +{ + std::vector y(A.rows(),0.0); + A.multiply(x,y); + return Vec3(y); +} + + +Vec3 operator* (const Vec3& a, real value) +{ + return Vec3(a.x*value, a.y*value, a.z*value); +} + + +Vec3 operator* (real value, const Vec3& a) +{ + return Vec3(a.x*value, a.y*value, a.z*value); +} + + +Vec3 operator/ (const Vec3& a, real value) +{ + return Vec3(a.x/value, a.y/value, a.z/value); +} + + +real operator* (const Vec3& a, const Vec3& b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + + +Vec3 operator+ (const Vec3& a, const Vec3& b) +{ + return Vec3(a.x+b.x, a.y+b.y, a.z+b.z); +} + + +Vec3 operator- (const Vec3& a, const Vec3& b) +{ + return Vec3(a.x-b.x, a.y-b.y, a.z-b.z); +} + + +bool operator== (const Vec3& a, const Vec3& b) +{ + return a.equal(b,Vec3::comparisonTolerance); +} + + +bool operator!= (const Vec3& a, const Vec3& b) +{ + return !a.equal(b,Vec3::comparisonTolerance); +} + + +bool operator< (const Vec3& a, const Vec3& b) +{ + return a.lessThan(b,Vec3::comparisonTolerance); +} + + +std::ostream& operator<< (std::ostream& os, const Vec3& a) +{ + return a.print(os); +} diff --git a/src/Utility/Vec3Oper.h b/src/Utility/Vec3Oper.h new file mode 100644 index 00000000..bef50a13 --- /dev/null +++ b/src/Utility/Vec3Oper.h @@ -0,0 +1,55 @@ +// $Id: Vec3Oper.h,v 1.2 2010-02-09 09:13:53 kmo Exp $ +//============================================================================== +//! +//! \file Vec3Oper.h +//! +//! \date Oct 1 2007 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Global algebraic operators involving Vec3 objects. +//! +//============================================================================== + +#ifndef _VEC3_OPER_H +#define _VEC3_OPER_H + +#include "matrix.h" + +class Vec3; + + +//! \brief Multiplication of a matrix and a vector. +Vec3 operator*(const utl::matrix& A, const std::vector& x); + +//! \brief Multiplication of a vector and a scalar. +Vec3 operator*(const Vec3& a, real value); + +//! \brief Multiplication of a scalar and a vector. +Vec3 operator*(real value, const Vec3& a); + +//! \brief Division of a vector by a scalar, +Vec3 operator/(const Vec3& a, real value); + +//! \brief Dot product between two vectors. +real operator*(const Vec3& a, const Vec3& b); + +//! \brief Summation of two vectors. +Vec3 operator+(const Vec3& a, const Vec3& b); + +//! \brief Subraction of two vectors. +Vec3 operator-(const Vec3& a, const Vec3& b); + +//! \brief Equality operator. +bool operator==(const Vec3& a, const Vec3& b); + +//! \brief Unequality operator. +bool operator!=(const Vec3& a, const Vec3& b); + +//! \brief Less-than operator. +bool operator<(const Vec3& a, const Vec3& b); + +//! \brief Output stream operator. +std::ostream& operator<<(std::ostream& os, const Vec3& a); + +#endif diff --git a/src/VolumePatch.C b/src/VolumePatch.C new file mode 100644 index 00000000..c99c3d86 --- /dev/null +++ b/src/VolumePatch.C @@ -0,0 +1,2241 @@ +// $Id: VolumePatch.C,v 1.25 2010-12-07 12:56:25 kmo Exp $ +//============================================================================== +//! +//! \file VolumePatch.C +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of a topological cube as a SplineVolume. +//! +//============================================================================== + +#include "GoTools/trivariate/SplineVolume.h" +#include "GoTools/geometry/ObjectHeader.h" + +#include "VolumePatch.h" +#include "GaussQuadrature.h" +#include "LinEqSystem.h" +#include "ElementBlock.h" +#include "Vec3Oper.h" +#include "Tensor.h" +#include "MPC.h" +#include "SAM.h" +#include "Utilities.h" +#include +#include +#include + + +typedef Go::SplineVolume::Dmatrix DoubleMat; //!< 2D double array +typedef Go::SplineVolume::Dvector DoubleVec; //!< 1D double array +typedef DoubleVec::const_iterator DoubleIter; //!< Iterator over DoubleVec + +int VolumePatch::gEl = 0; +int VolumePatch::gNod = 0; +int VolumePatch::splineEvalMethod = 2; +bool VolumePatch::mergeDuplNodes = true; +bool VolumePatch::swapJac = false; + + +VolumePatch::VolumePatch (const char* fileName, bool checkRHS) +{ + E = 2.1e7; + nu = 0.3; + rho = 7.85e3; + svol = 0; + swapW = false; + + std::cout <<"\nReading patch file "<< fileName << std::endl; + std::ifstream is(fileName); + if (!is.good()) + std::cerr <<" *** VolumePatch: Failure opening patch file"<< std::endl; + else if (this->read(is) && checkRHS) + this->checkRightHandSystem(); +} + + +VolumePatch::VolumePatch (std::istream& is, bool checkRHS) +{ + E = 2.1e7; + nu = 0.3; + rho = 7.85e3; + svol = 0; + swapW = false; + + if (this->read(is) && checkRHS) + this->checkRightHandSystem(); +} + + +bool VolumePatch::read (std::istream& is) +{ + Go::ObjectHeader head; + svol = new Go::SplineVolume; + is >> head >> *svol; + + // Eat white-space characters to see if there is more data to read + char c; + while (is.get(c)) + if (!isspace(c)) + { + is.putback(c); + break; + } + + if (is.good() || is.eof()) + return true; + + std::cerr <<" *** VolumePatch::read: Failure reading spline data"<< std::endl; + delete svol; + svol = 0; + return false; +} + + +bool VolumePatch::write (std::ostream& os) const +{ + if (!svol) return false; + + os <<"700 1 0 0\n"; + os << *svol; + + return os.good(); +} + + +VolumePatch::~VolumePatch () +{ + if (svol) delete svol; +} + + +void VolumePatch::clear () +{ + // Erase spline data + delete svol; + svol = 0; + + // Don't erase the elements, but set them to have zero nodes + for (size_t i = 0; i < MNPC.size(); i++) MNPC[i].clear(); + + // Erase the nodes, boundary conditions and multi-point constraints + nodeInd.clear(); + BCode.clear(); + mpcs.clear(); +} + + +bool VolumePatch::checkRightHandSystem () +{ + if (!svol) return false; + + // Evaluate the spline volume at its center + DoubleVec u(1,0.5*(svol->startparam(0) + svol->endparam(0))); + DoubleVec v(1,0.5*(svol->startparam(1) + svol->endparam(1))); + DoubleVec w(1,0.5*(svol->startparam(2) + svol->endparam(2))); + DoubleVec X(3), dXdu(3), dXdv(3), dXdw(3); + svol->gridEvaluator(u,v,w,X,dXdu,dXdv,dXdw); + + // Check that |J| = (dXdu x dXdv) * dXdw > 0.0 + if (Vec3(dXdu,dXdv) * dXdw > 0.0) return false; + + // This patch has a negative Jacobian determinant. Probably it is modelled + // in a left-hand-system. Swap the w-parameter direction to correct for this. + svol->reverseParameterDirection(2); + std::cout <<"\tSwapped."<< std::endl; + return swapW = true; +} + + +bool VolumePatch::uniformRefine (int dir, int nInsert) +{ + if (!svol || dir < 0 || dir > 2 || nInsert < 1) return false; + + DoubleVec extraKnots; + DoubleIter uit = svol->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != svol->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nInsert; i++) + { + double xi = (double)(i+1)/(double)(nInsert+1); + extraKnots.push_back(ucurr*xi + uprev*(1.0-xi)); + } + uprev = ucurr; + } + + svol->insertKnot(dir,extraKnots); + return true; +} + + +bool VolumePatch::raiseOrder (int ru, int rv, int rw) +{ + if (!svol) return false; + + svol->raiseOrder(ru,rv,rw); + return true; +} + + +bool VolumePatch::generateFEMTopology () +{ + if (!svol) return false; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + if (!nodeInd.empty()) + return nodeInd.size() == n1*n2*n3; + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); +#ifdef SP_DEBUG + std::cout <<"numCoefs: "<< n1 <<" "<< n2 <<" "<< n3 << std::endl; + std::cout <<"order: "<< p1 <<" "<< p2 <<" "<< p3 << std::endl; + for (int d = 0; d < 3; d++) + { + std::cout <<'d'<< char('u'+d) <<':'; + for (int i = 0; i < svol->numCoefs(d); i++) + std::cout <<' '<< svol->knotSpan(d,i); + std::cout << std::endl; + } +#endif + // Consistency checks, just to be fool-proof + if (n1 < 2 || n2 < 2 || n3 < 2) return false; + if (p1 < 1 || p1 < 1 || p3 < 1) return false; + if (p1 > n1 || p2 > n2 || p3 > n3) return false; + + nodeInd.resize(n1*n2*n3); + MNPC.resize((n1-p1+1)*(n2-p2+1)*(n3-p3+1)); + MLGE.resize(MNPC.size()); + + int iel = 0; + int inod = 0; + for (int i3 = 1; i3 <= n3; i3++) + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++) + { + nodeInd[inod].I = i1-1; + nodeInd[inod].J = i2-1; + nodeInd[inod].K = i3-1; + if (i1 >= p1 && i2 >= p2 && i3 >= p3) + { + MNPC[iel].resize(p1*p2*p3,0); + for (int j3 = 0; j3 < p3; j3++) + for (int j2 = 0; j2 < p2; j2++) + for (int j1 = 0; j1 < p1; j1++) + { + int gnod = inod - n1*n2*j3 - n1*j2 - j1; + int lnod = p1*p2*j3 + p1*j2 + j1; + MNPC[iel][lnod] = gnod; + } + MLGE[iel++] = ++gEl; // global element number over all patches + } + nodeInd[inod++].global = ++gNod; // global node number over all patches + } + +#ifdef SP_DEBUG + std::cout <<"NEL = "<< iel <<" NNOD = "<< inod << std::endl; +#endif + return true; +} + + +int VolumePatch::getNodeID (int inod) const +{ + if (inod < 0) + return -inod; + else if (inod < 1 || inod > nodeInd.size()) + return 0; + + return nodeInd[inod-1].global; +} + + +int VolumePatch::getNodeIndex (int globalNum) const +{ + for (int inod = 0; inod < nodeInd.size(); inod++) + if (nodeInd[inod].global == globalNum) + return inod+1; + + return 0; +} + + +/*! + \brief An unary function that checks whether a DOF object matches the fixed + status of a BC object. +*/ + +class fixed : public std::unary_function +{ + const MPC::DOF& myDof; //!< The DOF object to compare with +public: + //! \brief Constructor initializing the myDof reference. + fixed (const MPC::DOF& slaveDof) : myDof(slaveDof) {} + //! \brief Returns \e true if \a myDof has the same fixed status as \a bc. + bool operator() (const VolumePatch::BC& bc) + { + if (bc.node == myDof.node) + switch (myDof.dof) + { + case 1: return bc.CX == 0; + case 2: return bc.CY == 0; + case 3: return bc.CZ == 0; + } + return false; + } +}; + + +bool VolumePatch::addMPC (MPC* mpc) +{ + if (!mpc) return true; + + // Silently ignore MPC's on dofs that already are marked as FIXED + bool retVal = true; + if (find_if(BCode.begin(),BCode.end(),fixed(mpc->getSlave())) == BCode.end()) + if (mpcs.insert(mpc).second) + { +#if SP_DEBUG > 1 + std::cout <<"Added constraint: "<< *mpc; +#endif + return retVal; + } + else + { +#ifdef SP_DEBUG + std::cout <<"Ignored constraint (duplicated slave): "<< *mpc; +#endif + retVal = false; // This dof is already a slave in another MPC + } + + delete mpc; + return retVal; +} + + +bool VolumePatch::addSPC (int node, int dir, double value) +{ + return this->addMPC(new MPC(node,dir,value)); +} + + +bool VolumePatch::addPeriodicity (int master, int slave, int dir) +{ + slave = this->getNodeID(slave); + master = this->getNodeID(master); + if (slave < 1 || master < 1) return false; + + MPC* mpc = new MPC(slave,dir); + mpc->addMaster(master,dir); + if (this->addMPC(mpc)) + return true; + + // Try to swap master and slave + mpc = new MPC(master,dir); + mpc->addMaster(slave,dir); + if (this->addMPC(mpc)) + return true; + + std::cerr <<" *** VolumePatch::addPeriodicity: Failed to connect nodes " + << master <<" and "<< slave <<" in direction "<< dir << std::endl; + return false; +} + + +void VolumePatch::makePeriodic (int master, int slave, int code) +{ + switch (code) + { + case 1: + case 2: + case 3: + this->addPeriodicity(master,slave,code); + break; + case 12: + case 21: + for (int dir = 1; dir <= 2; dir++) + this->addPeriodicity(master,slave,dir); + break; + case 13: + case 31: + for (int dir = 1; dir <= 3; dir += 2) + this->addPeriodicity(master,slave,dir); + break; + case 23: + case 32: + for (int dir = 2; dir <= 3; dir++) + this->addPeriodicity(master,slave,dir); + break; + default: + for (int dir = 1; dir <= 3; dir++) + this->addPeriodicity(master,slave,dir); + } +} + + +void VolumePatch::prescribe (int node, int code, double value) +{ + node = this->getNodeID(node); + if (node < 1) return; + + switch (code) + { + case 1: + case 2: + case 3: + this->addSPC(node,code,value); + break; + case 12: + case 21: + for (int dir = 1; dir <= 2; dir++) + this->addSPC(node,dir,value); + break; + case 13: + case 31: + for (int dir = 1; dir <= 3; dir += 2) + this->addSPC(node,dir,value); + break; + case 23: + case 32: + for (int dir = 2; dir <= 3; dir++) + this->addSPC(node,dir,value); + break; + default: + for (int dir = 1; dir <= 3; dir++) + this->addSPC(node,dir,value); + } +} + + +void VolumePatch::fix (int node, int code) +{ + node = this->getNodeID(node); + if (node < 1) return; + + switch (code) + { + case 1: + BCode.push_back(BC(node,0,1,1)); + break; + case 2: + BCode.push_back(BC(node,1,0,1)); + break; + case 3: + BCode.push_back(BC(node,1,1,0)); + break; + case 12: + case 21: + BCode.push_back(BC(node,0,0,1)); + break; + case 13: + case 31: + BCode.push_back(BC(node,0,1,0)); + break; + case 23: + case 32: + BCode.push_back(BC(node,1,0,0)); + break; + default: + BCode.push_back(BC(node,0,0,0)); + } + +#if SP_DEBUG > 1 + std::cout <<"\tFixed node: "<< node <<" "<< code << std::endl; +#endif +} + + +bool VolumePatch::connectPatch (int face, VolumePatch& neighbor, + int nface, int norient) +{ + if (!svol) return false; + + if (swapW) // Account for swapped parameter direction + if (face == 5) + face = 6; + else if (face == 6) + face = 5; + + if (neighbor.swapW) // Account for swapped parameter direction + if (nface == 5) + nface = 6; + else if (nface == 6) + nface = 5; + + // Set up the slave node numbers for this volume patch + + int n1 = svol->numCoefs(0); + int n2 = svol->numCoefs(1); + int n3 = svol->numCoefs(2); + int node = 1, i1 = 0, i2 = 0; + + switch (face) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + i1 = n1; + n1 = n2; + n2 = n3; + break; + + case 4: // Positive J-direction + node = n1*(n2-1)+1; + case 3: // Negative J-direction + i2 = n1*(n2-1); + i1 = 1; + n2 = n3; + break; + + case 6: // Positive K-direction + node = n1*n2*(n3-1)+1; + case 5: // Negative K-direction + i1 = 1; + break; + + default: + std::cerr <<" *** VolumePatch::connectPatch: Invalid slave face " + << face << std::endl; + return false; + } + + int i, j; + IntMat slaveNodes(n1,IntVec(n2,0)); + for (j = 0; j < n2; j++, node += i2) + for (i = 0; i < n1; i++, node += i1) + slaveNodes[i][j] = node; + + // Set up the master node numbers for the neighboring volume patch + + n1 = neighbor.svol->numCoefs(0); + n2 = neighbor.svol->numCoefs(1); + n3 = neighbor.svol->numCoefs(2); + node = 1; i1 = i2 = 0; + + switch (nface) + { + case 2: // Positive I-direction + node = n1; + case 1: // Negative I-direction + i1 = n1; + n1 = n2; + n2 = n3; + break; + + case 4: // Positive J-direction + node = n1*(n2-1)+1; + case 3: // Negative J-direction + i2 = n1*(n2-1); + i1 = 1; + n2 = n3; + break; + + case 6: // Positive K-direction + node = n1*n2*(n3-1)+1; + case 5: // Negative K-direction + i1 = 1; + break; + + default: + std::cerr <<" *** VolumePatch::connectPatch: Invalid master face " + << nface << std::endl; + return false; + } + + bool matching = false; + if (norient < 0 || norient > 7) + { + std::cerr <<" *** VolumePatch::connectPatch: Orientation flag " + << norient <<" is out of range [0,7]"<< std::endl; + return false; + } + else if (norient < 4) + matching = (n1 == slaveNodes.size() && n2 == slaveNodes.front().size()); + else + matching = (n2 == slaveNodes.size() && n1 == slaveNodes.front().size()); + if (!matching) + { + std::cerr <<" *** VolumePatch::connectPatch: Non-matching faces, sizes " + << n1 <<","<< n2 <<" and " + << slaveNodes.size() <<","<< slaveNodes.front().size() + << std::endl; + return false; + } + + const double xtol = 1.0e-4; + for (j = 0; j < n2; j++, node += i2) + for (i = 0; i < n1; i++, node += i1) + { + int k = i, l = j; + switch (norient) + { + case 1: k = i ; l = n2-j-1; break; + case 2: k = n1-i-1; l = j ; break; + case 3: k = n1-i-1; l = n2-j-1; break; + case 4: k = j ; l = i ; break; + case 5: k = j ; l = n1-i-1; break; + case 6: k = n2-j-1; l = i ; break; + case 7: k = n2-j-1; l = n1-i-1; break; + default: k = i ; l = j ; + } + if (!neighbor.getCoord(node).equal(this->getCoord(slaveNodes[k][l]),xtol)) + { + std::cerr <<" *** VolumePatch::connectPatch: Non-matching nodes " + << node <<": "<< neighbor.getCoord(node) + <<"\n and " + << slaveNodes[k][l] <<": "<< this->getCoord(slaveNodes[k][l]) + << std::endl; + return false; + } + else if (mergeDuplNodes) + VolumePatch::mergeNodes(neighbor.nodeInd[node-1], + nodeInd[slaveNodes[k][l]-1]); + else + this->makePeriodic(-neighbor.getNodeID(node),slaveNodes[k][l]); + } + + return true; +} + + +void VolumePatch::closeFaces (int dir) +{ + if (!svol) return; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + int master = 1; + + switch (dir) + { + case 1: // Faces are closed in I-direction + for (int i3 = 1; i3 <= n3; i3++) + for (int i2 = 1; i2 <= n2; i2++, master += n1) + this->makePeriodic(master,master+n1-1); + break; + + case 2: // Faces are closed in J-direction + for (int i3 = 1; i3 <= n3; i3++, master += n1*(n2-1)) + for (int i1 = 1; i1 <= n1; i1++, master++) + this->makePeriodic(master,master+n1*(n2-1)); + break; + + case 3: // Faces are closed in K-direction + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++, master++) + this->makePeriodic(master,master+n1*n2*(n3-1)); + break; + } +} + + +void VolumePatch::constrainFace (int dir, int dof, double value) +{ + if (!svol) return; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + int node = 1; + + if (swapW) // Account for swapped parameter direction + if (dir == 3 || dir == -3) dir = -dir; + + switch (dir) + { + case 1: // Right face (positive I-direction) + node += n1-1; + case -1: // Left face (negative I-direction) + for (int i3 = 1; i3 <= n3; i3++) + for (int i2 = 1; i2 <= n2; i2++, node += n1) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + break; + + case 2: // Back face (positive J-direction) + node += n1*(n2-1); + case -2: // Front face (negative J-direction) + for (int i3 = 1; i3 <= n3; i3++, node += n1*(n2-1)) + for (int i1 = 1; i1 <= n1; i1++, node++) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + break; + + case 3: // Top face (positive K-direction) + node += n1*n2*(n3-1); + case -3: // Bottom face (negative K-direction) + for (int i2 = 1; i2 <= n2; i2++) + for (int i1 = 1; i1 <= n1; i1++, node++) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + break; + } +} + + +void VolumePatch::constrainLine (int fdir, int ldir, double xi, + int dof, double value) +{ + if (!svol) return; + if (xi < 0.0 || xi > 1.0) return; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + int node = 1; + + if (swapW) // Account for swapped parameter direction + if (fdir == 3 || fdir == -3) fdir = -fdir; + + switch (fdir) + { + case 1: // Right face (positive I-direction) + node += n1-1; + case -1: // Left face (negative I-direction) + if (ldir == 2) + { + // Line goes in J-direction + node += n1*n2*int(0.5+(n3-1)*(swapW ? 1.0-xi : xi)); + for (int i2 = 1; i2 <= n2; i2++, node += n1) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + } + else if (ldir == 3) + { + // Line goes in K-direction + node += n1*int(0.5+(n2-1)*xi); + for (int i3 = 1; i3 <= n3; i3++, node += n1*n2) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + } + break; + + case 2: // Back face (positive J-direction) + node += n1*(n2-1); + case -2: // Front face (negative J-direction) + if (ldir == 1) + { + // Line goes in I-direction + node += n1*n2*int(0.5+(n3-1)*(swapW ? 1.0-xi : xi)); + for (int i1 = 1; i1 <= n1; i1++, node++) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + } + else if (ldir == 3) + { + // Line goes in K-direction + node += int(0.5+(n1-1)*xi); + for (int i3 = 1; i3 <= n3; i3++, node += n1*n2) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + } + break; + + case 3: // Top face (positive K-direction) + node += n1*n2*(n3-1); + case -3: // Bottom face (negative K-direction) + if (ldir == 1) + { + // Line goes in I-direction + node += n1*int(0.5+(n2-1)*xi); + for (int i1 = 1; i1 <= n1; i1++, node++) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + } + else if (ldir == 2) + { + // Line goes in J-direction + node += int(0.5+(n1-1)*xi); + for (int i2 = 1; i2 <= n2; i2++, node += n1) + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); + } + break; + } +} + + +void VolumePatch::constrainCorner (int I, int J, int K, int dof, double value) +{ + if (!svol) return; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + if (swapW) // Account for swapped parameter direction + K = -K; + + int node = 1; + if (I > 0) node += n1-1; + if (J > 0) node += n1*(n2-1); + if (K > 0) node += n1*n2*(n3-1); + + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); +} + + +void VolumePatch::constrainNode (double xi, double eta, double zeta, + int dof, double value) +{ + if (!svol) return; + if (xi < 0.0 || xi > 1.0) return; + if (eta < 0.0 || eta > 1.0) return; + if (zeta < 0.0 || zeta > 1.0) return; + + if (swapW) // Account for swapped parameter direction + zeta = 1.0-zeta; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + int node = 1; + if (xi > 0.0) node += int(0.5+(n1-1)*xi); + if (eta > 0.0) node += n1*int(0.5+(n2-1)*eta); + if (zeta > 0.0) node += n1*n2*int(0.5+(n3-1)*zeta); + + if (value == 0.0) + this->fix(node,dof); + else + this->prescribe(node,dof,value); +} + + +void VolumePatch::resolveMPCchains (const std::vector& model) +{ + MPCSet allMPCs; + for (size_t i = 0; i < model.size(); i++) + allMPCs.insert(model[i]->begin_MPC(),model[i]->end_MPC()); + + int nresolved = 0; + for (MPCSet::iterator cit = allMPCs.begin(); cit != allMPCs.end(); cit++) + if (VolumePatch::resolveMPCchain(allMPCs,*cit)) nresolved++; + + if (nresolved > 0) + std::cout <<"Resolved "<< nresolved <<" MPC chains."<< std::endl; +} + + +// Recursive method to resolve (possibly multi-level) chaining in multi-point +// constraint equations (MPCs). If a master dof in one MPC is specified as a +// slave by another MPC, it is replaced by the master(s) of that other equation. + +bool VolumePatch::resolveMPCchain (const MPCSet& allMPCs, MPC* mpc) +{ + bool resolved = false; + for (size_t i = 0; i < mpc->getNoMaster();) + { + MPC master(mpc->getMaster(i).node,mpc->getMaster(i).dof); + MPCSet::iterator cit = allMPCs.find(&master); + if (cit != allMPCs.end()) + { + // We have a master dof which is a slave in another constraint equation. + // Invoke resolveMPCchain recursively to ensure that all master dofs + // of that equation are not slaves themselves. + VolumePatch::resolveMPCchain(allMPCs,*cit); + + // Remove current master specification + double coeff = mpc->getMaster(i).coeff; + mpc->removeMaster(i); + + // Add constant offset from the other equation + mpc->addOffset(coeff*(*cit)->getSlave().coeff); + + // Add masters from the other equations + for (size_t j = 0; j < (*cit)->getNoMaster(); j++) + mpc->addMaster((*cit)->getMaster(j).node, + (*cit)->getMaster(j).dof, + (*cit)->getMaster(j).coeff*coeff); + resolved = true; + } + else + i++; + } + +#if SP_DEBUG > 1 + if (resolved) std::cout <<"Resolved constraint: "<< *mpc; +#endif + return resolved; +} + + +void VolumePatch::mergeNodes (IJK& node1, IJK& node2) +{ + if (node1.global > node2.global) + node1.global = node2.global; + else if (node2.global > node1.global) + node2.global = node1.global; +} + + +bool VolumePatch::mergeNodes (int node, int globalNum) +{ + if (node < 1 || node > nodeInd.size()) + return false; + else if (nodeInd[node-1].global <= globalNum) + return false; + + nodeInd[node-1].global = globalNum; + return true; +} + + +int VolumePatch::renumberNodes (const std::vector& model) +{ + int nnod = 0; + int renum = 0; + size_t i, j; + std::map old2new; + for (i = 0; i < model.size(); i++) + for (j = 0; j < model[i]->nodeInd.size(); j++) + if (utl::renumber(model[i]->nodeInd[j].global,nnod,old2new)) + renum++; + + if (renum > 0) + { + for (i = 0; i < model.size(); i++) + model[i]->renumberNodes(old2new,false); + std::cout <<"\nRenumbered "<< renum <<" nodes"<< std::endl; + } + + return nnod; +} + + +bool VolumePatch::renumberNodes (const std::map& old2new, bool silent) +{ + int invalid = 0; + for (size_t j = 0; j < BCode.size(); j++) + if (!utl::renumber(BCode[j].node,old2new)) + invalid++; + + for (MPCSet::iterator mit = mpcs.begin(); mit != mpcs.end(); mit++) + invalid += (*mit)->renumberNodes(old2new); + + if (invalid == 0 || silent) return true; + + std::cerr <<" *** "<< invalid <<" invalid nodes found while renumbering\n"; + return false; +} + + +#define DERR -999.99 + +double VolumePatch::getParametricVolume (int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** VolumePatch::getParametricVolume: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + + int inod1 = MNPC[iel-1][0]; +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= nodeInd.size()) + { + std::cerr <<" *** VolumePatch::getParametricVolume: Node index "<< inod1 + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return DERR; + } +#endif + + double du = svol->knotSpan(0,nodeInd[inod1].I); + double dv = svol->knotSpan(1,nodeInd[inod1].J); + double dw = svol->knotSpan(2,nodeInd[inod1].K); + return du*dv*dw; +} + + +double VolumePatch::getParametricArea (int iel, int dir) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** VolumePatch::getParametricArea: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return DERR; + } +#endif + + int inod1 = MNPC[iel-1][0]; +#ifdef INDEX_CHECK + if (inod1 < 0 || inod1 >= nodeInd.size()) + { + std::cerr <<" *** VolumePatch::getParametricArea: Node index "<< inod1 + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return DERR; + } +#endif + + const int ni = nodeInd[inod1].I; + const int nj = nodeInd[inod1].J; + const int nk = nodeInd[inod1].K; + switch (dir) + { + case 1: return svol->knotSpan(1,nj)*svol->knotSpan(2,nk); + case 2: return svol->knotSpan(0,ni)*svol->knotSpan(2,nk); + case 3: return svol->knotSpan(0,ni)*svol->knotSpan(1,nj); + } + + std::cerr <<" *** VolumePatch::getParametricArea: Invalid face direction " + << dir << std::endl; + return DERR; +} + + +bool VolumePatch::getMaterialMatrix (Matrix& C, bool inverse) const +{ + C.resize(6,6,true); + + if (nu < 0.0 || nu >= 0.5) + { + std::cerr <<" *** VolumePatch::getMaterialMatrix: Poisson's ratio "<< nu + <<" out of range [0,0.5>."<< std::endl; + return false; + } + + const double one = 1.0; + const double half = 0.5; + const double two = 2.0; + const double fact = E / ((one + nu) * (one - nu - nu)); + + C(1,1) = inverse ? one / E : (one - nu) * fact; + C(2,1) = inverse ? -nu / E : nu * fact; + C(3,1) = C(2,1); + + C(1,2) = C(2,1); + C(2,2) = C(1,1); + C(3,2) = C(2,1); + + C(1,3) = C(2,1); + C(2,3) = C(2,1); + C(3,3) = C(1,1); + + C(4,4) = inverse ? (two + nu + nu) / E : E / (two + nu + nu); + C(5,5) = C(4,4); + C(6,6) = C(4,4); + + return true; +} + + +int VolumePatch::coeffInd (int inod) const +{ +#ifdef INDEX_CHECK + if (inod < 0 || inod >= nodeInd.size()) + { + std::cerr <<" *** VolumePatch::coeffInd: Node index "<< inod + <<" out of range [0,"<< nodeInd.size() <<">."<< std::endl; + return -1; + } +#endif + + const int ni = nodeInd[inod].I; + const int nj = nodeInd[inod].J; + const int nk = nodeInd[inod].K; + return (nk*svol->numCoefs(1) + nj)*svol->numCoefs(0) + ni; +} + + +bool VolumePatch::getElementCoordinates (Matrix& X, int iel) const +{ +#ifdef INDEX_CHECK + if (iel < 1 || iel > MNPC.size()) + { + std::cerr <<" *** VolumePatch::getElementCoordinates: Element index "<< iel + <<" out of range [1,"<< MNPC.size() <<"]."<< std::endl; + return false; + } +#endif + + const IntVec& mnpc = MNPC[iel-1]; + X.resize(3,mnpc.size()); + + DoubleIter cit = svol->coefs_begin(); + for (int n = 0; n < mnpc.size(); n++) + { + int ip = this->coeffInd(mnpc[n])*3; + if (ip < 0) return false; + + for (int i = 0; i < 3; i++) + X(i+1,n+1) = *(cit+(ip+i)); + } + +#if SP_DEBUG > 2 + std::cout <<"\nCoordinates for element "<< iel << X << std::endl; +#endif + return true; +} + + +void VolumePatch::getNodalCoordinates (Matrix& X) const +{ + X.resize(3,nodeInd.size()); + + DoubleIter cit = svol->coefs_begin(); + for (int inod = 0; inod < nodeInd.size(); inod++) + { + int ip = this->coeffInd(inod)*3; + for (int i = 0; i < 3; i++) + X(i+1,inod+1) = *(cit+(ip+i)); + } +} + + +Vec3 VolumePatch::getCoord (int inod) const +{ + int ip = this->coeffInd(inod-1)*3; + if (ip < 0) return Vec3(); + + DoubleIter cit = svol->coefs_begin() + ip; + return Vec3(*cit,*(cit+1),*(cit+2)); +} + + + +/*! + \brief Auxilliary function to compute GoTools basis function indices. +*/ + +static void scatterInd (int n1, int n2, int n3, + int p1, int p2, int p3, + int* start, IntVec& index) +{ + index.reserve(p1*p2*p3); + int ip = ((start[2]-p3+1)*n2 + (start[1]-p2+1))*n1 + (start[0]-p1+1); + for (int i3 = 0; i3 < p3; i3++, ip += n1*(n2-p2)) + for (int i2 = 0; i2 < p2; i2++, ip += n1-p1) + for (int i1 = 0; i1 < p1; i1++, ip++) + index.push_back(ip); +} + + +void VolumePatch::formBmatrix (Matrix& B, const Matrix& dNdX) +{ + const int nne = dNdX.rows(); + B.resize(18,nne,true); + + // Strain-displacement matrix for volume elements: + // + // | d/dx 0 0 | + // | 0 d/dy 0 | + // [B] = | 0 0 d/dz | * [N] + // | d/dy d/dx 0 | + // | d/dz 0 d/dx | + // | 0 d/dz d/dy | + +#define index(i,j) i+6*(j-1) + for (int i = 1; i <= nne; i++) + { + // Normal strain part + B(index(1,1),i) = dNdX(i,1); + B(index(2,2),i) = dNdX(i,2); + B(index(3,3),i) = dNdX(i,3); + // Shear strain part + B(index(4,1),i) = dNdX(i,2); + B(index(4,2),i) = dNdX(i,1); + B(index(5,1),i) = dNdX(i,3); + B(index(5,3),i) = dNdX(i,1); + B(index(6,2),i) = dNdX(i,3); + B(index(6,3),i) = dNdX(i,2); + } + + B.resize(6,3*nne); +#undef index +} + + +#if SP_DEBUG > 3 +std::ostream& operator<<(std::ostream& os, const Go::BasisPts& basis) +{ + os <<" : (u,v,w) = " + << basis.param[0] <<" " + << basis.param[1] <<" " + << basis.param[2] <<" left_idx = " + << basis.left_idx[0] <<" " + << basis.left_idx[1] <<" " + << basis.left_idx[2] << std::endl; + for (unsigned int i = 0; i < basis.basisValues.size(); i++) + os << 1+i <<'\t'<< basis.basisValues[i] << std::endl; + return os; +} + +std::ostream& operator<<(std::ostream& os, const Go::BasisDerivs& bder) +{ + os <<" : (u,v,w) = " + << bder.param[0] <<" " + << bder.param[1] <<" " + << bder.param[2] <<" left_idx = " + << bder.left_idx[0] <<" " + << bder.left_idx[1] <<" " + << bder.left_idx[2] << std::endl; + for (unsigned int i = 0; i < bder.basisValues.size(); i++) + os << 1+i <<'\t' + << bder.basisValues[i] <<'\t' + << bder.basisDerivs_u[i] <<'\t' + << bder.basisDerivs_v[i] <<'\t' + << bder.basisDerivs_w[i] << std::endl; + return os; +} +#endif + + +bool VolumePatch::assembleSystem (LinEqSystem& sys, const SAM& sam, + const Vec3& gravity, int nGauss, + const Vector& displ) +{ + if (!svol) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + int dir; + Matrix gpar[3]; + for (dir = 0; dir < 3; dir++) + { + int pm1 = svol->order(dir) - 1; + DoubleIter uit = svol->basis(dir).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(dir) - pm1; + gpar[dir].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[dir](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate the shape function derivatives at all integration points + std::cout <<"Spline evaluation " + << gpar[0].size() <<" "<< gpar[1].size() <<" "<< gpar[2].size() + <<" ... "<< std::flush; + DoubleMat N0, dN1, dN2, dN3; + std::vector spline; + if (splineEvalMethod == 1) + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],N0,dN1,dN2,dN3); + else + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + std::cout << std::endl; + +#if SP_DEBUG > 3 + unsigned int i, j; + for (i = 0; i < spline.size(); i++) + std::cout <<"\nShape functions for point "<< 1+i << spline[i]; + for (i = 0; i < N0.size(); i++) + { + std::cout <<"\nShape functions for point "<< 1+i << std::endl; + for (j = 0; j < N0[i].size(); j++) + std::cout << j+1 <<'\t'<< N0[i][j] <<'\t' + << dN1[i][j] <<'\t'<< dN2[i][j] <<'\t'<< dN3[i][j] << std::endl; + } +#endif + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N(p1*p2*p3), stress; + Matrix dNdu(p1*p2*p3,3); + Matrix dNdX(p1*p2*p3,3); + Matrix Xnod(3,p1*p2*p3); + Matrix Jac(3,3); + Matrix EK, EM, ES, B, CB, C, Dtmp; + + // Set up the consitutive matrix + this->getMaterialMatrix(C); + + // Also assemble geometric stiffness? + bool assembleKg = sys.M && !displ.empty(); + + // Also assemble mass matrix? + bool assembleMass = sys.M && rho > 0.0; + if (assembleKg) assembleMass = false; + + // Also assemble load vector? + bool assembleLoad = gravity.x != 0.0 || gravity.y != 0.0 || gravity.z != 0.0; + if (sys.RHS.empty()) assembleLoad = false; + + // Set up the body force vector due to gravity loading + Vector fb(3); + if (assembleLoad && rho > 0.0) + for (dir = 0; dir < 3; dir++) + fb[dir] = rho*gravity[dir]; + + + // === Assembly loop over all elements in the patch ========================== + + int iel = 1, counter = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + // Check that the current element has nonzero volume + double dV = this->getParametricVolume(iel); + if (dV == DERR) return false; // topology error (probably logic error) + if (dV <= 0.0) continue; // zero volume in the parametric domain + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + const IntVec& mnpc = MNPC[iel-1]; + + bool addTo = false; + if (assembleMass || assembleKg) EM.resize(3*p1*p2*p3,3*p1*p2*p3,true); + + // --- Integration loop over all Gauss points in each direction -------- + + int ip = (((i3-p3)*nGauss*nel2 + i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int k = 0; k < nGauss; k++, ip += nGauss*(nel2-1)*nGauss*nel1) + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.125*dV*wg[i]*wg[j]*wg[k]; + + // Fetch shape function derivatives at current integration point + for (int n = 0; n < mnpc.size(); n++) + if (splineEvalMethod == 1) + { + int jp = this->coeffInd(mnpc[n]); + if (jp < 0) return false; + + N(1+n) = N0[ip][jp]; + dNdu(1+n,1) = dN1[ip][jp]; + dNdu(1+n,2) = dN2[ip][jp]; + dNdu(1+n,3) = dN3[ip][jp]; + } + else + { + int jp = mnpc.size()-n-1; + + N(1+n) = spline[ip].basisValues[jp]; + dNdu(1+n,1) = spline[ip].basisDerivs_u[jp]; + dNdu(1+n,2) = spline[ip].basisDerivs_v[jp]; + dNdu(1+n,3) = spline[ip].basisDerivs_w[jp]; + } +#if SP_DEBUG > 4 + std::cout <<"\niel, ip = "<< iel <<" "<< ip << N << std::endl; +#endif + + // Compute Jacobian determinant and inverse + Jac.multiply(Xnod,dNdu); // Jac = Xnod * dNdu + double detJ = swapJac ? -Jac.inverse() : Jac.inverse(); + + // Compute strain-displacement matrix B from dNdX = dNdu * Jac^-1 + formBmatrix(B,dNdX.multiply(dNdu,Jac)); + + // Integrate the element stiffness matrix + CB.multiply(C,B).multiply(detJ*weight); // CB = C*B*|J|*w + EK.multiply(B,CB,true,false,addTo); // EK += B^T * CB + + // Integrate geometric stiffness contributions + if (assembleKg) + { + utl::gather(mnpc,3,displ,Dtmp); + CB.multiply(Dtmp,stress); // Sigma = CB * Dtmp + SymmTensor Sigma(stress); + for (int a = 1; a <= N.size(); a++) + for (int b = 1; b <= N.size(); b++) + { + double kg = 0.0; + for (int c = 1; c <= 3; c++) + for (int d = 1; d <= 3; d++) + kg += dNdX(a,c)*Sigma(c,d)*dNdX(b,d); + for (int d = 1; d <= 3; d++) + EM(3*(a-1)+d,3*(b-1)+d) += kg; + } + } + + // Integrate the element mass matrix + else if (assembleMass) + { + double rhow = rho*detJ*weight; + for (int a = 1; a <= N.size(); a++) + for (int b = 1; b <= N.size(); b++) + for (int d = 1; d <= 3; d++) + EM(3*(a-1)+d,3*(b-1)+d) += rhow*N(a)*N(b); + } + + // Integrate body force vector + if (assembleLoad && rho > 0.0) + ES.outer_product(fb*detJ*weight,N,addTo); + + addTo = true; + } + + // Assembly of system stiffness, mass and load +#if SP_DEBUG > 2 + std::cout <<"Stiffness matrix for element "<< iel << EK << std::endl; + if (assembleKg) + std::cout <<"Geometric stiffness for "<< iel << EM << std::endl; + else if (assembleMass) + std::cout <<"Mass matrix for element "<< iel << EM << std::endl; +#elif defined(SP_DEBUG) + std::cout <<"Assembling stiffness for element "<< iel << std::endl; +#else + std::cout << iel << ((counter++)%10 ? " " : "\n") << std::flush; +#endif + bool status = false; + if (!sys.RHS.empty()) + status = sam.assembleSystem(*sys.K,sys.RHS,EK,MLGE[iel-1]); + else + status = sam.assembleSystem(*sys.K,EK,MLGE[iel-1]); + + if ((assembleMass || assembleKg) && status) + status = sam.assembleSystem(*sys.M,EM,MLGE[iel-1]); + + if (assembleLoad && status && rho > 0.0) + status = sam.assembleSystem(sys.RHS,(const Vector&)ES,MLGE[iel-1]); + + if (!status) return false; + } + + if (counter%10 != 1) std::cout << std::endl; + return true; +} + + +bool VolumePatch::assembleForces (SystemVector& S, const SAM& sam, + const TractionFunc& t, int dir, int nGauss, + std::map* trac) +{ + if (!svol) return true; // silently ignore empty patches + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch face + Matrix gpar[3]; + for (int d = 0; d < 3; d++) + if (-1-d == dir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = svol->startparam(d); + } + else if (1+d == dir) + { + gpar[d].resize(1,1); + gpar[d](1,1) = svol->endparam(d); + } + else + { + int pm1 = svol->order(d) - 1; + DoubleIter uit = svol->basis(d).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(d) - pm1; + gpar[d].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[d](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate the shape function derivatives at all integration points + std::cout <<"Spline evaluation " + << gpar[0].size() <<" "<< gpar[1].size() <<" "<< gpar[2].size() + <<" ... "<< std::flush; + DoubleMat N0, dN1, dN2, dN3; + std::vector spline; + if (splineEvalMethod == 1) + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],N0,dN1,dN2,dN3); + else + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + std::cout << std::endl; + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N(p1*p2*p3); + Matrix dNdu(p1*p2*p3,3); + Matrix dNdX(p1*p2*p3,3); + Matrix Xnod(3,p1*p2*p3); + Matrix Jac(3,3); + Vector ES; + Vec3 T, X, normal; + + + // === Assembly loop over all elements on the patch face ===================== + + int iel = 1, counter = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + // Skip elements that are not on current boundary face + bool skipMe = false; + switch (dir) + { + case -1: if (i1 > p1) skipMe = true; break; + case 1: if (i1 < n1) skipMe = true; break; + case -2: if (i2 > p2) skipMe = true; break; + case 2: if (i2 < n2) skipMe = true; break; + case -3: if (i3 > p3) skipMe = true; break; + case 3: if (i3 < n3) skipMe = true; break; + } + if (skipMe) continue; + + // Check that the current element has nonzero face area + double dA = this->getParametricArea(iel,abs(dir)); + if (dA == DERR) return false; // topology error (probably logic error) + if (dA <= 0.0) continue; // zero area in the parametric domain + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + const IntVec& mnpc = MNPC[iel-1]; + + // Define some loop control variables depending on which face we are on + int nf1, j1, j2; + switch (abs(dir)) + { + case 1: nf1 = nel2; j2 = i3-p3; j1 = i2-p2; break; + case 2: nf1 = nel1; j2 = i3-p3; j1 = i1-p1; break; + case 3: nf1 = nel1; j2 = i2-p2; j1 = i1-p1; break; + } + int t1 = 1 + abs(dir)%3; // first tangent direction + int t2 = 1 + t1%3; // second tangent direction + + // --- Integration loop over all Gauss points in each direction -------- + + ES.resize(3*p1*p2*p3,true); + int n, ip = (j2*nGauss*nf1 + j1)*nGauss; + for (int j = 0; j < nGauss; j++, ip += nGauss*(nf1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.25*dA*wg[i]*wg[j]; + + // Fetch shape function values at current integration point + for (n = 0; n < mnpc.size(); n++) + if (splineEvalMethod == 1) + { + int jp = this->coeffInd(mnpc[n]); + if (jp < 0) return false; + + N(1+n) = N0[ip][jp]; + dNdu(1+n,1) = dN1[ip][jp]; + dNdu(1+n,2) = dN2[ip][jp]; + dNdu(1+n,3) = dN3[ip][jp]; + } + else + { + int jp = mnpc.size()-n-1; + + N(1+n) = spline[ip].basisValues[jp]; + dNdu(1+n,1) = spline[ip].basisDerivs_u[jp]; + dNdu(1+n,2) = spline[ip].basisDerivs_v[jp]; + dNdu(1+n,3) = spline[ip].basisDerivs_w[jp]; + } + + // Cartesian coordinates of current integration point + X = Xnod * N; + + // Compute Jacobian matrix + Jac.multiply(Xnod,dNdu); // Jac = Xnod * dNdu + + // Compute the face normal + normal.cross(Jac.getColumn(t1),Jac.getColumn(t2)); + double dS = normal.normalize(); + if (dir < 0) normal *= -1.0; + + // Evaluate the surface traction + T = t(X,normal); +#if SP_DEBUG > 3 + std::cout <<"iel, ip "<< iel <<" "<< ip + <<" : T = "<< T << std::endl; +#endif + // Store the traction value for vizualization + if (trac && !T.isZero()) + trac->insert(std::make_pair(X,T)); + + // Integrate the force vector + int idof, d; + T *= weight*dS; + for (idof = n = 1; n <= mnpc.size(); n++) + for (d = 0; d < 3; d++, idof++) + ES(idof) += T[d]*N(n); + } + + // Assembly of system force vector +#if SP_DEBUG > 2 + std::cout <<"Force vector for element "<< iel << ES << std::endl; +#elif defined(SP_DEBUG) + std::cout <<"Assembling pressure force for element "<< iel << std::endl; +#else + std::cout << iel << ((counter++)%10 ? " " : "\n") << std::flush; +#endif + if (!sam.assembleSystem(S,ES,MLGE[iel-1])) return false; + } + + if (counter%10 != 1) std::cout << std::endl; + return true; +} + + +bool VolumePatch::solutionNorms (Vector& gNorm, Matrix& eNorm, int nGauss, + const Vector& displ, const TensorFunc* sol) +{ + if (!svol) return true; // silently ignore empty patches + if (displ.empty()) return false; + + // Get Gaussian quadrature points and weights + const double* xg = GaussQuadrature::getCoord(nGauss); + const double* wg = GaussQuadrature::getWeight(nGauss); + if (!xg || !wg) return false; + + // Compute parameter values of the Gauss points over the whole patch + int dir; + Matrix gpar[3]; + for (dir = 0; dir < 3; dir++) + { + int pm1 = svol->order(dir) - 1; + DoubleIter uit = svol->basis(dir).begin() + pm1; + double ucurr, uprev = *(uit++); + int nCol = svol->numCoefs(dir) - pm1; + gpar[dir].resize(nGauss,nCol); + for (int j = 1; j <= nCol; uit++, j++) + { + ucurr = *uit; + for (int i = 1; i <= nGauss; i++) + gpar[dir](i,j) = 0.5*((ucurr-uprev)*xg[i-1] + ucurr+uprev); + uprev = ucurr; + } + } + + // Evaluate the shape function derivatives at all integration points + DoubleMat N0, dN1, dN2, dN3; + std::vector spline; + if (splineEvalMethod == 1) + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],N0,dN1,dN2,dN3); + else + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + + const int nel1 = n1 - p1 + 1; + const int nel2 = n2 - p2 + 1; + + Vector N(p1*p2*p3), sigma, sigmah; + Matrix dNdu(p1*p2*p3,3); + Matrix dNdX(p1*p2*p3,3); + Matrix Xnod(3,p1*p2*p3); + Matrix Jac(3,3); + Matrix B, CB, C, Cinv, Dtmp; + double pnorm[3]; + + // Set up the consitutive matrix and its inverse + this->getMaterialMatrix(C,false); + this->getMaterialMatrix(Cinv,true); + + // === Integration loop over all elements in the patch ======================= + + int iel = 1; + for (int i3 = p3; i3 <= n3; i3++) + for (int i2 = p2; i2 <= n2; i2++) + for (int i1 = p1; i1 <= n1; i1++, iel++) + { + // Check that the current element has nonzero volume + double dV = this->getParametricVolume(iel); + if (dV == DERR) return false; // topology error (probably logic error) + if (dV <= 0.0) continue; // zero volume in the parametric domain + + // Set up control point coordinates for current element + if (!this->getElementCoordinates(Xnod,iel)) return false; + + const IntVec& mnpc = MNPC[iel-1]; + + // --- Integration loop over all Gauss points in each direction -------- + + pnorm[0] = pnorm[1] = pnorm[2] = 0.0; + int ip = (((i3-p3)*nGauss*nel2 + i2-p2)*nGauss*nel1 + i1-p1)*nGauss; + for (int k = 0; k < nGauss; k++, ip += nGauss*(nel2-1)*nGauss*nel1) + for (int j = 0; j < nGauss; j++, ip += nGauss*(nel1-1)) + for (int i = 0; i < nGauss; i++, ip++) + { + // Weight of current integration point + double weight = 0.125*dV*wg[i]*wg[j]*wg[k]; + + // Fetch shape function derivatives at current integration point + for (int n = 0; n < mnpc.size(); n++) + if (splineEvalMethod == 1) + { + int jp = this->coeffInd(mnpc[n]); + if (jp < 0) return false; + + N(1+n) = N0[ip][jp]; + dNdu(1+n,1) = dN1[ip][jp]; + dNdu(1+n,2) = dN2[ip][jp]; + dNdu(1+n,3) = dN3[ip][jp]; + } + else + { + int jp = mnpc.size()-n-1; + + N(1+n) = spline[ip].basisValues[jp]; + dNdu(1+n,1) = spline[ip].basisDerivs_u[jp]; + dNdu(1+n,2) = spline[ip].basisDerivs_v[jp]; + dNdu(1+n,3) = spline[ip].basisDerivs_w[jp]; + } + + // Compute Jacobian determinant and inverse + Jac.multiply(Xnod,dNdu); // Jac = Xnod * dNdu + double detJ = swapJac ? -Jac.inverse() : Jac.inverse(); + + // Compute strain-displacement matrix B from dNdX = dNdu * Jac^-1 + formBmatrix(B,dNdX.multiply(dNdu,Jac)); + + // Evaluate the FE stress field + utl::gather(mnpc,3,displ,Dtmp); + CB.multiply(C,B).multiply(Dtmp,sigmah); // sigmah = C * B * Dtmp + + // Integrate the energy norm a(u^h,u^h) + pnorm[0] += sigmah.dot(Cinv*sigmah)*detJ*weight; + if (sol) + { + // Evaluate the analytical stress field + sigma = (*sol)(Xnod*N); + // Integrate the energy norm a(u,u) + pnorm[1] += sigma.dot(Cinv*sigma)*detJ*weight; + // Integrate the error in energy norm a(u-u^h,u-u^h) + sigma -= sigmah; + pnorm[2] += sigma.dot(Cinv*sigma)*detJ*weight; + } + } + + // Accumulate element and global norms + int i; + for (i = 1; i <= eNorm.rows() && i <= 3; i++) + eNorm(i,MLGE[iel-1]) = sqrt(pnorm[i-1]); + for (i = 1; i <= gNorm.size() && i <= 3; i++) + gNorm(i) += pnorm[i-1]; + } + + return true; +} + + +bool VolumePatch::getGridParameters (RealArray& prm, int dir, + int nSegPerSpan) const +{ + if (!svol) return false; + + if (nSegPerSpan < 1) + { + std::cerr <<" *** VolumePatch::getGridParameters: Too few knot-span points " + << nSegPerSpan+1 <<" in direction "<< dir << std::endl; + return false; + } + + DoubleIter uit = svol->basis(dir).begin(); + double ucurr, uprev = *(uit++); + while (uit != svol->basis(dir).end()) + { + ucurr = *(uit++); + if (ucurr > uprev) + for (int i = 0; i < nSegPerSpan; i++) + { + double xg = (double)(2*i-nSegPerSpan)/(double)nSegPerSpan; + prm.push_back(0.5*(ucurr*(1.0+xg) + uprev*(1.0-xg))); + } + uprev = ucurr; + } + + prm.push_back(svol->basis(dir).endparam()); + return true; +} + + +bool VolumePatch::convertToElementBlock (ElementBlock& grid, + const int* npe) const +{ + // Compute parameter values of the nodal points + RealArray gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the spline volume at all points + size_t nx = gpar[0].size(); + size_t ny = gpar[1].size(); + size_t nz = gpar[2].size(); + DoubleVec XYZ(3*nx*ny*nz); + svol->gridEvaluator(gpar[0],gpar[1],gpar[2],XYZ); + + // Establish the block grid coordinates + size_t i, j, k, l; + grid.resize(nx,ny,nz); + for (i = j = 0; i < grid.getNoNodes(); i++, j += 3) + grid.setCoor(i,XYZ[j],XYZ[j+1],XYZ[j+2]); + + // Establish the block grid topology + if (grid.getNoElmNodes() == 8) // Linear elements + { + int n[8], ip = 0; + for (k = 1, n[2] = 0; k < nz; k++) + for (j = 1, n[1] = n[2]; j < ny; j++) + { + n[0] = n[1]; + n[1] = n[0] + 1; + n[2] = n[1] + nx; + n[3] = n[1] + nx-1; + n[4] = n[0] + nx*ny; + n[5] = n[4] + 1; + n[6] = n[5] + nx; + n[7] = n[5] + nx-1; + for (i = 1; i < nx; i++) + for (l = 0; l < 8; l++) + grid.setNode(ip++,n[l]++); + } + + return true; + } + else if (grid.getNoElmNodes() == 27) // parabolic elements + if (nx%2 && ny%2 && nz%2 && nx > 2 && ny > 2 && nz > 2) + { + int n[27], ip = 0; + for (k = 1, n[23] = 0; k < nz; k += 2) + for (j = 1, n[20] = n[23]; j < ny; j += 2) + { + // Corner nodes + n[0] = n[20]; + n[1] = n[0] + 2; + n[2] = n[1] + 2*nx; + n[3] = n[1] + 2*nx-2; + n[4] = n[0] + 2*nx*ny; + n[5] = n[4] + 2; + n[6] = n[5] + 2*nx; + n[7] = n[5] + 2*nx-2; + // Mid-edge nodes + n[ 8] = n[ 0] + 1; + n[ 9] = n[ 1] + nx; + n[10] = n[ 3] + 1; + n[11] = n[ 0] + nx; + n[12] = n[ 8] + 2*nx*ny; + n[13] = n[ 9] + 2*nx*ny; + n[14] = n[10] + 2*nx*ny; + n[15] = n[11] + 2*nx*ny; + n[16] = n[ 0] + nx*ny; + n[17] = n[ 1] + nx*ny; + n[18] = n[ 2] + nx*ny; + n[19] = n[ 3] + nx*ny; + // Mid-face nodes + n[20] = n[11] + 1; + n[21] = n[16] + 1; + n[22] = n[17] + nx; + n[23] = n[19] + 1; + n[24] = n[16] + nx; + n[25] = n[15] + 1; + // Interior node + n[26] = n[24] + 1; + // Generate elements in I-direction + for (i = 1; i < nx; i += 2) + for (l = 0; l < 27; l++) + { + grid.setNode(ip++,n[l]); + n[l] += 2; + } + } + + return true; + } + + std::cerr <<" *** VolumePatch::convertToElementBlock: Can not convert " + << nx <<"x"<< ny <<"x"<< nz <<" points into a\n" + <<" " + << grid.getNoElmNodes() <<"-noded hexahedron grid."<< std::endl; + return false; +} + + +void VolumePatch::extractElmRes (const Matrix& globRes, Matrix& elmRes) const +{ + elmRes.resize(globRes.rows(),MLGE.size(),true); + int i, iel, ivel = 0; + for (iel = 1; iel <= MLGE.size(); iel++) + if (this->getParametricVolume(iel) > 0.0) + for (++ivel, i = 1; i <= globRes.rows(); i++) + elmRes(i,ivel) = globRes(i,MLGE[iel-1]); + + elmRes.resize(globRes.rows(),ivel); +} + + +void VolumePatch::extractSolution (const Vector& solution, Vector& displ) const +{ + displ.resize(3*nodeInd.size()); + for (size_t i = 0; i < nodeInd.size(); i++) + { + int n = nodeInd[i].global-1; + for (int j = 0; j < 3; j++) + displ[3*i+j] = solution[3*n+j]; + } +} + + +bool VolumePatch::evalDisplField (Matrix& dField, const Vector& displ, + const int* npe, const LocalSystem* cs) const +{ + // Compute parameter values of the nodal points + RealArray gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the displacement field + if (splineEvalMethod == 1) // slow and memory intensive + return evalDisplField1 (dField,displ,gpar,cs); + else if (splineEvalMethod == 2) // faster and less memory intensive + return evalDisplField2 (dField,displ,gpar,cs); + else + return false; +} + + +bool VolumePatch::evalDisplField1 (Matrix& dField, + const Vector& displ, + const RealArray* gpar, + const LocalSystem* cs) const +{ + // Evaluate the shape functions at all points +#ifdef SP_DEBUG + std::cout <<"Spline evaluation " + << gpar[0].size() <<" "<< gpar[1].size() <<" "<< gpar[2].size() + <<" ... "<< std::flush; +#endif + DoubleMat N; + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],N); + +#if SP_DEBUG > 3 + for (unsigned int k = 0; k < N.size(); k++) + { + std::cout <<"\nShape functions for point "<< 1+k << std::endl; + for (unsigned int l = 0; l < N[k].size(); l++) + std::cout << l+1 <<'\t'<< N[k][l] << std::endl; + } +#elif defined(SP_DEBUG) + std::cout << std::endl; +#endif + + size_t nComp = displ.size() / this->getNoNodes(); + if (nComp*this->getNoNodes() != displ.size()) + return false; + + // Fetch nodal (control point) coordinates if local coordinates are used + Matrix Xnod; + if (cs && nComp == 3) this->getNodalCoordinates(Xnod); + + // Evaluate the displacement field at each point + dField.resize(nComp,N.size()); + for (size_t i = 0; i < N.size(); i++) + { + for (size_t j = 0; j < nComp; j++) + dField(1+j,1+i) = displ.dot(N[i],j,nComp); +#if SP_DEBUG > 2 + std::cout <<"\nGlobal displacements at point "<< 1+i + <<" "<< dField.getColumn(1+i) << std::endl; +#endif + + if (cs && nComp == 3) + { + // Transformation to local coordinate system at current point + Vec3 d = cs->getTmat(Xnod*N[i]) * Vec3(dField.getColumn(1+i)); + dField(1,1+i) = d.x; + dField(2,1+i) = d.y; + dField(3,1+i) = d.z; + } + } + + return true; +} + + +bool VolumePatch::evalDisplField2 (Matrix& dField, + const Vector& displ, + const RealArray* gpar, + const LocalSystem* cs) const +{ + // Evaluate the shape functions at all points +#ifdef SP_DEBUG + std::cout <<"Spline evaluation " + << gpar[0].size() <<" "<< gpar[1].size() <<" "<< gpar[2].size() + <<" ... "<< std::flush; +#endif + std::vector spline; + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + +#if SP_DEBUG > 3 + for (unsigned int k = 0; k < spline.size(); k++) + std::cout <<"\nShape functions for point "<< 1+k << spline[k]; +#elif defined(SP_DEBUG) + std::cout << std::endl; +#endif + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + size_t nComp = displ.size() / this->getNoNodes(); + if (nComp*this->getNoNodes() != displ.size()) + return false; + + // Fetch nodal (control point) coordinates if local coordinates are used + Vector Ytmp; + Matrix Xnod, Xtmp; + if (cs && nComp == 3) this->getNodalCoordinates(Xnod); + + // Evaluate the displacement field at each point + dField.resize(nComp,spline.size()); + for (size_t i = 0; i < spline.size(); i++) + { + IntVec ip; + scatterInd(n1,n2,n3,p1,p2,p3,spline[i].left_idx,ip); + utl::gather(ip,nComp,displ,Xtmp); + Xtmp.multiply(spline[i].basisValues,Ytmp); + dField.fillColumn(1+i,Ytmp); +#if SP_DEBUG > 2 + std::cout <<"\nGlobal displacements at point "<< 1+i + <<" "<< dField.getColumn(1+i) << std::endl; +#endif + + if (cs && nComp == 3) + { + // Transformation to local coordinate system at current point + utl::gather(ip,3,Xnod,Xtmp); + Xtmp.multiply(spline[i].basisValues,Ytmp); + + Vec3 d = cs->getTmat(Ytmp) * Vec3(dField.getColumn(1+i)); + dField(1,1+i) = d.x; + dField(2,1+i) = d.y; + dField(3,1+i) = d.z; + } + } + + return true; +} + + +bool VolumePatch::evalStressField (Matrix& sField, const Vector& displ, + const int* npe, const LocalSystem* cs) const +{ + // Compute parameter values of the nodal points + RealArray gpar[3]; + for (int dir = 0; dir < 3; dir++) + if (!this->getGridParameters(gpar[dir],dir,npe[dir]-1)) + return false; + + // Evaluate the stress field + if (splineEvalMethod == 1) // slow and memory intensive + return evalStressField1 (sField,displ,gpar,cs); + else if (splineEvalMethod == 2) // faster and less memory intensive + return evalStressField2 (sField,displ,gpar,cs); + else + return false; +} + + +#ifndef epsZ +//! \brief Zero tolerance for the Jacobian determinant in stress evaluation. +#define epsZ 1.0e-16 +#endif + +bool VolumePatch::evalStressField1 (Matrix& sField, + const Vector& displ, + const RealArray* gpar, + const LocalSystem* cs) const +{ + // Evaluate the shape function derivatives at all points + std::cout <<"Spline evaluation " + << gpar[0].size() <<" "<< gpar[1].size() <<" "<< gpar[2].size() + <<" ... "<< std::flush; + DoubleMat N, dN1, dN2, dN3; + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],N,dN1,dN2,dN3); + std::cout << std::endl; + + // Fetch nodal (control point) coordinates and material matrix + Matrix B, C, CB, Xnod, Jac(3,3); + this->getNodalCoordinates(Xnod); + this->getMaterialMatrix(C); + + size_t nCoeffs = Xnod.cols(); + size_t nPoints = dN1.size(); + + // Evaluate the stress field at each point + sField.resize(6,nPoints); + Matrix dNdu(nCoeffs,3), dNdX(nCoeffs,3); + SymmTensor Sigma(3); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch shape function derivates at current point + for (size_t j = 0; j < nCoeffs; j++) +#ifdef INDEX_CHECK + if (j >= dN1[i].size()) + { + std::cerr <<" *** VolumePatch::evalStressField: Array size mismatch " + << nCoeffs <<", "<< dN1[i].size() << std::endl; + return false; + } + else +#endif + { + dNdu(1+j,1) = dN1[i][j]; + dNdu(1+j,2) = dN2[i][j]; + dNdu(1+j,3) = dN3[i][j]; + } + + // Evaluate the stress tensor + if (Jac.multiply(Xnod,dNdu).inverse() <= epsZ) // Jac = (Xnod * dNdu)^-1 + Sigma.zero(); // Set zero stress when Jacobian is singular + else + { + formBmatrix(B,dNdX.multiply(dNdu,Jac)); // B = strain-displacement matrix + CB.multiply(C,B).multiply(displ,Sigma); // Sigma = C * B * displ + + // Congruence transformation to local coordinate system at current point + if (cs) Sigma.transform(cs->getTmat(Xnod*N[i])); + } + sField.fillColumn(1+i,Sigma); + } + + return true; +} + + +bool VolumePatch::evalStressField2 (Matrix& sField, + const Vector& displ, + const RealArray* gpar, + const LocalSystem* cs) const +{ + // Evaluate the shape function derivatives at all points + std::cout <<"Spline evaluation " + << gpar[0].size() <<" "<< gpar[1].size() <<" "<< gpar[2].size() + <<" ... "<< std::flush; + std::vector spline; + svol->computeBasisGrid(gpar[0],gpar[1],gpar[2],spline); + std::cout << std::endl; + + const int p1 = svol->order(0); + const int p2 = svol->order(1); + const int p3 = svol->order(2); + const int n1 = svol->numCoefs(0); + const int n2 = svol->numCoefs(1); + const int n3 = svol->numCoefs(2); + + // Fetch nodal (control point) coordinates and material matrix + Matrix B, C, CB, Xnod, Xtmp, Dtmp, Jac(3,3); + this->getNodalCoordinates(Xnod); + this->getMaterialMatrix(C); + + size_t nCoeffs = p1*p2*p3; + size_t nPoints = spline.size(); + + // Evaluate the stress field at each point + sField.resize(6,nPoints); + Matrix dNdu(nCoeffs,3), dNdX(nCoeffs,3); + SymmTensor Sigma(3); + for (size_t i = 0; i < nPoints; i++) + { + // Fetch shape function derivates at current point + IntVec ip; + scatterInd(n1,n2,n3,p1,p2,p3,spline[i].left_idx,ip); + utl::gather(ip,3,Xnod,Xtmp); + utl::gather(ip,3,displ,Dtmp); + for (size_t j = 0; j < ip.size(); j++) + { + dNdu(1+j,1) = spline[i].basisDerivs_u[j]; + dNdu(1+j,2) = spline[i].basisDerivs_v[j]; + dNdu(1+j,3) = spline[i].basisDerivs_w[j]; + } + + // Evaluate the stress tensor + if (Jac.multiply(Xtmp,dNdu).inverse() <= epsZ) // Jac = (Xtmp * dNdu)^-1 + Sigma.zero(); // Set zero stress when Jacobian is singular + else + { + formBmatrix(B,dNdX.multiply(dNdu,Jac)); // B = strain-displacement matrix + CB.multiply(C,B).multiply(Dtmp,Sigma); // Sigma = C * B * Dtmp + + // Congruence transformation to local coordinate system at current point + if (cs) Sigma.transform(cs->getTmat(Xtmp*spline[i].basisValues)); + } + sField.fillColumn(1+i,Sigma); + } + + return true; +} + + +void VolumePatch::vonMises (const Matrix& s, Vector& vms) +{ + vms.resize(s.cols()); + for (size_t i = 1; i <= vms.size(); i++) + vms(i) = sqrt(s(1,i)*s(1,i) + s(2,i)*s(2,i) + s(3,i)*s(3,i) - + s(1,i)*s(2,i) - s(2,i)*s(3,i) - s(3,i)*s(1,i) + + 3.0*(s(4,i)*s(4,i) + s(5,i)*s(5,i) + s(6,i)*s(6,i))); +} diff --git a/src/VolumePatch.h b/src/VolumePatch.h new file mode 100644 index 00000000..70b1338e --- /dev/null +++ b/src/VolumePatch.h @@ -0,0 +1,448 @@ +// $Id: VolumePatch.h,v 1.22 2010-06-22 11:38:05 kmo Exp $ +//============================================================================== +//! +//! \file VolumePatch.h +//! +//! \date Dec 10 2008 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Representation of a topological cube as a SplineVolume. +//! \details The class contains the necessary data and methods to establish +//! a finite element algebraic system of equations for the stiffness relation +//! of the volume, using NURBS as basis functions via the GoTools library. +//! +//============================================================================== + +#ifndef _VOLUME_PATCH_H +#define _VOLUME_PATCH_H + +#include "Function.h" +#include "MPCLess.h" +#include "MatVec.h" +#include +#include + +namespace Go { + class SplineVolume; +} + +struct ElementBlock; +class SystemVector; +class LinEqSystem; +class LocalSystem; +class SAM; +class Vec3; +class Tensor; + +typedef std::vector IntVec; //!< General integer vector +typedef std::vector IntMat; //!< General 2D integer matrix +typedef std::set MPCSet; //!< Sorted set of MPC equations +typedef MPCSet::const_iterator MPCIter; //!< Iterator over a MPC equation set + + +/*! + \brief Assembly of finite element contributions from a SplineVolume object. + \details This class stores data and provides methods for the calculation of + element stiffness and mass matrices, as well as load vectors for a structured + isogeometric grid, using NURBS or splines as basis functions. +*/ + +class VolumePatch +{ + //! \brief Struct for nodal point data. + struct IJK + { + int I; //!< Index in first parameter direction + int J; //!< Index in second parameter direction + int K; //!< Index in third parameter direction + int global; //!< Global node number [1,nnod] + }; + +public: + //! \brief Struct for boundary condition codes. + struct BC + { + int node; //!< Global node number [1,nnod] + char CX; //!< Boundary condition code for X-translation + char CY; //!< Boundary condition code for Y-translation + char CZ; //!< Boundary condition code for Z-translation + //! \brief Constructor initializing a BC instance. + BC(int n, char x, char y, char z) : node(n), CX(x), CY(y), CZ(z) {} + }; + + //! \brief Constructor creating an instance by reading the given file. + VolumePatch(const char* fileName, bool checkRHS = false); + //! \brief Constructor creating an instance by reading the given input stream. + VolumePatch(std::istream& is, bool checkRHS = false); + //! \brief Default constructor creating an empty patch. + VolumePatch() { svol = 0; swapW = false; E = nu = rho = 0.0; } + //! \brief The Destructor frees the dynamically allocated SplineVolume object. + ~VolumePatch(); + + //! \brief Creates an instance by reading the given input stream, \a is. + bool read(std::istream& is); + //! \brief Writes the geometry of the SplineVolume object to the given stream. + bool write(std::ostream& os) const; + + //! \brief Check that the patch is modelled in a right-hand-side system. + //! \details If it isn't, the w-parameter direction is swapped. + bool checkRightHandSystem(); + + //! \brief Refine the parametrization by inserting extra knots uniformly. + //! \param[in] dir Parameter direction to refine + //! \param[in] nInsert Number of extra knots to insert in each knot-span + bool uniformRefine(int dir, int nInsert); + //! \brief Raise the order of the SplineVolume object for this patch. + //! \param[in] ru Number of times to raise the order i u-direction + //! \param[in] rv Number of times to raise the order i u-direction + //! \param[in] rw Number of times to raise the order i u-direction + bool raiseOrder(int ru, int rv, int rw); + + //! \brief Generates the finite element topology data for the patch. + bool generateFEMTopology(); + + //! \brief Clears the contents of the patch, making it empty. + void clear(); + + //! \brief Checks if the patch is empty. + bool empty() const { return svol == 0; } + + //! \brief Returns local 1-based index of the node with given global number. + //! \details If the given node number is not present, 0 is returned. + //! \param[in] globalNum Global node number + int getNodeIndex(int globalNum) const; + //! \brief Returns the global node number for the given node. + //! \param[in] inod 1-based node index local to current patch + int getNodeID(int inod) const; + //! \brief Returns the global coordinates for the given node. + //! \param[in] inod 1-based node index local to current patch + Vec3 getCoord(int inod) const; + + //! \brief Returns the total number of nodes in the patch. + size_t getNoNodes() const { return nodeInd.size(); } + //! \brief Returns the total number of elements in the patch. + size_t getNoElms() const { return MNPC.size(); } + //! \brief Returns the total number of MPC equations in the patch. + size_t getNoMPCs() const { return mpcs.size(); } + + //! \brief Returns the beginning of the BC array. + std::vector::const_iterator begin_BC() const { return BCode.begin(); } + //! \brief Returns the end of the BC array. + std::vector::const_iterator end_BC() const { return BCode.end(); } + + //! \brief Returns the beginning of the MNPC array. + IntMat::const_iterator begin_elm() const { return MNPC.begin(); } + //! \brief Returns the end of the MNPC array. + IntMat::const_iterator end_elm() const { return MNPC.end(); } + + //! \brief Returns the beginning of the MPC set. + MPCIter begin_MPC() const { return mpcs.begin(); } + //! \brief Returns the end of the MPC set. + MPCIter end_MPC() const { return mpcs.end(); } + + //! \brief Merges a given node in this patch with a given global node. + //! \param[in] node 1-based node index local to current patch + //! \param[in] globalNum Global number of the node to merge \a node with + bool mergeNodes(int node, int globalNum); + + //! \brief Renumbers the global node numbers referred by this patch. + //! \details The node numbers referred by boundary condition and + //! multi-point constraint objects in the patch are renumbered. + //! The noded themselves are assumed to already be up to date. + //! \param[in] old2new Old-to-new node number mapping + //! \param[in] silent If \e false, give error on missing node in \a old2new + bool renumberNodes(const std::map& old2new, bool silent); + + //! \brief Renumbers all global node numbers in the entire model. + //! \param[in] model All volume patches in the model + //! \return The number of unique nodes in the model + //! + //! \details After the renumbering, the global node numbers are in the range + //! [1,\a nnod ], where \a nnod is the number of unique nodes in the model. + static int renumberNodes(const std::vector& model); + + //! \brief Resolves (possibly multi-level) chaining in MPC equations. + //! \param[in] model All volume patches in the model + //! + //! \details If a master DOF in one MPC is specified as slave by another MPC, + //! it is replaced by the master(s) of that other equation. + //! Since an MPC-equation may couple nodes belonging to different patches, + //! this method must have all patches in the model available. + static void resolveMPCchains(const std::vector& model); + + //! \brief Defines material properties for current volume patch. + //! \param[in] Emod Young's modulus + //! \param[in] Poiss Poisson's ratio + //! \param[in] Density Mass density + void setMaterial(double Emod, double Poiss, double Density) + { E = Emod; nu = Poiss; rho = Density; } + //! \brief Retrieves material properties for current volume patch. + //! \param[out] Emod Young's modulus + //! \param[out] Poiss Poisson's ratio + //! \param[out] Density Mass density + void getMaterial(double& Emod, double& Poiss, double& Density) + { Emod = E; Poiss = nu; Density = rho; } + + //! \brief Makes two opposite boundary faces periodic. + //! \param[in] dir Parameter direction defining the periodic faces + void closeFaces(int dir); + + //! \brief Constrains all DOFs on a given boundary face. + //! \param[in] dir Parameter direction defining the face to constrain + //! \param[in] dof Which DOFs to constrain at each node on the face + //! \param[in] value Prescribed value of the constrained DOFs + void constrainFace(int dir, int dof = 123, double value = 0.0); + + //! \brief Constrains all DOFs along a line on a given boundary face. + //! \param[in] fdir Parameter direction defining the face to constrain + //! \param[in] ldir Parameter direction defining the line to constrain + //! \param[in] xi Parameter value defining the line to constrain + //! \param[in] dof Which DOFs to constrain at each node along the line + //! \param[in] value Prescribed value of the constrained DOFs + //! + //! \details The parameter \a xi has to be in the domain [0.0,1.0], where + //! 0.0 means the beginning of the domain and 1.0 means the end. The line to + //! constrain goes along the parameter direction \a ldir in the face with + //! with normal in parameter direction \a fdir, and positioned along the third + //! parameter direction as indicated by \a xi. The actual value of \a xi + //! is converted to the integer value closest to \a xi*n, where \a n is the + //! number of nodes (control points) in that parameter direction. + void constrainLine(int fdir, int ldir, double xi, + int dof = 123, double value = 0.0); + + //! \brief Constrains a corner node identified by the three parameter indices. + //! \param[in] I Parameter index in u-direction + //! \param[in] J Parameter index in v-direction + //! \param[in] K Parameter index in w-direction + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] value Prescribed value of the constrained DOFs + //! + //! \details The sign of the three indices is used to define whether we want + //! the node at the beginning or the end of that parameter direction. + //! The magnitude of the indices are not used. + void constrainCorner(int I, int J, int K, int dof = 123, double value = 0.0); + + //! \brief Constrains a node identified by three relative parameter values. + //! \param[in] xi Parameter in u-direction + //! \param[in] eta Parameter in v-direction + //! \param[in] zeta Parameter in w-direction + //! \param[in] dof Which DOFs to constrain at the node + //! \param[in] value Prescribed value of the constrained DOFs + //! + //! \details The parameter values have to be in the domain [0.0,1.0], where + //! 0.0 means the beginning of the domain and 1.0 means the end. For values + //! in between, the actual index is taken as the integer value closest to + //! \a r*n, where \a r denotes the given relative parameter value, + //! and \a n is the number of nodes along that parameter direction. + void constrainNode(double xi, double eta, double zeta, + int dof = 123, double value = 0.0); + + //! \brief Connects all matching nodes on two adjacent boundary faces. + //! \param[in] face Local face index of this patch, in range [1,6] + //! \param neighbor The neighbor patch + //! \param[in] nface Local face index of neighbor patch, in range [1,6] + //! \param[in] norient Relative face orientation flag (see below) + //! + //! \details The face orientation flag \a norient must be in range [0,7]. + //! When interpreted as a binary number, its 3 digits are decoded as follows: + //! - left digit = 1: The u and v parameters of the neighbor face are swapped + //! - middle digit = 1: Parameter \a u in neighbor patch face is reversed + //! - right digit = 1: Parameter \a v in neighbor patch face is reversed + bool connectPatch(int face, VolumePatch& neighbor, + int nface, int norient = 0); + + //! \brief Assembles coefficient matrices and right-hand-side vector. + //! \param sys The linear system of equations + //! \param[in] sam Data for finite element assembly management + //! \param[in] gravity Gravitation vector + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + //! \param[in] displ Solution vector in DOF-order + bool assembleSystem(LinEqSystem& sys, const SAM& sam, const Vec3& gravity, + int nGauss = 4, const Vector& displ = Vector()); + + //! \brief Assembles right-hand-side vector due to surface traction on a face. + //! \param S The right-hand-side vector + //! \param[in] sam Data for finite element assembly management + //! \param[in] t The surface traction function + //! \param[in] dir Local index of the face subjected to the traction + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + //! \param[out] trac Evaluated tractions at integration points (optional) + bool assembleForces(SystemVector& S, const SAM& sam, const TractionFunc& t, + int dir, int nGauss = 4, std::map* trac = 0); + + //! \brief Evaluates some norms of the finite element solution. + //! \details The energy norm of the solution is computed by numerical + //! integration of \f$a({\bf u}^h,{\bf u}^h)\f$ over the patch. + //! If an analytical solution is available, the norm of the exact error + //! \f$a({\bf u}-{\bf u}^h,{\bf u}-{\bf u}^h)\f$ is computed as well. + //! \param[out] gNorm Global norms + //! \param[out] eNorm Element norms + //! \param[in] nGauss Numerical integration scheme (number of points in 1D) + //! \param[in] displ Solution vector in DOF-order + //! \param[in] sol Pointer to analytical stress field (optional) + bool solutionNorms(Vector& gNorm, Matrix& eNorm, int nGauss, + const Vector& displ, const TensorFunc* sol = 0); + + //! \brief Creates a hexahedron element model of this patch for visualization. + //! \param[out] grid The generated hexahedron grid + //! \param[in] npe Number of visualization nodes over each knot span + //! \note The number of element nodes must be set in \a grid on input. + bool convertToElementBlock(ElementBlock& grid, const int* npe) const; + //! \brief Extracts element results for this patch from a global vector. + //! \param[in] globRes Global matrix of element results + //! \param[out] elmRes Element results for this patch + void extractElmRes(const Matrix& globRes, Matrix& elmRes) const; + //! \brief Extracts nodal displacements for this patch from the global vector. + //! \param[in] solution Global solution vector in DOF-order + //! \param[out] displ Nodal displacement vector for this patch + void extractSolution(const Vector& solution, Vector& displ) const; + //! \brief Evaluates the displacement field at all visualization points. + //! \param[out] dField Displacement field + //! \param[in] displ Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + //! \param[in] cs Local coordinate system + bool evalDisplField(Matrix& dField, const Vector& displ, + const int* npe, const LocalSystem* cs = 0) const; + //! \brief Evaluates the stress field at all visualization points. + //! \param[out] sField Stress field + //! \param[in] displ Solution vector in DOF-order + //! \param[in] npe Number of visualization nodes over each knot span + //! \param[in] cs Local coordinate system + bool evalStressField(Matrix& sField, const Vector& displ, + const int* npe, const LocalSystem* cs = 0) const; + + //! \brief Calculates von Mises stress field from a stress tensor field. + //! \param[in] sigma Stress tensor field + //! \param[out] vm von Mises stress field + static void vonMises(const Matrix& sigma, Vector& vm); + + //! \brief If \e true, matching nodes in two adjacent patches are merged. + static bool mergeDuplNodes; //!< If \e false, MPC-equations are used. + static bool swapJac; //!< Should the sign of the Jacobian be swapped? + static int splineEvalMethod; //!< Which spline evaluation method to use + +protected: + //! \brief Adds a general multi-point-constraint equation to the patch. + //! \param mpc Pointer to an MPC-object + bool addMPC(MPC* mpc); + //! \brief Creates and adds a single-point constraint to the patch. + //! \param[in] node Global node number of the node to constrain + //! \param[in] dir Which local DOF to constrain (1, 2, 3) + //! \param[in] value The prescribed value + bool addSPC(int node, int dir, double value = 0.0); + //! \brief Creates and adds a periodicity constraint to the patch. + //! \param[in] master Global node number of the master node + //! \param[in] slave Global node number of the slave node to constrain + //! \param[in] dir Which local DOF to constrain (1, 2, 3) + bool addPeriodicity(int master, int slave, int dir); + //! \brief Creates periodicity constraints between two nodes in the patch. + //! \param[in] master Global node number of the master node + //! \param[in] slave Global node number of the slave node to constrain + //! \param[in] code Which local DOFs to constrain (1, 2, 3, 12, 23, 123) + void makePeriodic(int master, int slave, int code = 123); + //! \brief Constrains DOFs in the given node to the given value. + //! \param[in] node 1-based node index local to current patch + //! \param[in] code Which local DOFs to constrain (1, 2, 3, 12, 23, 123) + //! \param[in] value The prescribed value + void prescribe(int node, int code = 123, double value = 0.0); + //! \brief Constrains DOFs in the given node to zero. + //! \param[in] node 1-based node index local to current patch + //! \param[in] code Which local DOFs to constrain (1, 2, 3, 12, 23, 123) + void fix(int node, int code = 123); + + //! \brief Recursive method used by \a resolveMPCchains. + static bool resolveMPCchain(const MPCSet& allMPCs, MPC* mpc); + + //! \brief Merges the given two nodes into one. + //! \details The global node number of the node with the highest number + //! is changed into the number of the other node. + static void mergeNodes(IJK& node1, IJK& node2); + + //! \brief Calculates the parameter values for all visualization points in + //! one direction. + //! \param[out] par Parameter values for all visualization points. + //! \param[in] dir Parameter direction (0,1,2) + //! \param[in] nSegSpan Number of visualization segments over each non-zero + //! knot-spans + bool getGridParameters(RealArray& par, int dir, int nSegSpan) const; + + //! \brief Returns the volume in the parameter space for an element. + //! \param[in] iel Element index + double getParametricVolume(int iel) const; + //! \brief Returns boundary face area in the parameter space for an element. + //! \param[in] iel Element index + //! \param[in] dir Local face index of the boundary face + double getParametricArea(int iel, int dir) const; + //! \brief Sets up the constitutive matrix for this patch. + //! \param[out] C 6\f$\times\f$6-matrix, representing the material tensor + //! \param[in] inverse If \e true, set up the inverse matrix instead + bool getMaterialMatrix(Matrix& C, bool inverse = false) const; + //! \brief Returns a matrix with nodal coordinates for an element. + //! \param[in] iel Element index + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in one element + bool getElementCoordinates(Matrix& X, int iel) const; + //! \brief Returns a matrix with all nodal coordinates within the patch. + //! \param[out] X 3\f$\times\f$n-matrix, where \a n is the number of nodes + //! in the patch + void getNodalCoordinates(Matrix& X) const; + //! \brief Returns an index into the internal coefficient array for a node. + //! \param[in] inod 1-based node index local to current patch + int coeffInd(int inod) const; + + //! \brief Evaluates the displacement field at all visualization points. + //! \details Memory intensive version, used when \a splineEvalMethod = 1. + //! \param[out] dField Displacement field + //! \param[in] displ Solution vector in DOF-order + //! \param[in] gpar Parameter values of the vizualization points + //! \param[in] cs Local coordinate system + bool evalDisplField1(Matrix& dField, const Vector& displ, + const RealArray* gpar, const LocalSystem* cs = 0) const; + //! \brief Evaluates the displacement field at all visualization points. + //! \details More efficient version, used when \a splineEvalMethod = 2. + //! \param[out] dField Displacement field + //! \param[in] displ Solution vector in DOF-order + //! \param[in] gpar Parameter values of the vizualization points + //! \param[in] cs Local coordinate system + bool evalDisplField2(Matrix& dField, const Vector& displ, + const RealArray* gpar, const LocalSystem* cs = 0) const; + + //! \brief Evaluates the stress field at all visualization points. + //! \details Memory intensive version, used when \a splineEvalMethod = 1. + //! \param[out] sField Stress field + //! \param[in] displ Solution vector in DOF-order + //! \param[in] gpar Parameter values of the vizualization points + //! \param[in] cs Local coordinate system + bool evalStressField1(Matrix& sField, const Vector& displ, + const RealArray* gpar, const LocalSystem* cs = 0) const; + //! \brief Evaluates the stress field at all visualization points. + //! \details More efficient version, used when \a splineEvalMethod = 2. + //! \param[out] sField Stress field + //! \param[in] displ Solution vector in DOF-order + //! \param[in] gpar Parameter values of the vizualization points + //! \param[in] cs Local coordinate system + bool evalStressField2(Matrix& sField, const Vector& displ, + const RealArray* gpar, const LocalSystem* cs = 0) const; + + //! \brief Calculates the Strain-displacement matrix \b B. + static void formBmatrix(Matrix& B, const Matrix& dNdX); + +private: + // Geometry and physical properties + Go::SplineVolume* svol; //!< Pointer to the actual spline volume object + bool swapW; //!< Has the w-parameter direction been swapped? + double E; //!< Young's modulus + double nu; //!< Poisson's ratio + double rho; //!< Mass density + // Finite element data structures + IntMat MNPC; //!< Matrix of Nodal Point Correspondance + std::vector MLGE; //!< Matrix of Local to Global Element numbers + std::vector nodeInd; //!< IJK-triplets for the control points (nodes) + std::vector BCode; //!< Boundary condition codes + MPCSet mpcs; //!< All multi-point constraints + static int gEl; //!< Global element counter + static int gNod; //!< Global node counter +}; + +#endif diff --git a/src/main_LinEl.C b/src/main_LinEl.C new file mode 100644 index 00000000..38456c36 --- /dev/null +++ b/src/main_LinEl.C @@ -0,0 +1,278 @@ +// $Id: main_LinEl.C,v 1.20 2010-05-06 17:31:18 kmo Exp $ +//============================================================================== +//! +//! \file main_LinEl.C +//! +//! \date Jan 28 2009 +//! +//! \author Knut Morten Okstad / SINTEF +//! +//! \brief Main program for the NURBS-based linear elasticity solver. +//! +//============================================================================== + +#include "LinearEl.h" +#include "VolumePatch.h" +#include +#include +#include + + +/*! + \brief Main program for the NURBS-based linear elasticity solver. + + The input to the program is specified through the following + command-line arguments. The arguments may be given in arbitrary order. + + \arg \a input-file : Input file with model definition + \arg -dense : Use the dense LAPACK matrix equation solver + \arg -superlu : Use the sparse superLU equation solver + \arg -samg : Use the sparse algebraic multi-grid equation solver + \arg -swapJ : Swap the sign of the Jacobian to account for inverse ordering + \arg -splineMethod \a iop : Spline evaluation method (1 or 2) + \arg -nGauss \a n : Number of Gauss points over a knot-span in each direction + \arg -vtf \a format : VTF-file format (0=ASCII, 1=BINARY) + \arg -nviz \a nviz : Number of visualization points over each knot-span + \arg -nu \a nu : Number of visualization points per knot-span in u-direction + \arg -nv \a nv : Number of visualization points per knot-span in v-direction + \arg -nw \a nw : Number of visualization points per knot-span in w-direction + \arg -ignore \a p1, \a p2, ... : Ignore these patches in the analysis + \arg -eig \a iop : Eigenproblem solver to use (1...6) + \arg -nev \a nev : Number of eigenvalues to compute + \arg -ncv \a ncv : Number of Arnoldi vectors to use in the eigenvalue analysis + \arg -shift \a shf : Shift value to use in the eigenproblem solver + \arg -free : Ignore all boundary conditions (use in free vibration analysis) + \arg -check : Data check only, read model and output to VTF (no solution) + \arg -checkRHS : Check that the patches are modelled in a right-hand system + \arg -fixDup : Resolve co-located nodes by merging them into a single node + \arg -vizRHS : Save the right-hand-side load vector on the VTF-file + \arg -dumpGeo : Dumps the (possibly refined) geometry to a g2-file + \arg -dumpMat : Dumps the system matrices to file + \arg -dumpDis : Dumps the solution vector to files, one for each component + \arg -dumpGrid : Dumps a grid file with 8-noded hexahedron elements + \arg -dumpGrid2 : Dumps a grid file with 27-noded hexahedron elements +*/ + +int main (const int argc, char** argv) +{ + SystemMatrix::Type solver = SystemMatrix::SPR; + int nGauss = 4; + int format = 0; + int n[3] = { 2, 2, 2 }; + std::vector ignoredPatches; + int iop = 0; + int nev = 10; + int ncv = 20; + double shf = 0.0; + bool free = false; + bool checkRHS = false; + bool vizRHS = false; + bool fixDup = false; + bool dumpGeo = false; + bool dumpMat = false; + bool dumpDis = false; + int dumpGrid = 0; + char* infile = 0; + + for (int i = 1; i < argc; i++) + if (!strcmp(argv[i],"-dense")) + solver = SystemMatrix::DENSE; + else if (!strcmp(argv[i],"-superlu")) + solver = SystemMatrix::SPARSE; + else if (!strcmp(argv[i],"-samg")) + solver = SystemMatrix::SAMG; + else if (!strcmp(argv[i],"-swapJ")) + VolumePatch::swapJac = true; + else if (!strcmp(argv[i],"-splineMethod") && i < argc-1) + VolumePatch::splineEvalMethod = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nGauss") && i < argc-1) + nGauss = atoi(argv[++i]); + else if (!strcmp(argv[i],"-vtf") && i < argc-1) + format = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nviz") && i < argc-1) + n[0] = n[1] = n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nu") && i < argc-1) + n[0] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nv") && i < argc-1) + n[1] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nw") && i < argc-1) + n[2] = atoi(argv[++i]); + else if (!strcmp(argv[i],"-ignore")) + while (i < argc-1 && isdigit(argv[i+1][0])) + { + char* endp = 0; + int endVal = 0; + ignoredPatches.push_back(strtol(argv[++i],&endp,10)); + if (endp && *endp == ':') + endVal = strtol(endp+1,&endp,10); + while (ignoredPatches.back() < endVal) + ignoredPatches.push_back(ignoredPatches.back()+1); + } + else if (!strcmp(argv[i],"-eig") && i < argc-1) + iop = atoi(argv[++i]); + else if (!strcmp(argv[i],"-nev") && i < argc-1) + nev = atoi(argv[++i]); + else if (!strcmp(argv[i],"-ncv") && i < argc-1) + ncv = atoi(argv[++i]); + else if (!strcmp(argv[i],"-shift")) + shf = atof(argv[++i]); + else if (!strcmp(argv[i],"-free")) + free = true; + else if (!strcmp(argv[i],"-check")) + iop = 100; + else if (!strcmp(argv[i],"-checkRHS")) + checkRHS = true; + else if (!strcmp(argv[i],"-vizRHS")) + vizRHS = true; + else if (!strcmp(argv[i],"-fixDup")) + fixDup = true; + else if (!strcmp(argv[i],"-dumpGeo")) + dumpGeo = true; + else if (!strcmp(argv[i],"-dumpMat")) + dumpMat = true; + else if (!strcmp(argv[i],"-dumpDis")) + dumpDis = true; + else if (!strcmp(argv[i],"-dumpGrid")) + dumpGrid = 1; + else if (!strcmp(argv[i],"-dumpGrid2")) + dumpGrid = 2; + else if (!infile) + infile = argv[i]; + + if (!infile) + { + std::cout <<"usage: "<< argv[0] <<" [-dense|-superlu|-samg]\n" + <<" [-splineMethod ] [-nGauss ] [-vtf ]\n" + <<" [-nviz ] [-nu ] [-nv ] [-nw ]\n" + <<" [-eig ] [-nev ] [-ncv ]\n" + <<" [-free] [-check] [-checkRHS] [-vizRHS] [-fixDup]\n" + <<" [-ignore ...] [-swapJ] [-dumpGeo]\n" + <<" [-dumpMat] [-dumpDis] [-dumpGrid|-dumpGrid2]\n"; + return 0; + } + + + // Load vector visualization is not available when using additional viz-points + if (n[0] > 2 || n[1] > 2 || n[2] > 2) vizRHS = false; + + // Boundary conditions can be ignored only in generalized eigenvalue analysis + if (abs(iop) != 4 && abs(iop) != 6) free = false; + + std::cout <<"\n >>> ICADA Spline FEM prototype <<<" + <<"\n ==================================\n" + <<"\nInput file: "<< infile + <<"\nEquation solver: "<< solver + <<"\nspline evaluation method: "<< VolumePatch::splineEvalMethod + <<"\nNumber of Gauss points: "<< nGauss + <<"\nVTF file format: "<< (format ? "BINARY":"ASCII") + <<"\nNumber of visualization points: " + << n[0] <<" "<< n[1] <<" "<< n[2]; + if (iop != 0 && iop < 100) + std::cout <<"\nEigenproblem solver: "<< iop + <<"\nNumber of eigenvalues: "<< nev + <<"\nNumber of Arnoldi vectors: "<< ncv + <<"\nShift value: "<< shf; + if (free) + std::cout <<"\nSpecified boundary conditions are ignored"; + if (VolumePatch::swapJac) + std::cout <<"\nSwapped Jacobian determinant"; + if (fixDup) + std::cout <<"\nCo-located nodes will be merged"; + if (checkRHS) + std::cout <<"\nChecking that each patch has a right-hand coordinate system"; + if (!ignoredPatches.empty()) + { + std::cout <<"\nIgnored patches:"; + for (size_t i = 0; i < ignoredPatches.size(); i++) + std::cout <<" "<< ignoredPatches[i]; + } + std::cout << std::endl; + + // Read in model definitions and establish the FE structures + LinearEl model(infile,checkRHS,free); + if (!model.preprocess(ignoredPatches,fixDup)) + return 1; + + if (dumpGeo) // Write the refined geometry to g2-file + model.dumpGeometry(strcat(strtok(infile,"."),".g2")); + + Result solution; + Matrix eNorm; + + switch (iop) { + case 0: + case 5: + case 54: + case 56: + // Assemble and solve the linear system of equations + if (!model.assembleKandR(solver,nGauss,vizRHS ? &solution.load : 0)) + return 2; + else if (!model.solve(solution.displ)) + return 3; + else if (iop == 0) + if (!model.solutionNorms(solution.norms,nGauss,solution.displ)) + return 4; + else + break; + + // Linearized buckling: Assemble stiffness matrices [K] and [Kg] + if (!model.assembleKandKg(solver,nGauss,solution.displ)) + return 2; + else if (dumpMat) + model.dumpMat("Km.mat","Kg.mat"); + else if (!model.modes(iop > 50 ? iop%50 : iop, nev,ncv,shf,solution.modes)) + return 3; + break; + + case -1: + case -2: + case 1: + case 2: + // Assemble and solve the regular eigenvalue problem + if (!model.assembleKonly(solver,nGauss)) + return 2; + else if (!model.modes(iop,nev,ncv,shf,solution.modes)) + return 3; + break; + + case 100: + case 101: + case 102: + break; // Model check + + default: + // Assemble and solve the generalized eigenvalue problem + solution.freq = true; + if (!model.assembleKandM(solver,nGauss)) + return 2; + else if (!model.modes(iop,nev,ncv,shf,solution.modes)) + return 3; + } + + // Write VTF-file with model and results + if (dumpGrid == 0 || iop < 100) + if (!model.writeGlv(infile,solution,n,format,iop==100)) return 4; + + // Write a global FE grid file for external processing + if (dumpGrid > 0) + if (!model.writeGlobalGrid(infile, n, dumpGrid == 2 ? 27 : 8)) return 5; + + // Write solution vectors to files for external viewing + if (dumpDis) + { + strcat(strtok(infile,"."),"_dis"); + if (!solution.displ.empty()) + model.dumpSolution(infile,solution.displ); + + infile[strlen(infile)-3] = 'm'; + infile[strlen(infile)-2] = '0'; + infile[strlen(infile)-1] = '\0'; + for (size_t j = 1; j < solution.modes.size() && j < 10; j++) + { + infile[strlen(infile)-1]++; + model.dumpSolution(infile,solution.modes[j-1].eigVec); + } + } + + return 0; +}