hypre/drivers/ParaGrid3D/parallel3D/Method_LocalM.cpp
2000-12-15 00:21:10 +00:00

694 lines
22 KiB
C++

#include <math.h>
#include <stdio.h>
#include "Matrix.h"
#include "Method.h"
#include "definitions.h"
#if CONCENTRATION == ON || CONVECTION == ON
extern real convection[3];
extern int Pressure;
#endif
#if OUTPUT_LM == ON
extern FILE *plotLM;
#endif
//============================================================================
// Add the local contribution from tetrahedron num_tr to the global stiffness
// matrix (for Reaction-Diffusion problem). The function has extension to add
// convection term doing discontinuous Galerkin approximation (used for con-
// vection dominated problems. The extension is used by setting CONVECTION
// to be ON. Another variant of using is in solving pressure equation, from
// which we get the velocity, which is the convection term in a convection
// dominated problem for the concentration.
//============================================================================
void Method::Reaction_Diffusion_LM(int num_tr, real *A, double *b){
int i, j;
real *coord, middle[3], vol, LM[4][4];
double f[5]; // used for initializing the RHS b
real K[3][3]; // diffusion matrix
real grad[4][3]; // grad[i] - gradient vector for node i = 0..3
double K_grad[4][3]; // K_grad[i] - flux vector ( K * grad[i])
real M, diag, off_diag;
for(i=0; i<4; i++)
for(j=0; j<4; j++)
LM[i][j] = 0.;
GetMiddle(num_tr, middle);
grad_nodal_basis(num_tr, grad);
#if CONCENTRATION == ON
if (Pressure == OFF){ // i.e. if we solve the concentration part - get
// the convection part from the pressure
K_grad[0][0] = K_grad[0][1] = K_grad[0][2] = 0.;
for(i=0; i<4; i++){
K_grad[0][0] += grad[i][0]*pressure[TR[num_tr].node[i]];
K_grad[0][1] += grad[i][1]*pressure[TR[num_tr].node[i]];
K_grad[0][2] += grad[i][2]*pressure[TR[num_tr].node[i]];
}
// get the Darcy velocity
Pressure = 1;
func_K(middle, K, TR[num_tr].atribut);
convection[0] = K[0][0]*K_grad[0][0] + K[0][1]*K_grad[0][1] +
K[0][2]*K_grad[0][2];
convection[1] = K[1][0]*K_grad[0][0] + K[1][1]*K_grad[0][1] +
K[1][2]*K_grad[0][2];
convection[2] = K[2][0]*K_grad[0][0] + K[2][1]*K_grad[0][1] +
K[2][2]*K_grad[0][2];
Pressure = 0;
// add convection to the local matrix LM
Convection_LM(num_tr, LM, convection);
}
#elif CONVECTION == ON
func_b(middle, convection);
Convection_LM(num_tr, LM, convection);// add convection
#endif
func_K( middle, K, TR[num_tr].atribut);
vol = volume(num_tr);
M = func_c( middle);
off_diag = M*vol*0.05; // 0.05 = 1/20
diag = 2*off_diag;
for(i=0; i<4; i++)
for(j=0; j<3; j++)
K_grad[i][j]=K[j][0]*grad[i][0]+K[j][1]*grad[i][1]+K[j][2]*grad[i][2];
for(i=0; i<4; i++){
coord = GetNode(TR[num_tr].node[i]);
f[i] = func_f( coord);
}
f[4] = 4*func_f(middle);
for(i=0;i<4;i++)
for(j=0;j<4;j++){
LM[i][j] += ( K_grad[i][0]*grad[j][0] + // add dispersion
K_grad[i][1]*grad[j][1] +
K_grad[i][2]*grad[j][2] ) * vol;
if (i==j) // add reaction
LM[i][j] += diag;
else
LM[i][j] += off_diag;
A[ ind_ij(level, TR[num_tr].node[i], TR[num_tr].node[j]) ] += LM[i][j];
}
vol *= 0.05; // vol=vol/20 (optimization)
b[TR[num_tr].node[0]]+=(f[0]+f[4])*vol;
b[TR[num_tr].node[1]]+=(f[1]+f[4])*vol;
b[TR[num_tr].node[2]]+=(f[2]+f[4])*vol;
b[TR[num_tr].node[3]]+=(f[3]+f[4])*vol;
//============= Fix Neumann Boundary =======================================
real m_edge[3][3];
real S, ff;
for(i=0; i<4; i++){ // for the 4 faces
ff = TR[num_tr].face[i];
if (F[ff].tetr[1] == NEUMANN){
S = area(num_tr, i);
GetMiddleEdge(F[ff].node[1], F[ff].node[2], m_edge[0]);
GetMiddleEdge(F[ff].node[0], F[ff].node[2], m_edge[1]);
GetMiddleEdge(F[ff].node[0], F[ff].node[1], m_edge[2]);
b[F[ff].node[0]] += (func_gn(m_edge[1])+func_gn(m_edge[2]))*S/6;
b[F[ff].node[1]] += (func_gn(m_edge[0])+func_gn(m_edge[2]))*S/6;
b[F[ff].node[2]] += (func_gn(m_edge[0])+func_gn(m_edge[1]))*S/6;
}
}
#if OUTPUT_LM == ON // print the local matrix
for(i=0; i<4; i++){
for(j=0; j<4; j++)
fprintf(plotLM,"%12.7f ", LM[i][j]);
fprintf(plotLM,"\n");
}
fprintf(plotLM,"\n");
#endif
}
//============================================================================
// Add the local contribution from tetrahedron num_tr to the global stiffness
// matrix (for Convection-Diffusion problem) using the streamline-diffusion
// method. If we make "delta = 0" (in the code below) we will do standard
// FE approximation for the convection part, which is "good" for "small" conv.
//============================================================================
void Method::Convection_Diffusion_LM(int num_tr, real *A, double *b){
int i, j, ii, jj, s;
real vol, a_ij;
real *coord, middle[3], conv[3]; // conv - convection direction
double f[4]; // used to initialize the RHS b
real K[3][3]; // diffusion matrix
real grad[4][3]; // grad[i] - gradient vector for node i = 0..3
double K_grad[4][3]; // K_grad[i] - flux vector ( K * grad[i]
double db[3],lb; // (lb -length) of convection direction
double h, delta; // db = delta . conv
double M, diag, off_diag;
GetMiddle(num_tr, middle);
func_K( middle, K, TR[num_tr].atribut);
func_b( middle, conv);
lb = sqrt(conv[0]*conv[0]+conv[1]*conv[1]+conv[2]*conv[2]);
h = length( F[ TR[num_tr].face[TR[num_tr].refine]].node[0],
F[ TR[num_tr].face[TR[num_tr].refine]].node[1]);
// fix delta : the constant 16
s = 0;
for(i=1; i<3; i++)
if (K[i][i] < K[s][s]) s = i;
if (K[s][s] < h*lb)
delta = h/(8*lb);
else
delta = 0.;
// fix db = delta . conv
for(i=0; i<3; i++)
db[i] = conv[i]*delta;
// fix the diffusion matrix K = K + bd conv
for(i=0; i<3; i++)
for(j=0; j<3; j++)
K[i][j] += db[i]*conv[j];
grad_nodal_basis(num_tr, grad);
vol = volume(num_tr);
M = func_c( middle);
off_diag = M*vol/20;
diag = 2*off_diag;
for(i=0; i<4; i++)
for(j=0; j<3; j++)
K_grad[i][j]=K[j][0]*grad[i][0]+K[j][1]*grad[i][1]+K[j][2]*grad[i][2];
for(i=0; i<4; i++){
coord = GetNode(TR[num_tr].node[i]);
f[i] = func_f( coord);
}
for(i=0; i<4; i++){
for(j=0; j<4; j++){
a_ij = 0.;
ii = TR[num_tr].node[i];
jj = TR[num_tr].node[j];
// contribution from (K grad(u), grad(phi_j))
a_ij += ( K_grad[i][0]*grad[j][0] +
K_grad[i][1]*grad[j][1] +
K_grad[i][2]*grad[j][2] ) * vol;
// contribution from (conv . grad(u), phi_j)
a_ij += ( grad[i][0]*conv[0] + grad[i][1]*conv[1] +
grad[i][2]*conv[2] ) * vol * 0.25;
// contribution from (c u, phi_j)
if (i==j) a_ij += diag;
else a_ij += off_diag;
// contribution from (c u, db . grad(phi_j))
a_ij += (grad[j][0]*db[0]+grad[j][1]*db[1]+grad[j][2]*db[2])*M*vol/4.;
// initialization of the RHS b
if (i==j) b[jj] += 2*f[i]*vol/20;
else b[jj] += f[i]*vol/20;
b[jj]+=(grad[j][0]*db[0]+grad[j][1]*db[1]+grad[j][2]*db[2])*f[i]*vol/4;
A[ ind_ij(level, ii, jj) ] += a_ij;
}
}
//============= Fix Neumann Boundary =======================================
real n[3], dbn = 0.; // dbn = delta conv . n
double S;
int ff, local;
for(s=0; s<4; s++){ // for the 4 faces
ff = TR[num_tr].face[s];
if (F[ff].tetr[1] == NEUMANN){
normal(n, num_tr, s);
S = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2])/3;
for(i=0; i<3; i++) dbn += db[i]*n[i];
for(i=0;i<3;i++)
for(j=0;j<3;j++){
a_ij = 0.;
local = (s+i+1)%4;
ii = F[ff].node[i];
jj = F[ff].node[j];
// contribution from -<conv.grad(u), delta conv . n phi_j>
a_ij -= (grad[local][0]*conv[0] + grad[local][1]*conv[1] +
grad[local][2]*conv[2])*dbn/3;
// contribution from -<c u, dbn phi_j>
if (i==j) a_ij -= M*dbn/6;
else a_ij -= M*dbn/12;
// contribution ftom -<f, dbn phi_j> (goes in the RHS)
if (i==j) b[jj] -= f[local]*dbn/6;
else b[jj] -= f[local]*dbn/12;
// contribution from <K grad u . n, phi_j>
b[jj] += func_gn( middle)*S;
A[ ind_ij(level, ii, jj) ] += a_ij;
}
} // end if face is NEUMANN
} // end for the 4 faces
}
//============================================================================
// Dot produnt of 3D vectors.
//============================================================================
template <class data>
data dot_product(data x[3], data y[3]){
return (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]);
}
//============================================================================
// Cross product (cross = x X y).
//============================================================================
template <class data>
void cross_product(data x[3], data y[3], data cross[3]){
cross[0] = x[1]*y[2] - x[2]*y[1];
cross[1] = x[2]*y[0] - x[0]*y[2];
cross[2] = x[0]*y[1] - x[1]*y[0];
}
//============================================================================
// This procedure adds convection (of the form div(b u)) to the stiffness
// matrix. It is a discontinuous Galerkin finite volume approximation. We
// go through the elements and for every element's edge we add the approp-
// riate contributions to the end points with function "add_convection".
//============================================================================
void Method::Convection_LM(real *A){
real conv[3], LM[4][4], middle[3], grad[4][3];
int i, j, k, ii, jj, end, num_tr;
real K[3][3];
for(num_tr=0; num_tr<NTR; num_tr++){
for(i=0; i<4; i++)
for(j=0; j<4; j++)
LM[i][j] = 0.;
GetMiddle(num_tr, middle);
#if CONCENTRATION == ON
grad_nodal_basis(num_tr, grad);
if (Pressure) func_K(middle, K, TR[num_tr].atribut);
else {Pressure=1; func_K(middle, K, TR[num_tr].atribut); Pressure=0;}
middle[0] = middle[1] = middle[2] = 0.;
for(i=0; i<4; i++){
middle[0] += grad[i][0]*pressure[TR[num_tr].node[i]];
middle[1] += grad[i][1]*pressure[TR[num_tr].node[i]];
middle[2] += grad[i][2]*pressure[TR[num_tr].node[i]];
}
conv[0] = K[0][0]*middle[0] + K[0][1]*middle[1] + K[0][2]*middle[2];
conv[1] = K[1][0]*middle[0] + K[1][1]*middle[1] + K[1][2]*middle[2];
conv[2] = K[2][0]*middle[0] + K[2][1]*middle[1] + K[2][2]*middle[2];
#else
func_b( middle, conv);
#endif
add_convection(0, 1, 2, 3, conv, LM, num_tr);
add_convection(1, 2, 0, 3, conv, LM, num_tr);
add_convection(2, 0, 1, 3, conv, LM, num_tr);
add_convection(0, 3, 1, 2, conv, LM, num_tr);
add_convection(1, 3, 2, 0, conv, LM, num_tr);
add_convection(2, 3, 1, 0, conv, LM, num_tr);
// Now check whether there are faces on the boundary
for(i=0; i<4; i++)
if (F[TR[num_tr].face[i]].tetr[1] == NEUMANN ||
F[TR[num_tr].face[i]].tetr[1] == ROBIN )
add_convection_face( i, conv, LM, num_tr);
for(i=0;i<4;i++)
for(j=0;j<4;j++){
ii = TR[num_tr].node[i];
jj = TR[num_tr].node[j];
end = V[level][ii+1];
for(k=V[level][ii]; k< end; k++)
if(jj==PN[level][k])
A[k] += LM[i][j];
}
}
}
//============================================================================
void Method::Convection_LM(int num_tr, real LM[][4], real conv[3]){
int i;
add_convection(0, 1, 2, 3, conv, LM, num_tr);
add_convection(1, 2, 0, 3, conv, LM, num_tr);
add_convection(2, 0, 1, 3, conv, LM, num_tr);
add_convection(0, 3, 1, 2, conv, LM, num_tr);
add_convection(1, 3, 2, 0, conv, LM, num_tr);
add_convection(2, 3, 1, 0, conv, LM, num_tr);
// Now check whether there are faces on the boundary
for(i=0; i<4; i++)
if (F[TR[num_tr].face[i]].tetr[1]== NEUMANN ||
F[TR[num_tr].face[i]].tetr[1]== ROBIN )
add_convection_face( i, conv, LM, num_tr);
}
//============================================================================
// This routine is used by the previous one. It adds the contribution asso-
// ciated with edge n1-n2 from element num_tr. n3 and n4 are the other two
// vertices.
//============================================================================
void Method::add_convection(int n1, int n2, int n3, int n4,
real conv[3], real LM[][4], int num_tr){
real v[4][3], dot = 0.;
int i;
for(i=0; i<3; i++){
// We put in v0 and v1 vectors which inner-product will give as the
// normal to the "integrating" face associated with the considered edge
// One fourth of the length is the area of that integrating face.
v[0][i] = (( Z[TR[num_tr].node[n1]].coord[i] +
Z[TR[num_tr].node[n2]].coord[i] +
Z[TR[num_tr].node[n3]].coord[i] )/3 -
Z[TR[num_tr].node[n4]].coord[i]);
v[1][i] = (( Z[TR[num_tr].node[n1]].coord[i] +
Z[TR[num_tr].node[n2]].coord[i] )*0.5 -
Z[TR[num_tr].node[n4]].coord[i]);
v[2][i] = Z[TR[num_tr].node[n1]].coord[i]-Z[TR[num_tr].node[n4]].coord[i];
}
cross_product( v[0], v[1], v[3]); // we get the normal in v3
if (dot_product(v[2], v[3]) > 0) // we want the nornal to be oriented
for(i=0; i<3; i++) // from p1 to p2 (always)
v[3][i] = -v[3][i];
dot = dot_product(conv,v[3])*0.25;
if (dot > 0){
LM[n1][n1] += dot;
LM[n2][n1] -= dot;
}
else{
LM[n1][n2] += dot;
LM[n2][n2] -= dot;
}
}
//============================================================================
// For Face i in tetrahedron num_tr add the corresponding contributions from
// the convection part (the face is on Neumann boundary).
//============================================================================
void Method::add_convection_face(int i, real conv[3], real LM[][4],
int num_tr){
real n[3], dot;
normal(n, num_tr, i);
dot = dot_product(conv, n)/3.;
if (dot > 0.){
LM[0][0] += dot; LM[1][1] += dot; LM[2][2] += dot; LM[3][3] += dot;
LM[i][i] -= dot;
}
}
//============================================================================
// LM is poiter to "poiters to 4 doubles". This is needed by the Hypre prec.
// Library (otherwise we could have given "double LM[][4]".
void Method::ComputeLocalMatrix(int num_tr, double **LM, double *b){
#if PROBLEM == 0 || PROBLEM == 1
Reaction_Diffusion_LM( num_tr, LM, b);
#elif PROBLEM == 2
Convection_Diffusion_LM(num_tr, LM, b);
#endif
}
//============================================================================
// For tetrahedron num_tr this function computes the local matrix LM[4][4]
// and the cotribution to the RHS b[4] for a reaction-diffusion problem.
//============================================================================
void Method::Reaction_Diffusion_LM(int num_tr, real **LM, double *b){
int i, j;
real *coord, middle[3], vol;
double f[5]; // used for initializing the RHS b
real K[3][3]; // diffusion matrix
real grad[4][3]; // grad[i] - gradient vector for node i = 0..3
double K_grad[4][3]; // K_grad[i] - flux vector ( K * grad[i])
real M, diag, off_diag;
for(i=0; i<4; i++)
for(j=0; j<4; j++)
LM[i][j] = 0.;
GetMiddle(num_tr, middle);
grad_nodal_basis(num_tr, grad);
#if CONCENTRATION == ON
if (Pressure == OFF){ // i.e. if we solve the concentration part - get
// the convection part from the pressure
K_grad[0][0] = K_grad[0][1] = K_grad[0][2] = 0.;
for(i=0; i<4; i++){
K_grad[0][0] += grad[i][0]*pressure[TR[num_tr].node[i]];
K_grad[0][1] += grad[i][1]*pressure[TR[num_tr].node[i]];
K_grad[0][2] += grad[i][2]*pressure[TR[num_tr].node[i]];
}
// get the Darcy velocity
Pressure = 1;
func_K(middle, K, TR[num_tr].atribut);
convection[0] = K[0][0]*K_grad[0][0] + K[0][1]*K_grad[0][1] +
K[0][2]*K_grad[0][2];
convection[1] = K[1][0]*K_grad[0][0] + K[1][1]*K_grad[0][1] +
K[1][2]*K_grad[0][2];
convection[2] = K[2][0]*K_grad[0][0] + K[2][1]*K_grad[0][1] +
K[2][2]*K_grad[0][2];
Pressure = 0;
// add convection to the local matrix LM
double CM[4][4];
for(i=0; i<4; i++) for(j=0; j<4; j++) CM[i][j] = 0.;
Convection_LM(num_tr, CM, convection);
for(i=0; i<4; i++) for(j=0; j<4; j++) LM[i][j] = CM[i][j];
}
#elif CONVECTION == ON
func_b(middle, convection);
double CM[4][4];
for(i=0; i<4; i++) for(j=0; j<4; j++) CM[i][j] = 0.;
Convection_LM(num_tr, CM, convection); // add convection
for(i=0; i<4; i++) for(j=0; j<4; j++) LM[i][j] = CM[i][j];
#endif
func_K( middle, K, TR[num_tr].atribut);
vol = volume(num_tr);
M = func_c( middle);
off_diag = M*vol*0.05; // 0.05 = 1/20
diag = 2*off_diag;
for(i=0; i<4; i++)
for(j=0; j<3; j++)
K_grad[i][j]=K[j][0]*grad[i][0]+K[j][1]*grad[i][1]+K[j][2]*grad[i][2];
for(i=0; i<4; i++){
coord = GetNode(TR[num_tr].node[i]);
f[i] = func_f( coord);
}
f[4] = 4*func_f(middle);
for(i=0;i<4;i++)
for(j=0;j<4;j++){
LM[i][j] += ( K_grad[i][0]*grad[j][0] + // add dispersion
K_grad[i][1]*grad[j][1] +
K_grad[i][2]*grad[j][2] ) * vol;
if (i==j) // add reaction
LM[i][j] += diag;
else
LM[i][j] += off_diag;
}
vol *= 0.05; // vol=vol/20 (optimization)
b[0] = (f[0]+f[4])*vol;
b[1] = (f[1]+f[4])*vol;
b[2] = (f[2]+f[4])*vol;
b[3] = (f[3]+f[4])*vol;
//============= Fix Neumann Boundary =======================================
real m_edge[3][3];
real S, ff;
for(i=0; i<4; i++){ // for the 4 faces
ff = TR[num_tr].face[i];
if (F[ff].tetr[1] == NEUMANN){
S = area(num_tr, i);
GetMiddleEdge(F[ff].node[1], F[ff].node[2], m_edge[0]);
GetMiddleEdge(F[ff].node[0], F[ff].node[2], m_edge[1]);
GetMiddleEdge(F[ff].node[0], F[ff].node[1], m_edge[2]);
for(j=0; j<4; j++)
if (TR[num_tr].node[j]==F[ff].node[0])
b[j] += (func_gn(m_edge[1])+func_gn(m_edge[2]))*S/6;
else if (TR[num_tr].node[j]==F[ff].node[1])
b[j] += (func_gn(m_edge[0])+func_gn(m_edge[2]))*S/6;
else if (TR[num_tr].node[j]==F[ff].node[2])
b[j] += (func_gn(m_edge[0])+func_gn(m_edge[1]))*S/6;
}
}
}
//============================================================================
// For tetrahedron num_tr this function computes the local matrix LM[4][4]
// and the cotribution to the RHS b[4] for a convection-diffusion problem.
//============================================================================
void Method::Convection_Diffusion_LM(int num_tr, real **LM, double *b){
int i, j, ii, jj, s;
real vol, a_ij;
real *coord, middle[3], conv[3]; // conv - convection direction
double f[4]; // used to initialize the RHS b
real K[3][3]; // diffusion matrix
real grad[4][3]; // grad[i] - gradient vector for node i = 0..3
double K_grad[4][3]; // K_grad[i] - flux vector ( K * grad[i]
double db[3],lb; // (lb -length) of convection direction
double h, delta; // db = delta . conv
double M, diag, off_diag;
GetMiddle(num_tr, middle);
func_K( middle, K, TR[num_tr].atribut);
func_b( middle, conv);
lb = sqrt(conv[0]*conv[0]+conv[1]*conv[1]+conv[2]*conv[2]);
h = length( F[ TR[num_tr].face[TR[num_tr].refine]].node[0],
F[ TR[num_tr].face[TR[num_tr].refine]].node[1]);
// fix delta : the constant 16
s = 0;
for(i=1; i<3; i++)
if (K[i][i] < K[s][s]) s = i;
if (K[s][s] < h*lb)
delta = h/(8*lb);
else
delta = 0.;
// fix db = delta . conv
for(i=0; i<3; i++)
db[i] = conv[i]*delta;
// fix the diffusion matrix K = K + bd conv
for(i=0; i<3; i++)
for(j=0; j<3; j++)
K[i][j] += db[i]*conv[j];
grad_nodal_basis(num_tr, grad);
vol = volume(num_tr);
M = func_c( middle);
off_diag = M*vol/20;
diag = 2*off_diag;
for(i=0; i<4; i++)
for(j=0; j<3; j++)
K_grad[i][j]=K[j][0]*grad[i][0]+K[j][1]*grad[i][1]+K[j][2]*grad[i][2];
for(i=0; i<4; i++){
coord = GetNode(TR[num_tr].node[i]);
f[i] = func_f( coord);
b[i] = 0.;
}
for(i=0; i<4; i++){
for(j=0; j<4; j++){
// contribution from (K grad(u), grad(phi_j))
LM[i][j] = ( K_grad[i][0]*grad[j][0] +
K_grad[i][1]*grad[j][1] +
K_grad[i][2]*grad[j][2] ) * vol;
// contribution from (conv . grad(u), phi_j)
LM[i][j] += ( grad[i][0]*conv[0] + grad[i][1]*conv[1] +
grad[i][2]*conv[2] ) * vol * 0.25;
// contribution from (c u, phi_j)
if (i==j) LM[i][j] += diag;
else LM[i][j] += off_diag;
// contribution from (c u, db . grad(phi_j))
LM[i][j]+=(grad[j][0]*db[0]+grad[j][1]*db[1]+grad[j][2]*db[2])*M*vol/4.;
// initialization of the RHS b
if (i==j) b[j] += 2*f[i]*vol/20;
else b[j] += f[i]*vol/20;
b[j]+=(grad[j][0]*db[0]+grad[j][1]*db[1]+grad[j][2]*db[2])*f[i]*vol/4;
}
}
//============= Fix Neumann Boundary =======================================
real n[3], dbn = 0.; // dbn = delta conv . n
double S;
int ff, local, k;
for(s=0; s<4; s++){ // for the 4 faces
ff = TR[num_tr].face[s];
if (F[ff].tetr[1] == NEUMANN){
normal(n, num_tr, s);
S = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2])/3;
for(i=0; i<3; i++) dbn += db[i]*n[i];
for(i=0;i<3;i++)
for(j=0;j<3;j++){
local = (s+i+1)%4;
for(k=0; k<4; k++){
if (TR[num_tr].node[k] == F[ff].node[i]) ii = k;
if (TR[num_tr].node[k] == F[ff].node[j]) jj = k;
}
// contribution from -<conv.grad(u), delta conv . n phi_j>
LM[ii][jj] -= (grad[local][0]*conv[0] + grad[local][1]*conv[1] +
grad[local][2]*conv[2])*dbn/3;
// contribution from -<c u, dbn phi_j>
if (ii==jj) LM[ii][jj] -= M*dbn/6;
else LM[ii][jj] -= M*dbn/12;
// contribution ftom -<f, dbn phi_j> (goes in the RHS)
if (ii==jj) b[jj] -= f[local]*dbn/6;
else b[jj] -= f[local]*dbn/12;
// contribution from <K grad u . n, phi_j>
b[jj] += func_gn( middle)*S;
}
} // end if face is NEUMANN
} // end for the 4 faces
}
//============================================================================