diff --git a/Code/INTERNODES_large/SPAI.m b/Code/INTERNODES_large/SPAI.m new file mode 100644 index 0000000..2625452 --- /dev/null +++ b/Code/INTERNODES_large/SPAI.m @@ -0,0 +1,94 @@ +function[M]=SPAI(A, S, varargin) + +% SPAI: Implements the SPAI algorithm +% A: Matrix +% S: Initial sparsity pattern + +n=size(A,1); +e=speye(n); + +% Define default parameters +% Maximum fill in per column of M per iteration +Param.s=5; +% Maximum number of iterations +Param.maxit=20; +% Tolerance on the norm of the residual +Param.tol=0.2; + +if nargin > 2 +[Param]=updateFields(Param, varargin{1}); +end + +% Retrieve parameters +s=Param.s; +tol=Param.tol; +maxit=Param.maxit; + +% Initialization +V=zeros(maxit*s*n,1); +In=zeros(maxit*s*n,2); +count=0; + + +for k=1:n + i=S{k}; + J=false(n,1); + J(i)=true; + % Set of row indices + I=any(A(:,J), 2); + As=A(I,J); + % Economy-size QR decomposition + [Q, R]=qr(As, 0); + mk_hat=R\(Q'*e(I,k)); + % Residual + r=A(:,J)*mk_hat-e(:,k); + norm_r=norm(r); + niter=0; + + while norm_r>tol && niterd + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(abs(detJK), 1, 1, ne).*F.*weights; + X=reshape(X, d*nq, ne); + + B=Q*X; + + % Assemblage + B=accumarray(I(:), B(:), [nb_dof 1]); + end + + function[B]=computeNeumann(Mesh, Data) + + % computeNeumann: Implements Neumann boundary conditions, returns a vector + % having for length the number of degrees of freedom. + % This vector is to be added to the RHS. + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of surface tractions + + % Retrieve mesh parameters + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Number of local degrees of freedom + d=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Boundary equation numbers + Eq_BC=Mesh.Eq_BC; + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + % Get quadrature + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nq=length(weights); + + % Initialization + B=zeros(nb_dof,1); + s=order+1; + Id=eye(d); + % Initialization + Q=zeros(s*d, nq*d); + + % Loop over the Gauss points + for i = 1:nq + Phi=phi_boundary(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + + % Neumann boundary conditions + N_BC=Data.N_BC; + % Number of boundaries where surface tractions are imposed + nb_bc=length(N_BC); + + for m=1:nb_bc + % Retrieve data + data=N_BC{m}; + edges=data.edges; + + for flag=edges + % Surface traction function + f=data.function; + % Retriveve edges making up the boundary + Neum_edges=Mesh.BC_nodes(Mesh.BC_tag==flag,:); + % Equation numbers + Eq=Eq_BC(Mesh.BC_tag==flag,:); + % Number of Neumann boundary edges + ne=size(Neum_edges, 1); + + % Initialization + X=zeros(ne, nq, 2); + + C=Neum_edges'; + G=C(:); + I=Eq'; + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + % Interpolated coordinates + P=a(:)+(b(:)-a(:))*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(norm_bk, 1, 1, ne).*F.*weights'; + X=reshape(X, d*nq, ne); + + Bl=Q*X; + + % Assemblage + B=B+accumarray(I(:), Bl(:), [nb_dof 1]); + + end + end + end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/computeStiffness.m b/Code/INTERNODES_large/computeStiffness.m new file mode 100644 index 0000000..6091151 --- /dev/null +++ b/Code/INTERNODES_large/computeStiffness.m @@ -0,0 +1,138 @@ +function[K]=computeStiffness(Mesh, Data) + +% computeStiffness: Function which assembles the global stiffness matrix +% Assumes all elements are of the same type +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OUTPUT: +% K: Global stiffness matrix + +% Mesh parameters +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Total number of degrees of freedom +nb_dof=Mesh.nb_dof; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Finite element order +order=Mesh.order; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Total number of elements +ne=Mesh.nb_elem; +% Matrix linking an element to the degrees of freedom associated to it. +Eq=Mesh.Eq; + +% Data parameters +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; + + +% Retrieve functions +[Functions]=getFunctions(order); +% Jacobian matrix +J_phi=Functions.J_phi; +% Define permutation matrix +Sdd=zeros(d*d); +Id=eye(d); +for k=1:d + Sdd=Sdd+kron(Id(:,k)', kron(Id, Id(:,k))); +end + +% Retrieve quadrature nodes and weights +switch order + case 1 + [points, weights] = getQuadrature(1, 'bulk'); + case 2 + [points, weights] = getQuadrature(2, 'bulk'); + case 3 + [points, weights] = getQuadrature(3, 'bulk'); + otherwise + error('Not yet implemented'); +end + +% Initialization +s=nne*d; +nq=length(weights); +Q=zeros(s^2, nq*d^4); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:)), Id); + Q(:, (i-1)*d^4+1:i*d^4)=kron(L, L); +end + +T=Eq'; +G=T(:); + +I=repmat(Eq, [1 nne*d])'; +J=repmat(G', [nne*d 1]); + +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); + +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +V=[(vecnorm(c-a,2,2).^2) -sum((c-a).*(b-a),2) -sum((c-a).*(b-a),2) (vecnorm(b-a,2,2).^2)]; +W=1./(detJK').^2.*(V'); + +V=reshape(W,d,d*ne); + +JK_inv=zeros(d, d*ne); +JK_inv(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(b(:,2)-a(:,2))]'; +JK_inv(:,2:d:d*ne)=1./detJK'.*[-(c(:,1)-a(:,1)) b(:,1)-a(:,1)]'; + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, d^2, ne); + +[S1]=product(V , repmat(Id, 1, ne), d, ne); +[S2]=product(JK_invT , JK_inv, d, ne); +S2=Sdd*S2; + +A1=reshape(S1, d^4, ne); +A2=reshape(S2, d^4, ne); +A3=kr(vec_JK_invT, vec_JK_invT); + + +Lambda1=(abs(detJK).*f1(X).*weights)'; +Lambda2=(abs(detJK).*f2(X).*weights)'; + +X=kr(Lambda1, A1+A2)+kr(Lambda2, A3); +K=Q*X; + +% Assemblage +K=sparse(I(:), J(:), K(:), nb_dof, nb_dof); + + function[M]=product(A,B,d,ne) + + % Blockwise Kronecker product of two matrices + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_large/constructInterpolants.m b/Code/INTERNODES_large/constructInterpolants.m new file mode 100644 index 0000000..f252815 --- /dev/null +++ b/Code/INTERNODES_large/constructInterpolants.m @@ -0,0 +1,55 @@ +function[Data]=constructInterpolants(Data, radiuses1, radiuses2, coord1, coord2) + +% constructInterpolants: Assembles the interpolation matrices and +% pre-computes sparse LU factorizations of Phi11 and Phi22 +% INPUT: +% Data: Structure containing all the data parameters +% radiusesk: Radiuses of support of the radial basis functions (k=1,2) +% coordk: Coordinates of the nodes on the potential contact interface +% of body k (k=1,2) +% OUTPUT: +% Data: Updated data structure with RBF interpolants + +[Phi11] = assembleInterpMat(coord1, coord1, radiuses1); +[Phi21] = assembleInterpMat(coord1, coord2, radiuses1); +[Phi22] = assembleInterpMat(coord2, coord2, radiuses2); +[Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + +% Store sparse factorization of matrices +[L1, U1, orl1, orc1]=lu(Phi11, 'vector'); +[L2, U2, orl2, orc2]=lu(Phi22, 'vector'); + +n1=length(radiuses1); +n2=length(radiuses2); + +y1=ones(n1,1); +y2=ones(n2,1); + +s1=zeros(n2,1); +s2=zeros(n1,1); + +s1(orc2)=U2\(L2\y2(orl2)); +s2(orc1)=U1\(L1\y1(orl1)); + +s1=Phi12*s1; +s2=Phi21*s2; + +% For future purposes +Data.L1=L1; +Data.U1=U1; +Data.L2=L2; +Data.U2=U2; +Data.Phi11=Phi11; +Data.Phi21=Phi21; +Data.Phi12=Phi12; +Data.Phi22=Phi22; + +Data.orl1=orl1; +Data.orc1=orc1; + +Data.orl2=orl2; +Data.orc2=orc2; + +Data.s1=s1; +Data.s2=s2; +end \ No newline at end of file diff --git a/Code/INTERNODES_large/detectGaps.m b/Code/INTERNODES_large/detectGaps.m new file mode 100644 index 0000000..190a7a6 --- /dev/null +++ b/Code/INTERNODES_large/detectGaps.m @@ -0,0 +1,67 @@ +function[add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution) + +% detectGaps: computes the gap between nodes of opposing interfaces and +% detects interpenetration +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% add_nodes1: Interface nodes of body 1 to be added to the active set +% add_nodes2: Interface nodes of body 2 to be added to the active set + +% Initial nodes +inodes1=Data.body{1}.initial_nodes; +inodes2=Data.body{2}.initial_nodes; + +nodes1=inodes1; +nodes2=inodes2; + +% Retrieve coordinates of nodes making up the interfaces +coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); +coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + +[radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); +[radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + +i1=nnzR12==0; +i2=nnzR21==0; + +% Check for isolated nodes +while any(i1) || any(i2) + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; +end + +[Data]=constructInterpolants(Data, radiuses1, radiuses2, coord1, coord2); + +Diff1=multiplyR12(Data, coord2)-coord1; +Diff2=multiplyR21(Data, coord1)-coord2; + +i1=ismember(inodes1, nodes1); +i2=ismember(inodes2, nodes2); + +% computes the scalar products +scalar1_g=sum(Diff1.*Data.body{1}.normal(i1,:),2); +scalar2_g=sum(Diff2.*Data.body{2}.normal(i2,:),2); + +% Thresholds are a fraction of the mesh size and could be specific to each +% interface (consider using the mesh sizes of body 1 and 2) +threshold1=-Data.Contact.tol*Data.Contact.h; +threshold2=-Data.Contact.tol*Data.Contact.h; + +% Detect interpenetration +add_nodes1=nodes1(scalar1_ggamma +% Used to ensure evaluation points are well within the +% support of radial basis functions (far enough from the boundary) +Gamma=Data.Contact.Gamma; +% Tolerance for contact detection +% If the tolerance is unknown, set it to some small value. +d0=Data.Contact.d0; +% Number of closest neighbors on current interface +c=Data.Contact.c; + +% Number of off-diagonal nonzeros per row of PhiMM +nnzRMM=zeros(M,1); +% Number of off-diagonal nonzeros per column of PhiMM +nnzCMM=zeros(M,1); +% Number of nonzeros per row of PhiNM +nnzRNM=zeros(N,1); +% Number of nonzeros per column of PhiNM +nnzCNM=zeros(M,1); + +maxS=inf; +f=0; +niter=0; +maxiter=10; + +% Version 2 +while maxS>f && niter= gamma*radius. + % If the point is too close (with respect to the radius), diagonal + % dominance will be lost very quickly. + if radius>rMM(1)/gamma + radius=rMM(1)/gamma; + end + + s1=distMMf + % Increase gamma (sequence converges to 1) + gamma=0.5*(1+gamma); + % Reinitialize counters + nnzRMM=zeros(M,1); + nnzCMM=zeros(M,1); + nnzRNM=zeros(N,1); + nnzCNM=zeros(M,1); + niter=niter+1; + end +end + +if niter==maxiter && maxS>f + warning('The maximum number of rounds for radius computations was reached. Try decreasing d0') +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/gmres_k.m b/Code/INTERNODES_large/gmres_k.m new file mode 100644 index 0000000..a57c826 --- /dev/null +++ b/Code/INTERNODES_large/gmres_k.m @@ -0,0 +1,123 @@ +function[x, residual, res_in] = gmres_k(Mesh, Data, linearOp, b, x0, level) + +% gmres_k: Restarted GMRES algorithm +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% linearOp: Linear Operator implementing matrix-vector multiplication +% and resolution of linear systems with the preconditioner +% b: Right-hand side +% x0: Starting vector +% level: Iteration level +% OUTPUT: +% x: Approximate solution +% residual: Vector containing the norms of the residuals on the +% outer iterations +% res_inner: Cell array containing the norms of the residuals on the +% inner iterations + +% Maximum total number of iterations +maxiter=Data.Solver.maxiter; +% Number of iterations before restart +riter=Data.Solver.riter; +% Reorthogonalization tolerance +reorth_tol=Data.Solver.reorth_tol; +% Tolerance on the norm of the residual +tol=Data.Solver.tol; + +if strcmp(level, 'level 2') + tol=1e-11; + riter=100; +end + +% Initialization +x=x0; +res_error=1; +budget=maxiter; +residual=[]; + +while res_error>tol && budget>0 + [x, res1, ~, iter] = gmresPrec(b, x, min([riter, budget]), reorth_tol, tol); + residual=[residual; res1]; + res_error=residual(end); + budget=budget-iter; +end + +if res_error>tol && budget==0 + disp(['The restarted GMRES method on ' level ' did not converge after ' num2str(maxiter-budget) ' iterations']) +end + + + + function [x, varargout] = gmresPrec(b, x0, riter, reorth_tol, tol) + + % gmresPrec: Right preconditioned GMRES + % INPUT: + % b: Right-hand side + % x0: Starting vector + % riter: Maximum number of iterations before restart + % reorth_tol: Reorthogonalization tolerance + % tol: Tolerance on the norm of the residual + % OUTPUT: + % x: Approximate solution + % res: Vector containing the norms of the residuals on the + % outer iterations + % res_in: Cell array containing the norms of the residuals on the + % inner iterations + % U: Orthonormal Krylov basis + % H: Upper Hessenberg matrix + % k: Number of iterations + + r0=b-linearOp(Mesh, Data, x0, 'matvec'); + U=zeros(length(r0), riter); + H=zeros(riter+1, riter); + res=zeros(riter+1,1); + res(1)=norm(r0); + U(:,1)=r0/res(1); + + + for k=1:riter + [w, res_in{k}]=linearOp(Mesh, Data, U(:,k), 'solve'); + h=U(:,1:k)'*w; + u_tilde=w-U(:,1:k)*h; + + if norm(u_tilde)nb_dof_local + G=reshape(G, nb_nodes, nb_dof_local)'; + G=G(:); + else + G=repmat(G, nb_nodes, 1); + end + U(dofs(:))=G; + + end + + % Update solution + Solution.U=U; + % Reshape to an array of the same size as the coordinate matrix + Solution.U_sort=reshape(U, nb_dof_local, [])'; + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_large/internodes1.m b/Code/INTERNODES_large/internodes1.m new file mode 100644 index 0000000..edd86d4 --- /dev/null +++ b/Code/INTERNODES_large/internodes1.m @@ -0,0 +1,65 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to both bodies. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact2_p1_0_01.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Dirichlet', 'Edges', 10, 'Function', @(x) [0; -0.1]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'radius', 0.2); +[Data]=setNumericalParameters(Mesh, Data, 'Solver', 'name', 'GMRES', 'riter', 100); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_large/internodes2.m b/Code/INTERNODES_large/internodes2.m new file mode 100644 index 0000000..716d741 --- /dev/null +++ b/Code/INTERNODES_large/internodes2.m @@ -0,0 +1,66 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to the lower body and Neumann boundary conditions applied to the +% upper body. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact2_p1_0_01.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with singular stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Neumann', 'Edges', 10, 'Function', @(x) [0; -1e9]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); + +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'maxiter', 30, 'radius', inf, 'tol', 0.9); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_large/kr.m b/Code/INTERNODES_large/kr.m new file mode 100644 index 0000000..874aea5 --- /dev/null +++ b/Code/INTERNODES_large/kr.m @@ -0,0 +1,31 @@ +function X = kr(U,varargin) +%KR Khatri-Rao product. +% kr(A,B) returns the Khatri-Rao product of two matrices A and B, of +% dimensions I-by-K and J-by-K respectively. The result is an I*J-by-K +% matrix formed by the matching columnwise Kronecker products, i.e. +% the k-th column of the Khatri-Rao product is defined as +% kron(A(:,k),B(:,k)). +% +% kr(A,B,C,...) and kr({A B C ...}) compute a string of Khatri-Rao +% products A o B o C o ..., where o denotes the Khatri-Rao product. +% +% See also kron. + +% Version: 21/10/10 +% Authors: Laurent Sorber (Laurent.Sorber@cs.kuleuven.be) + +if ~iscell(U), U = [U varargin]; end +K = size(U{1},2); +if any(cellfun('size',U,2)-K) + error('kr:ColumnMismatch', ... + 'Input matrices must have the same number of columns.'); +end +J = size(U{end},1); +X = reshape(U{end},[J 1 K]); +for n = length(U)-1:-1:1 + I = size(U{n},1); + A = reshape(U{n},[1 I K]); + X = reshape(bsxfun(@times,A,X),[I*J 1 K]); + J = I*J; +end +X = reshape(X,[size(X,1) K]); diff --git a/Code/INTERNODES_large/linearOp1.m b/Code/INTERNODES_large/linearOp1.m new file mode 100644 index 0000000..513ab8d --- /dev/null +++ b/Code/INTERNODES_large/linearOp1.m @@ -0,0 +1,164 @@ +function[y, varargout]=linearOp1(Mesh, Data, x, type) + +% linearOp1: Wrap around function for operations related to the application +% of linear operators in the outer iterations +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% type: Type of operation to perform +% OUTPUT: +% y: Result of the operation +% res: Optional residual for the solution of a linear system + +switch type + case 'solve' + [y, res, Data]=solvePrec(Mesh, Data, x); + y=multiplyMatVec(Mesh, Data, y); + varargout{1}=res; + varargout{2}=Data; + + case 'solvePrec' + [y, res, Data]=solvePrec(Mesh, Data, x); + varargout{1}=res; + varargout{2}=Data; + + case 'matvec' + y=multiplyMatVec(Mesh, Data, x); +end + + + function[y]=multiplyMatVec(Mesh, Data, x) + + % multiplyMatVec: Implements the mutiplication between the + % INTERNODES matrix A and a vector x and stores the result in y + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % x: Vector + % OUTPUT: + % y: Result of the multiplication of A with x + + % Retrieve data parameters + % Number of free degrees of freedom + nf=Data.nf; + % Number of constraints + nc=Data.nc; + + % Initialization + y=zeros(nf+nc,1); + + y(1:nf)=Data.K*x(1:nf)+multiplyB(Mesh, Data, x(nf+1:nf+nc), 'vec'); + y(nf+1:nf+nc)=multiplyB_tilde(Mesh, Data, x(1:nf), 'vec'); + end + + function[y, res, Data]=solvePrec(Mesh, Data, x) + + % solvePrec: Implements the resolution of a linear system with the + % Liu preconditioner and a vector x and stores the result in y + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % x: Vector + % OUTPUT: + % y: Solution of the linear system + % res: Vector containing the norms of the residuals on the inner iterations + + + % Retrive mesh parameters + d=Mesh.nb_dof_local; + % Parameter k + k=2; + + % Retrieve data parameters + % Cholesky factor of the reordered perturbed stiffness matrix + Lr=Data.Lr; + L22=Data.L22; +% L=Data.L; + % Ordering computed with Nested Dissection +% r=Data.p; + % Number of free degrees of freedom + nf=Data.nf; + % Number of constraints + nc=Data.nc; + % Small matrix G +% G=Data.G; +% V=Data.V; + % Perturbed reordering of Nested Dissection + or=Data.or; + % Size of block 1 + b1=Data.b1; + + % Initialization + y=zeros(nf+nc,1); + w1=zeros(nf,1); + + p1=x(1:nf); + p2=x(nf+1:nf+nc); + + w2=reshape(p2, d, []); + w2=-Data.Contact.alpha*(w2/Data.body{1}.interface_mass); + + p1=p1-k*multiplyB(Mesh, Data, w2, 'mat'); + + % Method without L22 +% p1=p1(or); +% v1=p1(1:b1); +% v2=p1(b1+1:end); +% v1=L11\v1; +% v2=-L21*v1+v2; +% v2=V\v2; +% v1=v1-L21'*v2; +% v1=L11'\v1; +% w1(or)=[v1; v2]; + + % Method with L22 +% p2=Lr\p1(or); +% p2(b1+1:end)=L22'*(V\(L22*p2(b1+1:end))); +% w1(or)=Lr'\p2; + +% % Method for small problems + Lv=Data.Lv; + Uv=Data.Uv; + + p2=Lr\p1(or); + p3=p2(b1+1:end); + p3=L22*p3; + x0=zeros(length(p3),1); + [p3, res] = gmres_k(Mesh, Data, @linearOp2, p3, x0, 'level 2'); +% p3(Data.orc)=Uv\(Lv\p3(Data.orl)); +% res=0; + p2(b1+1:end)=L22'*p3; + w1(or)=Lr'\p2; + +% % % Method for small problems +% L0=Data.L0; +% U0=Data.U0; +% Nk=Data.Nk; +% +% p2(Data.orl)=U0\(L0\p1(Data.orc)); +% w1=Nk*p2; +% res=0; + + % Test case +% L22=Data.L22; +% p2=Lr\p1(or); +% p3=L22*p2(b1+1:end); +% [p3, res]=gmresRestart(Data.V, p3, zeros(length(p3),1), Data, Data.V_hat); +% p2(b1+1:end)=L22'*p3; +% w1(or)=Lr'\p2; + +% p1(r)=L'\(L\p1(r)); +% p2=multiplyB_tilde(Mesh, Data, p1, 'vec'); +% p2=Data.S\p2; +% p2=multiplyB(Mesh, Data, p2, 'vec'); +% p2(r)=L'\(L\p2(r)); +% w1=p1-p2; +% res=0; + +% res=0; + y(1:nf)=w1; + y(nf+1:nf+nc)=w2; + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_large/linearOp2.m b/Code/INTERNODES_large/linearOp2.m new file mode 100644 index 0000000..3e29d89 --- /dev/null +++ b/Code/INTERNODES_large/linearOp2.m @@ -0,0 +1,68 @@ +function[y, varargout]=linearOp2(Mesh, Data, x, type) + +% linearOp2: Wrap around function for operations related to the application +% of linear operators in the inner iterations +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% type: Type of operation to perform +% OUTPUT: +% y: Result of the operation +% res: Optional residual for the solution of a linear system + +switch type + case 'solve' + [y, res]=solvePrec(Data, x); + y=multiplyMatVec(Mesh, Data, y); + varargout{1}=res; + + case 'solvePrec' + [y, res]=solvePrec(Data, x); + varargout{1}=res; + + case 'matvec' + y=multiplyMatVec(Mesh, Data, x); +end + + function[y]=multiplyMatVec(Mesh, Data, x) + + % multiplyMatVec: Implements the matrix-vector multiplication for + % the inner iterations + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % x: Vector + % OUTPUT: + % y: Result of the matrix-vector multiplication + + L22=Data.L22; + y=multiplyY2T(Mesh, Data, x); + y=multiplyY1(Mesh, Data, y); + y=L22*(L22'*x)-Data.Contact.alpha*y; + end + + function[y, res]=solvePrec(Data, x) + + % solvePrec: Implements the resolution of a linear system with the + % preconditioner and a vector x for the inner iterations + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % x: Vector + % OUTPUT: + % y: Solution of the linear system + % res: Norm of the residuals for the inner iterations + + + % Retrieve data parameters + Lv=Data.Lv; + Uv=Data.Uv; + y=zeros(Data.b2,1); + + y(Data.orc)=Uv\(Lv\x(Data.orl)); + res=0; + end + + + +end \ No newline at end of file diff --git a/Code/INTERNODES_large/mapGlobal2Local.m b/Code/INTERNODES_large/mapGlobal2Local.m new file mode 100644 index 0000000..6b14a3a --- /dev/null +++ b/Code/INTERNODES_large/mapGlobal2Local.m @@ -0,0 +1,13 @@ +function[Data]=mapGlobal2Local(Data) + +% mapGlobal2Local: Function which maps the global degrees of freedom to the +% local ones for the INTERNODES matrix +% INPUT: +% Data: Structure containing all the data parameters +% OUTPUT: +% Data: Updated structure with the local degrees of freedom + +Nf=Data.Nf; +[~, Data.body{1}.indx_local, ~]=intersect(Nf, Data.body{1}.interface_dof); +[~, Data.body{2}.indx_local, ~]=intersect(Nf, Data.body{2}.interface_dof); +end \ No newline at end of file diff --git a/Code/INTERNODES_large/multiplyB.m b/Code/INTERNODES_large/multiplyB.m new file mode 100644 index 0000000..d1e08b7 --- /dev/null +++ b/Code/INTERNODES_large/multiplyB.m @@ -0,0 +1,25 @@ +function[y]=multiplyB(Mesh, Data, x, in) + +% multiplyB: Implements the mutiplication between B and a vector x and +% stores the result in y +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector or matrix +% in: Parameter specifiying whether the input is a vector or a matrix +% OUTPUT: +% y: Result of the multiplication of B with x + +if strcmp(in, 'vec') +x=reshape(x, Mesh.nb_dof_local, []); +end + +y=zeros(Data.nf,1); + +y1=-x*Data.body{1}.interface_mass; +y2=multiplyR21(Data, x'); +y2=y2'*Data.body{2}.interface_mass; + +y(Data.body{1}.indx_local)=y1(:); +y(Data.body{2}.indx_local)=y2(:); +end \ No newline at end of file diff --git a/Code/INTERNODES_large/multiplyB_tilde.m b/Code/INTERNODES_large/multiplyB_tilde.m new file mode 100644 index 0000000..30b5678 --- /dev/null +++ b/Code/INTERNODES_large/multiplyB_tilde.m @@ -0,0 +1,27 @@ +function[y]=multiplyB_tilde(Mesh, Data, x, out) + +% multiplyB_tilde: Implements the mutiplication between B_tilde and a +% vector x and stores the result in y +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% out: Parameter specifying whether the output should be a vector or +% a matrix +% OUTPUT: +% y: Result of the multiplication of B_tilde with x + +% Retrive mesh parameters +% Number of local degrees of freedom +d=Mesh.nb_dof_local; + +w1=reshape(x(Data.body{1}.indx_local,:), d, []); +w2=reshape(x(Data.body{2}.indx_local,:), d, []); +w2=multiplyR12(Data, w2')'; + +y=w1-w2; + +if strcmp(out, 'vec') + y=y(:); +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/multiplyR12.m b/Code/INTERNODES_large/multiplyR12.m new file mode 100644 index 0000000..e4d873c --- /dev/null +++ b/Code/INTERNODES_large/multiplyR12.m @@ -0,0 +1,14 @@ +function[x]=multiplyR12(Data, x) + +% multiplyR12: Implements the multiplication between the interpolation matrix +% R12 and a vector x. The result is overwritten in x +% INPUT: +% Data: Structure containing all the data parameters +% x: Input matrix or vector +% OUTPUT: +% x: Result of R12*x + +x(Data.orc2,:)=Data.U2\(Data.L2\x(Data.orl2,:)); +x=Data.Phi12*x; +x=x./Data.s1; +end \ No newline at end of file diff --git a/Code/INTERNODES_large/multiplyR21.m b/Code/INTERNODES_large/multiplyR21.m new file mode 100644 index 0000000..19c8d88 --- /dev/null +++ b/Code/INTERNODES_large/multiplyR21.m @@ -0,0 +1,14 @@ +function[x]=multiplyR21(Data, x) + +% multiplyR21: Implements the multiplication between the interpolation matrix +% R21 and a vector x. The result is overwritten in x +% INPUT: +% Data: Structure containing all the data parameters +% x: Input matrix or vector +% OUTPUT: +% x: Result of R21*x + +x(Data.orc1,:)=Data.U1\(Data.L1\x(Data.orl1,:)); +x=Data.Phi21*x; +x=x./Data.s2; +end \ No newline at end of file diff --git a/Code/INTERNODES_large/multiplyY1.m b/Code/INTERNODES_large/multiplyY1.m new file mode 100644 index 0000000..208b790 --- /dev/null +++ b/Code/INTERNODES_large/multiplyY1.m @@ -0,0 +1,22 @@ +function[y]=multiplyY1(Mesh, Data, x) + +% multiplyY1: Implements the multiplication between the matrix Y1 and a +% vector x and stores the result in y +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% OUTPUT: +% y: Result of the multiplication of Y1 with x + +index=Data.index; +y=zeros(Data.b2,1); + +w=reshape(x, Mesh.nb_dof_local, []); + +w=w/Data.body{1}.interface_mass; +w=multiplyR21(Data, w')'; +w=w*Data.body{2}.interface_mass; + +y(index)=[x; -w(:)]; +end \ No newline at end of file diff --git a/Code/INTERNODES_large/multiplyY2T.m b/Code/INTERNODES_large/multiplyY2T.m new file mode 100644 index 0000000..84a6db5 --- /dev/null +++ b/Code/INTERNODES_large/multiplyY2T.m @@ -0,0 +1,21 @@ +function[y]=multiplyY2T(Mesh, Data, x) + +% multiplyY1: Implements the multiplication between the transpose of Y2 and +% a vector x and stores the result in y +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% OUTPUT: +% y: Result of the multiplication of the transpose of Y2 with x + +x=x(Data.index); + +x1=x(1:Data.l1); +x2=x(Data.l1+1:end); + +x2=reshape(x2, Mesh.nb_dof_local, []); +x2=multiplyR12(Data, x2')'; + +y=x1-x2(:); +end \ No newline at end of file diff --git a/Code/INTERNODES_large/plotSolution.m b/Code/INTERNODES_large/plotSolution.m new file mode 100644 index 0000000..e30d799 --- /dev/null +++ b/Code/INTERNODES_large/plotSolution.m @@ -0,0 +1,811 @@ +function[]=plotSolution(Mesh, Data, Solution, varargin) + +% plotSolution: Function which plots the computed quantities +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% Solution: Structure containing the solution computed +% OPTIONAL INPUT +% snapshot: selected time-step at which the solution should be viewed +% 1 -> none (default) +% any time step in the range defined +% view_video: view the video of the solution +% 1 -> yes +% 0 -> no (default) +% save_video: choose whether to save the video or not +% 1 -> yes +% 0 -> no (default) +% plot_arg: choose which solution to visualize +% dynamic_solid or static_solid module +% disp (default) +% stress +% strain +% transient_heat module +% temp (default) +% flux +% plot_type: decide which type of metric should be used for visualization +% displacement metric +% standard -> structure with amplified displacements (default) +% norm +% absx +% absy +% temperature metric +% standard (default) +% stress metric +% von_mises (default) +% sigma_xx +% sigma_yy +% sigma_xy +% sigma_xx_abs +% sigma_yy_abs +% sigma_xy_abs +% frobenius +% strain metric +% epsilon_xx (default) +% epsilon_yy +% epsilon_xy +% epsilon_xx_abs +% epsilon_yy_abs +% epsilon_xy_abs +% frobenius +% flux metric +% standard (default) + + +%% Initialize and ckeck Parameters + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +% Specify number of sub-intervals in time +if contains(Data.Model.name, 'static') + static=true; + N=0; +else + static=false; + N=Data.Discretization.N; +end + +switch Data.Model.name + case {'static_solid', 'dynamic_solid'} + plot_argv={'disp', 'stress', 'strain'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard', 'norm', 'absx', 'absy'}; + plot_typev{2}={'von_mises', 'sigma_xx', 'sigma_yy', 'sigma_xy', 'sigma_xx_abs', 'sigma_yy_abs', 'sigma_xy_abs', 'frobenius'}; + plot_typev{3}={'epsilon_xx', 'epsilon_yy', 'epsilon_xy', 'epsilon_xx_abs', 'epsilon_yy_abs', 'epsilon_xy_abs', 'frobenius'}; + + case 'transient_heat' + plot_argv={'temp', 'flux'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard'}; + plot_typev{2}={'standard'}; + +end + +% Set all default parameters +% Default visualization parameters +Param.visua_param.snapshot=1; +Param.visua_param.view_video=false; +Param.visua_param.write_video=false; +% Default plot argument +Param.plot_param.arg=plot_argv(1); +% Default plot type +Param.plot_param.type=plot_typev{1}(1); + +plot_arg={}; +plot_type={}; +nb_plot=0; + +for k=1:length(labels_in) + arg=lower(labels_in{k}); + val=values_in{k}; + + switch arg + case 'snapshot' + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + elseif val < 0 || val > Data.Discretization.N+1 + error(['Time snapshot invalid: it must be an integer between ' num2str(0) ' and ' num2str(Data.Discretization.N+1)]) + else + Param.visua_param.snapshot=val; + end + + case {'view_video', 'write_video'} + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + else + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Param.visua_param.view_video=logical(val); + end + + elseif isa(val, 'logical') + Param.visua_param.view_video=val; + else + error([arg ' parameter must be a boolean']) + end + end + + case plot_argv + + % Check if desired quantities have been computed + switch arg + case 'stress' + if ~isfield(Solution, 'sigma') + error('Stresses have not been computed') + end + + case 'strain' + if ~isfield(Solution, 'epsilon') + error('Strains have not been computed') + end + + case 'flux' + if ~isfield(Solution, 'Q') + error('Fluxes have not been computed') + end + end + + plot_arg{nb_plot+1}=arg; + index=strcmp(arg, plot_argv); + + if any(ismember(plot_typev{index}, val)) + plot_type{nb_plot+1}=val; + else + error('Plot type invalid: see available plot types') + end + + nb_plot=nb_plot+1; + otherwise + error('Unrecognized argument') + end + +end + +if nb_plot>0 + Param.plot_param.arg=plot_arg; + Param.plot_param.type=plot_type; +end + +%% Plotting interface + +plot_arg=Param.plot_param.arg; +plot_type=Param.plot_param.type; +n=length(plot_arg); +Plot=cell(n,1); +Axis=cell(n,1); +Colorbar=cell(n,1); +Dynamic=cell(n,1); + +fig=figure; +% fig.Units='normalized'; +% fig.Position=[0 0 1 1]; + +% fig.Units='normalized'; +% fig.Position=[0 1 1/2 1/2]; + +for k=1:n + switch plot_arg{k} + case {'disp', 'temp'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case {'strain', 'stress'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, plot_arg{k}, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case 'flux' + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=quiver(axis, PlotParam{1,2}, PlotParam{2,2}); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + end + + Plot{k}=p; + Axis{k}=axis; + Colorbar{k}=c; + Dynamic{k}=DynamicParam; +end + +if Param.visua_param.view_video % View the solution over the entire simulation + + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, 1, N); + end + + frames(1)=getframe(fig); + for step = 2:N+1 + pause(0.05) + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + refreshdata(Axis{k}); + end + frames(step)=getframe(fig); + end + +else % View solution at a particular time snapshot + step=Param.visua_param.snapshot; + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + end +end + + +if Param.visua_param.write_video +% vid_duration=25; % seconds + vid_duration=10; % seconds + video=VideoWriter('Output.mp4', 'MPEG-4'); + video.FrameRate=(N+1)/vid_duration; + open(video); + writeVideo(video,frames); + close(video); +end + +%% Parameters for patch plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, type) + + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Total number of nodes + nb_nodes=Mesh.nb_nodes; + % Frame surrounding the structure + frame=Mesh.frame; + % Largest dimension + L_max=Mesh.L_max; + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + + % Retrieve solution + U=Solution.U; + + + switch Data.Model.name + + case {'static_solid', 'dynamic_solid'} + + % Initilization + Vertices=zeros(nb_nodes,2,N+1); + CData=cell(N+1,1); + + + switch type + case 'standard' + + if strcmp(Data.PostProcessing.amplification, 'auto') + % Maximum displacement + disp_max=max(max(abs(U))); + % Amplification factor + fact_ampl=L_max/(2*disp_max); + else + fact_ampl=Data.PostProcessing.amplification; + end + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + % Aplified displacements + ux_ampl=fact_ampl*U(d1,:); + uy_ampl=fact_ampl*U(d2,:); + % Deformed state + def_x=coord(:,1)+ux_ampl; + def_y=coord(:,2)+uy_ampl; + % Vertices in deformed state + Vertices(:,1,:)=def_x; + Vertices(:,2,:)=def_y; + XLim=[min(def_x, [], 'all'); + max(def_x, [], 'all')]; + YLim=[min(def_y, [], 'all'); + max(def_y, [], 'all')]; + CLim=[0,1]; + Colormap=[]; + ColorbarStatus='off'; + ColorbarLabel=''; + +% Title='Amplified displacements'; + Title='Displacements'; + EdgeColor='black'; + FaceColor='none'; + + case 'norm' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + U_norm=sqrt(Ux.^2+Uy.^2); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(U_norm, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Norm of displacements'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=U_norm(:,m); + end + + case 'absx' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + Ux_abs=abs(Ux); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Ux_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along x'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Ux_abs(:,m); + end + + case 'absy' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + Uy_abs=abs(Uy); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Uy_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along y'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Uy_abs(:,m); + end + + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + case 'transient_heat' + + % Number of sub-intervals in time + N=Data.Discretization.N; + % Initilization + CData=cell(N+1,1); + + switch type + case 'standard' + + XLim=frame(:,1); + YLim=frame(:,2); + CLim=[min(U, [], 'all') max(U, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Temperature [ºC]'; + % ColorbarLabel='Temperature [K]'; + Title='Temperature'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + CData{m}=U(:,m); + end + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'CData', CData;... + 'Title', Title}; + + end + + + end + +%% Parameters for surf plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, arg, type) + + % Retrieve mesh parameters + % Coordinate matrix of interpolation nodes + coord_inter=Mesh.coord_inter; + + % Retrieve data parameters + % Number of evaluation points per element + nq=Data.PostProcessing.eval_points; + + % Total number of interpolation nodes + nb_nodes_inter=size(coord_inter,1); + + % Initilization + Vertices=zeros(nb_nodes_inter,2,N+1); + CData=cell(N+1,1); + + switch arg + + case 'stress' + + % Retrieve solution + sigma=permute(Solution.sigma, [2 1 3]); + + switch type + case 'von_mises' + + % Von Mises stress + select=sqrt(sigma(:,1,:).^2-sigma(:,1,:).*sigma(:,2,:)+sigma(:,2,:).^2+3*sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Von Mises stress field'; + + case 'sigma_xx' + + % Value of sigma_xx + select=sigma(:,1,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xx}'; + + case 'sigma_yy' + + % Value of sigma_yy + select=sigma(:,3,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{yy}'; + + case 'sigma_xy' + + % Value of sigma_xy + select=sigma(:,2,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xy}'; + + case 'sigma_xx_abs' + + % Abolute value of sigma_xx + select=abs(sigma(:,1,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xx}|'; + + case 'sigma_yy_abs' + + % Absolute value of sigma_yy + select=abs(sigma(:,3,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{yy}|'; + + case 'sigma_xy_abs' + + % Absolute value of sigma_xy + select=abs(sigma(:,2,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xy}|'; + + case 'frobenius' + + % Frobenius norm of sigma + select=sqrt(sigma(:,2,:).^2+2*sigma(:,2,:).^2+sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Frobenius norm of \sigma'; + + end + + case 'strain' + + % Retrieve solution + epsilon=permute(Solution.epsilon, [2 1 3]); + + switch type + case 'epsilon_xx' + + % Value of epsilon_xx + select=epsilon(:,1,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xx}'; + + case 'epsilon_yy' + + % Value of epsilon_yy + select=epsilon(:,3,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{yy}'; + + case 'epsilon_xy' + + % Value of esilon_xy + select=epsilon(:,2,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xy}'; + + case 'epsilon_xx_abs' + + % Abolute value of epsilon_xx + select=abs(epsilon(:,1,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xx}|'; + + case 'epsilon_yy_abs' + + % Absolute value of epsilon_yy + select=abs(epsilon(:,3,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{yy}|'; + + case 'epsilon_xy_abs' + + % Absolute value of epsilon_xy + select=abs(epsilon(:,2,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xy}|'; + + case 'frobenius' + + % Frobenius norm of epsilon + select=sqrt(epsilon(:,2,:).^2+2*epsilon(:,2,:).^2+epsilon(:,3,:).^2); + ColorbarLabel='Strain [-]'; + Title='Frobenius norm of \epsilon'; + + end + + end + + % Common parameters + XLim=[min(coord_inter(:,1,:), [], 'all'); + max(coord_inter(:,1,:), [], 'all')]; + YLim=[min(coord_inter(:,2,:), [], 'all'); + max(coord_inter(:,2,:), [], 'all')]; + CLim=[min(select, [], 'all') max(select, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=coord_inter(:,1,m); + Vertices(:,2,m)=coord_inter(:,2,m); + CData{m}=select(:,m); + end + + + switch nq + case 3 + % 3-point evaluation inside each element + I=1:3*Mesh.nb_elem; + I=reshape(I, 3, Mesh.nb_elem)'; + + case 6 + % 6-point evaluation inside each element + i1=[1 4 5 2 2 5 6 3 3 6 4 1]; + i2=[4 5 6]; + I1=i1+(0:Mesh.nb_elem-1)'*6; + I2=i2+(0:Mesh.nb_elem-1)'*6; + I1=reshape(I1', 4, 3*Mesh.nb_elem)'; + I2=[I2 nan*zeros(size(I2,1),1)]; + I=[I1; I2]; + end + + % Set static parameters + PlotParam = {'Vertices', coord_inter;... + 'Faces', I;... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + end + +%% Parameters for quiver plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, type) + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve solution + Q_sort=Solution.Q_sort; + + % Initialization + U=cell(1,N+1); + V=cell(1,N+1); + + switch type + case 'standard' + % Standard visualization of fluxes + Title='Flux field'; + + for m=1:N+1 + U{m}=Q_sort(:,2*m-1); + V{m}=Q_sort(:,2*m); + end + + end + + + % Set static parameters + PlotParam = {'XData', coord(:,1);... + 'YData', coord(:,2);... + 'LineWidth', 1}; + + AxisParam = {'XLim', frame(:,1);... + 'YLim', frame(:,2);... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', 'off';... + 'Label.String', '';... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'UData', U;... + 'VData', V;... + 'Title', Title}; + + + end + + + + + + +%% Setting and updating graphic properties + function[object]=setObject(object, param) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument) + if contains(argument{i}, '.') + field=extractBetween(argument{i}, 1, '.'); + subfield=extractAfter(argument{i}, '.'); + object.(field{1}).(subfield)=value{i}; + else + object.(argument{i})=value{i}; + end + end + + end + + function[plot, axis]=updateObject(plot, axis, param, step, N) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument)-1 + plot.(argument{i})=value{i}{step}; + end + + axis.Title.String=[value{end} ' at step ' num2str(step) ' out of ' num2str(N+1)]; + end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/postProcess.m b/Code/INTERNODES_large/postProcess.m new file mode 100644 index 0000000..364859c --- /dev/null +++ b/Code/INTERNODES_large/postProcess.m @@ -0,0 +1,18 @@ +function[Mesh, Solution]=postProcess(Mesh, Data, Solution) + +% postProcess: General post-processing function used to compute derived +% quantities +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Mesh: Updated mesh structure with coordinates of nodes where stresses +% and strains were evaluated +% Solution: Updated solution structure with derived quantities + + +if any(contains(Data.PostProcessing.quantity, {'stress', 'strain'})) + [Mesh, Solution]=postProcessDisp(Mesh, Data, Solution); +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/postProcessDisp.m b/Code/INTERNODES_large/postProcessDisp.m new file mode 100644 index 0000000..f1f6613 --- /dev/null +++ b/Code/INTERNODES_large/postProcessDisp.m @@ -0,0 +1,147 @@ +function[Mesh, Solution]=postProcessDisp(Mesh, Data, Solution) + +% postProcessDisp: Function used to compute strains and stresses once the +% displacements are known. +% INPUT: +% Mesh: Structure containing all mesh parameters +% Data: Structure containing all data parameters +% Solution: Structure containing the computed solutions +% OUTPUT: +% Mesh: Updated mesh structure with evaluation points coordinates +% Solution: Updated solution structure including strains and stresses + + +% Finite element order +order=Mesh.order; +% Number of local degrees of freedom +d=Mesh.nb_dof_local; +% Number of elements +ne=Mesh.nb_elem; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Retrieve basis functions +[Functions]=getFunctions(order); +% Retrieve Jacobian matrix +J_phi=Functions.J_phi; +% Number of evaluation points per element +nq=Data.PostProcessing.eval_points; +% Retrieve evaluation points +[points, ~] = getQuadrature(nq, 'interpolation'); +% Number of independent strain and stress components +Mesh.nb_comp_ind=1/2*d*(d+1); +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; +% Retrieve solution +U_sort=Solution.U_sort; +% Compute stresses and strains using current position +coord=coord+U_sort; + +% Define permutation matrix +Sdd=zeros(d*d); +Id1=eye(d); +Id2=eye(d^2); + +for k=1:d + Sdd=Sdd+kron(Id1(:,k)', kron(Id1, Id1(:,k))); +end + +% Reduce size of matrices to avoid unnecessary computations +% sigma_xx, sigma_xy, sigma_yy +h=[1 2 4]; + +I1=(Id2+Sdd); +I2=Id1(:); + +% Truncate the matrices +I1=I1(h,:); +I2=I2(h); + +% Initialization +Q=zeros(nq*d^2, nne*d); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:))', Id1); + Q((i-1)*d^2+1:i*d^2, :)=L; +end +Q=sparse(Q); +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Displacements of the nodes of the elements +W=reshape(U_sort(G,:)', nne*d, ne); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); +coord_inter=reshape(P', nq*ne, d); +% Update mesh +Mesh.coord_inter=coord_inter; + +% Computation of geometric quantities +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, 1, d^2*ne); + +% Coefficient evaluation +F1=f1(X); +F2=f2(X); +if size(F1,1)>1 +F1=reshape(F1, 1, nq, ne); +end +if size(F2,1)>1 +F2=reshape(F2, 1, nq, ne); +end + +% Matrix product computations +W=reshape(Q*W, d^2, nq, ne); + +B1=reshape(product(JK_invT,repmat(Id1, 1, ne),d,ne), d^2, d^2, ne); +B2=reshape(vec_JK_invT, 1, d^2, ne); + +% Computation of strains +if any(contains(Data.PostProcessing.quantity, 'strain')) + E=pagemtimes(B1, W); + E=0.5*I1*reshape(E, d^2, nq*ne); + Solution.epsilon=E; +end + +% Computation of stresses +if any(contains(Data.PostProcessing.quantity, 'stress')) + S1=pagemtimes(B1, F1.*W); + S2=pagemtimes(B2, F2.*W); + S1=I1*reshape(S1, d^2, nq*ne); + S2=I2*reshape(S2, 1, nq*ne); + S=S1+S2; + Solution.sigma=S; +end + + + function[M]=product(A,B,d,ne) + + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/preCompute.m b/Code/INTERNODES_large/preCompute.m new file mode 100644 index 0000000..ecb4137 --- /dev/null +++ b/Code/INTERNODES_large/preCompute.m @@ -0,0 +1,103 @@ +function[Data]=preCompute(Mesh, Data) + +% preCompute: Function which pre-computes quantities needed in a single +% iteration of the contact algorithm +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OUTPUT: +% Data: Updated data structure + +d=Mesh.nb_dof_local; + +% Retrieve bodies +body=Data.body; +% Retrieve local degrees of freedom +indx1=body{1}.indx_local; +indx2=body{2}.indx_local; +l1=length(indx1); +l2=length(indx2); + +Data.l1=l1; +Data.l2=l2; + +b2=Data.b2; + +L22=Data.L22; + +i2_old=Data.i2; +i2=[indx1; indx2]; +% Beware of the ordering in the linear operators +[~, index, reor]=intersect(i2_old, i2, 'stable'); +Data.index=index; + +%% Modified LU + +Phi11=Data.Phi11; +Phi21=Data.Phi21; +Phi12=Data.Phi12; +Phi22=Data.Phi22; + +r1=size(Phi11,1); +r2=size(Phi22,1); + +q1=ones(r1,1); +q2=ones(r2,1); + +s1=Phi12*(Phi22\q2); +s2=Phi21*(Phi11\q1); + +% Define initial sparsity +S1=num2cell(1:r1); +S2=num2cell(1:r2); +% Maximum fill in per column of M per iteration +Param.s=5; +Param.maxit=20; +Param.tol=0.2; + +[Phi11ia]=SPAI(Phi11, S1, Param); +[Phi22ia]=SPAI(Phi22, S2, Param); + +R12a=Phi12*Phi22ia; +R21a=Phi21*Phi11ia; + +% Rescaling +R12a=R12a./s1; +R21a=R21a./s2; + +M1=kron(body{1}.interface_mass, speye(d)); +M2=kron(body{2}.interface_mass, speye(d)); + +R12a=kron(R12a, speye(d)); +R21a=kron(R21a, speye(d)); + +Q1a=-diag(M2).*R21a./(diag(M1)'); +Q2a=-R12a'; + +Y1a=[eye(l1); Q1a]; +Y2a=[eye(l1); Q2a]; + +Z1=sparse(b2, l1); +Z2=sparse(b2, l1); + +% Z1(index,:)=-alpha*Y1a(reor,:); +% Z2(index,:)=Y2a(reor,:); + +Z1(index,:)=Y1a(reor,:); +Z2(index,:)=Y2a(reor,:); + +Lv=L22; +Uv=L22; + +[i,j]=find(Z1); +[I]=findNNZc(i,j); + +Lv(:,I)=Z1; +Uv(:,I)=Z2; + +Data.Lv=Lv; +Data.Uv=Uv'; +Data.orl=1:b2; +Data.orc=1:b2; + +end \ No newline at end of file diff --git a/Code/INTERNODES_large/preProcess.m b/Code/INTERNODES_large/preProcess.m new file mode 100644 index 0000000..6547c56 --- /dev/null +++ b/Code/INTERNODES_large/preProcess.m @@ -0,0 +1,86 @@ +function[Data]=preProcess(Data, Solution, K) + +% preProcess: Pre-computes the incomplete Cholesky factor of the perturbed +% stiffness matrix and part of the right-hand side vector. These quantities +% do not change during the course of the contact algorithm +% INPUT: +% Data: Structure containing all the data parameters +% Solution: Structure containing the initialized solution +% K: Global stiffness matrix +% OUTPUT: +% Data: Updated data structure with part of the right-hand side vector, +% reordering indices, Cholesky factors and matrices + + +% Retrieve data parameters +% Free degrees of freedom +Nf=Data.Nf; +% Blocked degrees of freedom +Nd=Data.Nd; +% Retrieve bodies +body=Data.body; +% Rescaling parameter +r=Data.Contact.rescaling; +% Perturbation added to the stiffness matrix to make it SPD +Data.Contact.epsilon=1e-8; +% Right-hand side force vector +f=Data.F; +% Retrieve solution +U=Solution.U; +% Number of free degrees of freedom +nf=length(Nf); +% Retrieve local degrees of freedom +indx1=body{1}.indx_local; +indx2=body{2}.indx_local; + +l1=length(indx1); +l2=length(indx2); + +% Rescaled stiffness matrix +Kff=1/r*K(Nf, Nf); +% Perturbed stiffness matrix +K_tilde=Kff+Data.Contact.epsilon*speye(nf,nf); +% Cholesky decomposition of the perturbed stiffness matrix +p=dissect(K_tilde); + +% Block sizes for partitioning +b1=nf-(l1+l2); +b2=l1+l2; + +% Cholesky decomposition of the reordered perturbed stiffness matrix +i2=[indx1; indx2]; +or=[setdiff(p, i2, 'stable')'; i2]; +Kr=K_tilde(or, or); +% diagcomp=1e-3; +Options.type='ict'; +Options.droptol=1e-4; +Options.michol='off'; +% Options.diagcomp=diagcomp; +Lr=ichol(Kr, Options); +L22=Lr(b1+1:end,b1+1:end); +Data.L22=L22; +Data.i2=i2; +Data.i2c=i2; +Data.or=or; +Data.A=L22*L22'; + +Data.bc=1/r*(f(Nf)-K(Nf,Nd)*U(Nd)); + +% Block sizes for partitioning +Data.b1=b1; +Data.b2=b2; + +% Number of free degrees of freedom +Data.nf=nf; +% Parameter alpha +if strcmp(Data.Contact.alpha, 'auto') + Data.Contact.alpha=-norm(Kff, Inf); +end +% Rescaled stiffness matrix +Data.K=Kff; +% Cholesky factor of the reordered perturbed stiffness matrix +Data.Lr=Lr; +% Initial sizes of the active sets +Data.li1=l1; +Data.li2=l2; +end \ No newline at end of file diff --git a/Code/INTERNODES_large/readGMSH.m b/Code/INTERNODES_large/readGMSH.m new file mode 100755 index 0000000..21425ca --- /dev/null +++ b/Code/INTERNODES_large/readGMSH.m @@ -0,0 +1,86 @@ +function [Mesh] = readGMSH(file_name) + +% readGMSH: Reads a GMSH mesh file (.msh) and constructs the coordinate, +% connectivity, boundary nodes, material tag and boundary tag matrices +% INPUT: +% file_name: GMSH mesh file (.msh) +% OUTPUT: +% Mesh: Structure containing all fields related to the geometry, boundary +% conditions and material tags +% coord: Matrix of node coordinates +% connectivites: Connectivity matrix of the system +% boundary_nodes: Matrix containing the boundary nodes (both displacements +% and external forces) +% material_tag: Vector containing the material tag of the elements +% boundary_tag: Vector containing the boundary tags of the elements +% forming boundaries (enables to make the distinction between Dirichlet +% and Neumann boundary conditions) + +fileID = fopen(file_name); +text = textscan(fileID, '%s', 'delimiter', '\n'); +fclose(fileID); + +text=text{1}; +% Construction of the coordinate matrix +nodes_start = findPos(text, '$Nodes') + 1; + +fileID = fopen(file_name); +% Node coordinates +coord = textscan(fileID, '%f %f %f %f', 'HeaderLines', nodes_start); +% Elements +connect = textscan(fileID, [repmat('%f', 1, 20) '%*[^\n]'], 'HeaderLines', 3, 'EndOfLine', '\n', 'CollectOutput', 1); +fclose(fileID); + +% Coordinate matrix +coord=cell2mat(coord); +% Ignore z component +coord=coord(:,2:end-1); + +% Connectivity matrix +connect=cell2mat(connect); + +I=sum(~isnan(connect), 2); +v=unique(I); + +% Boundary elements +B=connect(I==v(1), 1:v(1)); +% Elements +E=connect(I==v(2), 1:v(2)); + +% Boundary tags +B_tags=B(:,4); +% Element tags +E_tags=E(:,4); + +% Boundary elements connectivity matrix +B_connect=B(:,6:end); +% Elements connectivity matrix +E_connect=E(:,6:end); + +% Initialize mesh structure +Mesh.coord_mat=coord; +Mesh.BC_nodes=B_connect; +Mesh.connect_mat=E_connect; +Mesh.BC_tag=B_tags; +Mesh.material_tag=E_tags; + +% Identification of the type of element in GMSH +% 2 -> T3 +% 9 -> T6 +% 21 -> T10 +% 1 -> line +% 8 -> quadratic curve +% 26 -> cubic curve +end + + +function[start]=findPos(text, string) +lines = size(text, 1); +start = 1; +for i = 1:lines + if strcmp(text{i}, string) + start = i; + break + end +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/removeTraction.m b/Code/INTERNODES_large/removeTraction.m new file mode 100644 index 0000000..a2cefb7 --- /dev/null +++ b/Code/INTERNODES_large/removeTraction.m @@ -0,0 +1,71 @@ +function[Data]=removeTraction(Mesh, Data, Solution, n) + +% removeTraction: Checks for convergence and updates the interface properties +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% n: Current iteration number +% OUTPUT: +% Data: Updated data structure with updated interface properties + +% Compute the normals based on deformed structure +% Normals for initial interface +[Data]=computeNormals(Mesh, Data, Solution); + +normal1=Data.body{1}.normal; +normal2=Data.body{2}.normal; + +% Retrieve Lagrange multipliers +lambda_sort1=Solution.lambda_sort; + +% Project the lambdas on interface 2 +lambda_sort2=-multiplyR21(Data, lambda_sort1); + +% Computes the scalar products +scalar1=sum(lambda_sort1.*normal1(Data.body{1}.active_set,:),2); +scalar2=sum(lambda_sort2.*normal2(Data.body{2}.active_set,:),2); + +% Detect nodes to be removed from the interfaces +dump_nodes1=Data.body{1}.interface_nodes(scalar1>0); +dump_nodes2=Data.body{2}.interface_nodes(scalar2>0); + +% Update the interfaces +% If only compression test penetration, otherwise remove nodes in traction +if isempty(dump_nodes1) && isempty(dump_nodes2) + % Gap verification + [add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution); + + [Data, nodediff1]=updateInterface(Mesh, Data, 1, add_nodes1, 'add'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, add_nodes2, 'add'); + +else + % Skip gap verification + [Data, nodediff1]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); +end + +fprintf('Iteration %d: \n', n) +fprintf('%+d nodes on interface 1 \n', nodediff1) +fprintf('%+d nodes on interface 2 \n', nodediff2) +fprintf('----------------------------------- \n') + +% Check for convergence failure +if isempty(Data.body{1}.interface_nodes) || isempty(Data.body{2}.interface_nodes) + msg=['The contact algorithm stopped prematurely because at least one of ' ... + 'the sets of nodes of the potential contact interface is empty. '... + 'Potential causes for this error are:\n']; + + causes=['1) The boundary conditions are incorrect \n'... + '2) The potential contact interface was misidentified \n'... + '3) The mesh is too coarse \n']; + + error(sprintf([msg, causes])) +end + +% Check for convergence +if abs(nodediff1)+abs(nodediff2)==0 % Stop the iterations + Data.Contact.iterate=0; + disp('Successfully converged') +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/setBoundaryConditions.m b/Code/INTERNODES_large/setBoundaryConditions.m new file mode 100644 index 0000000..2d375dc --- /dev/null +++ b/Code/INTERNODES_large/setBoundaryConditions.m @@ -0,0 +1,267 @@ +function[Data]=setBoundaryConditions(Mesh, Data, varargin) + +% setBoundaryConditions: Function which initializes the boundary data +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OPTIONAL INPUT: +% Any type of boundary condition. If no Dirichlet boundary conditions are +% specified, the program returns an error +% Types of boundary conditions: +% Dirichlet +% Neumann +% OUTPUT: +% Data: Updated data structure with boundary conditions + + +%% Set boundary conditions +if mod(length(varargin{1}), 2)~=0 + error('Missing a name or value') +end + +% Retrieve boundary nodes +BC_tags=unique(Mesh.BC_tag); + +% Dirichlet type +D_types={'dirichlet'}; +% Neumann types +N_types={'neumann'}; +% Available types of boundary conditions +available_types=union(D_types, N_types); +% Count to detect duplicate boundaries +count=[]; +% Count number of Dirichlet and Neumann boundaries specified +nb_D_BC=0; +nb_N_BC=0; + +% Retrieve labels and values entered by the user +labels_in=varargin{1}(1:2:end); +values_in=varargin{1}(2:2:end); + +% Initilization +Data.BC=[]; + +% Checking for boundary conditions +c1=strcmp(labels_in, 'BC'); + +if any(c1) + BC=values_in{c1}; + nb_BC=length(BC); + Data.BC=cell(nb_BC,1); + + for i=1:nb_BC + bc=BC{i}; + l=length(bc); + + if mod(l,2)~=0 + error('Missing a name or value') + end + + for j=1:2:l + arg=lower(bc{j}); + val=bc{j+1}; + + switch arg + + case 'type' + if any(ismember(available_types, lower(val))) + Data.BC{i}.(arg)=lower(val); + + if any(ismember(D_types, lower(val))) + nb_D_BC=nb_D_BC+1; + else + nb_N_BC=nb_N_BC+1; + end + else + error('Unrecognized boundary condition') + end + + case 'edges' + + edges=intersect(BC_tags, val); + + if isempty(edges) + error('One of the edges specified does not exist') + elseif ~isempty(intersect(count, val)) || length(unique(val))~=length(val) + error('Duplicate edges detected') + end + count=[count val]; + Data.BC{i}.(arg)=val; + + case {'function'} + + if isa(val,'double') + Data.BC{i}.label='const'; + Data.BC{i}.(arg)= @(x) val; + + elseif isa(val,'function_handle') + Data.BC{i}.label=detectDependency(val); + Data.BC{i}.(arg)=val; + else + error('Input parameters must be either constants or function handles') + end + + otherwise + error('Unrecognized argument') + end + + end + + + end + +end + +%% Post-Processing: Partitioning the structure BC into Dirichlet BC and Neumann BC and checking for missing data + +if nb_D_BC==0 + error('Dirichlet boundary conditions must be prescribed') +end + +BC=Data.BC; +l=length(BC); + +% Initialize cells +D_BC=cell(nb_D_BC,1); +N_BC=cell(nb_N_BC,1); +% Set of Dirichlet and Neumann tags +D_tags=[]; +N_tags=[]; +% Initialize counters +d=1; +n=1; + +for i=1:l + bc=BC{i}; + type=bc.type; + + if ~isfield(bc, 'edges') + error('The edges must be specified') + end + + if any(ismember(D_types, type)) + D_tags=[D_tags bc.edges]; + D_BC{d}.type=type; + D_BC{d}.edges=bc.edges; + + if isfield(bc, 'function') + D_BC{d}.function=bc.function; + D_BC{d}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + D_BC{d}.function=@(x) [0; 0]; + D_BC{d}.label='const'; + end + + d=d+1; + + elseif any(ismember(N_types, type)) + N_tags=[N_tags bc.edges]; + N_BC{n}.type=type; + N_BC{n}.edges=bc.edges; + + + if isfield(bc, 'function') + N_BC{n}.function=bc.function; + N_BC{n}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + N_BC{n}.function=@(x) [0; 0]; + N_BC{n}.label='const'; + end + + n=n+1; + end +end + +Data.D_BC=D_BC; +Data.N_BC=N_BC; + +Data.D_tags=D_tags; +Data.N_tags=N_tags; + +%% Sorting nodes +% If there is a node belonging to conflicting boundary conditions, it is +% set using the following priority list: Dirichlet, Neumann, Interior +BC_nodes=Mesh.BC_nodes; +BC_tag=Mesh.BC_tag; +nb_nodes=Mesh.nb_nodes; +Dof_set=Mesh.Dof_set; + +set_D_nodes=cell(nb_D_BC,1); +set_N_nodes=cell(nb_N_BC,1); + +for k=1:nb_D_BC + nodes=BC_nodes(any(BC_tag==D_BC{k}.edges,2),:); + set_D_nodes{k}=unique(nodes); +end +for k=1:nb_N_BC + nodes=BC_nodes(any(BC_tag==N_BC{k}.edges,2),:); + set_N_nodes{k}=unique(nodes); +end +% Define the sets of nodes +% Dirichlet nodes +Nodes_D=unique(cell2mat(set_D_nodes)); +% Neumann nodes +Nodes_N=setdiff(unique(BC_nodes), Nodes_D); +% Interior nodes +Nodes_I=setdiff(1:nb_nodes, union(Nodes_D, Nodes_N)); +% Free nodes +Nodes_F=union(Nodes_I, Nodes_N); + +% Correct the set for Neumann nodes +for k=1:nb_N_BC + set_N_nodes{k}=setdiff(set_N_nodes{k}, Nodes_D); +end + + +% Define the sets of degrees of freedom +Nd=Dof_set(:,Nodes_D); +Nn=Dof_set(:,Nodes_N); +Ni=Dof_set(:,Nodes_I); + +Ni=Ni(:); +Nd=Nd(:); +Nn=Nn(:); +Nf=union(Ni, Nn); + +% Update data +Data.Nodes_I=Nodes_I; +Data.Nodes_N=Nodes_N; +Data.Nodes_D=Nodes_D; +Data.Nodes_F=Nodes_F; + +Data.Ni=Ni; +Data.Nd=Nd; +Data.Nn=Nn; +Data.Nf=Nf; + +Data.set_D_nodes=set_D_nodes; +Data.set_N_nodes=set_N_nodes; + +%% Auxiliary function + + function[label]=detectDependency(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end + +end diff --git a/Code/INTERNODES_large/setCoefficient.m b/Code/INTERNODES_large/setCoefficient.m new file mode 100644 index 0000000..d15a956 --- /dev/null +++ b/Code/INTERNODES_large/setCoefficient.m @@ -0,0 +1,103 @@ +function[Data]=setCoefficient(Data, varargin) + +% setCoefficient: Initializes the model coefficients +% OPTIONAL INPUT: +% rho: Specific mass +% mu: Lamé constant +% lambda: Lamé constant +% f: Function "f" of the PDE +% OUTPUT: +% Data: Data structure with initialized coefficients + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +available_coeff={'rho', 'lambda', 'mu', 'f'}; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + if any(ismember(available_coeff, arg)) + + + switch arg + case {'rho', 'lambda', 'mu'} % Only space dependent + if isa(val,'double') + + if val ~= 0 + Data.Coefficient.(arg).label='const'; + else + Data.Coefficient.(arg).label='zero'; + end + Data.Coefficient.(arg).function=@(x) val; + + elseif isa(val,'function_handle') + Data.Coefficient.(arg).label=detectDependency1(val); + Data.Coefficient.(arg).function=val; + else + error('Input parameters must be either constants or function handles') + end + + case 'f' + if isa(val,'double') % Only space dependent + Data.Coefficient.f.label='const'; + Data.Coefficient.f.function=@(x) val; + elseif isa(val,'function_handle') + Data.Coefficient.f.label=detectDependency2(val); + Data.Coefficient.f.function=@(x) val(x); + else + error('Input parameters must be either constants or function handles') + end + + end + + else + error('Unrecognized coefficient') + end +end + + + function[label]=detectDependency1(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Coefficient can only be constant or space dependent') + end + + if contains(string(5:end), 'x') + label='space'; + elseif contains(string(5), '0') + label='zero'; + else + label='const'; + end + end + + function[label]=detectDependency2(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/setModel.m b/Code/INTERNODES_large/setModel.m new file mode 100644 index 0000000..58c27a3 --- /dev/null +++ b/Code/INTERNODES_large/setModel.m @@ -0,0 +1,75 @@ +function[Data]=setModel(varargin) + +% setModel: Initializes the numerical model +% OPTIONAL INPUT: +% model: Specifies which model should be used +% Default: transient_heat +% submodel: Specifies which submodel from the specified model should be +% used +% Default: standard +% type: Specifies whether the model is linear or nonlinear +% Default: linear +% OUTPUT: +% Data: Data structure with initialized model + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +models={'transient_heat', 'static_solid', 'dynamic_solid'}; +n=length(models); +submodels=cell(n,1); +submodels{1}={'standard'}; +submodels{2}={'standard', 'contact'}; +submodels{3}={'standard'}; +types={'linear', 'nonlinear'}; + +% Set default Parameters +Data.Model.name=models{1}; +Data.Model.submodel=submodels{1}; +Data.Model.type=types{1}; + +% Define constants +% Stefan Boltzman constant W/(m^2*K^4) +Data.Constants.sigma=5.670373e-8; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'model' + if any(ismember(models, val)) + Data.Model.name=val; + else + error('Unrecognized model') + end + + case 'submodel' + + if isfield(Data.Model, 'name') + index=strcmp(Data.Model.name, models); + else + error('Submodel must be defined after the model') + end + if any(ismember(submodels{index}, val)) + Data.Model.submodel=val; + else + error('Unrecognized submodel') + end + + case 'type' + if any(ismember(types, val)) + Data.Model.type=val; + else + error('Unrecognized type') + end + + otherwise + error('Unrecognized argument') + end + +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/setNumericalParameters.m b/Code/INTERNODES_large/setNumericalParameters.m new file mode 100644 index 0000000..3cbd970 --- /dev/null +++ b/Code/INTERNODES_large/setNumericalParameters.m @@ -0,0 +1,270 @@ +function[Data]=setNumericalParameters(Mesh, Data, varargin) + +% setNumericalParameters: Initializes the numerical parameters of the +% solver +% OPTIONAL INPUT: +% Contact: +% iterate: Parameter controlling whether iterations should +% take place (1) or not (0) +% Default: 1 +% maxiter: Maximum number of iterations for solving the +% contact problem +% Default: 10 +% rescaling: Rescaling parameter for the stiffness matrix +% Default: 1 +% alpha: Parameter for the preconditioner +% Default: auto +% c: Number of nearest neighbors for computing the +% radius of support of the radial basis functions +% Default: 1 +% radius: Intersection radius used to define the initial +% potential contact interface. +% Default: infinity +% gamma: Value of gamma for preventing quick loss of diagonal +% dominance of the matrices PhiMM +% Default: 0.5 +% Gamma: Value of Gamma to avoid very small nonzero entries +% in the matrices PhiNM +% Default: 0.95 +% d0: Measure of tolerance for contact detection. +% Default: 0.05 +% tol: Tolerance for gap detection +% Default: 1e-1 +% Solver: +% name: Name of the solver to be used. +% Default: GMRES +% maxiter: Total number of GMRES iterations +% Default: 1000 +% riter: Number of iterations before GMRES restarts +% Default: 20 +% tol: Tolerance (stopping creterion) for GMRES +% Default: 1e-7 +% reorth_tol: Re-orthogonalization tolerance for GMRES +% Default: 0.7 +% PostProcessing: +% quantity: Additional quantities to be computed (for example +% stresses, strains, fluxes,...) +% Default: none +% ampl_fact: Value of amplification factor to apply to +% displacements +% Default: auto +% eval_points: Number of evaluation points used for computing +% stresses or strains +% Default: 3 (P1 finite elements) +% 6 (P2 or higher order finite elements) + +if nargin>2 +if mod(length(varargin)-1, 2)~=0 + error('Missing a name or value') +end + +id=lower(varargin{1}); +labels_in=varargin(2:2:end); +values_in=varargin(3:2:end); +end + +% Set default Parameters +if ~isfield(Data, 'Contact') + % Boolean indicator for contact algorithm iterations + Data.Contact.iterate=true; + % Maximum number of iterations of the contact algorithm + Data.Contact.maxiter=10; + % Rescaling parameter for the INTERNODES matrix + Data.Contact.rescaling=1; + % Parameter alpha of the preconditioner + Data.Contact.alpha='auto'; + % Number of nearest neighbors + Data.Contact.c=1; + % Intersection radius + Data.Contact.radius=inf; + % gamma value + Data.Contact.gamma=0.5; + % Gamma value + Data.Contact.Gamma=0.95; + % Contact tolerance + Data.Contact.d0=0.05; + % Gap tolerance + Data.Contact.tol=1e-1; +end +if ~isfield(Data, 'Solver') + % Solver name + Data.Solver.name='GMRES'; + % GMRES parameters + % Total maximum number of iterations + Data.Solver.maxiter=1000; + % Maximum number of iterations before restart + Data.Solver.riter=20; + % Tolerance on the residual + Data.Solver.tol=1e-7; + % Reorthogonalization tolerance + Data.Solver.reorth_tol=0.7; +end +if ~isfield(Data, 'PostProcessing') + % Additional quantities to be computed + Data.PostProcessing.quantity='none'; + % Amplification factor + Data.PostProcessing.amplification='auto'; + % Number of evaluation points per element for computing stresses/strains + eval_points={3,6}; + + if Mesh.order==1 + Data.PostProcessing.eval_points=eval_points{1}; + else + Data.PostProcessing.eval_points=eval_points{2}; + end + +end + +% Override the defaults +if exist('id', 'var') + switch id + case 'contact' + %% Contact algorithm specific parameters + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'iterate' + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Data.Contact.(arg)=logical(val); + end + + elseif isa(val, 'logical') + Data.Contact.(arg)=val; + else + error([arg ' parameter must be a boolean']) + end + + case {'maxiter', 'c', 'radius', 'd0'} + if val<=0 + error(['Parameter ' arg ' must be strictly greater than zero']) + else + Data.Contact.(arg)=val; + end + + case 'rescaling' + if val==0 + error(['Parameter ' arg ' must be different from zero']) + else + Data.Contact.(arg)=val; + end + + case {'gamma', 'Gamma', 'tol'} + if val<=0 || val>=1 + error([arg ' must be strictly greater than 0 and strictly smaller than 1']) + else + Data.Contact.(arg)=val; + end + + case 'alpha' + if isa(val, 'char') + if ~strcmp(val, 'auto') + error('Invalid alpha parameter') + else + Data.Contact.(arg)=val; + end + + elseif isa(val, 'double') + if val == 0 + error('Invalid alpha parameter') + else + Data.Contact.(arg)=val; + end + else + error('Invalid alpha parameter') + end + + otherwise + error('Unrecognized argument') + end + end + + + case 'solver' + %% Solver parameters + names={'GMRES'}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'name' + if any(ismember(names, val)) + Data.Solver.(arg)=val; + else + error('Unrecognized solver name') + end + + case {'maxiter', 'riter', 'tol', 'reorth_tol'} + if val<=0 + error(['Parameter ' arg ' must be strictly positive']) + else + Data.Solver.(arg)=val; + end + + otherwise + error('Unrecognized argument') + end + + end + + case 'postprocessing' + %% Post-processing parameters + quantities={'none', 'flux', 'error', 'stress', 'strain'}; + eval_points={3,6}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'quantity' + if any(ismember(quantities, val)) + Data.PostProcessing.quantity=val; + else + error('Unrecognized quantity') + end + + case 'eval_points' + if any(ismember(eval_points, val)) + Data.PostProcessing.eval_points=val; + else + error('Invalid number of evaluation points') + end + + case 'ampl_fact' + + if isa(val, 'char') + if ~strcmp(val, 'auto') + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + + elseif isa(val, 'double') + if val <= 0 + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + else + error('Invalid amplification factor') + end + + otherwise + error('Unrecognized argument') + end + end + + otherwise + error('Unrecognized argument') + end +end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/solve.m b/Code/INTERNODES_large/solve.m new file mode 100644 index 0000000..d6523e1 --- /dev/null +++ b/Code/INTERNODES_large/solve.m @@ -0,0 +1,94 @@ +function[Mesh, Data, Solution]=solve(Mesh, Data, it) + +% solve: Solves the contact problem +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% it: Cell array containing the boundary tags of the interfaces (master +% interface and slave interface) +% OUTPUT: +% Mesh: Updated structure containing all the mesh parameters +% Data: Updated structure containing all the data parameters +% Solution: Structure containing the solution + + +% Initialization of the system +[Data, Solution]=initializeSystem(Mesh, Data, it); +% Initialize count +n=1; + +while Data.Contact.iterate && n<= Data.Contact.maxiter + + % Solve the system of equations + [Solution, Data]=solveSystem(Mesh, Data, Solution); + % Update the system + [Data]=updateSystem(Mesh, Data, Solution); + n=n+1; +end + +if n > Data.Contact.maxiter && Data.Contact.iterate + warning('Failed to converge within the specified number of iterations') +end + + function[Solution, Data]=solveSystem(Mesh, Data, Solution) + + % Retrieve data parameters + % Free degrees of freedom + Nf=Data.Nf; + % Number of free degrees of freedom + nf=Data.nf; + % Number of constraints + nc=Data.nc; + + % Retrieve mesh parameters + d=Mesh.nb_dof_local; + + % Retrieve solution + U=Solution.U; + % Solve the linear system iteratively + [x, Data]=iterativeSolver(Mesh, Data); + % Retrieve the displacements + U(Nf)=x(1:nf); + + % Update solution + Solution.U=U; + Solution.U_sort=reshape(U, d, [])'; + Solution.lambda=Data.Contact.rescaling*x(nf+1:nf+nc); + Solution.lambda_sort=reshape(Solution.lambda, d, [])'; + end + + function[x, Data]=iterativeSolver(Mesh, Data) + + b=Data.b; + [Data]=preCompute(Mesh, Data); + + if strcmp(Data.Solver.name,'GMRES') + x0=zeros(length(b),1); + [x, ~, ~] = gmres_k(Mesh, Data, @linearOp1, b, x0, 'level 1'); + + + else + error('Solver unrecognized or not yet implemented') + end + + end + + function[Data]=updateSystem(Mesh, Data, Solution) + + % updateSystem: Updates all quantities which have changed + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Solution structure with computed displacements + % OUPUT: + % Data: Data structure with updated interface properties + + % Check for convergence + [Data]=removeTraction(Mesh, Data, Solution, n); + % Reassemble the interpolation matrices and update interface + [Data]=subAssemble(Mesh, Data, Solution, 'updating'); + % Update right-hand side + [Data]=updateRHS(Mesh, Data); + + end +end \ No newline at end of file diff --git a/Code/INTERNODES_large/subAssemble.m b/Code/INTERNODES_large/subAssemble.m new file mode 100644 index 0000000..11464ed --- /dev/null +++ b/Code/INTERNODES_large/subAssemble.m @@ -0,0 +1,176 @@ +function[Data]=subAssemble(Mesh, Data, Solution, operation) + +% subAssemble: Assembles the part of the internodes matrix which is +% changing from one iteration to the next +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% operation: Specifies the type of operation (initialization or +% updating) +% OUTPUT: +% Data: Updated structure containing all the data parameters + +if strcmp(operation, 'initialization') + [Data]=computeBoundaryMass(Mesh, Data, 'initialization'); +end + +% Assemble interpolation matrices +[Data]=assembleRs(Mesh, Data, Solution); +% Assemble boundary mass matrices +[Data]=computeBoundaryMass(Mesh, Data, 'updating'); + + function[Data]=assembleRs(Mesh, Data, Solution) + + % assembleRs: Constructs the interpolation matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % Data: Updated data structure with interpolation matrices + + % Retrieve nodes making up the interfaces + nodes1=Data.body{1}.interface_nodes; + nodes2=Data.body{2}.interface_nodes; + + % Retrieve coordinates of nodes making up the interfaces + coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); + coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + + % Check for distant bodies + if all(i1) || all(i2) + warning('One of the bodies will be translated because they are too far away from each other. Boundary conditions might be affected') + [Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + % Check for isolated nodes + while any(i1) || any(i2) + + dump_nodes1=nodes1(i1); + dump_nodes2=nodes2(i2); + + % Updating + [Data]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + [Data]=constructInterpolants(Data, radiuses1, radiuses2, coord1, coord2); + end + + function[Data]=computeBoundaryMass(Mesh, Data, operation) + + % computeBoundaryMass: Assembles the boundary mass matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % operation: Specifies the type of operation (initialization or + % updating) + % OUTPUT: + % Data: Updated data structure with boundary mass matrices + + + % Retrieve mesh parameters + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Get quadrature nodes and weights + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nb_points=length(weights); + + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + + % Retrieve bodies + body=Data.body; + % Number of bodies + nb_bodies=length(body); + + if strcmp(operation, 'initialization') + + for n=1:nb_bodies + % Retrieve data + % Retriveve edges making up the interface + Edges=body{n}.interface_connect; + % Retrieve nodes making up the interface + nodes=body{n}.interface_nodes; + nb_nodes=length(nodes); + s=order+1; + + % Initialization + Phi=zeros(s, nb_points); + + % Map global nodes to local nodes + func=@(x) find(nodes==x); + connect=arrayfun(func, Edges); + T=connect'; + G=T(:); + + I=repmat(connect, [1 s])'; + J=repmat(G', [s 1]); + + % Loop over the Gauss points + for i = 1:nb_points + Phi(:,i)=phi_boundary(points(i,:)); + end + + Tr=Edges'; + Gr=Tr(:); + % Coordinates of the nodes of the elements + coord_nodes=coord(Gr,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + lambda=weights.*norm_bk'; + + M=kr(Phi, Phi)*lambda; + M=sparse(I(:), J(:), M(:), nb_nodes, nb_nodes); + + Data.body{n}.iinterface_mass=M; + % Expansion to full size + M=kron(M, speye(nb_dof_local)); + Data.body{n}.interface_mass=M; + end + else + for n=1:nb_bodies + active_set=Data.body{n}.active_set; + Data.body{n}.interface_mass=Data.body{n}.iinterface_mass(active_set, active_set); + end + end + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_large/translateBodies.m b/Code/INTERNODES_large/translateBodies.m new file mode 100644 index 0000000..f5a398f --- /dev/null +++ b/Code/INTERNODES_large/translateBodies.m @@ -0,0 +1,60 @@ +function[Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2) + +% translateBodies: Performs a translation of one of the bodies in case the +% initial potential contact interface is empty (because the bodies are too +% far away from each other) +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% radiusesk: Radiuses of support of the radial basis functions (k=1,2) +% nodesk: Nodes on the potential contact interface of body k (k=1,2) +% coordk: Coordinates of the nodes on the potential contact interface +% of body k (k=1,2) +% OUTPUT: +% Mesh: Mesh structure with updated coordinate matrix +% coordk: New coordinates of the nodes on the potential contact +% interface of body k (k=1,2) + +n1=length(radiuses1); + +dist=inf; +node1=0; +node2=0; + +for k=1:n1 + + [d, i]=min(vecnorm(coord1(k,:)-coord2,2,2)); + + if d1 + radiuses=radiuses'; +end + +m=size(coord_ref,1); +n=size(coord_interp,1); +dim=size(coord_ref,2); + +X_ref=reshape(coord_ref,1,m,dim); +X_interp=reshape(coord_interp,n,1,dim); + +X=X_interp-X_ref; +% 2-norm (euclidean norm) along the third dimension +N=vecnorm(X,2,3); +indx=N<=radiuses; +Phi=zeros(n,m); + +% Wendland C^2 radial basis functions +F=(1-N./radiuses).^4.*(1+4*N./radiuses); +Phi(indx)=F(indx); +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/computeNormals.m b/Code/INTERNODES_medium/computeNormals.m new file mode 100644 index 0000000..9a30e45 --- /dev/null +++ b/Code/INTERNODES_medium/computeNormals.m @@ -0,0 +1,77 @@ +function[Data]=computeNormals(Mesh, Data, Solution) + +% computeNormals: Computes the normal vectors for each interface +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Data: Updated structure with the interface normals + +% Retrieve mesh data +% Connectivity matrix +connect=Mesh.connect_mat; + +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Retrieve bodies +body=Data.body; +% Compute the normals based on the deformed structure +def=Mesh.coord_mat+Solution.U_sort; + +nb_bodies=length(body); + + +for k=1:nb_bodies + % Normal vectors are computed for all nodes in the initial potential + % contact interface + connecti=body{k}.initial_interface_connect; + nodesi=body{k}.initial_nodes; + + % Reduced connectivity matrix + connect_red=connect(any(ismember(connect, nodesi), 2), :); + % Reduced set of body nodes + nodesb=setdiff(unique(connect_red), nodesi); + n=length(nodesi); + m=size(connecti,1); + % Coordinates of the endpoints of the segments + coord1=def(connecti(:,1),:); + coord2=def(connecti(:,2),:); + % Tangent vectors + V=coord2-coord1; + % Segment lengths + L=vecnorm(V,2,2); + V=V./vecnorm(V,2,2); + + % !! Only for a two-dimensional case !! + N=zeros(m, d); + N(:,1)=-V(:,2); + N(:,2)=V(:,1); + + % Initialization + normals=zeros(n,d); + % Step size + gamma=1e-3; + + for j=1:n + [id,~]=find(connecti==nodesi(j)); + l=L(id); + % Weighted average of normal vectors + normals(j,:)=1/sum(l)*sum(N(id,:).*l,1); + % Current node + x=def(nodesi(j),:); + % Step in the direction of the gradient + y_plus=x+gamma*normals(j,:); + % Step in the opposite direction of the gradient + y_minus=x-gamma*normals(j,:); + min_plus=min(vecnorm(def(nodesb,:)-y_plus,2,2)); + min_minus=min(vecnorm(def(nodesb,:)-y_minus,2,2)); + if min_plus < min_minus + normals(j,:)=-normals(j,:); + end + end + + normals=normals./vecnorm(normals,2,2); + Data.body{k}.normal=normals; +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/computeRHS.m b/Code/INTERNODES_medium/computeRHS.m new file mode 100644 index 0000000..b6f4112 --- /dev/null +++ b/Code/INTERNODES_medium/computeRHS.m @@ -0,0 +1,223 @@ +function[Data]=computeRHS(Mesh, Data) + +% computeRHS: Function which assembles the right-hand side consisting of +% body forces and surface tractions +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% OUTPUT: +% Data: Updated data structure + +% Assemble the global vector of body forces +[Bs]=computeBody(Mesh, Data); +% Assemble the global vector of surface tractions +[Bn]=computeNeumann(Mesh, Data); + +% Sum the contribution from body forces and Neumann boundary +% conditions +B=Bs+Bn; +% Update data +Data.F=B; + + function[B]=computeBody(Mesh, Data) + + % computeBody: Assembles the component for the body forces + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of body forces + + % Retrieve mesh data + % Local number of degrees of freedom (dimension in solid mechanics) + d=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Finite element order + order=Mesh.order; + % Number of elements + ne=Mesh.nb_elem; + % Number of nodes per element + nne=Mesh.nb_nodes_elem; + % Matrix of equation numbers linking an element to the degrees of freedom + % associated to it. + Eq=Mesh.Eq; + + % Retrieve data parameters + % Body force vector + f=Data.Coefficient.f.function; + % Retrieve the basis functions + [Functions]=getFunctions(order); + % Retrieve basis functions + phi=Functions.phi; + + + % Retrieve quadrature points and weights + switch order + case 1 + [points, weights] = getQuadrature(3, 'bulk'); + case 2 + [points, weights] = getQuadrature(4, 'bulk'); + case 3 + [points, weights] = getQuadrature(6, 'bulk'); + otherwise + error('Not yet implemented'); + end + + % Number of quadrature points + nq=length(weights); + s=nne*d; + Id=eye(d); + % Initialization + X=zeros(ne, nq, 2); + Q=zeros(s, nq*d); + + + % Loop over the Gauss points + for i = 1:nq + Phi=phi(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + I=Eq'; + T=connect'; + G=T(:); + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Vertices of the elements + a=coord_nodes(1:nne:end,:); + b=coord_nodes(2:nne:end,:); + c=coord_nodes(3:nne:end,:); + % Interpolated coordinates + P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Determinants of Jacobian matrices + detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(abs(detJK), 1, 1, ne).*F.*weights; + X=reshape(X, d*nq, ne); + + B=Q*X; + + % Assemblage + B=accumarray(I(:), B(:), [nb_dof 1]); + end + + function[B]=computeNeumann(Mesh, Data) + + % computeNeumann: Implements Neumann boundary conditions, returns a vector + % having for length the number of degrees of freedom. + % This vector is to be added to the RHS. + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of surface tractions + + % Retrieve mesh parameters + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Number of local degrees of freedom + d=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Boundary equation numbers + Eq_BC=Mesh.Eq_BC; + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + % Get quadrature + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nq=length(weights); + + % Initialization + B=zeros(nb_dof,1); + s=order+1; + Id=eye(d); + % Initialization + Q=zeros(s*d, nq*d); + + % Loop over the Gauss points + for i = 1:nq + Phi=phi_boundary(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + + % Neumann boundary conditions + N_BC=Data.N_BC; + % Number of boundaries where surface tractions are imposed + nb_bc=length(N_BC); + + for m=1:nb_bc + % Retrieve data + data=N_BC{m}; + edges=data.edges; + + for flag=edges + % Surface traction function + f=data.function; + % Retriveve edges making up the boundary + Neum_edges=Mesh.BC_nodes(Mesh.BC_tag==flag,:); + % Equation numbers + Eq=Eq_BC(Mesh.BC_tag==flag,:); + % Number of Neumann boundary edges + ne=size(Neum_edges, 1); + + % Initialization + X=zeros(ne, nq, 2); + + C=Neum_edges'; + G=C(:); + I=Eq'; + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + % Interpolated coordinates + P=a(:)+(b(:)-a(:))*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(norm_bk, 1, 1, ne).*F.*weights'; + X=reshape(X, d*nq, ne); + + Bl=Q*X; + + % Assemblage + B=B+accumarray(I(:), Bl(:), [nb_dof 1]); + + end + end + end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/computeStiffness.m b/Code/INTERNODES_medium/computeStiffness.m new file mode 100644 index 0000000..6091151 --- /dev/null +++ b/Code/INTERNODES_medium/computeStiffness.m @@ -0,0 +1,138 @@ +function[K]=computeStiffness(Mesh, Data) + +% computeStiffness: Function which assembles the global stiffness matrix +% Assumes all elements are of the same type +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OUTPUT: +% K: Global stiffness matrix + +% Mesh parameters +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Total number of degrees of freedom +nb_dof=Mesh.nb_dof; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Finite element order +order=Mesh.order; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Total number of elements +ne=Mesh.nb_elem; +% Matrix linking an element to the degrees of freedom associated to it. +Eq=Mesh.Eq; + +% Data parameters +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; + + +% Retrieve functions +[Functions]=getFunctions(order); +% Jacobian matrix +J_phi=Functions.J_phi; +% Define permutation matrix +Sdd=zeros(d*d); +Id=eye(d); +for k=1:d + Sdd=Sdd+kron(Id(:,k)', kron(Id, Id(:,k))); +end + +% Retrieve quadrature nodes and weights +switch order + case 1 + [points, weights] = getQuadrature(1, 'bulk'); + case 2 + [points, weights] = getQuadrature(2, 'bulk'); + case 3 + [points, weights] = getQuadrature(3, 'bulk'); + otherwise + error('Not yet implemented'); +end + +% Initialization +s=nne*d; +nq=length(weights); +Q=zeros(s^2, nq*d^4); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:)), Id); + Q(:, (i-1)*d^4+1:i*d^4)=kron(L, L); +end + +T=Eq'; +G=T(:); + +I=repmat(Eq, [1 nne*d])'; +J=repmat(G', [nne*d 1]); + +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); + +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +V=[(vecnorm(c-a,2,2).^2) -sum((c-a).*(b-a),2) -sum((c-a).*(b-a),2) (vecnorm(b-a,2,2).^2)]; +W=1./(detJK').^2.*(V'); + +V=reshape(W,d,d*ne); + +JK_inv=zeros(d, d*ne); +JK_inv(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(b(:,2)-a(:,2))]'; +JK_inv(:,2:d:d*ne)=1./detJK'.*[-(c(:,1)-a(:,1)) b(:,1)-a(:,1)]'; + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, d^2, ne); + +[S1]=product(V , repmat(Id, 1, ne), d, ne); +[S2]=product(JK_invT , JK_inv, d, ne); +S2=Sdd*S2; + +A1=reshape(S1, d^4, ne); +A2=reshape(S2, d^4, ne); +A3=kr(vec_JK_invT, vec_JK_invT); + + +Lambda1=(abs(detJK).*f1(X).*weights)'; +Lambda2=(abs(detJK).*f2(X).*weights)'; + +X=kr(Lambda1, A1+A2)+kr(Lambda2, A3); +K=Q*X; + +% Assemblage +K=sparse(I(:), J(:), K(:), nb_dof, nb_dof); + + function[M]=product(A,B,d,ne) + + % Blockwise Kronecker product of two matrices + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/detectGaps.m b/Code/INTERNODES_medium/detectGaps.m new file mode 100644 index 0000000..ffe94c0 --- /dev/null +++ b/Code/INTERNODES_medium/detectGaps.m @@ -0,0 +1,81 @@ +function[add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution) + +% detectGaps: computes the gap between nodes of opposing interfaces and +% detects interpenetration +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% add_nodes1: Interface nodes of body 1 to be added to the active set +% add_nodes2: Interface nodes of body 2 to be added to the active set + +% Initial nodes +inodes1=Data.body{1}.initial_nodes; +inodes2=Data.body{2}.initial_nodes; + +nodes1=inodes1; +nodes2=inodes2; + +% Retrieve coordinates of nodes making up the interfaces +coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); +coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + +[radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); +[radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + +i1=nnzR12==0; +i2=nnzR21==0; + +% Check for isolated nodes +while any(i1) || any(i2) + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; +end + +[Phi11] = assembleInterpMat(coord1, coord1, radiuses1); +[Phi21] = assembleInterpMat(coord1, coord2, radiuses1); +[Phi22] = assembleInterpMat(coord2, coord2, radiuses2); +[Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + +% Interpolation matrices +R12=Phi12/Phi22; +R21=Phi21/Phi11; + +s1=sum(R12,2); +s2=sum(R21,2); + +% Rescaling +R12=R12./s1; +R21=R21./s2; + +Diff1=R12*coord2-coord1; +Diff2=R21*coord1-coord2; + +i1=ismember(inodes1, nodes1); +i2=ismember(inodes2, nodes2); + +% computes the scalar products +scalar1_g=sum(Diff1.*Data.body{1}.normal(i1,:),2); +scalar2_g=sum(Diff2.*Data.body{2}.normal(i2,:),2); + +% Thresholds are a fraction of the mesh size and could be specific to each +% interface (consider using the mesh sizes of body 1 and 2) +threshold1=-Data.Contact.tol*Data.Contact.h; +threshold2=-Data.Contact.tol*Data.Contact.h; + +% Detect interpenetration +add_nodes1=nodes1(scalar1_ggamma +% Used to ensure evaluation points are well within the +% support of radial basis functions (far enough from the boundary) +Gamma=Data.Contact.Gamma; +% Tolerance for contact detection +% If the tolerance is unknown, set it to some small value. +d0=Data.Contact.d0; +% Number of closest neighbors on current interface +c=Data.Contact.c; + +% Number of off-diagonal nonzeros per row of PhiMM +nnzRMM=zeros(M,1); +% Number of off-diagonal nonzeros per column of PhiMM +nnzCMM=zeros(M,1); +% Number of nonzeros per row of PhiNM +nnzRNM=zeros(N,1); +% Number of nonzeros per column of PhiNM +nnzCNM=zeros(M,1); + +maxS=inf; +f=0; +niter=0; +maxiter=10; + +% Version 2 +while maxS>f && niter= gamma*radius. + % If the point is too close (with respect to the radius), diagonal + % dominance will be lost very quickly. + if radius>rMM(1)/gamma + radius=rMM(1)/gamma; + end + + s1=distMMf + % Increase gamma (sequence converges to 1) + gamma=0.5*(1+gamma); + % Reinitialize counters + nnzRMM=zeros(M,1); + nnzCMM=zeros(M,1); + nnzRNM=zeros(N,1); + nnzCNM=zeros(M,1); + niter=niter+1; + end +end + +if niter==maxiter && maxS>f + warning('The maximum number of rounds for radius computations was reached. Try decreasing d0') +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/gmres_k.m b/Code/INTERNODES_medium/gmres_k.m new file mode 100644 index 0000000..793689d --- /dev/null +++ b/Code/INTERNODES_medium/gmres_k.m @@ -0,0 +1,112 @@ +function[x, residual] = gmres_k(Mesh, Data, linearOp, b, x0) + +% gmres_k: Restarted GMRES algorithm +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% linearOp: Linear Operator implementing matrix-vector multiplication and +% resolution of linear systems with the preconditioner +% b: Right-hand side vector +% x0: Starting vector +% OUTPUT: +% x: Approximate solution +% residual: Vector containing the norms of the residuals + + +% Maximum total number of iterations +maxiter=Data.Solver.maxiter; +% Number of iterations before restart +riter=Data.Solver.riter; +% Reorthogonalization tolerance +reorth_tol=Data.Solver.reorth_tol; +% Tolerance on the norm of the residual +tol=Data.Solver.tol; + +% Initialization +x=x0; +res_error=1; +budget=maxiter; +residual=[]; + +while res_error>tol && budget>0 + [x, beta1, iter] = gmresPrec(b, x, min([riter, budget]), reorth_tol, tol); + residual=[residual; beta1]; + res_error=residual(end); + budget=budget-iter; +end + +if res_error>tol && budget==0 + disp(['The restarted GMRES method did not converge after ' num2str(maxiter-budget) ' iterations']) +end + + + + function [x, varargout] = gmresPrec(b, x0, riter, reorth_tol, tol) + + % gmresPrec: Right preconditioned GMRES + % INPUT: + % b: Right-hand side vector + % x0: Starting vector + % riter: Maximum number of iterations before restart + % reorth_tol: Reorthogonalization tolerance + % tol: Tolerance on the norm of the residual + % OUTPUT: + % x: Approximate solution + % res: Vector containing the norms of the residuals + % k: Number of iterations + % U: Orthonormal Krylov basis + % H: Upper Hessenberg matrix + + + r0=b-linearOp(Mesh, Data, x0, 'matvec'); + U=zeros(length(r0), riter); + H=zeros(riter+1, riter); + res=zeros(riter+1,1); + res(1)=norm(r0); + U(:,1)=r0/res(1); + + + for k=1:riter + w=linearOp(Mesh, Data, U(:,k), 'solve'); + h=U(:,1:k)'*w; + u_tilde=w-U(:,1:k)*h; + + if norm(u_tilde)nb_dof_local + G=reshape(G, nb_nodes, nb_dof_local)'; + G=G(:); + else + G=repmat(G, nb_nodes, 1); + end + U(dofs(:))=G; + + end + + % Update solution + Solution.U=U; + % Reshape to an array of the same size as the coordinate matrix + Solution.U_sort=reshape(U, nb_dof_local, [])'; + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/internodes1.m b/Code/INTERNODES_medium/internodes1.m new file mode 100644 index 0000000..85268c7 --- /dev/null +++ b/Code/INTERNODES_medium/internodes1.m @@ -0,0 +1,65 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to both bodies. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact2_p1_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Dirichlet', 'Edges', 10, 'Function', @(x) [0; -0.1]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'radius', 0.2); +[Data]=setNumericalParameters(Mesh, Data, 'Solver', 'name', 'GMRES', 'riter', 100); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_medium/internodes2.m b/Code/INTERNODES_medium/internodes2.m new file mode 100644 index 0000000..2447f2e --- /dev/null +++ b/Code/INTERNODES_medium/internodes2.m @@ -0,0 +1,66 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to the lower body and Neumann boundary conditions applied to the +% upper body. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact2_p1_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with singular stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Neumann', 'Edges', 10, 'Function', @(x) [0; -1e9]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); + +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'maxiter', 30, 'radius', inf, 'tol', 0.9); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_medium/kr.m b/Code/INTERNODES_medium/kr.m new file mode 100644 index 0000000..874aea5 --- /dev/null +++ b/Code/INTERNODES_medium/kr.m @@ -0,0 +1,31 @@ +function X = kr(U,varargin) +%KR Khatri-Rao product. +% kr(A,B) returns the Khatri-Rao product of two matrices A and B, of +% dimensions I-by-K and J-by-K respectively. The result is an I*J-by-K +% matrix formed by the matching columnwise Kronecker products, i.e. +% the k-th column of the Khatri-Rao product is defined as +% kron(A(:,k),B(:,k)). +% +% kr(A,B,C,...) and kr({A B C ...}) compute a string of Khatri-Rao +% products A o B o C o ..., where o denotes the Khatri-Rao product. +% +% See also kron. + +% Version: 21/10/10 +% Authors: Laurent Sorber (Laurent.Sorber@cs.kuleuven.be) + +if ~iscell(U), U = [U varargin]; end +K = size(U{1},2); +if any(cellfun('size',U,2)-K) + error('kr:ColumnMismatch', ... + 'Input matrices must have the same number of columns.'); +end +J = size(U{end},1); +X = reshape(U{end},[J 1 K]); +for n = length(U)-1:-1:1 + I = size(U{n},1); + A = reshape(U{n},[1 I K]); + X = reshape(bsxfun(@times,A,X),[I*J 1 K]); + J = I*J; +end +X = reshape(X,[size(X,1) K]); diff --git a/Code/INTERNODES_medium/linearOp1.m b/Code/INTERNODES_medium/linearOp1.m new file mode 100644 index 0000000..7eabec9 --- /dev/null +++ b/Code/INTERNODES_medium/linearOp1.m @@ -0,0 +1,107 @@ +function[y]=linearOp1(Mesh, Data, x, type) + +% linearOp1: Wrap around function for operations related to the application +% of linear operators +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% type: Type of operation to perform +% OUTPUT: +% y: Result of the operation + +switch type + case 'solve' + y=solvePrec(Mesh, Data, x); + y=multiplyMatVec(Mesh, Data, y); + + case 'solvePrec' + y=solvePrec(Mesh, Data, x); + + case 'matvec' + y=multiplyMatVec(Mesh, Data, x); +end + + + function[y]=multiplyMatVec(Mesh, Data, x) + + % multiplyMatVec: Implements the mutiplication between the + % INTERNODES matrix A and a vector x and stores the result in y + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % x: Vector + % OUTPUT: + % y: Result of the multiplication of A with x + + % Retrieve data parameters + % Number of free degrees of freedom + nf=Data.nf; + % Number of constraints + nc=Data.nc; + + % Initialization + y=zeros(nf+nc,1); + + y(1:nf)=Data.K*x(1:nf)+multiplyB(Mesh, Data, x(nf+1:nf+nc), 'vec'); + y(nf+1:nf+nc)=multiplyB_tilde(Mesh, Data, x(1:nf), 'vec'); + end + + function[y]=solvePrec(Mesh, Data, x) + + % solvePrec: Implements the resolution of a linear system with the + % preconditioner and a right-hand side vector x and stores the result in y + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % x: Right-hand side vector + % OUTPUT: + % y: Solution of the linear system + + + % Retrive mesh parameters + d=Mesh.nb_dof_local; + % Parameter k + k=2; + + % Retrieve data parameters + % Cholesky factor of the reordered perturbed stiffness matrix + Lr=Data.Lr; + L22=Data.L22; + % Number of free degrees of freedom + nf=Data.nf; + % Number of constraints + nc=Data.nc; + % Perturbed reordering of Nested Dissection + or=Data.or; + % Size of block 1 + b1=Data.b1; + + % Initialization + y=zeros(nf+nc,1); + w1=zeros(nf,1); + + p1=x(1:nf); + p2=x(nf+1:nf+nc); + + w2=reshape(p2, d, []); + w2=-Data.Contact.alpha*(w2/Data.body{1}.interface_mass); + + p1=p1-k*multiplyB(Mesh, Data, w2, 'mat'); + + % Method for small problems + Lv=Data.Lv; + Uv=Data.Uv; + + p2=Lr\p1(or); + p3=p2(b1+1:end); + p3=L22*p3; + p3(Data.orc)=Uv\(Lv\p3(Data.orl)); + p2(b1+1:end)=L22'*p3; + w1(or)=Lr'\p2; + + y(1:nf)=w1; + y(nf+1:nf+nc)=w2; + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/mapGlobal2Local.m b/Code/INTERNODES_medium/mapGlobal2Local.m new file mode 100644 index 0000000..6b14a3a --- /dev/null +++ b/Code/INTERNODES_medium/mapGlobal2Local.m @@ -0,0 +1,13 @@ +function[Data]=mapGlobal2Local(Data) + +% mapGlobal2Local: Function which maps the global degrees of freedom to the +% local ones for the INTERNODES matrix +% INPUT: +% Data: Structure containing all the data parameters +% OUTPUT: +% Data: Updated structure with the local degrees of freedom + +Nf=Data.Nf; +[~, Data.body{1}.indx_local, ~]=intersect(Nf, Data.body{1}.interface_dof); +[~, Data.body{2}.indx_local, ~]=intersect(Nf, Data.body{2}.interface_dof); +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/multiplyB.m b/Code/INTERNODES_medium/multiplyB.m new file mode 100644 index 0000000..0a2ccd4 --- /dev/null +++ b/Code/INTERNODES_medium/multiplyB.m @@ -0,0 +1,24 @@ +function[y]=multiplyB(Mesh, Data, x, in) + +% multiplyB: Implements the mutiplication between B and a vector x and +% stores the result in y +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector or matrix +% in: Parameter specifiying whether the input is a vector or a matrix +% OUTPUT: +% y: Result of the multiplication of B with x + +if strcmp(in, 'vec') +x=reshape(x, Mesh.nb_dof_local, []); +end + +y=zeros(Data.nf,1); + +y1=-x*Data.body{1}.interface_mass; +y2=(x*Data.R21')*Data.body{2}.interface_mass; + +y(Data.body{1}.indx_local)=y1(:); +y(Data.body{2}.indx_local)=y2(:); +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/multiplyB_tilde.m b/Code/INTERNODES_medium/multiplyB_tilde.m new file mode 100644 index 0000000..c3d09ba --- /dev/null +++ b/Code/INTERNODES_medium/multiplyB_tilde.m @@ -0,0 +1,26 @@ +function[y]=multiplyB_tilde(Mesh, Data, x, out) + +% multiplyB_tilde: Implements the mutiplication between B_tilde and a +% vector x and stores the result in y +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% x: Vector +% out: Parameter specifying whether the output should be a vector or +% a matrix +% OUTPUT: +% y: Result of the multiplication of B_tilde with x + +% Retrieve mesh parameters +% Number of local degrees of freedom +d=Mesh.nb_dof_local; + +w1=reshape(x(Data.body{1}.indx_local,:), d, []); +w2=reshape(x(Data.body{2}.indx_local,:), d, []); + +y=w1-w2*Data.R12'; + +if strcmp(out, 'vec') + y=y(:); +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/plotSolution.m b/Code/INTERNODES_medium/plotSolution.m new file mode 100644 index 0000000..47ba494 --- /dev/null +++ b/Code/INTERNODES_medium/plotSolution.m @@ -0,0 +1,813 @@ +function[]=plotSolution(Mesh, Data, Solution, varargin) + +% plotSolution: Function which plots the computed quantities +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% Solution: Structure containing the solution computed +% OPTIONAL INPUT +% snapshot: selected time-step at which the solution should be viewed +% 1 -> none (default) +% any time step in the range defined +% view_video: view the video of the solution +% 1 -> yes +% 0 -> no (default) +% save_video: choose whether to save the video or not +% 1 -> yes +% 0 -> no (default) +% plot_arg: choose which solution to visualize +% dynamic_solid or static_solid module +% disp (default) +% stress +% strain +% transient_heat module +% temp (default) +% flux +% plot_type: decide which type of metric should be used for visualization +% displacement metric +% standard -> structure with amplified displacements (default) +% norm +% absx +% absy +% temperature metric +% standard (default) +% stress metric +% von_mises (default) +% sigma_xx +% sigma_yy +% sigma_xy +% sigma_xx_abs +% sigma_yy_abs +% sigma_xy_abs +% frobenius +% strain metric +% epsilon_xx (default) +% epsilon_yy +% epsilon_xy +% epsilon_xx_abs +% epsilon_yy_abs +% epsilon_xy_abs +% frobenius +% flux metric +% standard (default) + + +%% Initialize and ckeck Parameters + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +% Specify number of sub-intervals in time +if contains(Data.Model.name, 'static') + static=true; + N=0; +else + static=false; + N=Data.Discretization.N; +end + +switch Data.Model.name + case {'static_solid', 'dynamic_solid'} + plot_argv={'disp', 'stress', 'strain'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard', 'norm', 'absx', 'absy'}; + plot_typev{2}={'von_mises', 'sigma_xx', 'sigma_yy', 'sigma_xy', 'sigma_xx_abs', 'sigma_yy_abs', 'sigma_xy_abs', 'frobenius'}; + plot_typev{3}={'epsilon_xx', 'epsilon_yy', 'epsilon_xy', 'epsilon_xx_abs', 'epsilon_yy_abs', 'epsilon_xy_abs', 'frobenius'}; + + case 'transient_heat' + plot_argv={'temp', 'flux'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard'}; + plot_typev{2}={'standard'}; + +end + +% Set all default parameters +% Default visualization parameters +Param.visua_param.snapshot=1; +Param.visua_param.view_video=false; +Param.visua_param.write_video=false; +% Default plot argument +Param.plot_param.arg=plot_argv(1); +% Default plot type +Param.plot_param.type=plot_typev{1}(1); + +plot_arg={}; +plot_type={}; +nb_plot=0; + +for k=1:length(labels_in) + arg=lower(labels_in{k}); + val=values_in{k}; + + switch arg + case 'snapshot' + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + elseif val < 0 || val > Data.Discretization.N+1 + error(['Time snapshot invalid: it must be an integer between ' num2str(0) ' and ' num2str(Data.Discretization.N+1)]) + else + Param.visua_param.snapshot=val; + end + + case {'view_video', 'write_video'} + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + else + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Param.visua_param.view_video=logical(val); + end + + elseif isa(val, 'logical') + Param.visua_param.view_video=val; + else + error([arg ' parameter must be a boolean']) + end + end + + case plot_argv + + % Check if desired quantities have been computed + switch arg + case 'stress' + if ~isfield(Solution, 'sigma') + error('Stresses have not been computed') + end + + case 'strain' + if ~isfield(Solution, 'epsilon') + error('Strains have not been computed') + end + + case 'flux' + if ~isfield(Solution, 'Q') + error('Fluxes have not been computed') + end + end + + plot_arg{nb_plot+1}=arg; + index=strcmp(arg, plot_argv); + + if any(ismember(plot_typev{index}, val)) + plot_type{nb_plot+1}=val; + else + error('Plot type invalid: see available plot types') + end + + nb_plot=nb_plot+1; + otherwise + error('Unrecognized argument') + end + +end + +if nb_plot>0 + Param.plot_param.arg=plot_arg; + Param.plot_param.type=plot_type; +end + +%% Plotting interface + +plot_arg=Param.plot_param.arg; +plot_type=Param.plot_param.type; +n=length(plot_arg); +Plot=cell(n,1); +Axis=cell(n,1); +Colorbar=cell(n,1); +Dynamic=cell(n,1); + +fig=figure; +% fig.Units='normalized'; +% fig.Position=[0 0 1 1]; + +% fig.Units='normalized'; +% fig.Position=[0 1 1/2 1/2]; + +for k=1:n + switch plot_arg{k} + case {'disp', 'temp'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case {'strain', 'stress'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, plot_arg{k}, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case 'flux' + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=quiver(axis, PlotParam{1,2}, PlotParam{2,2}); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + end + + Plot{k}=p; + Axis{k}=axis; + Colorbar{k}=c; + Dynamic{k}=DynamicParam; +end + +if Param.visua_param.view_video % View the solution over the entire simulation + + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, 1, N); + end + + frames(1)=getframe(fig); + for step = 2:N+1 + pause(0.05) + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + refreshdata(Axis{k}); + end + frames(step)=getframe(fig); + end + +else % View solution at a particular time snapshot + step=Param.visua_param.snapshot; + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + end +end + + +if Param.visua_param.write_video +% vid_duration=25; % seconds + vid_duration=10; % seconds + video=VideoWriter('Output.mp4', 'MPEG-4'); + video.FrameRate=(N+1)/vid_duration; + open(video); + writeVideo(video,frames); + close(video); +end + +%% Parameters for patch plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, type) + + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Total number of nodes + nb_nodes=Mesh.nb_nodes; + % Frame surrounding the structure + frame=Mesh.frame; + % Largest dimension + L_max=Mesh.L_max; + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + + % Retrieve solution + U=Solution.U; + + + switch Data.Model.name + + case {'static_solid', 'dynamic_solid'} + + % Initilization + Vertices=zeros(nb_nodes,2,N+1); + CData=cell(N+1,1); + + + switch type + case 'standard' + + if strcmp(Data.PostProcessing.amplification, 'auto') + % Maximum displacement + disp_max=max(max(abs(U))); + % Amplification factor + fact_ampl=L_max/(2*disp_max); + else + fact_ampl=Data.PostProcessing.amplification; + end + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + % Aplified displacements + ux_ampl=fact_ampl*U(d1,:); + uy_ampl=fact_ampl*U(d2,:); + % Deformed state + def_x=coord(:,1)+ux_ampl; + def_y=coord(:,2)+uy_ampl; + % Vertices in deformed state + Vertices(:,1,:)=def_x; + Vertices(:,2,:)=def_y; + XLim=[min(def_x, [], 'all'); + max(def_x, [], 'all')]; + YLim=[min(def_y, [], 'all'); + max(def_y, [], 'all')]; + CLim=[0,1]; + Colormap=[]; + ColorbarStatus='off'; + ColorbarLabel=''; + +% Title='Amplified displacements'; + Title='Displacements'; + EdgeColor='black'; + FaceColor='none'; + + case 'norm' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + U_norm=sqrt(Ux.^2+Uy.^2); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(U_norm, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Norm of displacements'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=U_norm(:,m); + end + + case 'absx' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + Ux_abs=abs(Ux); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Ux_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along x'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Ux_abs(:,m); + end + + case 'absy' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + Uy_abs=abs(Uy); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Uy_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along y'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Uy_abs(:,m); + end + + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + case 'transient_heat' + + % Number of sub-intervals in time + N=Data.Discretization.N; + % Initilization + CData=cell(N+1,1); + + switch type + case 'standard' + + XLim=frame(:,1); + YLim=frame(:,2); + CLim=[min(U, [], 'all') max(U, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Temperature [ºC]'; + % ColorbarLabel='Temperature [K]'; + Title='Temperature'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + CData{m}=U(:,m); + end + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'CData', CData;... + 'Title', Title}; + + end + + + end + +%% Parameters for surf plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, arg, type) + + % Retrieve mesh parameters + % Coordinate matrix of interpolation nodes + coord_inter=Mesh.coord_inter; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve data parameters + % Number of evaluation points per element + nq=Data.PostProcessing.eval_points; + + % Total number of interpolation nodes + nb_nodes_inter=size(coord_inter,1); + + % Initilization + Vertices=zeros(nb_nodes_inter,2,N+1); + CData=cell(N+1,1); + + switch arg + + case 'stress' + + % Retrieve solution + sigma=permute(Solution.sigma, [2 1 3]); + + switch type + case 'von_mises' + + % Von Mises stress + select=sqrt(sigma(:,1,:).^2-sigma(:,1,:).*sigma(:,2,:)+sigma(:,2,:).^2+3*sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Von Mises stress field'; + + case 'sigma_xx' + + % Value of sigma_xx + select=sigma(:,1,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xx}'; + + case 'sigma_yy' + + % Value of sigma_yy + select=sigma(:,3,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{yy}'; + + case 'sigma_xy' + + % Value of sigma_xy + select=sigma(:,2,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xy}'; + + case 'sigma_xx_abs' + + % Abolute value of sigma_xx + select=abs(sigma(:,1,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xx}|'; + + case 'sigma_yy_abs' + + % Absolute value of sigma_yy + select=abs(sigma(:,3,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{yy}|'; + + case 'sigma_xy_abs' + + % Absolute value of sigma_xy + select=abs(sigma(:,2,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xy}|'; + + case 'frobenius' + + % Frobenius norm of sigma + select=sqrt(sigma(:,2,:).^2+2*sigma(:,2,:).^2+sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Frobenius norm of \sigma'; + + end + + case 'strain' + + % Retrieve solution + epsilon=permute(Solution.epsilon, [2 1 3]); + + switch type + case 'epsilon_xx' + + % Value of epsilon_xx + select=epsilon(:,1,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xx}'; + + case 'epsilon_yy' + + % Value of epsilon_yy + select=epsilon(:,3,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{yy}'; + + case 'epsilon_xy' + + % Value of esilon_xy + select=epsilon(:,2,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xy}'; + + case 'epsilon_xx_abs' + + % Abolute value of epsilon_xx + select=abs(epsilon(:,1,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xx}|'; + + case 'epsilon_yy_abs' + + % Absolute value of epsilon_yy + select=abs(epsilon(:,3,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{yy}|'; + + case 'epsilon_xy_abs' + + % Absolute value of epsilon_xy + select=abs(epsilon(:,2,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xy}|'; + + case 'frobenius' + + % Frobenius norm of epsilon + select=sqrt(epsilon(:,2,:).^2+2*epsilon(:,2,:).^2+epsilon(:,3,:).^2); + ColorbarLabel='Strain [-]'; + Title='Frobenius norm of \epsilon'; + + end + + end + + % Common parameters + XLim=[min(coord_inter(:,1,:), [], 'all'); + max(coord_inter(:,1,:), [], 'all')]; + YLim=[min(coord_inter(:,2,:), [], 'all'); + max(coord_inter(:,2,:), [], 'all')]; + CLim=[min(select, [], 'all') max(select, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=coord_inter(:,1,m); + Vertices(:,2,m)=coord_inter(:,2,m); + CData{m}=select(:,m); + end + + + switch nq + case 3 + % 3-point evaluation inside each element + I=1:3*Mesh.nb_elem; + I=reshape(I, 3, Mesh.nb_elem)'; + + case 6 + % 6-point evaluation inside each element + i1=[1 4 5 2 2 5 6 3 3 6 4 1]; + i2=[4 5 6]; + I1=i1+(0:Mesh.nb_elem-1)'*6; + I2=i2+(0:Mesh.nb_elem-1)'*6; + I1=reshape(I1', 4, 3*Mesh.nb_elem)'; + I2=[I2 nan*zeros(size(I2,1),1)]; + I=[I1; I2]; + end + + % Set static parameters + PlotParam = {'Vertices', coord_inter;... + 'Faces', I;... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + end + +%% Parameters for quiver plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, type) + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve solution + Q_sort=Solution.Q_sort; + + % Initialization + U=cell(1,N+1); + V=cell(1,N+1); + + switch type + case 'standard' + % Standard visualization of fluxes + Title='Flux field'; + + for m=1:N+1 + U{m}=Q_sort(:,2*m-1); + V{m}=Q_sort(:,2*m); + end + + end + + + % Set static parameters + PlotParam = {'XData', coord(:,1);... + 'YData', coord(:,2);... + 'LineWidth', 1}; + + AxisParam = {'XLim', frame(:,1);... + 'YLim', frame(:,2);... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', 'off';... + 'Label.String', '';... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'UData', U;... + 'VData', V;... + 'Title', Title}; + + + end + + + + + + +%% Setting and updating graphic properties + function[object]=setObject(object, param) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument) + if contains(argument{i}, '.') + field=extractBetween(argument{i}, 1, '.'); + subfield=extractAfter(argument{i}, '.'); + object.(field{1}).(subfield)=value{i}; + else + object.(argument{i})=value{i}; + end + end + + end + + function[plot, axis]=updateObject(plot, axis, param, step, N) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument)-1 + plot.(argument{i})=value{i}{step}; + end + + axis.Title.String=[value{end} ' at step ' num2str(step) ' out of ' num2str(N+1)]; + end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/postProcess.m b/Code/INTERNODES_medium/postProcess.m new file mode 100644 index 0000000..364859c --- /dev/null +++ b/Code/INTERNODES_medium/postProcess.m @@ -0,0 +1,18 @@ +function[Mesh, Solution]=postProcess(Mesh, Data, Solution) + +% postProcess: General post-processing function used to compute derived +% quantities +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Mesh: Updated mesh structure with coordinates of nodes where stresses +% and strains were evaluated +% Solution: Updated solution structure with derived quantities + + +if any(contains(Data.PostProcessing.quantity, {'stress', 'strain'})) + [Mesh, Solution]=postProcessDisp(Mesh, Data, Solution); +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/postProcessDisp.m b/Code/INTERNODES_medium/postProcessDisp.m new file mode 100644 index 0000000..f1f6613 --- /dev/null +++ b/Code/INTERNODES_medium/postProcessDisp.m @@ -0,0 +1,147 @@ +function[Mesh, Solution]=postProcessDisp(Mesh, Data, Solution) + +% postProcessDisp: Function used to compute strains and stresses once the +% displacements are known. +% INPUT: +% Mesh: Structure containing all mesh parameters +% Data: Structure containing all data parameters +% Solution: Structure containing the computed solutions +% OUTPUT: +% Mesh: Updated mesh structure with evaluation points coordinates +% Solution: Updated solution structure including strains and stresses + + +% Finite element order +order=Mesh.order; +% Number of local degrees of freedom +d=Mesh.nb_dof_local; +% Number of elements +ne=Mesh.nb_elem; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Retrieve basis functions +[Functions]=getFunctions(order); +% Retrieve Jacobian matrix +J_phi=Functions.J_phi; +% Number of evaluation points per element +nq=Data.PostProcessing.eval_points; +% Retrieve evaluation points +[points, ~] = getQuadrature(nq, 'interpolation'); +% Number of independent strain and stress components +Mesh.nb_comp_ind=1/2*d*(d+1); +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; +% Retrieve solution +U_sort=Solution.U_sort; +% Compute stresses and strains using current position +coord=coord+U_sort; + +% Define permutation matrix +Sdd=zeros(d*d); +Id1=eye(d); +Id2=eye(d^2); + +for k=1:d + Sdd=Sdd+kron(Id1(:,k)', kron(Id1, Id1(:,k))); +end + +% Reduce size of matrices to avoid unnecessary computations +% sigma_xx, sigma_xy, sigma_yy +h=[1 2 4]; + +I1=(Id2+Sdd); +I2=Id1(:); + +% Truncate the matrices +I1=I1(h,:); +I2=I2(h); + +% Initialization +Q=zeros(nq*d^2, nne*d); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:))', Id1); + Q((i-1)*d^2+1:i*d^2, :)=L; +end +Q=sparse(Q); +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Displacements of the nodes of the elements +W=reshape(U_sort(G,:)', nne*d, ne); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); +coord_inter=reshape(P', nq*ne, d); +% Update mesh +Mesh.coord_inter=coord_inter; + +% Computation of geometric quantities +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, 1, d^2*ne); + +% Coefficient evaluation +F1=f1(X); +F2=f2(X); +if size(F1,1)>1 +F1=reshape(F1, 1, nq, ne); +end +if size(F2,1)>1 +F2=reshape(F2, 1, nq, ne); +end + +% Matrix product computations +W=reshape(Q*W, d^2, nq, ne); + +B1=reshape(product(JK_invT,repmat(Id1, 1, ne),d,ne), d^2, d^2, ne); +B2=reshape(vec_JK_invT, 1, d^2, ne); + +% Computation of strains +if any(contains(Data.PostProcessing.quantity, 'strain')) + E=pagemtimes(B1, W); + E=0.5*I1*reshape(E, d^2, nq*ne); + Solution.epsilon=E; +end + +% Computation of stresses +if any(contains(Data.PostProcessing.quantity, 'stress')) + S1=pagemtimes(B1, F1.*W); + S2=pagemtimes(B2, F2.*W); + S1=I1*reshape(S1, d^2, nq*ne); + S2=I2*reshape(S2, 1, nq*ne); + S=S1+S2; + Solution.sigma=S; +end + + + function[M]=product(A,B,d,ne) + + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/preCompute.m b/Code/INTERNODES_medium/preCompute.m new file mode 100644 index 0000000..7003bd1 --- /dev/null +++ b/Code/INTERNODES_medium/preCompute.m @@ -0,0 +1,57 @@ +function[Data]=preCompute(Mesh, Data) + +% preCompute: Function which pre-computes quantities needed in a single +% iteration of the contact algorithm +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OUTPUT: +% Data: Updated data structure + +% Number of local degrees of freedom +d=Mesh.nb_dof_local; +% Retrieve bodies +body=Data.body; +% Retrieve local degrees of freedom +indx1=body{1}.indx_local; +indx2=body{2}.indx_local; +% alpha Parameter +alpha=Data.Contact.alpha; + +l1=length(indx1); +% Block size +b2=Data.b2; + +i2i=Data.i2; +i2=[indx1; indx2]; +% Beware of the ordering in the linear operators +[~, index, reor]=intersect(i2i, i2, 'stable'); + +%% Solution method for small systems + +M1=kron(body{1}.interface_mass, speye(d)); +M2=kron(body{2}.interface_mass, speye(d)); + +R12=kron(Data.R12, speye(d)); +R21=kron(Data.R21, speye(d)); + +Q1=-M2*(R21/M1); +Q2=-R12'; + +Y1=[eye(l1); Q1]; +Y2=[eye(l1); Q2]; +C=-Y1*Y2'; + +Z=sparse(b2, b2); +Z(index,index)=C(reor, reor); + +V=Data.A+alpha*Z; +Data.V=V; + +[Lv, Uv, orl, orc]=lu(V, 'vector'); + +Data.Lv=Lv; +Data.Uv=Uv; +Data.orl=orl; +Data.orc=orc; +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/preProcess.m b/Code/INTERNODES_medium/preProcess.m new file mode 100644 index 0000000..9a757eb --- /dev/null +++ b/Code/INTERNODES_medium/preProcess.m @@ -0,0 +1,81 @@ +function[Data]=preProcess(Data, Solution, K) + +% preProcess: Pre-computes the Cholesky factor of the perturbed stiffness +% matrix and part of the right-hand side vector. These quantities do not +% change during the course of the contact algorithm +% INPUT: +% Data: Structure containing all the data parameters +% Solution: Structure containing the initialized solution +% K: Global stiffness matrix +% OUTPUT: +% Data: Updated data structure with part of the right-hand side vector, +% reordering indices, Cholesky factors and matrices + +% Retrieve data parameters +% Free degrees of freedom +Nf=Data.Nf; +% Blocked degrees of freedom +Nd=Data.Nd; +% Retrieve bodies +body=Data.body; +% Rescaling parameter +r=Data.Contact.rescaling; +% Perturbation added to the stiffness matrix to make it SPD +Data.Contact.epsilon=1e-8; +% Right-hand side force vector +f=Data.F; +% Number of free degrees of freedom +nf=length(Nf); +% Retrieve local degrees of freedom +indx1=body{1}.indx_local; +indx2=body{2}.indx_local; + +l1=length(indx1); +l2=length(indx2); + +% Retrieve solution +U=Solution.U; + +% Rescaled stiffness matrix +Kff=1/r*K(Nf, Nf); +% Perturbed stiffness matrix +K_tilde=Kff+Data.Contact.epsilon*speye(nf,nf); +% Cholesky decomposition of the perturbed stiffness matrix +p=dissect(K_tilde); + +% Block sizes for partitioning +b1=nf-(l1+l2); +b2=l1+l2; + +% Cholesky decomposition of the reordered perturbed stiffness matrix +i2=[indx1; indx2]; +or=[setdiff(p, i2, 'stable')'; i2]; +Kr=K_tilde(or, or); +Lr=chol(Kr, 'lower'); +L22=Lr(b1+1:end,b1+1:end); +Data.L22=L22; +Data.i2=i2; +Data.or=or; +Data.A=L22*L22'; + +% Right-hand side +bc=1/r*(f(Nf)-K(Nf,Nd)*U(Nd)); + +% Block sizes for partitioning +Data.b1=b1; +Data.b2=b2; + +% Number of free degrees of freedom +Data.nf=nf; + +% Parameter alpha +if strcmp(Data.Contact.alpha, 'auto') + Data.Contact.alpha=-norm(Kff, Inf); +end +% Rescaled stiffness matrix +Data.K=Kff; +% Cholesky factor of the reordered perturbed stiffness matrix +Data.Lr=Lr; +% First vector component of the RHS vector +Data.bc=bc; +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/readGMSH.m b/Code/INTERNODES_medium/readGMSH.m new file mode 100755 index 0000000..21425ca --- /dev/null +++ b/Code/INTERNODES_medium/readGMSH.m @@ -0,0 +1,86 @@ +function [Mesh] = readGMSH(file_name) + +% readGMSH: Reads a GMSH mesh file (.msh) and constructs the coordinate, +% connectivity, boundary nodes, material tag and boundary tag matrices +% INPUT: +% file_name: GMSH mesh file (.msh) +% OUTPUT: +% Mesh: Structure containing all fields related to the geometry, boundary +% conditions and material tags +% coord: Matrix of node coordinates +% connectivites: Connectivity matrix of the system +% boundary_nodes: Matrix containing the boundary nodes (both displacements +% and external forces) +% material_tag: Vector containing the material tag of the elements +% boundary_tag: Vector containing the boundary tags of the elements +% forming boundaries (enables to make the distinction between Dirichlet +% and Neumann boundary conditions) + +fileID = fopen(file_name); +text = textscan(fileID, '%s', 'delimiter', '\n'); +fclose(fileID); + +text=text{1}; +% Construction of the coordinate matrix +nodes_start = findPos(text, '$Nodes') + 1; + +fileID = fopen(file_name); +% Node coordinates +coord = textscan(fileID, '%f %f %f %f', 'HeaderLines', nodes_start); +% Elements +connect = textscan(fileID, [repmat('%f', 1, 20) '%*[^\n]'], 'HeaderLines', 3, 'EndOfLine', '\n', 'CollectOutput', 1); +fclose(fileID); + +% Coordinate matrix +coord=cell2mat(coord); +% Ignore z component +coord=coord(:,2:end-1); + +% Connectivity matrix +connect=cell2mat(connect); + +I=sum(~isnan(connect), 2); +v=unique(I); + +% Boundary elements +B=connect(I==v(1), 1:v(1)); +% Elements +E=connect(I==v(2), 1:v(2)); + +% Boundary tags +B_tags=B(:,4); +% Element tags +E_tags=E(:,4); + +% Boundary elements connectivity matrix +B_connect=B(:,6:end); +% Elements connectivity matrix +E_connect=E(:,6:end); + +% Initialize mesh structure +Mesh.coord_mat=coord; +Mesh.BC_nodes=B_connect; +Mesh.connect_mat=E_connect; +Mesh.BC_tag=B_tags; +Mesh.material_tag=E_tags; + +% Identification of the type of element in GMSH +% 2 -> T3 +% 9 -> T6 +% 21 -> T10 +% 1 -> line +% 8 -> quadratic curve +% 26 -> cubic curve +end + + +function[start]=findPos(text, string) +lines = size(text, 1); +start = 1; +for i = 1:lines + if strcmp(text{i}, string) + start = i; + break + end +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/removeTraction.m b/Code/INTERNODES_medium/removeTraction.m new file mode 100644 index 0000000..b96c60f --- /dev/null +++ b/Code/INTERNODES_medium/removeTraction.m @@ -0,0 +1,71 @@ +function[Data]=removeTraction(Mesh, Data, Solution, n) + +% removeTraction: Checks for convergence and updates the interface properties +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% n: Current iteration number +% OUTPUT: +% Data: Updated data structure with updated interface properties + +% Compute the normals based on deformed structure +% Normals for initial interface +[Data]=computeNormals(Mesh, Data, Solution); + +normal1=Data.body{1}.normal; +normal2=Data.body{2}.normal; + +% Retrieve Lagrange multipliers +lambda_sort1=Solution.lambda_sort; + +% Project the lambdas on interface 2 +lambda_sort2=-Data.R21*lambda_sort1; + +% Computes the scalar products +scalar1=sum(lambda_sort1.*normal1(Data.body{1}.active_set,:),2); +scalar2=sum(lambda_sort2.*normal2(Data.body{2}.active_set,:),2); + +% Detect nodes to be removed from the interfaces +dump_nodes1=Data.body{1}.interface_nodes(scalar1>0); +dump_nodes2=Data.body{2}.interface_nodes(scalar2>0); + +% Update the interfaces +% If only compression test penetration, otherwise remove nodes in traction +if isempty(dump_nodes1) && isempty(dump_nodes2) + % Gap verification + [add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution); + + [Data, nodediff1]=updateInterface(Mesh, Data, 1, add_nodes1, 'add'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, add_nodes2, 'add'); + +else + % Skip gap verification + [Data, nodediff1]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); +end + +fprintf('Iteration %d: \n', n) +fprintf('%+d nodes on interface 1 \n', nodediff1) +fprintf('%+d nodes on interface 2 \n', nodediff2) +fprintf('----------------------------------- \n') + +% Check for convergence failure +if isempty(Data.body{1}.interface_nodes) || isempty(Data.body{2}.interface_nodes) + msg=['The contact algorithm stopped prematurely because at least one of ' ... + 'the sets of nodes of the potential contact interface is empty. '... + 'Potential causes for this error are:\n']; + + causes=['1) The boundary conditions are incorrect \n'... + '2) The potential contact interface was misidentified \n'... + '3) The mesh is too coarse \n']; + + error(sprintf([msg, causes])) +end + +% Check for convergence +if abs(nodediff1)+abs(nodediff2)==0 % Stop the iterations + Data.Contact.iterate=0; + disp('Successfully converged') +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/setBoundaryConditions.m b/Code/INTERNODES_medium/setBoundaryConditions.m new file mode 100644 index 0000000..2d375dc --- /dev/null +++ b/Code/INTERNODES_medium/setBoundaryConditions.m @@ -0,0 +1,267 @@ +function[Data]=setBoundaryConditions(Mesh, Data, varargin) + +% setBoundaryConditions: Function which initializes the boundary data +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OPTIONAL INPUT: +% Any type of boundary condition. If no Dirichlet boundary conditions are +% specified, the program returns an error +% Types of boundary conditions: +% Dirichlet +% Neumann +% OUTPUT: +% Data: Updated data structure with boundary conditions + + +%% Set boundary conditions +if mod(length(varargin{1}), 2)~=0 + error('Missing a name or value') +end + +% Retrieve boundary nodes +BC_tags=unique(Mesh.BC_tag); + +% Dirichlet type +D_types={'dirichlet'}; +% Neumann types +N_types={'neumann'}; +% Available types of boundary conditions +available_types=union(D_types, N_types); +% Count to detect duplicate boundaries +count=[]; +% Count number of Dirichlet and Neumann boundaries specified +nb_D_BC=0; +nb_N_BC=0; + +% Retrieve labels and values entered by the user +labels_in=varargin{1}(1:2:end); +values_in=varargin{1}(2:2:end); + +% Initilization +Data.BC=[]; + +% Checking for boundary conditions +c1=strcmp(labels_in, 'BC'); + +if any(c1) + BC=values_in{c1}; + nb_BC=length(BC); + Data.BC=cell(nb_BC,1); + + for i=1:nb_BC + bc=BC{i}; + l=length(bc); + + if mod(l,2)~=0 + error('Missing a name or value') + end + + for j=1:2:l + arg=lower(bc{j}); + val=bc{j+1}; + + switch arg + + case 'type' + if any(ismember(available_types, lower(val))) + Data.BC{i}.(arg)=lower(val); + + if any(ismember(D_types, lower(val))) + nb_D_BC=nb_D_BC+1; + else + nb_N_BC=nb_N_BC+1; + end + else + error('Unrecognized boundary condition') + end + + case 'edges' + + edges=intersect(BC_tags, val); + + if isempty(edges) + error('One of the edges specified does not exist') + elseif ~isempty(intersect(count, val)) || length(unique(val))~=length(val) + error('Duplicate edges detected') + end + count=[count val]; + Data.BC{i}.(arg)=val; + + case {'function'} + + if isa(val,'double') + Data.BC{i}.label='const'; + Data.BC{i}.(arg)= @(x) val; + + elseif isa(val,'function_handle') + Data.BC{i}.label=detectDependency(val); + Data.BC{i}.(arg)=val; + else + error('Input parameters must be either constants or function handles') + end + + otherwise + error('Unrecognized argument') + end + + end + + + end + +end + +%% Post-Processing: Partitioning the structure BC into Dirichlet BC and Neumann BC and checking for missing data + +if nb_D_BC==0 + error('Dirichlet boundary conditions must be prescribed') +end + +BC=Data.BC; +l=length(BC); + +% Initialize cells +D_BC=cell(nb_D_BC,1); +N_BC=cell(nb_N_BC,1); +% Set of Dirichlet and Neumann tags +D_tags=[]; +N_tags=[]; +% Initialize counters +d=1; +n=1; + +for i=1:l + bc=BC{i}; + type=bc.type; + + if ~isfield(bc, 'edges') + error('The edges must be specified') + end + + if any(ismember(D_types, type)) + D_tags=[D_tags bc.edges]; + D_BC{d}.type=type; + D_BC{d}.edges=bc.edges; + + if isfield(bc, 'function') + D_BC{d}.function=bc.function; + D_BC{d}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + D_BC{d}.function=@(x) [0; 0]; + D_BC{d}.label='const'; + end + + d=d+1; + + elseif any(ismember(N_types, type)) + N_tags=[N_tags bc.edges]; + N_BC{n}.type=type; + N_BC{n}.edges=bc.edges; + + + if isfield(bc, 'function') + N_BC{n}.function=bc.function; + N_BC{n}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + N_BC{n}.function=@(x) [0; 0]; + N_BC{n}.label='const'; + end + + n=n+1; + end +end + +Data.D_BC=D_BC; +Data.N_BC=N_BC; + +Data.D_tags=D_tags; +Data.N_tags=N_tags; + +%% Sorting nodes +% If there is a node belonging to conflicting boundary conditions, it is +% set using the following priority list: Dirichlet, Neumann, Interior +BC_nodes=Mesh.BC_nodes; +BC_tag=Mesh.BC_tag; +nb_nodes=Mesh.nb_nodes; +Dof_set=Mesh.Dof_set; + +set_D_nodes=cell(nb_D_BC,1); +set_N_nodes=cell(nb_N_BC,1); + +for k=1:nb_D_BC + nodes=BC_nodes(any(BC_tag==D_BC{k}.edges,2),:); + set_D_nodes{k}=unique(nodes); +end +for k=1:nb_N_BC + nodes=BC_nodes(any(BC_tag==N_BC{k}.edges,2),:); + set_N_nodes{k}=unique(nodes); +end +% Define the sets of nodes +% Dirichlet nodes +Nodes_D=unique(cell2mat(set_D_nodes)); +% Neumann nodes +Nodes_N=setdiff(unique(BC_nodes), Nodes_D); +% Interior nodes +Nodes_I=setdiff(1:nb_nodes, union(Nodes_D, Nodes_N)); +% Free nodes +Nodes_F=union(Nodes_I, Nodes_N); + +% Correct the set for Neumann nodes +for k=1:nb_N_BC + set_N_nodes{k}=setdiff(set_N_nodes{k}, Nodes_D); +end + + +% Define the sets of degrees of freedom +Nd=Dof_set(:,Nodes_D); +Nn=Dof_set(:,Nodes_N); +Ni=Dof_set(:,Nodes_I); + +Ni=Ni(:); +Nd=Nd(:); +Nn=Nn(:); +Nf=union(Ni, Nn); + +% Update data +Data.Nodes_I=Nodes_I; +Data.Nodes_N=Nodes_N; +Data.Nodes_D=Nodes_D; +Data.Nodes_F=Nodes_F; + +Data.Ni=Ni; +Data.Nd=Nd; +Data.Nn=Nn; +Data.Nf=Nf; + +Data.set_D_nodes=set_D_nodes; +Data.set_N_nodes=set_N_nodes; + +%% Auxiliary function + + function[label]=detectDependency(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end + +end diff --git a/Code/INTERNODES_medium/setCoefficient.m b/Code/INTERNODES_medium/setCoefficient.m new file mode 100644 index 0000000..d15a956 --- /dev/null +++ b/Code/INTERNODES_medium/setCoefficient.m @@ -0,0 +1,103 @@ +function[Data]=setCoefficient(Data, varargin) + +% setCoefficient: Initializes the model coefficients +% OPTIONAL INPUT: +% rho: Specific mass +% mu: Lamé constant +% lambda: Lamé constant +% f: Function "f" of the PDE +% OUTPUT: +% Data: Data structure with initialized coefficients + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +available_coeff={'rho', 'lambda', 'mu', 'f'}; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + if any(ismember(available_coeff, arg)) + + + switch arg + case {'rho', 'lambda', 'mu'} % Only space dependent + if isa(val,'double') + + if val ~= 0 + Data.Coefficient.(arg).label='const'; + else + Data.Coefficient.(arg).label='zero'; + end + Data.Coefficient.(arg).function=@(x) val; + + elseif isa(val,'function_handle') + Data.Coefficient.(arg).label=detectDependency1(val); + Data.Coefficient.(arg).function=val; + else + error('Input parameters must be either constants or function handles') + end + + case 'f' + if isa(val,'double') % Only space dependent + Data.Coefficient.f.label='const'; + Data.Coefficient.f.function=@(x) val; + elseif isa(val,'function_handle') + Data.Coefficient.f.label=detectDependency2(val); + Data.Coefficient.f.function=@(x) val(x); + else + error('Input parameters must be either constants or function handles') + end + + end + + else + error('Unrecognized coefficient') + end +end + + + function[label]=detectDependency1(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Coefficient can only be constant or space dependent') + end + + if contains(string(5:end), 'x') + label='space'; + elseif contains(string(5), '0') + label='zero'; + else + label='const'; + end + end + + function[label]=detectDependency2(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/setModel.m b/Code/INTERNODES_medium/setModel.m new file mode 100644 index 0000000..58c27a3 --- /dev/null +++ b/Code/INTERNODES_medium/setModel.m @@ -0,0 +1,75 @@ +function[Data]=setModel(varargin) + +% setModel: Initializes the numerical model +% OPTIONAL INPUT: +% model: Specifies which model should be used +% Default: transient_heat +% submodel: Specifies which submodel from the specified model should be +% used +% Default: standard +% type: Specifies whether the model is linear or nonlinear +% Default: linear +% OUTPUT: +% Data: Data structure with initialized model + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +models={'transient_heat', 'static_solid', 'dynamic_solid'}; +n=length(models); +submodels=cell(n,1); +submodels{1}={'standard'}; +submodels{2}={'standard', 'contact'}; +submodels{3}={'standard'}; +types={'linear', 'nonlinear'}; + +% Set default Parameters +Data.Model.name=models{1}; +Data.Model.submodel=submodels{1}; +Data.Model.type=types{1}; + +% Define constants +% Stefan Boltzman constant W/(m^2*K^4) +Data.Constants.sigma=5.670373e-8; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'model' + if any(ismember(models, val)) + Data.Model.name=val; + else + error('Unrecognized model') + end + + case 'submodel' + + if isfield(Data.Model, 'name') + index=strcmp(Data.Model.name, models); + else + error('Submodel must be defined after the model') + end + if any(ismember(submodels{index}, val)) + Data.Model.submodel=val; + else + error('Unrecognized submodel') + end + + case 'type' + if any(ismember(types, val)) + Data.Model.type=val; + else + error('Unrecognized type') + end + + otherwise + error('Unrecognized argument') + end + +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/setNumericalParameters.m b/Code/INTERNODES_medium/setNumericalParameters.m new file mode 100644 index 0000000..3cbd970 --- /dev/null +++ b/Code/INTERNODES_medium/setNumericalParameters.m @@ -0,0 +1,270 @@ +function[Data]=setNumericalParameters(Mesh, Data, varargin) + +% setNumericalParameters: Initializes the numerical parameters of the +% solver +% OPTIONAL INPUT: +% Contact: +% iterate: Parameter controlling whether iterations should +% take place (1) or not (0) +% Default: 1 +% maxiter: Maximum number of iterations for solving the +% contact problem +% Default: 10 +% rescaling: Rescaling parameter for the stiffness matrix +% Default: 1 +% alpha: Parameter for the preconditioner +% Default: auto +% c: Number of nearest neighbors for computing the +% radius of support of the radial basis functions +% Default: 1 +% radius: Intersection radius used to define the initial +% potential contact interface. +% Default: infinity +% gamma: Value of gamma for preventing quick loss of diagonal +% dominance of the matrices PhiMM +% Default: 0.5 +% Gamma: Value of Gamma to avoid very small nonzero entries +% in the matrices PhiNM +% Default: 0.95 +% d0: Measure of tolerance for contact detection. +% Default: 0.05 +% tol: Tolerance for gap detection +% Default: 1e-1 +% Solver: +% name: Name of the solver to be used. +% Default: GMRES +% maxiter: Total number of GMRES iterations +% Default: 1000 +% riter: Number of iterations before GMRES restarts +% Default: 20 +% tol: Tolerance (stopping creterion) for GMRES +% Default: 1e-7 +% reorth_tol: Re-orthogonalization tolerance for GMRES +% Default: 0.7 +% PostProcessing: +% quantity: Additional quantities to be computed (for example +% stresses, strains, fluxes,...) +% Default: none +% ampl_fact: Value of amplification factor to apply to +% displacements +% Default: auto +% eval_points: Number of evaluation points used for computing +% stresses or strains +% Default: 3 (P1 finite elements) +% 6 (P2 or higher order finite elements) + +if nargin>2 +if mod(length(varargin)-1, 2)~=0 + error('Missing a name or value') +end + +id=lower(varargin{1}); +labels_in=varargin(2:2:end); +values_in=varargin(3:2:end); +end + +% Set default Parameters +if ~isfield(Data, 'Contact') + % Boolean indicator for contact algorithm iterations + Data.Contact.iterate=true; + % Maximum number of iterations of the contact algorithm + Data.Contact.maxiter=10; + % Rescaling parameter for the INTERNODES matrix + Data.Contact.rescaling=1; + % Parameter alpha of the preconditioner + Data.Contact.alpha='auto'; + % Number of nearest neighbors + Data.Contact.c=1; + % Intersection radius + Data.Contact.radius=inf; + % gamma value + Data.Contact.gamma=0.5; + % Gamma value + Data.Contact.Gamma=0.95; + % Contact tolerance + Data.Contact.d0=0.05; + % Gap tolerance + Data.Contact.tol=1e-1; +end +if ~isfield(Data, 'Solver') + % Solver name + Data.Solver.name='GMRES'; + % GMRES parameters + % Total maximum number of iterations + Data.Solver.maxiter=1000; + % Maximum number of iterations before restart + Data.Solver.riter=20; + % Tolerance on the residual + Data.Solver.tol=1e-7; + % Reorthogonalization tolerance + Data.Solver.reorth_tol=0.7; +end +if ~isfield(Data, 'PostProcessing') + % Additional quantities to be computed + Data.PostProcessing.quantity='none'; + % Amplification factor + Data.PostProcessing.amplification='auto'; + % Number of evaluation points per element for computing stresses/strains + eval_points={3,6}; + + if Mesh.order==1 + Data.PostProcessing.eval_points=eval_points{1}; + else + Data.PostProcessing.eval_points=eval_points{2}; + end + +end + +% Override the defaults +if exist('id', 'var') + switch id + case 'contact' + %% Contact algorithm specific parameters + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'iterate' + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Data.Contact.(arg)=logical(val); + end + + elseif isa(val, 'logical') + Data.Contact.(arg)=val; + else + error([arg ' parameter must be a boolean']) + end + + case {'maxiter', 'c', 'radius', 'd0'} + if val<=0 + error(['Parameter ' arg ' must be strictly greater than zero']) + else + Data.Contact.(arg)=val; + end + + case 'rescaling' + if val==0 + error(['Parameter ' arg ' must be different from zero']) + else + Data.Contact.(arg)=val; + end + + case {'gamma', 'Gamma', 'tol'} + if val<=0 || val>=1 + error([arg ' must be strictly greater than 0 and strictly smaller than 1']) + else + Data.Contact.(arg)=val; + end + + case 'alpha' + if isa(val, 'char') + if ~strcmp(val, 'auto') + error('Invalid alpha parameter') + else + Data.Contact.(arg)=val; + end + + elseif isa(val, 'double') + if val == 0 + error('Invalid alpha parameter') + else + Data.Contact.(arg)=val; + end + else + error('Invalid alpha parameter') + end + + otherwise + error('Unrecognized argument') + end + end + + + case 'solver' + %% Solver parameters + names={'GMRES'}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'name' + if any(ismember(names, val)) + Data.Solver.(arg)=val; + else + error('Unrecognized solver name') + end + + case {'maxiter', 'riter', 'tol', 'reorth_tol'} + if val<=0 + error(['Parameter ' arg ' must be strictly positive']) + else + Data.Solver.(arg)=val; + end + + otherwise + error('Unrecognized argument') + end + + end + + case 'postprocessing' + %% Post-processing parameters + quantities={'none', 'flux', 'error', 'stress', 'strain'}; + eval_points={3,6}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'quantity' + if any(ismember(quantities, val)) + Data.PostProcessing.quantity=val; + else + error('Unrecognized quantity') + end + + case 'eval_points' + if any(ismember(eval_points, val)) + Data.PostProcessing.eval_points=val; + else + error('Invalid number of evaluation points') + end + + case 'ampl_fact' + + if isa(val, 'char') + if ~strcmp(val, 'auto') + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + + elseif isa(val, 'double') + if val <= 0 + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + else + error('Invalid amplification factor') + end + + otherwise + error('Unrecognized argument') + end + end + + otherwise + error('Unrecognized argument') + end +end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/solve.m b/Code/INTERNODES_medium/solve.m new file mode 100644 index 0000000..8eff1ba --- /dev/null +++ b/Code/INTERNODES_medium/solve.m @@ -0,0 +1,94 @@ +function[Mesh, Data, Solution]=solve(Mesh, Data, it) + +% solve: Solves the contact problem +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% it: Cell array containing the boundary tags of the interfaces (master +% interface and slave interface) +% OUTPUT: +% Mesh: Updated structure containing all the mesh parameters +% Data: Updated structure containing all the data parameters +% Solution: Structure containing the solution + + +% Initialization of the system +[Data, Solution]=initializeSystem(Mesh, Data, it); +% Initialize count +n=1; + +while Data.Contact.iterate && n<= Data.Contact.maxiter + + % Solve the system of equations + [Solution, Data]=solveSystem(Mesh, Data, Solution); + % Update the system + [Data]=updateSystem(Mesh, Data, Solution); + n=n+1; +end + +if n > Data.Contact.maxiter && Data.Contact.iterate + warning('Failed to converge within the specified number of iterations') +end + + function[Solution, Data]=solveSystem(Mesh, Data, Solution) + + % Retrieve data parameters + % Free degrees of freedom + Nf=Data.Nf; + % Number of free degrees of freedom + nf=Data.nf; + % Number of constraints + nc=Data.nc; + + % Retrieve mesh parameters + d=Mesh.nb_dof_local; + + % Retrieve solution + U=Solution.U; + % Solve the linear system iteratively + [x, Data]=iterativeSolver(Mesh, Data); + % Retrieve the displacements + U(Nf)=x(1:nf); + + % Update solution + Solution.U=U; + Solution.U_sort=reshape(U, d, [])'; + Solution.lambda=Data.Contact.rescaling*x(nf+1:nf+nc); + Solution.lambda_sort=reshape(Solution.lambda, d, [])'; + + end + + function[x, Data]=iterativeSolver(Mesh, Data) + + b=Data.b; + [Data]=preCompute(Mesh, Data); + + if strcmp(Data.Solver.name,'GMRES') + x0=zeros(length(b),1); + [x, ~] = gmres_k(Mesh, Data, @linearOp1, b, x0); + + else + error('Solver unrecognized or not yet implemented') + end + + end + + function[Data]=updateSystem(Mesh, Data, Solution) + + % updateSystem: Updates all quantities which have changed + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Solution structure with computed displacements + % OUPUT: + % Data: Data structure with updated interface properties + + % Check for convergence + [Data]=removeTraction(Mesh, Data, Solution, n); + % Reassemble the interpolation matrices and update interface + [Data]=subAssemble(Mesh, Data, Solution, 'updating'); + % Update right-hand side + [Data]=updateRHS(Mesh, Data); + + end +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/subAssemble.m b/Code/INTERNODES_medium/subAssemble.m new file mode 100644 index 0000000..f58cc86 --- /dev/null +++ b/Code/INTERNODES_medium/subAssemble.m @@ -0,0 +1,204 @@ +function[Data]=subAssemble(Mesh, Data, Solution, operation) + +% subAssemble: Assembles the part of the internodes matrix which is +% changing from one iteration to the next +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% operation: Specifies the type of operation (initialization or +% updating) +% OUTPUT: +% Data: Updated structure containing all the data parameters + +if strcmp(operation, 'initialization') + [Data]=computeBoundaryMass(Mesh, Data, 'initialization'); +end + +% Assemble interpolation matrices +[Data]=assembleRs(Mesh, Data, Solution); +% Assemble boundary mass matrices +[Data]=computeBoundaryMass(Mesh, Data, 'updating'); + + function[Data]=assembleRs(Mesh, Data, Solution) + + % assembleRs: Constructs the interpolation matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Structure containing the solution + % OUTPUT: + % Data: Updated data structure with interpolation matrices + + % Retrieve nodes making up the interfaces + nodes1=Data.body{1}.interface_nodes; + nodes2=Data.body{2}.interface_nodes; + + % Retrieve coordinates of nodes making up the interfaces + coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); + coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + + % Check for distant bodies + if all(i1) || all(i2) + warning('One of the bodies will be translated because they are too far away from each other. Boundary conditions might be affected') + [Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + % Check for isolated nodes + while any(i1) || any(i2) + + dump_nodes1=nodes1(i1); + dump_nodes2=nodes2(i2); + + % Updating + [Data]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + [Phi11] = assembleInterpMat(coord1, coord1, radiuses1); + [Phi21] = assembleInterpMat(coord1, coord2, radiuses1); + [Phi22] = assembleInterpMat(coord2, coord2, radiuses2); + [Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + + % Interpolation matrices + R12=Phi12/Phi22; + R21=Phi21/Phi11; + + s1=sum(R12,2); + s2=sum(R21,2); + + % Rescaling + R12=R12./s1; + R21=R21./s2; + + Data.R12=R12; + Data.R21=R21; + + % For future purposes + Data.Phi11=Phi11; + Data.Phi21=Phi21; + Data.Phi12=Phi12; + Data.Phi22=Phi22; + + Data.s1=s1; + Data.s2=s2; + + end + + function[Data]=computeBoundaryMass(Mesh, Data, operation) + + % computeBoundaryMass: Assembles the boundary mass matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % operation: Specifies the type of operation (initialization or + % updating) + % OUTPUT: + % Data: Updated data structure with boundary mass matrices + + + % Retrieve mesh parameters + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Get quadrature nodes and weights + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nb_points=length(weights); + + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + + % Retrieve bodies + body=Data.body; + % Number of bodies + nb_bodies=length(body); + + if strcmp(operation, 'initialization') + + for n=1:nb_bodies + % Retrieve data + % Retriveve edges making up the interface + Edges=body{n}.interface_connect; + % Retrieve nodes making up the interface + nodes=body{n}.interface_nodes; + nb_nodes=length(nodes); + s=order+1; + + % Initialization + Phi=zeros(s, nb_points); + + % Map global nodes to local nodes + func=@(x) find(nodes==x); + connect=arrayfun(func, Edges); + T=connect'; + G=T(:); + + I=repmat(connect, [1 s])'; + J=repmat(G', [s 1]); + + % Loop over the Gauss points + for i = 1:nb_points + Phi(:,i)=phi_boundary(points(i,:)); + end + + Tr=Edges'; + Gr=Tr(:); + % Coordinates of the nodes of the elements + coord_nodes=coord(Gr,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + lambda=weights.*norm_bk'; + + M=kr(Phi, Phi)*lambda; + M=sparse(I(:), J(:), M(:), nb_nodes, nb_nodes); + + Data.body{n}.iinterface_mass=M; + % Expansion to full size + M=kron(M, speye(nb_dof_local)); + Data.body{n}.interface_mass=M; + end + else + for n=1:nb_bodies + active_set=Data.body{n}.active_set; + Data.body{n}.interface_mass=Data.body{n}.iinterface_mass(active_set, active_set); + end + end + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_medium/translateBodies.m b/Code/INTERNODES_medium/translateBodies.m new file mode 100644 index 0000000..f5a398f --- /dev/null +++ b/Code/INTERNODES_medium/translateBodies.m @@ -0,0 +1,60 @@ +function[Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2) + +% translateBodies: Performs a translation of one of the bodies in case the +% initial potential contact interface is empty (because the bodies are too +% far away from each other) +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% radiusesk: Radiuses of support of the radial basis functions (k=1,2) +% nodesk: Nodes on the potential contact interface of body k (k=1,2) +% coordk: Coordinates of the nodes on the potential contact interface +% of body k (k=1,2) +% OUTPUT: +% Mesh: Mesh structure with updated coordinate matrix +% coordk: New coordinates of the nodes on the potential contact +% interface of body k (k=1,2) + +n1=length(radiuses1); + +dist=inf; +node1=0; +node2=0; + +for k=1:n1 + + [d, i]=min(vecnorm(coord1(k,:)-coord2,2,2)); + + if d1 + radiuses=radiuses'; +end + +m=size(coord_ref,1); +n=size(coord_interp,1); +dim=size(coord_ref,2); + +X_ref=reshape(coord_ref,1,m,dim); +X_interp=reshape(coord_interp,n,1,dim); + +X=X_interp-X_ref; +% 2-norm (euclidean norm) along the third dimension +N=vecnorm(X,2,3); +indx=N<=radiuses; +Phi=zeros(n,m); + +% Wendland C^2 radial basis functions +F=(1-N./radiuses).^4.*(1+4*N./radiuses); +Phi(indx)=F(indx); +end \ No newline at end of file diff --git a/Code/INTERNODES_small/computeNormals.m b/Code/INTERNODES_small/computeNormals.m new file mode 100644 index 0000000..9a30e45 --- /dev/null +++ b/Code/INTERNODES_small/computeNormals.m @@ -0,0 +1,77 @@ +function[Data]=computeNormals(Mesh, Data, Solution) + +% computeNormals: Computes the normal vectors for each interface +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Data: Updated structure with the interface normals + +% Retrieve mesh data +% Connectivity matrix +connect=Mesh.connect_mat; + +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Retrieve bodies +body=Data.body; +% Compute the normals based on the deformed structure +def=Mesh.coord_mat+Solution.U_sort; + +nb_bodies=length(body); + + +for k=1:nb_bodies + % Normal vectors are computed for all nodes in the initial potential + % contact interface + connecti=body{k}.initial_interface_connect; + nodesi=body{k}.initial_nodes; + + % Reduced connectivity matrix + connect_red=connect(any(ismember(connect, nodesi), 2), :); + % Reduced set of body nodes + nodesb=setdiff(unique(connect_red), nodesi); + n=length(nodesi); + m=size(connecti,1); + % Coordinates of the endpoints of the segments + coord1=def(connecti(:,1),:); + coord2=def(connecti(:,2),:); + % Tangent vectors + V=coord2-coord1; + % Segment lengths + L=vecnorm(V,2,2); + V=V./vecnorm(V,2,2); + + % !! Only for a two-dimensional case !! + N=zeros(m, d); + N(:,1)=-V(:,2); + N(:,2)=V(:,1); + + % Initialization + normals=zeros(n,d); + % Step size + gamma=1e-3; + + for j=1:n + [id,~]=find(connecti==nodesi(j)); + l=L(id); + % Weighted average of normal vectors + normals(j,:)=1/sum(l)*sum(N(id,:).*l,1); + % Current node + x=def(nodesi(j),:); + % Step in the direction of the gradient + y_plus=x+gamma*normals(j,:); + % Step in the opposite direction of the gradient + y_minus=x-gamma*normals(j,:); + min_plus=min(vecnorm(def(nodesb,:)-y_plus,2,2)); + min_minus=min(vecnorm(def(nodesb,:)-y_minus,2,2)); + if min_plus < min_minus + normals(j,:)=-normals(j,:); + end + end + + normals=normals./vecnorm(normals,2,2); + Data.body{k}.normal=normals; +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/computeRHS.m b/Code/INTERNODES_small/computeRHS.m new file mode 100644 index 0000000..b6f4112 --- /dev/null +++ b/Code/INTERNODES_small/computeRHS.m @@ -0,0 +1,223 @@ +function[Data]=computeRHS(Mesh, Data) + +% computeRHS: Function which assembles the right-hand side consisting of +% body forces and surface tractions +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% OUTPUT: +% Data: Updated data structure + +% Assemble the global vector of body forces +[Bs]=computeBody(Mesh, Data); +% Assemble the global vector of surface tractions +[Bn]=computeNeumann(Mesh, Data); + +% Sum the contribution from body forces and Neumann boundary +% conditions +B=Bs+Bn; +% Update data +Data.F=B; + + function[B]=computeBody(Mesh, Data) + + % computeBody: Assembles the component for the body forces + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of body forces + + % Retrieve mesh data + % Local number of degrees of freedom (dimension in solid mechanics) + d=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Finite element order + order=Mesh.order; + % Number of elements + ne=Mesh.nb_elem; + % Number of nodes per element + nne=Mesh.nb_nodes_elem; + % Matrix of equation numbers linking an element to the degrees of freedom + % associated to it. + Eq=Mesh.Eq; + + % Retrieve data parameters + % Body force vector + f=Data.Coefficient.f.function; + % Retrieve the basis functions + [Functions]=getFunctions(order); + % Retrieve basis functions + phi=Functions.phi; + + + % Retrieve quadrature points and weights + switch order + case 1 + [points, weights] = getQuadrature(3, 'bulk'); + case 2 + [points, weights] = getQuadrature(4, 'bulk'); + case 3 + [points, weights] = getQuadrature(6, 'bulk'); + otherwise + error('Not yet implemented'); + end + + % Number of quadrature points + nq=length(weights); + s=nne*d; + Id=eye(d); + % Initialization + X=zeros(ne, nq, 2); + Q=zeros(s, nq*d); + + + % Loop over the Gauss points + for i = 1:nq + Phi=phi(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + I=Eq'; + T=connect'; + G=T(:); + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Vertices of the elements + a=coord_nodes(1:nne:end,:); + b=coord_nodes(2:nne:end,:); + c=coord_nodes(3:nne:end,:); + % Interpolated coordinates + P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Determinants of Jacobian matrices + detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(abs(detJK), 1, 1, ne).*F.*weights; + X=reshape(X, d*nq, ne); + + B=Q*X; + + % Assemblage + B=accumarray(I(:), B(:), [nb_dof 1]); + end + + function[B]=computeNeumann(Mesh, Data) + + % computeNeumann: Implements Neumann boundary conditions, returns a vector + % having for length the number of degrees of freedom. + % This vector is to be added to the RHS. + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of surface tractions + + % Retrieve mesh parameters + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Number of local degrees of freedom + d=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Boundary equation numbers + Eq_BC=Mesh.Eq_BC; + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + % Get quadrature + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nq=length(weights); + + % Initialization + B=zeros(nb_dof,1); + s=order+1; + Id=eye(d); + % Initialization + Q=zeros(s*d, nq*d); + + % Loop over the Gauss points + for i = 1:nq + Phi=phi_boundary(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + + % Neumann boundary conditions + N_BC=Data.N_BC; + % Number of boundaries where surface tractions are imposed + nb_bc=length(N_BC); + + for m=1:nb_bc + % Retrieve data + data=N_BC{m}; + edges=data.edges; + + for flag=edges + % Surface traction function + f=data.function; + % Retriveve edges making up the boundary + Neum_edges=Mesh.BC_nodes(Mesh.BC_tag==flag,:); + % Equation numbers + Eq=Eq_BC(Mesh.BC_tag==flag,:); + % Number of Neumann boundary edges + ne=size(Neum_edges, 1); + + % Initialization + X=zeros(ne, nq, 2); + + C=Neum_edges'; + G=C(:); + I=Eq'; + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + % Interpolated coordinates + P=a(:)+(b(:)-a(:))*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(norm_bk, 1, 1, ne).*F.*weights'; + X=reshape(X, d*nq, ne); + + Bl=Q*X; + + % Assemblage + B=B+accumarray(I(:), Bl(:), [nb_dof 1]); + + end + end + end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/computeStiffness.m b/Code/INTERNODES_small/computeStiffness.m new file mode 100644 index 0000000..6091151 --- /dev/null +++ b/Code/INTERNODES_small/computeStiffness.m @@ -0,0 +1,138 @@ +function[K]=computeStiffness(Mesh, Data) + +% computeStiffness: Function which assembles the global stiffness matrix +% Assumes all elements are of the same type +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OUTPUT: +% K: Global stiffness matrix + +% Mesh parameters +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Total number of degrees of freedom +nb_dof=Mesh.nb_dof; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Finite element order +order=Mesh.order; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Total number of elements +ne=Mesh.nb_elem; +% Matrix linking an element to the degrees of freedom associated to it. +Eq=Mesh.Eq; + +% Data parameters +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; + + +% Retrieve functions +[Functions]=getFunctions(order); +% Jacobian matrix +J_phi=Functions.J_phi; +% Define permutation matrix +Sdd=zeros(d*d); +Id=eye(d); +for k=1:d + Sdd=Sdd+kron(Id(:,k)', kron(Id, Id(:,k))); +end + +% Retrieve quadrature nodes and weights +switch order + case 1 + [points, weights] = getQuadrature(1, 'bulk'); + case 2 + [points, weights] = getQuadrature(2, 'bulk'); + case 3 + [points, weights] = getQuadrature(3, 'bulk'); + otherwise + error('Not yet implemented'); +end + +% Initialization +s=nne*d; +nq=length(weights); +Q=zeros(s^2, nq*d^4); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:)), Id); + Q(:, (i-1)*d^4+1:i*d^4)=kron(L, L); +end + +T=Eq'; +G=T(:); + +I=repmat(Eq, [1 nne*d])'; +J=repmat(G', [nne*d 1]); + +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); + +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +V=[(vecnorm(c-a,2,2).^2) -sum((c-a).*(b-a),2) -sum((c-a).*(b-a),2) (vecnorm(b-a,2,2).^2)]; +W=1./(detJK').^2.*(V'); + +V=reshape(W,d,d*ne); + +JK_inv=zeros(d, d*ne); +JK_inv(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(b(:,2)-a(:,2))]'; +JK_inv(:,2:d:d*ne)=1./detJK'.*[-(c(:,1)-a(:,1)) b(:,1)-a(:,1)]'; + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, d^2, ne); + +[S1]=product(V , repmat(Id, 1, ne), d, ne); +[S2]=product(JK_invT , JK_inv, d, ne); +S2=Sdd*S2; + +A1=reshape(S1, d^4, ne); +A2=reshape(S2, d^4, ne); +A3=kr(vec_JK_invT, vec_JK_invT); + + +Lambda1=(abs(detJK).*f1(X).*weights)'; +Lambda2=(abs(detJK).*f2(X).*weights)'; + +X=kr(Lambda1, A1+A2)+kr(Lambda2, A3); +K=Q*X; + +% Assemblage +K=sparse(I(:), J(:), K(:), nb_dof, nb_dof); + + function[M]=product(A,B,d,ne) + + % Blockwise Kronecker product of two matrices + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_small/conforming1.m b/Code/INTERNODES_small/conforming1.m new file mode 100644 index 0000000..ef735e7 --- /dev/null +++ b/Code/INTERNODES_small/conforming1.m @@ -0,0 +1,64 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to both bodies. Conforming meshes. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact3_p1_0_1.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Dirichlet', 'Edges', 10, 'Function', @(x) [0; -0.1]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'maxiter', 30); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_small/conforming2.m b/Code/INTERNODES_small/conforming2.m new file mode 100644 index 0000000..50d85a8 --- /dev/null +++ b/Code/INTERNODES_small/conforming2.m @@ -0,0 +1,66 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to the lower body and Neumann boundary conditions applied to the +% upper body. Conforming meshes. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact3_p1_0_1.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with singular stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Neumann', 'Edges', 10, 'Function', @(x) [0; -1e10]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); + +%% Set numerical parameters + +% For more information, type 'help setNumericalParameters' +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_small/detectGaps.m b/Code/INTERNODES_small/detectGaps.m new file mode 100644 index 0000000..ffe94c0 --- /dev/null +++ b/Code/INTERNODES_small/detectGaps.m @@ -0,0 +1,81 @@ +function[add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution) + +% detectGaps: computes the gap between nodes of opposing interfaces and +% detects interpenetration +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% add_nodes1: Interface nodes of body 1 to be added to the active set +% add_nodes2: Interface nodes of body 2 to be added to the active set + +% Initial nodes +inodes1=Data.body{1}.initial_nodes; +inodes2=Data.body{2}.initial_nodes; + +nodes1=inodes1; +nodes2=inodes2; + +% Retrieve coordinates of nodes making up the interfaces +coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); +coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + +[radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); +[radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + +i1=nnzR12==0; +i2=nnzR21==0; + +% Check for isolated nodes +while any(i1) || any(i2) + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; +end + +[Phi11] = assembleInterpMat(coord1, coord1, radiuses1); +[Phi21] = assembleInterpMat(coord1, coord2, radiuses1); +[Phi22] = assembleInterpMat(coord2, coord2, radiuses2); +[Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + +% Interpolation matrices +R12=Phi12/Phi22; +R21=Phi21/Phi11; + +s1=sum(R12,2); +s2=sum(R21,2); + +% Rescaling +R12=R12./s1; +R21=R21./s2; + +Diff1=R12*coord2-coord1; +Diff2=R21*coord1-coord2; + +i1=ismember(inodes1, nodes1); +i2=ismember(inodes2, nodes2); + +% computes the scalar products +scalar1_g=sum(Diff1.*Data.body{1}.normal(i1,:),2); +scalar2_g=sum(Diff2.*Data.body{2}.normal(i2,:),2); + +% Thresholds are a fraction of the mesh size and could be specific to each +% interface (consider using the mesh sizes of body 1 and 2) +threshold1=-Data.Contact.tol*Data.Contact.h; +threshold2=-Data.Contact.tol*Data.Contact.h; + +% Detect interpenetration +add_nodes1=nodes1(scalar1_ggamma +% Used to ensure evaluation points are well within the +% support of radial basis functions (far enough from the boundary) +Gamma=Data.Contact.Gamma; +% Tolerance for contact detection +% If the tolerance is unknown, set it to some small value. +d0=Data.Contact.d0; +% Number of closest neighbors on current interface +c=Data.Contact.c; + +% Number of off-diagonal nonzeros per row of PhiMM +nnzRMM=zeros(M,1); +% Number of off-diagonal nonzeros per column of PhiMM +nnzCMM=zeros(M,1); +% Number of nonzeros per row of PhiNM +nnzRNM=zeros(N,1); +% Number of nonzeros per column of PhiNM +nnzCNM=zeros(M,1); + +maxS=inf; +f=0; +niter=0; +maxiter=10; + +% Version 2 +while maxS>f && niter= gamma*radius. + % If the point is too close (with respect to the radius), diagonal + % dominance will be lost very quickly. + if radius>rMM(1)/gamma + radius=rMM(1)/gamma; + end + + s1=distMMf + % Increase gamma (sequence converges to 1) + gamma=0.5*(1+gamma); + % Reinitialize counters + nnzRMM=zeros(M,1); + nnzCMM=zeros(M,1); + nnzRNM=zeros(N,1); + nnzCNM=zeros(M,1); + niter=niter+1; + end +end + +if niter==maxiter && maxS>f + warning('The maximum number of rounds for radius computations was reached. Try decreasing d0') +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/initializeInterface.m b/Code/INTERNODES_small/initializeInterface.m new file mode 100644 index 0000000..9f57511 --- /dev/null +++ b/Code/INTERNODES_small/initializeInterface.m @@ -0,0 +1,107 @@ +function[Data]=initializeInterface(Mesh, Data, it) + +% initializeInterface: Initializes the interfaces properties +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% it: Boundary tags for either a portion of the boundary or the entire boundary +% The first component is the tag for body 1 +% The second component is the tag for body 2 +% OUTPUT: +% Data: Updated data structure with interface properties + +% Retrieve mesh parameters +% Retrieve boundary tag +BC_tag=Mesh.BC_tag; +% Retrieve coordinate matrix +coord=Mesh.coord_mat; +% Define radius of intersection +if isfield(Data.Contact, 'radius') + r=Data.Contact.radius; +else + r=0.052; +end + +% Boundary elements +elements1=Mesh.BC_nodes(ismember(BC_tag,it{1}),:); +elements2=Mesh.BC_nodes(ismember(BC_tag,it{2}),:); + +% Boundary nodes +bc_nodes1=unique(elements1); +bc_nodes2=unique(elements2); + +m1=length(bc_nodes1); +m2=length(bc_nodes2); + +% Compute element lengths +l1=vecnorm(coord(elements1(:,2),:)-coord(elements1(:,1),:),2,2); +l2=vecnorm(coord(elements2(:,2),:)-coord(elements2(:,1),:),2,2); + +% Compute mesh sizes +h1=min(l1); +h2=min(l2); + +body_1.h=h1; +body_2.h=h2; + +h=min([h1, h2]); +Data.Contact.h=h; + +% Indicators +ic1=zeros(m1,1); +ic2=zeros(m2,1); + +% Create interfaces based on closest nodes from opposing interface +for k=1:m1 + node=bc_nodes1(k); + coord_node=coord(node,:); + dist=vecnorm(coord(bc_nodes2,:)-coord_node,2,2); + ic1(k)=any(distnb_dof_local + G=reshape(G, nb_nodes, nb_dof_local)'; + G=G(:); + else + G=repmat(G, nb_nodes, 1); + end + U(dofs(:))=G; + + end + + % Create solution structure + Solution.U=U; + % Reshape to an array of the same size as the coordinate matrix + Solution.U_sort=reshape(U, nb_dof_local, [])'; + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_small/internodes1.m b/Code/INTERNODES_small/internodes1.m new file mode 100644 index 0000000..8a9f5d2 --- /dev/null +++ b/Code/INTERNODES_small/internodes1.m @@ -0,0 +1,64 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to both bodies. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact2_p1_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Dirichlet', 'Edges', 10, 'Function', @(x) [0; -0.1]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'maxiter', 10, 'radius', 0.2); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_small/internodes2.m b/Code/INTERNODES_small/internodes2.m new file mode 100644 index 0000000..124d693 --- /dev/null +++ b/Code/INTERNODES_small/internodes2.m @@ -0,0 +1,66 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to the lower body and Neumann boundary conditions applied to the +% upper body. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact2_p1_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with singular stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 3, 'Function', @(x) [0; 0]},... + {'Type', 'Neumann', 'Edges', 10, 'Function', @(x) [0; -1e9]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); + +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'maxiter', 30, 'radius', inf, 'tol', 0.9); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {3:6, 7:10}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_small/internodes3.m b/Code/INTERNODES_small/internodes3.m new file mode 100644 index 0000000..aa9c582 --- /dev/null +++ b/Code/INTERNODES_small/internodes3.m @@ -0,0 +1,64 @@ +% INTERNODES method for contact mechanics +% Case study: contact between two rectangular blocks with Dirichlet +% boundary conditions applied to both bodies. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact6_p1_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 11, 'Function', @(x) [0; 0]},... + {'Type', 'Dirichlet', 'Edges', 17, 'Function', @(x) [0; -0.1]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'radius', 0.3, 'maxiter', 30); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {11:14, 15:18}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_small/internodes4.m b/Code/INTERNODES_small/internodes4.m new file mode 100644 index 0000000..92a47c1 --- /dev/null +++ b/Code/INTERNODES_small/internodes4.m @@ -0,0 +1,81 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to both bodies. Elastic contact between a sphere and a half +% space. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact10_p2_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; +% Amplification factor for the stiffness of the upper body +f=10; +% Radius of the upper sphere +R=0.5; +% Prescribed displacement +d=0.15; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', @(x) t*(lambda*(x(2)<=2)+f*lambda*(x(2)>2))); +[Data]=setCoefficient(Data, 'mu', @(x) t*(mu*(x(2)<=2)+f*mu*(x(2)>2))); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 9, 'Function', @(x) [0; 0]},... + {'Type', 'Dirichlet', 'Edges', 14, 'Function', @(x) [0; -d]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'radius', 0.2, 'maxiter', 30); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {9:12, 13:14}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); + +%% Comparison between numerical and analytical solutions + +E_star=(1/E*(1-nu^2)+1/(10*E)*(1-nu^2))^(-1); +F=4/3*E_star*sqrt(R)*d^(3/2); +% Maximum pressure +p0=1/pi*(6*F*E_star^2/R^2)^(1/3); + +lambda=Solution.lambda; +lambda_max=max(abs(lambda)); \ No newline at end of file diff --git a/Code/INTERNODES_small/internodes5.m b/Code/INTERNODES_small/internodes5.m new file mode 100644 index 0000000..7b3a260 --- /dev/null +++ b/Code/INTERNODES_small/internodes5.m @@ -0,0 +1,65 @@ +% INTERNODES method for contact mechanics +% Case study: Hertzian contact problem with Dirichlet boundary conditions +% applied to both bodies. Structure with asperities and multiple contact +% points. + +% Limitations: 2D +clc +clear variables +close all + +% Read GMSH mesh file (.msh) +[Mesh] = readGMSH('../../GMSH/Meshes/contact4_p1_0_05.msh'); + +%% Parameter prescription +% Mesh parameters +[Mesh]=getParameters(Mesh); +% Initialize model +[Data]=setModel('model', 'static_solid', 'submodel', 'contact', 'type', 'linear'); + +%% Set material parameters +% Elastic modulus [N\m^2] +E=30e9; +% Poisson ratio [-] +nu=0.2; +% Specific mass [kg/m^3] +rho=2500; +% Thickness [m] +t=1; + +% Plane stress conditions +lambda=E*nu/(1-nu^2); +mu=E/(2*(1+nu)); + +[Data]=setCoefficient(Data, 'rho', t*rho); +[Data]=setCoefficient(Data, 'lambda', t*lambda); +[Data]=setCoefficient(Data, 'mu', t*mu); +[Data]=setCoefficient(Data, 'f', @(x) [0; 0]); + +%% Set boundary conditions + +% Used for testing with invertible stiffness matrix +BC={{'Type', 'Dirichlet', 'Edges', 25, 'Function', @(x) [0; 0]},... + {'Type', 'Neumann', 'Edges', 28, 'Function', @(x) [0; -1e9]}}; + +p2={'BC', BC}; + +[Data]=setBoundaryConditions(Mesh, Data, p2); +%% Set numerical parameters +% For more information, type 'help setNumericalParameters' + +[Data]=setNumericalParameters(Mesh, Data, 'Contact', 'rescaling', E, 'radius', 0.2, 'maxiter', 30); +[Data]=setNumericalParameters(Mesh, Data, 'PostProcessing', 'ampl_fact', 1, 'quantity', {'stress', 'strain'}); + +%% Assemblage of the right-hand side +[Data]=computeRHS(Mesh, Data); + +%% Solve the contact problem +[Mesh, Data, Solution, A, b]=solve(Mesh, Data, {[23 25:27], [24 28:30]}); + +%% Computation of the stresses and strains +[Mesh, Solution]=postProcess(Mesh, Data, Solution); + +%% Visualization of the solution +% For more information, type 'help plotSolution' +plotSolution(Mesh, Data, Solution, 'disp', 'standard', 'stress', 'sigma_yy'); \ No newline at end of file diff --git a/Code/INTERNODES_small/kr.m b/Code/INTERNODES_small/kr.m new file mode 100644 index 0000000..874aea5 --- /dev/null +++ b/Code/INTERNODES_small/kr.m @@ -0,0 +1,31 @@ +function X = kr(U,varargin) +%KR Khatri-Rao product. +% kr(A,B) returns the Khatri-Rao product of two matrices A and B, of +% dimensions I-by-K and J-by-K respectively. The result is an I*J-by-K +% matrix formed by the matching columnwise Kronecker products, i.e. +% the k-th column of the Khatri-Rao product is defined as +% kron(A(:,k),B(:,k)). +% +% kr(A,B,C,...) and kr({A B C ...}) compute a string of Khatri-Rao +% products A o B o C o ..., where o denotes the Khatri-Rao product. +% +% See also kron. + +% Version: 21/10/10 +% Authors: Laurent Sorber (Laurent.Sorber@cs.kuleuven.be) + +if ~iscell(U), U = [U varargin]; end +K = size(U{1},2); +if any(cellfun('size',U,2)-K) + error('kr:ColumnMismatch', ... + 'Input matrices must have the same number of columns.'); +end +J = size(U{end},1); +X = reshape(U{end},[J 1 K]); +for n = length(U)-1:-1:1 + I = size(U{n},1); + A = reshape(U{n},[1 I K]); + X = reshape(bsxfun(@times,A,X),[I*J 1 K]); + J = I*J; +end +X = reshape(X,[size(X,1) K]); diff --git a/Code/INTERNODES_small/mapGlobal2Local.m b/Code/INTERNODES_small/mapGlobal2Local.m new file mode 100644 index 0000000..6b14a3a --- /dev/null +++ b/Code/INTERNODES_small/mapGlobal2Local.m @@ -0,0 +1,13 @@ +function[Data]=mapGlobal2Local(Data) + +% mapGlobal2Local: Function which maps the global degrees of freedom to the +% local ones for the INTERNODES matrix +% INPUT: +% Data: Structure containing all the data parameters +% OUTPUT: +% Data: Updated structure with the local degrees of freedom + +Nf=Data.Nf; +[~, Data.body{1}.indx_local, ~]=intersect(Nf, Data.body{1}.interface_dof); +[~, Data.body{2}.indx_local, ~]=intersect(Nf, Data.body{2}.interface_dof); +end \ No newline at end of file diff --git a/Code/INTERNODES_small/plotSolution.m b/Code/INTERNODES_small/plotSolution.m new file mode 100644 index 0000000..47ba494 --- /dev/null +++ b/Code/INTERNODES_small/plotSolution.m @@ -0,0 +1,813 @@ +function[]=plotSolution(Mesh, Data, Solution, varargin) + +% plotSolution: Function which plots the computed quantities +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% Solution: Structure containing the solution computed +% OPTIONAL INPUT +% snapshot: selected time-step at which the solution should be viewed +% 1 -> none (default) +% any time step in the range defined +% view_video: view the video of the solution +% 1 -> yes +% 0 -> no (default) +% save_video: choose whether to save the video or not +% 1 -> yes +% 0 -> no (default) +% plot_arg: choose which solution to visualize +% dynamic_solid or static_solid module +% disp (default) +% stress +% strain +% transient_heat module +% temp (default) +% flux +% plot_type: decide which type of metric should be used for visualization +% displacement metric +% standard -> structure with amplified displacements (default) +% norm +% absx +% absy +% temperature metric +% standard (default) +% stress metric +% von_mises (default) +% sigma_xx +% sigma_yy +% sigma_xy +% sigma_xx_abs +% sigma_yy_abs +% sigma_xy_abs +% frobenius +% strain metric +% epsilon_xx (default) +% epsilon_yy +% epsilon_xy +% epsilon_xx_abs +% epsilon_yy_abs +% epsilon_xy_abs +% frobenius +% flux metric +% standard (default) + + +%% Initialize and ckeck Parameters + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +% Specify number of sub-intervals in time +if contains(Data.Model.name, 'static') + static=true; + N=0; +else + static=false; + N=Data.Discretization.N; +end + +switch Data.Model.name + case {'static_solid', 'dynamic_solid'} + plot_argv={'disp', 'stress', 'strain'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard', 'norm', 'absx', 'absy'}; + plot_typev{2}={'von_mises', 'sigma_xx', 'sigma_yy', 'sigma_xy', 'sigma_xx_abs', 'sigma_yy_abs', 'sigma_xy_abs', 'frobenius'}; + plot_typev{3}={'epsilon_xx', 'epsilon_yy', 'epsilon_xy', 'epsilon_xx_abs', 'epsilon_yy_abs', 'epsilon_xy_abs', 'frobenius'}; + + case 'transient_heat' + plot_argv={'temp', 'flux'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard'}; + plot_typev{2}={'standard'}; + +end + +% Set all default parameters +% Default visualization parameters +Param.visua_param.snapshot=1; +Param.visua_param.view_video=false; +Param.visua_param.write_video=false; +% Default plot argument +Param.plot_param.arg=plot_argv(1); +% Default plot type +Param.plot_param.type=plot_typev{1}(1); + +plot_arg={}; +plot_type={}; +nb_plot=0; + +for k=1:length(labels_in) + arg=lower(labels_in{k}); + val=values_in{k}; + + switch arg + case 'snapshot' + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + elseif val < 0 || val > Data.Discretization.N+1 + error(['Time snapshot invalid: it must be an integer between ' num2str(0) ' and ' num2str(Data.Discretization.N+1)]) + else + Param.visua_param.snapshot=val; + end + + case {'view_video', 'write_video'} + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + else + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Param.visua_param.view_video=logical(val); + end + + elseif isa(val, 'logical') + Param.visua_param.view_video=val; + else + error([arg ' parameter must be a boolean']) + end + end + + case plot_argv + + % Check if desired quantities have been computed + switch arg + case 'stress' + if ~isfield(Solution, 'sigma') + error('Stresses have not been computed') + end + + case 'strain' + if ~isfield(Solution, 'epsilon') + error('Strains have not been computed') + end + + case 'flux' + if ~isfield(Solution, 'Q') + error('Fluxes have not been computed') + end + end + + plot_arg{nb_plot+1}=arg; + index=strcmp(arg, plot_argv); + + if any(ismember(plot_typev{index}, val)) + plot_type{nb_plot+1}=val; + else + error('Plot type invalid: see available plot types') + end + + nb_plot=nb_plot+1; + otherwise + error('Unrecognized argument') + end + +end + +if nb_plot>0 + Param.plot_param.arg=plot_arg; + Param.plot_param.type=plot_type; +end + +%% Plotting interface + +plot_arg=Param.plot_param.arg; +plot_type=Param.plot_param.type; +n=length(plot_arg); +Plot=cell(n,1); +Axis=cell(n,1); +Colorbar=cell(n,1); +Dynamic=cell(n,1); + +fig=figure; +% fig.Units='normalized'; +% fig.Position=[0 0 1 1]; + +% fig.Units='normalized'; +% fig.Position=[0 1 1/2 1/2]; + +for k=1:n + switch plot_arg{k} + case {'disp', 'temp'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case {'strain', 'stress'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, plot_arg{k}, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case 'flux' + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=quiver(axis, PlotParam{1,2}, PlotParam{2,2}); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + end + + Plot{k}=p; + Axis{k}=axis; + Colorbar{k}=c; + Dynamic{k}=DynamicParam; +end + +if Param.visua_param.view_video % View the solution over the entire simulation + + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, 1, N); + end + + frames(1)=getframe(fig); + for step = 2:N+1 + pause(0.05) + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + refreshdata(Axis{k}); + end + frames(step)=getframe(fig); + end + +else % View solution at a particular time snapshot + step=Param.visua_param.snapshot; + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + end +end + + +if Param.visua_param.write_video +% vid_duration=25; % seconds + vid_duration=10; % seconds + video=VideoWriter('Output.mp4', 'MPEG-4'); + video.FrameRate=(N+1)/vid_duration; + open(video); + writeVideo(video,frames); + close(video); +end + +%% Parameters for patch plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, type) + + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Total number of nodes + nb_nodes=Mesh.nb_nodes; + % Frame surrounding the structure + frame=Mesh.frame; + % Largest dimension + L_max=Mesh.L_max; + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + + % Retrieve solution + U=Solution.U; + + + switch Data.Model.name + + case {'static_solid', 'dynamic_solid'} + + % Initilization + Vertices=zeros(nb_nodes,2,N+1); + CData=cell(N+1,1); + + + switch type + case 'standard' + + if strcmp(Data.PostProcessing.amplification, 'auto') + % Maximum displacement + disp_max=max(max(abs(U))); + % Amplification factor + fact_ampl=L_max/(2*disp_max); + else + fact_ampl=Data.PostProcessing.amplification; + end + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + % Aplified displacements + ux_ampl=fact_ampl*U(d1,:); + uy_ampl=fact_ampl*U(d2,:); + % Deformed state + def_x=coord(:,1)+ux_ampl; + def_y=coord(:,2)+uy_ampl; + % Vertices in deformed state + Vertices(:,1,:)=def_x; + Vertices(:,2,:)=def_y; + XLim=[min(def_x, [], 'all'); + max(def_x, [], 'all')]; + YLim=[min(def_y, [], 'all'); + max(def_y, [], 'all')]; + CLim=[0,1]; + Colormap=[]; + ColorbarStatus='off'; + ColorbarLabel=''; + +% Title='Amplified displacements'; + Title='Displacements'; + EdgeColor='black'; + FaceColor='none'; + + case 'norm' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + U_norm=sqrt(Ux.^2+Uy.^2); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(U_norm, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Norm of displacements'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=U_norm(:,m); + end + + case 'absx' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + Ux_abs=abs(Ux); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Ux_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along x'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Ux_abs(:,m); + end + + case 'absy' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + Uy_abs=abs(Uy); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Uy_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along y'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Uy_abs(:,m); + end + + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + case 'transient_heat' + + % Number of sub-intervals in time + N=Data.Discretization.N; + % Initilization + CData=cell(N+1,1); + + switch type + case 'standard' + + XLim=frame(:,1); + YLim=frame(:,2); + CLim=[min(U, [], 'all') max(U, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Temperature [ºC]'; + % ColorbarLabel='Temperature [K]'; + Title='Temperature'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + CData{m}=U(:,m); + end + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'CData', CData;... + 'Title', Title}; + + end + + + end + +%% Parameters for surf plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, arg, type) + + % Retrieve mesh parameters + % Coordinate matrix of interpolation nodes + coord_inter=Mesh.coord_inter; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve data parameters + % Number of evaluation points per element + nq=Data.PostProcessing.eval_points; + + % Total number of interpolation nodes + nb_nodes_inter=size(coord_inter,1); + + % Initilization + Vertices=zeros(nb_nodes_inter,2,N+1); + CData=cell(N+1,1); + + switch arg + + case 'stress' + + % Retrieve solution + sigma=permute(Solution.sigma, [2 1 3]); + + switch type + case 'von_mises' + + % Von Mises stress + select=sqrt(sigma(:,1,:).^2-sigma(:,1,:).*sigma(:,2,:)+sigma(:,2,:).^2+3*sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Von Mises stress field'; + + case 'sigma_xx' + + % Value of sigma_xx + select=sigma(:,1,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xx}'; + + case 'sigma_yy' + + % Value of sigma_yy + select=sigma(:,3,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{yy}'; + + case 'sigma_xy' + + % Value of sigma_xy + select=sigma(:,2,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xy}'; + + case 'sigma_xx_abs' + + % Abolute value of sigma_xx + select=abs(sigma(:,1,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xx}|'; + + case 'sigma_yy_abs' + + % Absolute value of sigma_yy + select=abs(sigma(:,3,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{yy}|'; + + case 'sigma_xy_abs' + + % Absolute value of sigma_xy + select=abs(sigma(:,2,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xy}|'; + + case 'frobenius' + + % Frobenius norm of sigma + select=sqrt(sigma(:,2,:).^2+2*sigma(:,2,:).^2+sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Frobenius norm of \sigma'; + + end + + case 'strain' + + % Retrieve solution + epsilon=permute(Solution.epsilon, [2 1 3]); + + switch type + case 'epsilon_xx' + + % Value of epsilon_xx + select=epsilon(:,1,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xx}'; + + case 'epsilon_yy' + + % Value of epsilon_yy + select=epsilon(:,3,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{yy}'; + + case 'epsilon_xy' + + % Value of esilon_xy + select=epsilon(:,2,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xy}'; + + case 'epsilon_xx_abs' + + % Abolute value of epsilon_xx + select=abs(epsilon(:,1,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xx}|'; + + case 'epsilon_yy_abs' + + % Absolute value of epsilon_yy + select=abs(epsilon(:,3,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{yy}|'; + + case 'epsilon_xy_abs' + + % Absolute value of epsilon_xy + select=abs(epsilon(:,2,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xy}|'; + + case 'frobenius' + + % Frobenius norm of epsilon + select=sqrt(epsilon(:,2,:).^2+2*epsilon(:,2,:).^2+epsilon(:,3,:).^2); + ColorbarLabel='Strain [-]'; + Title='Frobenius norm of \epsilon'; + + end + + end + + % Common parameters + XLim=[min(coord_inter(:,1,:), [], 'all'); + max(coord_inter(:,1,:), [], 'all')]; + YLim=[min(coord_inter(:,2,:), [], 'all'); + max(coord_inter(:,2,:), [], 'all')]; + CLim=[min(select, [], 'all') max(select, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=coord_inter(:,1,m); + Vertices(:,2,m)=coord_inter(:,2,m); + CData{m}=select(:,m); + end + + + switch nq + case 3 + % 3-point evaluation inside each element + I=1:3*Mesh.nb_elem; + I=reshape(I, 3, Mesh.nb_elem)'; + + case 6 + % 6-point evaluation inside each element + i1=[1 4 5 2 2 5 6 3 3 6 4 1]; + i2=[4 5 6]; + I1=i1+(0:Mesh.nb_elem-1)'*6; + I2=i2+(0:Mesh.nb_elem-1)'*6; + I1=reshape(I1', 4, 3*Mesh.nb_elem)'; + I2=[I2 nan*zeros(size(I2,1),1)]; + I=[I1; I2]; + end + + % Set static parameters + PlotParam = {'Vertices', coord_inter;... + 'Faces', I;... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + end + +%% Parameters for quiver plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, type) + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve solution + Q_sort=Solution.Q_sort; + + % Initialization + U=cell(1,N+1); + V=cell(1,N+1); + + switch type + case 'standard' + % Standard visualization of fluxes + Title='Flux field'; + + for m=1:N+1 + U{m}=Q_sort(:,2*m-1); + V{m}=Q_sort(:,2*m); + end + + end + + + % Set static parameters + PlotParam = {'XData', coord(:,1);... + 'YData', coord(:,2);... + 'LineWidth', 1}; + + AxisParam = {'XLim', frame(:,1);... + 'YLim', frame(:,2);... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', 'off';... + 'Label.String', '';... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'UData', U;... + 'VData', V;... + 'Title', Title}; + + + end + + + + + + +%% Setting and updating graphic properties + function[object]=setObject(object, param) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument) + if contains(argument{i}, '.') + field=extractBetween(argument{i}, 1, '.'); + subfield=extractAfter(argument{i}, '.'); + object.(field{1}).(subfield)=value{i}; + else + object.(argument{i})=value{i}; + end + end + + end + + function[plot, axis]=updateObject(plot, axis, param, step, N) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument)-1 + plot.(argument{i})=value{i}{step}; + end + + axis.Title.String=[value{end} ' at step ' num2str(step) ' out of ' num2str(N+1)]; + end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/postProcess.m b/Code/INTERNODES_small/postProcess.m new file mode 100644 index 0000000..364859c --- /dev/null +++ b/Code/INTERNODES_small/postProcess.m @@ -0,0 +1,18 @@ +function[Mesh, Solution]=postProcess(Mesh, Data, Solution) + +% postProcess: General post-processing function used to compute derived +% quantities +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Mesh: Updated mesh structure with coordinates of nodes where stresses +% and strains were evaluated +% Solution: Updated solution structure with derived quantities + + +if any(contains(Data.PostProcessing.quantity, {'stress', 'strain'})) + [Mesh, Solution]=postProcessDisp(Mesh, Data, Solution); +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/postProcessDisp.m b/Code/INTERNODES_small/postProcessDisp.m new file mode 100644 index 0000000..f1f6613 --- /dev/null +++ b/Code/INTERNODES_small/postProcessDisp.m @@ -0,0 +1,147 @@ +function[Mesh, Solution]=postProcessDisp(Mesh, Data, Solution) + +% postProcessDisp: Function used to compute strains and stresses once the +% displacements are known. +% INPUT: +% Mesh: Structure containing all mesh parameters +% Data: Structure containing all data parameters +% Solution: Structure containing the computed solutions +% OUTPUT: +% Mesh: Updated mesh structure with evaluation points coordinates +% Solution: Updated solution structure including strains and stresses + + +% Finite element order +order=Mesh.order; +% Number of local degrees of freedom +d=Mesh.nb_dof_local; +% Number of elements +ne=Mesh.nb_elem; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Retrieve basis functions +[Functions]=getFunctions(order); +% Retrieve Jacobian matrix +J_phi=Functions.J_phi; +% Number of evaluation points per element +nq=Data.PostProcessing.eval_points; +% Retrieve evaluation points +[points, ~] = getQuadrature(nq, 'interpolation'); +% Number of independent strain and stress components +Mesh.nb_comp_ind=1/2*d*(d+1); +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; +% Retrieve solution +U_sort=Solution.U_sort; +% Compute stresses and strains using current position +coord=coord+U_sort; + +% Define permutation matrix +Sdd=zeros(d*d); +Id1=eye(d); +Id2=eye(d^2); + +for k=1:d + Sdd=Sdd+kron(Id1(:,k)', kron(Id1, Id1(:,k))); +end + +% Reduce size of matrices to avoid unnecessary computations +% sigma_xx, sigma_xy, sigma_yy +h=[1 2 4]; + +I1=(Id2+Sdd); +I2=Id1(:); + +% Truncate the matrices +I1=I1(h,:); +I2=I2(h); + +% Initialization +Q=zeros(nq*d^2, nne*d); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:))', Id1); + Q((i-1)*d^2+1:i*d^2, :)=L; +end +Q=sparse(Q); +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Displacements of the nodes of the elements +W=reshape(U_sort(G,:)', nne*d, ne); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); +coord_inter=reshape(P', nq*ne, d); +% Update mesh +Mesh.coord_inter=coord_inter; + +% Computation of geometric quantities +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, 1, d^2*ne); + +% Coefficient evaluation +F1=f1(X); +F2=f2(X); +if size(F1,1)>1 +F1=reshape(F1, 1, nq, ne); +end +if size(F2,1)>1 +F2=reshape(F2, 1, nq, ne); +end + +% Matrix product computations +W=reshape(Q*W, d^2, nq, ne); + +B1=reshape(product(JK_invT,repmat(Id1, 1, ne),d,ne), d^2, d^2, ne); +B2=reshape(vec_JK_invT, 1, d^2, ne); + +% Computation of strains +if any(contains(Data.PostProcessing.quantity, 'strain')) + E=pagemtimes(B1, W); + E=0.5*I1*reshape(E, d^2, nq*ne); + Solution.epsilon=E; +end + +% Computation of stresses +if any(contains(Data.PostProcessing.quantity, 'stress')) + S1=pagemtimes(B1, F1.*W); + S2=pagemtimes(B2, F2.*W); + S1=I1*reshape(S1, d^2, nq*ne); + S2=I2*reshape(S2, 1, nq*ne); + S=S1+S2; + Solution.sigma=S; +end + + + function[M]=product(A,B,d,ne) + + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/readGMSH.m b/Code/INTERNODES_small/readGMSH.m new file mode 100755 index 0000000..21425ca --- /dev/null +++ b/Code/INTERNODES_small/readGMSH.m @@ -0,0 +1,86 @@ +function [Mesh] = readGMSH(file_name) + +% readGMSH: Reads a GMSH mesh file (.msh) and constructs the coordinate, +% connectivity, boundary nodes, material tag and boundary tag matrices +% INPUT: +% file_name: GMSH mesh file (.msh) +% OUTPUT: +% Mesh: Structure containing all fields related to the geometry, boundary +% conditions and material tags +% coord: Matrix of node coordinates +% connectivites: Connectivity matrix of the system +% boundary_nodes: Matrix containing the boundary nodes (both displacements +% and external forces) +% material_tag: Vector containing the material tag of the elements +% boundary_tag: Vector containing the boundary tags of the elements +% forming boundaries (enables to make the distinction between Dirichlet +% and Neumann boundary conditions) + +fileID = fopen(file_name); +text = textscan(fileID, '%s', 'delimiter', '\n'); +fclose(fileID); + +text=text{1}; +% Construction of the coordinate matrix +nodes_start = findPos(text, '$Nodes') + 1; + +fileID = fopen(file_name); +% Node coordinates +coord = textscan(fileID, '%f %f %f %f', 'HeaderLines', nodes_start); +% Elements +connect = textscan(fileID, [repmat('%f', 1, 20) '%*[^\n]'], 'HeaderLines', 3, 'EndOfLine', '\n', 'CollectOutput', 1); +fclose(fileID); + +% Coordinate matrix +coord=cell2mat(coord); +% Ignore z component +coord=coord(:,2:end-1); + +% Connectivity matrix +connect=cell2mat(connect); + +I=sum(~isnan(connect), 2); +v=unique(I); + +% Boundary elements +B=connect(I==v(1), 1:v(1)); +% Elements +E=connect(I==v(2), 1:v(2)); + +% Boundary tags +B_tags=B(:,4); +% Element tags +E_tags=E(:,4); + +% Boundary elements connectivity matrix +B_connect=B(:,6:end); +% Elements connectivity matrix +E_connect=E(:,6:end); + +% Initialize mesh structure +Mesh.coord_mat=coord; +Mesh.BC_nodes=B_connect; +Mesh.connect_mat=E_connect; +Mesh.BC_tag=B_tags; +Mesh.material_tag=E_tags; + +% Identification of the type of element in GMSH +% 2 -> T3 +% 9 -> T6 +% 21 -> T10 +% 1 -> line +% 8 -> quadratic curve +% 26 -> cubic curve +end + + +function[start]=findPos(text, string) +lines = size(text, 1); +start = 1; +for i = 1:lines + if strcmp(text{i}, string) + start = i; + break + end +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/removeTraction.m b/Code/INTERNODES_small/removeTraction.m new file mode 100644 index 0000000..e4c5c16 --- /dev/null +++ b/Code/INTERNODES_small/removeTraction.m @@ -0,0 +1,73 @@ +function[Data]=removeTraction(Mesh, Data, Solution, n) + +% removeTraction: Checks for convergence and updates the interface properties +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% n: Current iteration number +% OUTPUT: +% Data: Updated data structure with updated interface properties + +% Compute the normals based on deformed structure +% Normals for initial interface +[Data]=computeNormals(Mesh, Data, Solution); + +normal1=Data.body{1}.normal; +normal2=Data.body{2}.normal; + +% Retrieve Lagrange multipliers +lambda1=Solution.lambda; +lambda_sort1=Solution.lambda_sort; + +% Project the lambdas on interface 2 +lambda2=-Data.R21*lambda1; +lambda_sort2=reshape(lambda2, Mesh.nb_dof_local, [])'; + +% Computes the scalar products +scalar1=sum(lambda_sort1.*normal1(Data.body{1}.active_set,:),2); +scalar2=sum(lambda_sort2.*normal2(Data.body{2}.active_set,:),2); + +% Detect nodes to be removed from the interfaces +dump_nodes1=Data.body{1}.interface_nodes(scalar1>0); +dump_nodes2=Data.body{2}.interface_nodes(scalar2>0); + +% Update the interfaces +% If only compression test penetration, otherwise remove nodes in traction +if isempty(dump_nodes1) && isempty(dump_nodes2) + % Gap verification + [add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution); + + [Data, nodediff1]=updateInterface(Mesh, Data, 1, add_nodes1, 'add'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, add_nodes2, 'add'); + +else + % Skip gap verification + [Data, nodediff1]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); +end + +fprintf('Iteration %d: \n', n) +fprintf('%+d nodes on interface 1 \n', nodediff1) +fprintf('%+d nodes on interface 2 \n', nodediff2) +fprintf('----------------------------------- \n') + +% Check for convergence failure +if isempty(Data.body{1}.interface_nodes) || isempty(Data.body{2}.interface_nodes) + msg=['The contact algorithm stopped prematurely because at least one of ' ... + 'the sets of nodes of the potential contact interface is empty. '... + 'Potential causes for this error are:\n']; + + causes=['1) The boundary conditions are incorrect \n'... + '2) The potential contact interface was misidentified \n'... + '3) The mesh is too coarse \n']; + + error(sprintf([msg, causes])) +end + +% Check for convergence +if abs(nodediff1)+abs(nodediff2)==0 % Stop the iterations + Data.Contact.iterate=0; + disp('Successfully converged') +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/setBoundaryConditions.m b/Code/INTERNODES_small/setBoundaryConditions.m new file mode 100644 index 0000000..2d375dc --- /dev/null +++ b/Code/INTERNODES_small/setBoundaryConditions.m @@ -0,0 +1,267 @@ +function[Data]=setBoundaryConditions(Mesh, Data, varargin) + +% setBoundaryConditions: Function which initializes the boundary data +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OPTIONAL INPUT: +% Any type of boundary condition. If no Dirichlet boundary conditions are +% specified, the program returns an error +% Types of boundary conditions: +% Dirichlet +% Neumann +% OUTPUT: +% Data: Updated data structure with boundary conditions + + +%% Set boundary conditions +if mod(length(varargin{1}), 2)~=0 + error('Missing a name or value') +end + +% Retrieve boundary nodes +BC_tags=unique(Mesh.BC_tag); + +% Dirichlet type +D_types={'dirichlet'}; +% Neumann types +N_types={'neumann'}; +% Available types of boundary conditions +available_types=union(D_types, N_types); +% Count to detect duplicate boundaries +count=[]; +% Count number of Dirichlet and Neumann boundaries specified +nb_D_BC=0; +nb_N_BC=0; + +% Retrieve labels and values entered by the user +labels_in=varargin{1}(1:2:end); +values_in=varargin{1}(2:2:end); + +% Initilization +Data.BC=[]; + +% Checking for boundary conditions +c1=strcmp(labels_in, 'BC'); + +if any(c1) + BC=values_in{c1}; + nb_BC=length(BC); + Data.BC=cell(nb_BC,1); + + for i=1:nb_BC + bc=BC{i}; + l=length(bc); + + if mod(l,2)~=0 + error('Missing a name or value') + end + + for j=1:2:l + arg=lower(bc{j}); + val=bc{j+1}; + + switch arg + + case 'type' + if any(ismember(available_types, lower(val))) + Data.BC{i}.(arg)=lower(val); + + if any(ismember(D_types, lower(val))) + nb_D_BC=nb_D_BC+1; + else + nb_N_BC=nb_N_BC+1; + end + else + error('Unrecognized boundary condition') + end + + case 'edges' + + edges=intersect(BC_tags, val); + + if isempty(edges) + error('One of the edges specified does not exist') + elseif ~isempty(intersect(count, val)) || length(unique(val))~=length(val) + error('Duplicate edges detected') + end + count=[count val]; + Data.BC{i}.(arg)=val; + + case {'function'} + + if isa(val,'double') + Data.BC{i}.label='const'; + Data.BC{i}.(arg)= @(x) val; + + elseif isa(val,'function_handle') + Data.BC{i}.label=detectDependency(val); + Data.BC{i}.(arg)=val; + else + error('Input parameters must be either constants or function handles') + end + + otherwise + error('Unrecognized argument') + end + + end + + + end + +end + +%% Post-Processing: Partitioning the structure BC into Dirichlet BC and Neumann BC and checking for missing data + +if nb_D_BC==0 + error('Dirichlet boundary conditions must be prescribed') +end + +BC=Data.BC; +l=length(BC); + +% Initialize cells +D_BC=cell(nb_D_BC,1); +N_BC=cell(nb_N_BC,1); +% Set of Dirichlet and Neumann tags +D_tags=[]; +N_tags=[]; +% Initialize counters +d=1; +n=1; + +for i=1:l + bc=BC{i}; + type=bc.type; + + if ~isfield(bc, 'edges') + error('The edges must be specified') + end + + if any(ismember(D_types, type)) + D_tags=[D_tags bc.edges]; + D_BC{d}.type=type; + D_BC{d}.edges=bc.edges; + + if isfield(bc, 'function') + D_BC{d}.function=bc.function; + D_BC{d}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + D_BC{d}.function=@(x) [0; 0]; + D_BC{d}.label='const'; + end + + d=d+1; + + elseif any(ismember(N_types, type)) + N_tags=[N_tags bc.edges]; + N_BC{n}.type=type; + N_BC{n}.edges=bc.edges; + + + if isfield(bc, 'function') + N_BC{n}.function=bc.function; + N_BC{n}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + N_BC{n}.function=@(x) [0; 0]; + N_BC{n}.label='const'; + end + + n=n+1; + end +end + +Data.D_BC=D_BC; +Data.N_BC=N_BC; + +Data.D_tags=D_tags; +Data.N_tags=N_tags; + +%% Sorting nodes +% If there is a node belonging to conflicting boundary conditions, it is +% set using the following priority list: Dirichlet, Neumann, Interior +BC_nodes=Mesh.BC_nodes; +BC_tag=Mesh.BC_tag; +nb_nodes=Mesh.nb_nodes; +Dof_set=Mesh.Dof_set; + +set_D_nodes=cell(nb_D_BC,1); +set_N_nodes=cell(nb_N_BC,1); + +for k=1:nb_D_BC + nodes=BC_nodes(any(BC_tag==D_BC{k}.edges,2),:); + set_D_nodes{k}=unique(nodes); +end +for k=1:nb_N_BC + nodes=BC_nodes(any(BC_tag==N_BC{k}.edges,2),:); + set_N_nodes{k}=unique(nodes); +end +% Define the sets of nodes +% Dirichlet nodes +Nodes_D=unique(cell2mat(set_D_nodes)); +% Neumann nodes +Nodes_N=setdiff(unique(BC_nodes), Nodes_D); +% Interior nodes +Nodes_I=setdiff(1:nb_nodes, union(Nodes_D, Nodes_N)); +% Free nodes +Nodes_F=union(Nodes_I, Nodes_N); + +% Correct the set for Neumann nodes +for k=1:nb_N_BC + set_N_nodes{k}=setdiff(set_N_nodes{k}, Nodes_D); +end + + +% Define the sets of degrees of freedom +Nd=Dof_set(:,Nodes_D); +Nn=Dof_set(:,Nodes_N); +Ni=Dof_set(:,Nodes_I); + +Ni=Ni(:); +Nd=Nd(:); +Nn=Nn(:); +Nf=union(Ni, Nn); + +% Update data +Data.Nodes_I=Nodes_I; +Data.Nodes_N=Nodes_N; +Data.Nodes_D=Nodes_D; +Data.Nodes_F=Nodes_F; + +Data.Ni=Ni; +Data.Nd=Nd; +Data.Nn=Nn; +Data.Nf=Nf; + +Data.set_D_nodes=set_D_nodes; +Data.set_N_nodes=set_N_nodes; + +%% Auxiliary function + + function[label]=detectDependency(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end + +end diff --git a/Code/INTERNODES_small/setCoefficient.m b/Code/INTERNODES_small/setCoefficient.m new file mode 100644 index 0000000..d15a956 --- /dev/null +++ b/Code/INTERNODES_small/setCoefficient.m @@ -0,0 +1,103 @@ +function[Data]=setCoefficient(Data, varargin) + +% setCoefficient: Initializes the model coefficients +% OPTIONAL INPUT: +% rho: Specific mass +% mu: Lamé constant +% lambda: Lamé constant +% f: Function "f" of the PDE +% OUTPUT: +% Data: Data structure with initialized coefficients + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +available_coeff={'rho', 'lambda', 'mu', 'f'}; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + if any(ismember(available_coeff, arg)) + + + switch arg + case {'rho', 'lambda', 'mu'} % Only space dependent + if isa(val,'double') + + if val ~= 0 + Data.Coefficient.(arg).label='const'; + else + Data.Coefficient.(arg).label='zero'; + end + Data.Coefficient.(arg).function=@(x) val; + + elseif isa(val,'function_handle') + Data.Coefficient.(arg).label=detectDependency1(val); + Data.Coefficient.(arg).function=val; + else + error('Input parameters must be either constants or function handles') + end + + case 'f' + if isa(val,'double') % Only space dependent + Data.Coefficient.f.label='const'; + Data.Coefficient.f.function=@(x) val; + elseif isa(val,'function_handle') + Data.Coefficient.f.label=detectDependency2(val); + Data.Coefficient.f.function=@(x) val(x); + else + error('Input parameters must be either constants or function handles') + end + + end + + else + error('Unrecognized coefficient') + end +end + + + function[label]=detectDependency1(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Coefficient can only be constant or space dependent') + end + + if contains(string(5:end), 'x') + label='space'; + elseif contains(string(5), '0') + label='zero'; + else + label='const'; + end + end + + function[label]=detectDependency2(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/setModel.m b/Code/INTERNODES_small/setModel.m new file mode 100644 index 0000000..58c27a3 --- /dev/null +++ b/Code/INTERNODES_small/setModel.m @@ -0,0 +1,75 @@ +function[Data]=setModel(varargin) + +% setModel: Initializes the numerical model +% OPTIONAL INPUT: +% model: Specifies which model should be used +% Default: transient_heat +% submodel: Specifies which submodel from the specified model should be +% used +% Default: standard +% type: Specifies whether the model is linear or nonlinear +% Default: linear +% OUTPUT: +% Data: Data structure with initialized model + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +models={'transient_heat', 'static_solid', 'dynamic_solid'}; +n=length(models); +submodels=cell(n,1); +submodels{1}={'standard'}; +submodels{2}={'standard', 'contact'}; +submodels{3}={'standard'}; +types={'linear', 'nonlinear'}; + +% Set default Parameters +Data.Model.name=models{1}; +Data.Model.submodel=submodels{1}; +Data.Model.type=types{1}; + +% Define constants +% Stefan Boltzman constant W/(m^2*K^4) +Data.Constants.sigma=5.670373e-8; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'model' + if any(ismember(models, val)) + Data.Model.name=val; + else + error('Unrecognized model') + end + + case 'submodel' + + if isfield(Data.Model, 'name') + index=strcmp(Data.Model.name, models); + else + error('Submodel must be defined after the model') + end + if any(ismember(submodels{index}, val)) + Data.Model.submodel=val; + else + error('Unrecognized submodel') + end + + case 'type' + if any(ismember(types, val)) + Data.Model.type=val; + else + error('Unrecognized type') + end + + otherwise + error('Unrecognized argument') + end + +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/setNumericalParameters.m b/Code/INTERNODES_small/setNumericalParameters.m new file mode 100644 index 0000000..9e15a42 --- /dev/null +++ b/Code/INTERNODES_small/setNumericalParameters.m @@ -0,0 +1,223 @@ +function[Data]=setNumericalParameters(Mesh, Data, varargin) + +% setNumericalParameters: Initializes the numerical parameters of the +% solver +% OPTIONAL INPUT: +% Contact: +% iterate: Parameter controlling whether iterations should +% take place (1) or not (0) +% Default: 1 +% maxiter: Maximum number of iterations for solving the +% contact problem +% Default: 10 +% rescaling: Rescaling parameter for the stiffness matrix +% Default: 1 +% c: Number of nearest neighbors for computing the +% radius of support of the radial basis functions +% Default: 1 +% radius: Intersection radius used to define the initial +% potential contact interface. +% Default: infinity +% gamma: Value of gamma for preventing quick loss of diagonal +% dominance of the matrices PhiMM +% Default: 0.5 +% Gamma: Value of Gamma to avoid very small nonzero entries +% in the matrices PhiNM +% Default: 0.95 +% d0: Measure of tolerance for contact detection. +% Default: 0.05 +% tol: Tolerance for gap detection +% Default: 1e-1 +% Solver: +% name: Name of the solver to be used. +% Default: direct +% PostProcessing: +% quantity: Additional quantities to be computed (for example +% stresses, strains, fluxes,...) +% Default: none +% ampl_fact: Value of amplification factor to apply to +% displacements +% Default: auto +% eval_points: Number of evaluation points used for computing +% stresses or strains +% Default: 3 (P1 finite elements) +% 6 (P2 or higher order finite elements) + +if nargin>2 +if mod(length(varargin)-1, 2)~=0 + error('Missing a name or value') +end + +id=lower(varargin{1}); +labels_in=varargin(2:2:end); +values_in=varargin(3:2:end); +end + +% Set default Parameters +if ~isfield(Data, 'Contact') + % Boolean indicator for contact algorithm iterations + Data.Contact.iterate=true; + % Maximum number of iterations of the contact algorithm + Data.Contact.maxiter=10; + % Rescaling parameter for the INTERNODES matrix + Data.Contact.rescaling=1; + % Number of nearest neighbors + Data.Contact.c=1; + % Intersection radius + Data.Contact.radius=inf; + % gamma value + Data.Contact.gamma=0.5; + % Gamma value + Data.Contact.Gamma=0.95; + % Contact tolerance + Data.Contact.d0=0.05; + % Gap tolerance + Data.Contact.tol=1e-1; +end +if ~isfield(Data, 'Solver') + % Solver name + Data.Solver.name='direct'; +end +if ~isfield(Data, 'PostProcessing') + % Additional quantities to be computed + Data.PostProcessing.quantity='none'; + % Amplification factor + Data.PostProcessing.amplification='auto'; + % Number of evaluation points per element for computing stresses/strains + eval_points={3,6}; + + if Mesh.order==1 + Data.PostProcessing.eval_points=eval_points{1}; + else + Data.PostProcessing.eval_points=eval_points{2}; + end + +end + +% Override the defaults +if exist('id', 'var') + switch id + case 'contact' + %% Contact algorithm specific parameters + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'iterate' + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Data.Contact.(arg)=logical(val); + end + + elseif isa(val, 'logical') + Data.Contact.(arg)=val; + else + error([arg ' parameter must be a boolean']) + end + + case {'maxiter', 'c', 'radius', 'd0'} + if val<=0 + error(['Parameter ' arg ' must be strictly greater than zero']) + else + Data.Contact.(arg)=val; + end + + case 'rescaling' + if val==0 + error(['Parameter ' arg ' must be different from zero']) + else + Data.Contact.(arg)=val; + end + + case {'gamma', 'Gamma', 'tol'} + if val<=0 || val>=1 + error([arg ' must be strictly greater than 0 and strictly smaller than 1']) + else + Data.Contact.(arg)=val; + end + + otherwise + error('Unrecognized argument') + end + end + + case 'solver' + %% Solver parameters + names={'direct'}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'name' + if any(ismember(names, val)) + Data.Solver.(arg)=val; + else + error('Unrecognized solver name') + end + + otherwise + error('Unrecognized argument') + end + + end + + case 'postprocessing' + %% Post-processing parameters + quantities={'none', 'flux', 'error', 'stress', 'strain'}; + eval_points={3,6}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'quantity' + if any(ismember(quantities, val)) + Data.PostProcessing.quantity=val; + else + error('Unrecognized quantity') + end + + case 'eval_points' + if any(ismember(eval_points, val)) + Data.PostProcessing.eval_points=val; + else + error('Invalid number of evaluation points') + end + + case 'ampl_fact' + + if isa(val, 'char') + if ~strcmp(val, 'auto') + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + + elseif isa(val, 'double') + if val <= 0 + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + else + error('Invalid amplification factor') + end + + otherwise + error('Unrecognized argument') + end + end + + otherwise + error('Unrecognized argument') + end +end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/solve.m b/Code/INTERNODES_small/solve.m new file mode 100644 index 0000000..12638e4 --- /dev/null +++ b/Code/INTERNODES_small/solve.m @@ -0,0 +1,93 @@ +function[Mesh, Data, Solution, A, b]=solve(Mesh, Data, it) + +% solve: Solves the contact problem +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% it: Cell array containing the boundary tags of the interfaces (master +% interface and slave interface) +% OUTPUT: +% Mesh: Updated structure containing all the mesh parameters +% Data: Updated structure containing all the data parameters +% Solution: Structure containing the solution +% A: Global INTERNODES matrix +% b: Global right-hand side + + +% Initialization of the system +[Data, Solution, A, b, K]=initializeSystem(Mesh, Data, it); +% Initialize count +n=1; + +while Data.Contact.iterate && n<= Data.Contact.maxiter + + % Solve the system of equations + [Solution]=solveSystem(Mesh, Data, Solution, A, b); + % Update the system + [Data, A, b]=updateSystem(Mesh, Data, Solution); + n=n+1; +end + +if n > Data.Contact.maxiter && Data.Contact.iterate + warning('Failed to converge within the specified number of iterations') +end + + function[Solution]=solveSystem(Mesh, Data, Solution, A, b) + + % solveSystem: Solves the linear system of equations + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Initialized solution structure with Dirichlet BC + % A: INTERNODES matrix + % b: Right-hand side vector + % OUTPUT: + % Solution: Updated solution structure with computed displacements + % and Lagrange multipliers + + + % Retrieve data parameters + % Free degrees of freedom + Nf=Data.Nf; + % Number of free degrees of freedom + nf=length(Nf); + % Number of constraints + nc=length(Data.body{1}.interface_dof); + + % Retrieve mesh parameters + d=Mesh.nb_dof_local; + % Retrieve solution + U=Solution.U; + % Direct method + x=A\b; + % Retrieve the displacements + U(Nf)=x(1:nf); + + % Update solution + Solution.U=U; + Solution.U_sort=reshape(U, d, [])'; + Solution.lambda=Data.Contact.rescaling*x(nf+1:nf+nc); + Solution.lambda_sort=reshape(Solution.lambda, d, [])'; + end + + function[Data, A, b]=updateSystem(Mesh, Data, Solution) + + % updateSystem: Updates all quantities which have changed + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Solution structure with computed displacements + % OUPUT: + % Data: Data structure with updated interface properties + % A: New INTERNODES matrix + % b: New right-hand side + + + % Check for convergence + [Data]=removeTraction(Mesh, Data, Solution, n); + % Reassemble the blocks which have changed + [Data]=subAssemble(Mesh, Data, Solution, 'updating'); + % Reassemble the entire internodes matrix + [A,b]=assembleInternodesMat(Mesh, Data, Solution, K); + end +end \ No newline at end of file diff --git a/Code/INTERNODES_small/subAssemble.m b/Code/INTERNODES_small/subAssemble.m new file mode 100644 index 0000000..26e78d1 --- /dev/null +++ b/Code/INTERNODES_small/subAssemble.m @@ -0,0 +1,202 @@ +function[Data]=subAssemble(Mesh, Data, Solution, operation) + +% subAssemble: Assembles the part of the internodes matrix which is +% changing from one iteration to the next +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% operation: Specifies the type of operation (initialization or +% updating) +% OUTPUT: +% Data: Updated structure containing all the data parameters + +if strcmp(operation, 'initialization') + [Data]=computeBoundaryMass(Mesh, Data, 'initialization'); +end + +% Assemble interpolation matrices +[Data]=assembleRs(Mesh, Data, Solution); +% Assemble boundary mass matrices +[Data]=computeBoundaryMass(Mesh, Data, 'updating'); + + function[Data]=assembleRs(Mesh, Data, Solution) + + % assembleRs: Constructs the interpolation matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Structure containing the solution + % OUTPUT: + % Data: Updated data structure with interpolation matrices + + % Retrieve mesh parameters + nb_dof_local=Mesh.nb_dof_local; + + % Retrieve nodes making up the interfaces + nodes1=Data.body{1}.interface_nodes; + nodes2=Data.body{2}.interface_nodes; + + % Retrieve coordinates of nodes making up the interfaces + coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); + coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + + % Check for distant bodies + if all(i1) || all(i2) + warning('One of the bodies will be translated because they are too far away from each other. Boundary conditions might be affected') + [Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + % Check for isolated nodes + while any(i1) || any(i2) + + dump_nodes1=nodes1(i1); + dump_nodes2=nodes2(i2); + + % Updating + [Data]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + [Phi11] = assembleInterpMat(coord1, coord1, radiuses1); + [Phi21] = assembleInterpMat(coord1, coord2, radiuses1); + [Phi22] = assembleInterpMat(coord2, coord2, radiuses2); + [Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + + % Interpolation matrices + R12=Phi12/Phi22; + R21=Phi21/Phi11; + + s1=sum(R12,2); + s2=sum(R21,2); + + % Rescaling + R12=R12./s1; + R21=R21./s2; + + Data.R12_normal=R12; + Data.R21_normal=R21; + + % Expansion to full size + Data.R12=kron(R12, speye(nb_dof_local)); + Data.R21=kron(R21, speye(nb_dof_local)); + + end + + function[Data]=computeBoundaryMass(Mesh, Data, operation) + + % computeBoundaryMass: Assembles the boundary mass matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % operation: Specifies the type of operation (initialization or + % updating) + % OUTPUT: + % Data: Updated data structure with boundary mass matrices + + + % Retrieve mesh parameters + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Get quadrature nodes and weights + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nb_points=length(weights); + + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + + % Retrieve bodies + body=Data.body; + % Number of bodies + nb_bodies=length(body); + + if strcmp(operation, 'initialization') + + for n=1:nb_bodies + % Retrieve data + % Retriveve edges making up the interface + Edges=body{n}.interface_connect; + % Retrieve nodes making up the interface + nodes=body{n}.interface_nodes; + nb_nodes=length(nodes); + s=order+1; + + % Initialization + Phi=zeros(s, nb_points); + + % Map global nodes to local nodes + func=@(x) find(nodes==x); + connect=arrayfun(func, Edges); + T=connect'; + G=T(:); + + I=repmat(connect, [1 s])'; + J=repmat(G', [s 1]); + + % Loop over the Gauss points + for i = 1:nb_points + Phi(:,i)=phi_boundary(points(i,:)); + end + + Tr=Edges'; + Gr=Tr(:); + % Coordinates of the nodes of the elements + coord_nodes=coord(Gr,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + lambda=weights.*norm_bk'; + + M=kr(Phi, Phi)*lambda; + M=sparse(I(:), J(:), M(:), nb_nodes, nb_nodes); + + Data.body{n}.iinterface_mass=M; + % Expansion to full size + M=kron(M, speye(nb_dof_local)); + Data.body{n}.interface_mass=M; + end + else + for n=1:nb_bodies + active_set=Data.body{n}.active_set; + Data.body{n}.interface_mass=kron(Data.body{n}.iinterface_mass(active_set, active_set), speye(nb_dof_local)); + end + end + end + +end \ No newline at end of file diff --git a/Code/INTERNODES_small/translateBodies.m b/Code/INTERNODES_small/translateBodies.m new file mode 100644 index 0000000..f5a398f --- /dev/null +++ b/Code/INTERNODES_small/translateBodies.m @@ -0,0 +1,60 @@ +function[Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2) + +% translateBodies: Performs a translation of one of the bodies in case the +% initial potential contact interface is empty (because the bodies are too +% far away from each other) +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% radiusesk: Radiuses of support of the radial basis functions (k=1,2) +% nodesk: Nodes on the potential contact interface of body k (k=1,2) +% coordk: Coordinates of the nodes on the potential contact interface +% of body k (k=1,2) +% OUTPUT: +% Mesh: Mesh structure with updated coordinate matrix +% coordk: New coordinates of the nodes on the potential contact +% interface of body k (k=1,2) + +n1=length(radiuses1); + +dist=inf; +node1=0; +node2=0; + +for k=1:n1 + + [d, i]=min(vecnorm(coord1(k,:)-coord2,2,2)); + + if d1 + radiuses=radiuses'; +end + +m=size(coord_ref,1); +n=size(coord_interp,1); +dim=size(coord_ref,2); + +X_ref=reshape(coord_ref,1,m,dim); +X_interp=reshape(coord_interp,n,1,dim); + +X=X_interp-X_ref; +% 2-norm (euclidean norm) along the third dimension +N=vecnorm(X,2,3); +indx=N<=radiuses; +Phi=zeros(n,m); + +% Wendland C^2 radial basis functions +F=(1-N./radiuses).^4.*(1+4*N./radiuses); +Phi(indx)=F(indx); +end \ No newline at end of file diff --git a/Code/Westergaard/computeNormals.m b/Code/Westergaard/computeNormals.m new file mode 100644 index 0000000..9a7a340 --- /dev/null +++ b/Code/Westergaard/computeNormals.m @@ -0,0 +1,83 @@ +function[Data]=computeNormals(Mesh, Data, Solution) + +% computeNormals: Computes the normal vectors for each interface +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Data: Updated structure with the interface normals + +% Retrieve mesh data +% Connectivity matrix +connect=Mesh.connect_mat; + +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Retrieve bodies +body=Data.body; +% Compute the normals based on the deformed structure +def=Mesh.coord_mat+Solution.U_sort; + +nb_bodies=length(body); + + +for k=1:nb_bodies + % Normal vectors are computed for all nodes in the initial potential + % contact interface + connecti=body{k}.initial_interface_connect; + nodesi=body{k}.initial_nodes; + + % Reduced connectivity matrix + connect_red=connect(any(ismember(connect, nodesi), 2), :); + % Reduced set of body nodes + nodesb=setdiff(unique(connect_red), nodesi); + n=length(nodesi); + m=size(connecti,1); + % Coordinates of the endpoints of the segments + coord1=def(connecti(:,1),:); + coord2=def(connecti(:,2),:); + % Tangent vectors + V=coord2-coord1; + if Data.Contact.average + % Segment lengths + V=V./vecnorm(V,2,2); + L=vecnorm(V,2,2); + else + % Segment lengths + L=vecnorm(V,2,2); + V=V./vecnorm(V,2,2); + end + + % !! Only for a two-dimensional case !! + N=zeros(m, d); + N(:,1)=-V(:,2); + N(:,2)=V(:,1); + + % Initialization + normals=zeros(n,d); + % Step size + gamma=1e-3; + + for j=1:n + [id,~]=find(connecti==nodesi(j)); + l=L(id); + % Weighted average of normal vectors + normals(j,:)=1/sum(l)*sum(N(id,:).*l,1); + % Current node + x=def(nodesi(j),:); + % Step in the direction of the gradient + y_plus=x+gamma*normals(j,:); + % Step in the opposite direction of the gradient + y_minus=x-gamma*normals(j,:); + min_plus=min(vecnorm(def(nodesb,:)-y_plus,2,2)); + min_minus=min(vecnorm(def(nodesb,:)-y_minus,2,2)); + if min_plus < min_minus + normals(j,:)=-normals(j,:); + end + end + + normals=normals./vecnorm(normals,2,2); + Data.body{k}.normal=normals; +end +end \ No newline at end of file diff --git a/Code/Westergaard/computeRHS.m b/Code/Westergaard/computeRHS.m new file mode 100644 index 0000000..b6f4112 --- /dev/null +++ b/Code/Westergaard/computeRHS.m @@ -0,0 +1,223 @@ +function[Data]=computeRHS(Mesh, Data) + +% computeRHS: Function which assembles the right-hand side consisting of +% body forces and surface tractions +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% OUTPUT: +% Data: Updated data structure + +% Assemble the global vector of body forces +[Bs]=computeBody(Mesh, Data); +% Assemble the global vector of surface tractions +[Bn]=computeNeumann(Mesh, Data); + +% Sum the contribution from body forces and Neumann boundary +% conditions +B=Bs+Bn; +% Update data +Data.F=B; + + function[B]=computeBody(Mesh, Data) + + % computeBody: Assembles the component for the body forces + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of body forces + + % Retrieve mesh data + % Local number of degrees of freedom (dimension in solid mechanics) + d=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Finite element order + order=Mesh.order; + % Number of elements + ne=Mesh.nb_elem; + % Number of nodes per element + nne=Mesh.nb_nodes_elem; + % Matrix of equation numbers linking an element to the degrees of freedom + % associated to it. + Eq=Mesh.Eq; + + % Retrieve data parameters + % Body force vector + f=Data.Coefficient.f.function; + % Retrieve the basis functions + [Functions]=getFunctions(order); + % Retrieve basis functions + phi=Functions.phi; + + + % Retrieve quadrature points and weights + switch order + case 1 + [points, weights] = getQuadrature(3, 'bulk'); + case 2 + [points, weights] = getQuadrature(4, 'bulk'); + case 3 + [points, weights] = getQuadrature(6, 'bulk'); + otherwise + error('Not yet implemented'); + end + + % Number of quadrature points + nq=length(weights); + s=nne*d; + Id=eye(d); + % Initialization + X=zeros(ne, nq, 2); + Q=zeros(s, nq*d); + + + % Loop over the Gauss points + for i = 1:nq + Phi=phi(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + I=Eq'; + T=connect'; + G=T(:); + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Vertices of the elements + a=coord_nodes(1:nne:end,:); + b=coord_nodes(2:nne:end,:); + c=coord_nodes(3:nne:end,:); + % Interpolated coordinates + P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Determinants of Jacobian matrices + detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(abs(detJK), 1, 1, ne).*F.*weights; + X=reshape(X, d*nq, ne); + + B=Q*X; + + % Assemblage + B=accumarray(I(:), B(:), [nb_dof 1]); + end + + function[B]=computeNeumann(Mesh, Data) + + % computeNeumann: Implements Neumann boundary conditions, returns a vector + % having for length the number of degrees of freedom. + % This vector is to be added to the RHS. + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % OUTPUT: + % B: Global vector of surface tractions + + % Retrieve mesh parameters + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + % Number of local degrees of freedom + d=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Boundary equation numbers + Eq_BC=Mesh.Eq_BC; + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + % Get quadrature + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nq=length(weights); + + % Initialization + B=zeros(nb_dof,1); + s=order+1; + Id=eye(d); + % Initialization + Q=zeros(s*d, nq*d); + + % Loop over the Gauss points + for i = 1:nq + Phi=phi_boundary(points(i,:)); + Q(:, (i-1)*d+1:i*d)=kron(Phi, Id); + end + + + % Neumann boundary conditions + N_BC=Data.N_BC; + % Number of boundaries where surface tractions are imposed + nb_bc=length(N_BC); + + for m=1:nb_bc + % Retrieve data + data=N_BC{m}; + edges=data.edges; + + for flag=edges + % Surface traction function + f=data.function; + % Retriveve edges making up the boundary + Neum_edges=Mesh.BC_nodes(Mesh.BC_tag==flag,:); + % Equation numbers + Eq=Eq_BC(Mesh.BC_tag==flag,:); + % Number of Neumann boundary edges + ne=size(Neum_edges, 1); + + % Initialization + X=zeros(ne, nq, 2); + + C=Neum_edges'; + G=C(:); + I=Eq'; + + % Coordinates of the nodes of the elements + coord_nodes=coord(G,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + % Interpolated coordinates + P=a(:)+(b(:)-a(:))*points'; + X(:,:,1)=P(1:ne,:); + X(:,:,2)=P(ne+1:end,:); + + % Evaluation at the Gauss points + F=f(X); + if size(F,1)>d + F=reshape(F, ne, d*nq); + F=reshape(F', d, nq, ne); + end + + X=reshape(norm_bk, 1, 1, ne).*F.*weights'; + X=reshape(X, d*nq, ne); + + Bl=Q*X; + + % Assemblage + B=B+accumarray(I(:), Bl(:), [nb_dof 1]); + + end + end + end +end \ No newline at end of file diff --git a/Code/Westergaard/computeStiffness.m b/Code/Westergaard/computeStiffness.m new file mode 100644 index 0000000..6091151 --- /dev/null +++ b/Code/Westergaard/computeStiffness.m @@ -0,0 +1,138 @@ +function[K]=computeStiffness(Mesh, Data) + +% computeStiffness: Function which assembles the global stiffness matrix +% Assumes all elements are of the same type +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OUTPUT: +% K: Global stiffness matrix + +% Mesh parameters +% Local number of degrees of freedom (dimension in solid mechanics) +d=Mesh.nb_dof_local; +% Total number of degrees of freedom +nb_dof=Mesh.nb_dof; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Finite element order +order=Mesh.order; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Total number of elements +ne=Mesh.nb_elem; +% Matrix linking an element to the degrees of freedom associated to it. +Eq=Mesh.Eq; + +% Data parameters +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; + + +% Retrieve functions +[Functions]=getFunctions(order); +% Jacobian matrix +J_phi=Functions.J_phi; +% Define permutation matrix +Sdd=zeros(d*d); +Id=eye(d); +for k=1:d + Sdd=Sdd+kron(Id(:,k)', kron(Id, Id(:,k))); +end + +% Retrieve quadrature nodes and weights +switch order + case 1 + [points, weights] = getQuadrature(1, 'bulk'); + case 2 + [points, weights] = getQuadrature(2, 'bulk'); + case 3 + [points, weights] = getQuadrature(3, 'bulk'); + otherwise + error('Not yet implemented'); +end + +% Initialization +s=nne*d; +nq=length(weights); +Q=zeros(s^2, nq*d^4); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:)), Id); + Q(:, (i-1)*d^4+1:i*d^4)=kron(L, L); +end + +T=Eq'; +G=T(:); + +I=repmat(Eq, [1 nne*d])'; +J=repmat(G', [nne*d 1]); + +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); + +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +V=[(vecnorm(c-a,2,2).^2) -sum((c-a).*(b-a),2) -sum((c-a).*(b-a),2) (vecnorm(b-a,2,2).^2)]; +W=1./(detJK').^2.*(V'); + +V=reshape(W,d,d*ne); + +JK_inv=zeros(d, d*ne); +JK_inv(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(b(:,2)-a(:,2))]'; +JK_inv(:,2:d:d*ne)=1./detJK'.*[-(c(:,1)-a(:,1)) b(:,1)-a(:,1)]'; + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, d^2, ne); + +[S1]=product(V , repmat(Id, 1, ne), d, ne); +[S2]=product(JK_invT , JK_inv, d, ne); +S2=Sdd*S2; + +A1=reshape(S1, d^4, ne); +A2=reshape(S2, d^4, ne); +A3=kr(vec_JK_invT, vec_JK_invT); + + +Lambda1=(abs(detJK).*f1(X).*weights)'; +Lambda2=(abs(detJK).*f2(X).*weights)'; + +X=kr(Lambda1, A1+A2)+kr(Lambda2, A3); +K=Q*X; + +% Assemblage +K=sparse(I(:), J(:), K(:), nb_dof, nb_dof); + + function[M]=product(A,B,d,ne) + + % Blockwise Kronecker product of two matrices + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end + +end \ No newline at end of file diff --git a/Code/Westergaard/detectGaps.m b/Code/Westergaard/detectGaps.m new file mode 100644 index 0000000..ffe94c0 --- /dev/null +++ b/Code/Westergaard/detectGaps.m @@ -0,0 +1,81 @@ +function[add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution) + +% detectGaps: computes the gap between nodes of opposing interfaces and +% detects interpenetration +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% add_nodes1: Interface nodes of body 1 to be added to the active set +% add_nodes2: Interface nodes of body 2 to be added to the active set + +% Initial nodes +inodes1=Data.body{1}.initial_nodes; +inodes2=Data.body{2}.initial_nodes; + +nodes1=inodes1; +nodes2=inodes2; + +% Retrieve coordinates of nodes making up the interfaces +coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); +coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + +[radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); +[radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + +i1=nnzR12==0; +i2=nnzR21==0; + +% Check for isolated nodes +while any(i1) || any(i2) + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; +end + +[Phi11] = assembleInterpMat(coord1, coord1, radiuses1); +[Phi21] = assembleInterpMat(coord1, coord2, radiuses1); +[Phi22] = assembleInterpMat(coord2, coord2, radiuses2); +[Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + +% Interpolation matrices +R12=Phi12/Phi22; +R21=Phi21/Phi11; + +s1=sum(R12,2); +s2=sum(R21,2); + +% Rescaling +R12=R12./s1; +R21=R21./s2; + +Diff1=R12*coord2-coord1; +Diff2=R21*coord1-coord2; + +i1=ismember(inodes1, nodes1); +i2=ismember(inodes2, nodes2); + +% computes the scalar products +scalar1_g=sum(Diff1.*Data.body{1}.normal(i1,:),2); +scalar2_g=sum(Diff2.*Data.body{2}.normal(i2,:),2); + +% Thresholds are a fraction of the mesh size and could be specific to each +% interface (consider using the mesh sizes of body 1 and 2) +threshold1=-Data.Contact.tol*Data.Contact.h; +threshold2=-Data.Contact.tol*Data.Contact.h; + +% Detect interpenetration +add_nodes1=nodes1(scalar1_ggamma +% Used to ensure evaluation points are well within the +% support of radial basis functions (far enough from the boundary) +Gamma=Data.Contact.Gamma; +% Tolerance for contact detection +% If the tolerance is unknown, set it to some small value. +d0=Data.Contact.d0; +% Number of closest neighbors on current interface +c=Data.Contact.c; + +% Number of off-diagonal nonzeros per row of PhiMM +nnzRMM=zeros(M,1); +% Number of off-diagonal nonzeros per column of PhiMM +nnzCMM=zeros(M,1); +% Number of nonzeros per row of PhiNM +nnzRNM=zeros(N,1); +% Number of nonzeros per column of PhiNM +nnzCNM=zeros(M,1); + +maxS=inf; +f=0; +niter=0; +maxiter=10; + +% Version 2 +while maxS>f && niter= gamma*radius. + % If the point is too close (with respect to the radius), diagonal + % dominance will be lost very quickly. + if radius>rMM(1)/gamma + radius=rMM(1)/gamma; + end + + s1=distMMf + % Increase gamma (sequence converges to 1) + gamma=0.5*(1+gamma); + % Reinitialize counters + nnzRMM=zeros(M,1); + nnzCMM=zeros(M,1); + nnzRNM=zeros(N,1); + nnzCNM=zeros(M,1); + niter=niter+1; + end +end + +if niter==maxiter && maxS>f + warning('The maximum number of rounds for radius computations was reached. Try decreasing d0') +end +end \ No newline at end of file diff --git a/Code/Westergaard/initializeInterface.m b/Code/Westergaard/initializeInterface.m new file mode 100644 index 0000000..9f57511 --- /dev/null +++ b/Code/Westergaard/initializeInterface.m @@ -0,0 +1,107 @@ +function[Data]=initializeInterface(Mesh, Data, it) + +% initializeInterface: Initializes the interfaces properties +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% it: Boundary tags for either a portion of the boundary or the entire boundary +% The first component is the tag for body 1 +% The second component is the tag for body 2 +% OUTPUT: +% Data: Updated data structure with interface properties + +% Retrieve mesh parameters +% Retrieve boundary tag +BC_tag=Mesh.BC_tag; +% Retrieve coordinate matrix +coord=Mesh.coord_mat; +% Define radius of intersection +if isfield(Data.Contact, 'radius') + r=Data.Contact.radius; +else + r=0.052; +end + +% Boundary elements +elements1=Mesh.BC_nodes(ismember(BC_tag,it{1}),:); +elements2=Mesh.BC_nodes(ismember(BC_tag,it{2}),:); + +% Boundary nodes +bc_nodes1=unique(elements1); +bc_nodes2=unique(elements2); + +m1=length(bc_nodes1); +m2=length(bc_nodes2); + +% Compute element lengths +l1=vecnorm(coord(elements1(:,2),:)-coord(elements1(:,1),:),2,2); +l2=vecnorm(coord(elements2(:,2),:)-coord(elements2(:,1),:),2,2); + +% Compute mesh sizes +h1=min(l1); +h2=min(l2); + +body_1.h=h1; +body_2.h=h2; + +h=min([h1, h2]); +Data.Contact.h=h; + +% Indicators +ic1=zeros(m1,1); +ic2=zeros(m2,1); + +% Create interfaces based on closest nodes from opposing interface +for k=1:m1 + node=bc_nodes1(k); + coord_node=coord(node,:); + dist=vecnorm(coord(bc_nodes2,:)-coord_node,2,2); + ic1(k)=any(distnb_dof_local + G=reshape(G, nb_nodes, nb_dof_local)'; + G=G(:); + else + G=repmat(G, nb_nodes, 1); + end + U(dofs(:))=G; + + else + % Degrees of freedom + dofs=set_Nd{i}; + % Coordinates + X=reshape(coord(nodes,:), nb_nodes, 1, nb_dof_local); + % Evaluation + G=g(X); + U(dofs)=G; + end + + end + + % Update solution + Solution.U=U; + % Reshape to an array of the same size as the coordinate matrix + Solution.U_sort=reshape(U, nb_dof_local, [])'; + end + +end \ No newline at end of file diff --git a/Code/Westergaard/kr.m b/Code/Westergaard/kr.m new file mode 100644 index 0000000..874aea5 --- /dev/null +++ b/Code/Westergaard/kr.m @@ -0,0 +1,31 @@ +function X = kr(U,varargin) +%KR Khatri-Rao product. +% kr(A,B) returns the Khatri-Rao product of two matrices A and B, of +% dimensions I-by-K and J-by-K respectively. The result is an I*J-by-K +% matrix formed by the matching columnwise Kronecker products, i.e. +% the k-th column of the Khatri-Rao product is defined as +% kron(A(:,k),B(:,k)). +% +% kr(A,B,C,...) and kr({A B C ...}) compute a string of Khatri-Rao +% products A o B o C o ..., where o denotes the Khatri-Rao product. +% +% See also kron. + +% Version: 21/10/10 +% Authors: Laurent Sorber (Laurent.Sorber@cs.kuleuven.be) + +if ~iscell(U), U = [U varargin]; end +K = size(U{1},2); +if any(cellfun('size',U,2)-K) + error('kr:ColumnMismatch', ... + 'Input matrices must have the same number of columns.'); +end +J = size(U{end},1); +X = reshape(U{end},[J 1 K]); +for n = length(U)-1:-1:1 + I = size(U{n},1); + A = reshape(U{n},[1 I K]); + X = reshape(bsxfun(@times,A,X),[I*J 1 K]); + J = I*J; +end +X = reshape(X,[size(X,1) K]); diff --git a/Code/Westergaard/mapGlobal2Local.m b/Code/Westergaard/mapGlobal2Local.m new file mode 100644 index 0000000..6b14a3a --- /dev/null +++ b/Code/Westergaard/mapGlobal2Local.m @@ -0,0 +1,13 @@ +function[Data]=mapGlobal2Local(Data) + +% mapGlobal2Local: Function which maps the global degrees of freedom to the +% local ones for the INTERNODES matrix +% INPUT: +% Data: Structure containing all the data parameters +% OUTPUT: +% Data: Updated structure with the local degrees of freedom + +Nf=Data.Nf; +[~, Data.body{1}.indx_local, ~]=intersect(Nf, Data.body{1}.interface_dof); +[~, Data.body{2}.indx_local, ~]=intersect(Nf, Data.body{2}.interface_dof); +end \ No newline at end of file diff --git a/Code/Westergaard/plotSolution.m b/Code/Westergaard/plotSolution.m new file mode 100644 index 0000000..47ba494 --- /dev/null +++ b/Code/Westergaard/plotSolution.m @@ -0,0 +1,813 @@ +function[]=plotSolution(Mesh, Data, Solution, varargin) + +% plotSolution: Function which plots the computed quantities +% INPUT: +% Mesh: Structure containing the mesh parameters +% Data: Structure containing the data parameters +% Solution: Structure containing the solution computed +% OPTIONAL INPUT +% snapshot: selected time-step at which the solution should be viewed +% 1 -> none (default) +% any time step in the range defined +% view_video: view the video of the solution +% 1 -> yes +% 0 -> no (default) +% save_video: choose whether to save the video or not +% 1 -> yes +% 0 -> no (default) +% plot_arg: choose which solution to visualize +% dynamic_solid or static_solid module +% disp (default) +% stress +% strain +% transient_heat module +% temp (default) +% flux +% plot_type: decide which type of metric should be used for visualization +% displacement metric +% standard -> structure with amplified displacements (default) +% norm +% absx +% absy +% temperature metric +% standard (default) +% stress metric +% von_mises (default) +% sigma_xx +% sigma_yy +% sigma_xy +% sigma_xx_abs +% sigma_yy_abs +% sigma_xy_abs +% frobenius +% strain metric +% epsilon_xx (default) +% epsilon_yy +% epsilon_xy +% epsilon_xx_abs +% epsilon_yy_abs +% epsilon_xy_abs +% frobenius +% flux metric +% standard (default) + + +%% Initialize and ckeck Parameters + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +% Specify number of sub-intervals in time +if contains(Data.Model.name, 'static') + static=true; + N=0; +else + static=false; + N=Data.Discretization.N; +end + +switch Data.Model.name + case {'static_solid', 'dynamic_solid'} + plot_argv={'disp', 'stress', 'strain'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard', 'norm', 'absx', 'absy'}; + plot_typev{2}={'von_mises', 'sigma_xx', 'sigma_yy', 'sigma_xy', 'sigma_xx_abs', 'sigma_yy_abs', 'sigma_xy_abs', 'frobenius'}; + plot_typev{3}={'epsilon_xx', 'epsilon_yy', 'epsilon_xy', 'epsilon_xx_abs', 'epsilon_yy_abs', 'epsilon_xy_abs', 'frobenius'}; + + case 'transient_heat' + plot_argv={'temp', 'flux'}; + n=length(plot_argv); + plot_typev=cell(n,1); + plot_typev{1}={'standard'}; + plot_typev{2}={'standard'}; + +end + +% Set all default parameters +% Default visualization parameters +Param.visua_param.snapshot=1; +Param.visua_param.view_video=false; +Param.visua_param.write_video=false; +% Default plot argument +Param.plot_param.arg=plot_argv(1); +% Default plot type +Param.plot_param.type=plot_typev{1}(1); + +plot_arg={}; +plot_type={}; +nb_plot=0; + +for k=1:length(labels_in) + arg=lower(labels_in{k}); + val=values_in{k}; + + switch arg + case 'snapshot' + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + elseif val < 0 || val > Data.Discretization.N+1 + error(['Time snapshot invalid: it must be an integer between ' num2str(0) ' and ' num2str(Data.Discretization.N+1)]) + else + Param.visua_param.snapshot=val; + end + + case {'view_video', 'write_video'} + + if static + warning([arg ' parameter will be ignored because the simulation is in statics']) + else + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Param.visua_param.view_video=logical(val); + end + + elseif isa(val, 'logical') + Param.visua_param.view_video=val; + else + error([arg ' parameter must be a boolean']) + end + end + + case plot_argv + + % Check if desired quantities have been computed + switch arg + case 'stress' + if ~isfield(Solution, 'sigma') + error('Stresses have not been computed') + end + + case 'strain' + if ~isfield(Solution, 'epsilon') + error('Strains have not been computed') + end + + case 'flux' + if ~isfield(Solution, 'Q') + error('Fluxes have not been computed') + end + end + + plot_arg{nb_plot+1}=arg; + index=strcmp(arg, plot_argv); + + if any(ismember(plot_typev{index}, val)) + plot_type{nb_plot+1}=val; + else + error('Plot type invalid: see available plot types') + end + + nb_plot=nb_plot+1; + otherwise + error('Unrecognized argument') + end + +end + +if nb_plot>0 + Param.plot_param.arg=plot_arg; + Param.plot_param.type=plot_type; +end + +%% Plotting interface + +plot_arg=Param.plot_param.arg; +plot_type=Param.plot_param.type; +n=length(plot_arg); +Plot=cell(n,1); +Axis=cell(n,1); +Colorbar=cell(n,1); +Dynamic=cell(n,1); + +fig=figure; +% fig.Units='normalized'; +% fig.Position=[0 0 1 1]; + +% fig.Units='normalized'; +% fig.Position=[0 1 1/2 1/2]; + +for k=1:n + switch plot_arg{k} + case {'disp', 'temp'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case {'strain', 'stress'} + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, plot_arg{k}, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=patch(axis); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + + + case 'flux' + [PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, plot_type{k}); + subplot(1,n,k) + axis=gca; + p=quiver(axis, PlotParam{1,2}, PlotParam{2,2}); + c=colorbar(axis); + [p]= setObject(p, PlotParam); + [axis]= setObject(axis, AxisParam); + [c]= setObject(c, ColorbarParam); + + end + + Plot{k}=p; + Axis{k}=axis; + Colorbar{k}=c; + Dynamic{k}=DynamicParam; +end + +if Param.visua_param.view_video % View the solution over the entire simulation + + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, 1, N); + end + + frames(1)=getframe(fig); + for step = 2:N+1 + pause(0.05) + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + refreshdata(Axis{k}); + end + frames(step)=getframe(fig); + end + +else % View solution at a particular time snapshot + step=Param.visua_param.snapshot; + for k=1:n + [Plot{k}, Axis{k}]=updateObject(Plot{k}, Axis{k}, Dynamic{k}, step, N); + end +end + + +if Param.visua_param.write_video +% vid_duration=25; % seconds + vid_duration=10; % seconds + video=VideoWriter('Output.mp4', 'MPEG-4'); + video.FrameRate=(N+1)/vid_duration; + open(video); + writeVideo(video,frames); + close(video); +end + +%% Parameters for patch plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotPatch(Mesh, Data, Solution, type) + + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Connectivity matrix + connect=Mesh.connect_mat; + % Total number of nodes + nb_nodes=Mesh.nb_nodes; + % Frame surrounding the structure + frame=Mesh.frame; + % Largest dimension + L_max=Mesh.L_max; + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Total number of degrees of freedom + nb_dof=Mesh.nb_dof; + + % Retrieve solution + U=Solution.U; + + + switch Data.Model.name + + case {'static_solid', 'dynamic_solid'} + + % Initilization + Vertices=zeros(nb_nodes,2,N+1); + CData=cell(N+1,1); + + + switch type + case 'standard' + + if strcmp(Data.PostProcessing.amplification, 'auto') + % Maximum displacement + disp_max=max(max(abs(U))); + % Amplification factor + fact_ampl=L_max/(2*disp_max); + else + fact_ampl=Data.PostProcessing.amplification; + end + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + % Aplified displacements + ux_ampl=fact_ampl*U(d1,:); + uy_ampl=fact_ampl*U(d2,:); + % Deformed state + def_x=coord(:,1)+ux_ampl; + def_y=coord(:,2)+uy_ampl; + % Vertices in deformed state + Vertices(:,1,:)=def_x; + Vertices(:,2,:)=def_y; + XLim=[min(def_x, [], 'all'); + max(def_x, [], 'all')]; + YLim=[min(def_y, [], 'all'); + max(def_y, [], 'all')]; + CLim=[0,1]; + Colormap=[]; + ColorbarStatus='off'; + ColorbarLabel=''; + +% Title='Amplified displacements'; + Title='Displacements'; + EdgeColor='black'; + FaceColor='none'; + + case 'norm' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + U_norm=sqrt(Ux.^2+Uy.^2); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(U_norm, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Norm of displacements'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=U_norm(:,m); + end + + case 'absx' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + + Ux_abs=abs(Ux); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Ux_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along x'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Ux_abs(:,m); + end + + case 'absy' + + % Retrieve degrees of freedom + d1=1:nb_dof_local:nb_dof; + d2=2:nb_dof_local:nb_dof; + Ux=U(d1,:); + Uy=U(d2,:); + x=coord(:,1)+Ux; + y=coord(:,2)+Uy; + Uy_abs=abs(Uy); + + XLim=[min(x, [], 'all'); + max(x, [], 'all')]; + YLim=[min(y, [], 'all'); + max(y, [], 'all')]; + CLim=[0 max(Uy_abs, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Displacement [m]'; + Title='Displacements in absolute value along y'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=x(:,m); + Vertices(:,2,m)=y(:,m); + CData{m}=Uy_abs(:,m); + end + + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + case 'transient_heat' + + % Number of sub-intervals in time + N=Data.Discretization.N; + % Initilization + CData=cell(N+1,1); + + switch type + case 'standard' + + XLim=frame(:,1); + YLim=frame(:,2); + CLim=[min(U, [], 'all') max(U, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + ColorbarLabel='Temperature [ºC]'; + % ColorbarLabel='Temperature [K]'; + Title='Temperature'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + CData{m}=U(:,m); + end + + end + + + % Set static parameters + PlotParam = {'Vertices', coord;... + 'Faces', connect(:,1:3);... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'CData', CData;... + 'Title', Title}; + + end + + + end + +%% Parameters for surf plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotSurf(Mesh, Data, Solution, arg, type) + + % Retrieve mesh parameters + % Coordinate matrix of interpolation nodes + coord_inter=Mesh.coord_inter; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve data parameters + % Number of evaluation points per element + nq=Data.PostProcessing.eval_points; + + % Total number of interpolation nodes + nb_nodes_inter=size(coord_inter,1); + + % Initilization + Vertices=zeros(nb_nodes_inter,2,N+1); + CData=cell(N+1,1); + + switch arg + + case 'stress' + + % Retrieve solution + sigma=permute(Solution.sigma, [2 1 3]); + + switch type + case 'von_mises' + + % Von Mises stress + select=sqrt(sigma(:,1,:).^2-sigma(:,1,:).*sigma(:,2,:)+sigma(:,2,:).^2+3*sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Von Mises stress field'; + + case 'sigma_xx' + + % Value of sigma_xx + select=sigma(:,1,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xx}'; + + case 'sigma_yy' + + % Value of sigma_yy + select=sigma(:,3,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{yy}'; + + case 'sigma_xy' + + % Value of sigma_xy + select=sigma(:,2,:); + ColorbarLabel='Stress [Pa]'; + Title='\sigma_{xy}'; + + case 'sigma_xx_abs' + + % Abolute value of sigma_xx + select=abs(sigma(:,1,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xx}|'; + + case 'sigma_yy_abs' + + % Absolute value of sigma_yy + select=abs(sigma(:,3,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{yy}|'; + + case 'sigma_xy_abs' + + % Absolute value of sigma_xy + select=abs(sigma(:,2,:)); + ColorbarLabel='Stress [Pa]'; + Title='|\sigma_{xy}|'; + + case 'frobenius' + + % Frobenius norm of sigma + select=sqrt(sigma(:,2,:).^2+2*sigma(:,2,:).^2+sigma(:,3,:).^2); + ColorbarLabel='Stress [Pa]'; + Title='Frobenius norm of \sigma'; + + end + + case 'strain' + + % Retrieve solution + epsilon=permute(Solution.epsilon, [2 1 3]); + + switch type + case 'epsilon_xx' + + % Value of epsilon_xx + select=epsilon(:,1,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xx}'; + + case 'epsilon_yy' + + % Value of epsilon_yy + select=epsilon(:,3,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{yy}'; + + case 'epsilon_xy' + + % Value of esilon_xy + select=epsilon(:,2,:); + ColorbarLabel='Strain [-]'; + Title='\epsilon_{xy}'; + + case 'epsilon_xx_abs' + + % Abolute value of epsilon_xx + select=abs(epsilon(:,1,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xx}|'; + + case 'epsilon_yy_abs' + + % Absolute value of epsilon_yy + select=abs(epsilon(:,3,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{yy}|'; + + case 'epsilon_xy_abs' + + % Absolute value of epsilon_xy + select=abs(epsilon(:,2,:)); + ColorbarLabel='Strain [-]'; + Title='|\epsilon_{xy}|'; + + case 'frobenius' + + % Frobenius norm of epsilon + select=sqrt(epsilon(:,2,:).^2+2*epsilon(:,2,:).^2+epsilon(:,3,:).^2); + ColorbarLabel='Strain [-]'; + Title='Frobenius norm of \epsilon'; + + end + + end + + % Common parameters + XLim=[min(coord_inter(:,1,:), [], 'all'); + max(coord_inter(:,1,:), [], 'all')]; + YLim=[min(coord_inter(:,2,:), [], 'all'); + max(coord_inter(:,2,:), [], 'all')]; + CLim=[min(select, [], 'all') max(select, [], 'all')]; + Colormap=jet; + ColorbarStatus='on'; + EdgeColor='none'; + FaceColor='interp'; + + for m=1:N+1 + Vertices(:,1,m)=coord_inter(:,1,m); + Vertices(:,2,m)=coord_inter(:,2,m); + CData{m}=select(:,m); + end + + + switch nq + case 3 + % 3-point evaluation inside each element + I=1:3*Mesh.nb_elem; + I=reshape(I, 3, Mesh.nb_elem)'; + + case 6 + % 6-point evaluation inside each element + i1=[1 4 5 2 2 5 6 3 3 6 4 1]; + i2=[4 5 6]; + I1=i1+(0:Mesh.nb_elem-1)'*6; + I2=i2+(0:Mesh.nb_elem-1)'*6; + I1=reshape(I1', 4, 3*Mesh.nb_elem)'; + I2=[I2 nan*zeros(size(I2,1),1)]; + I=[I1; I2]; + end + + % Set static parameters + PlotParam = {'Vertices', coord_inter;... + 'Faces', I;... + 'FaceVertexCData', CData{1};... + 'FaceColor', FaceColor;... + 'EdgeColor', EdgeColor;... + 'LineWidth', 1}; + + AxisParam = {'XLim', XLim;... + 'YLim', YLim;... + 'CLim', CLim;... + 'Colormap', Colormap;... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', ColorbarStatus;... + 'Label.String', ColorbarLabel;... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'Vertices', num2cell(Vertices, [1,2]);... + 'CData', CData;... + 'Title', Title}; + + end + +%% Parameters for quiver plots + function[PlotParam, AxisParam, ColorbarParam, DynamicParam]=plotQuiver(Mesh, Solution, type) + + % Retrieve mesh parameters + % Coordinate matrix + coord=Mesh.coord_mat; + % Frame surrounding the structure + frame=Mesh.frame; + + % Retrieve solution + Q_sort=Solution.Q_sort; + + % Initialization + U=cell(1,N+1); + V=cell(1,N+1); + + switch type + case 'standard' + % Standard visualization of fluxes + Title='Flux field'; + + for m=1:N+1 + U{m}=Q_sort(:,2*m-1); + V{m}=Q_sort(:,2*m); + end + + end + + + % Set static parameters + PlotParam = {'XData', coord(:,1);... + 'YData', coord(:,2);... + 'LineWidth', 1}; + + AxisParam = {'XLim', frame(:,1);... + 'YLim', frame(:,2);... + 'DataAspectRatio', [1 1 1];... + 'XLabel.String', 'x coordinate [m]';... + 'YLabel.String', 'y coordinate [m]'}; + + ColorbarParam = {'Visible', 'off';... + 'Label.String', '';... + 'Label.FontSize', 11}; + + % Set dynamic parameters + DynamicParam={'UData', U;... + 'VData', V;... + 'Title', Title}; + + + end + + + + + + +%% Setting and updating graphic properties + function[object]=setObject(object, param) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument) + if contains(argument{i}, '.') + field=extractBetween(argument{i}, 1, '.'); + subfield=extractAfter(argument{i}, '.'); + object.(field{1}).(subfield)=value{i}; + else + object.(argument{i})=value{i}; + end + end + + end + + function[plot, axis]=updateObject(plot, axis, param, step, N) + + argument=param(:,1); + value=param(:,2); + + for i=1:length(argument)-1 + plot.(argument{i})=value{i}{step}; + end + + axis.Title.String=[value{end} ' at step ' num2str(step) ' out of ' num2str(N+1)]; + end +end \ No newline at end of file diff --git a/Code/Westergaard/postProcess.m b/Code/Westergaard/postProcess.m new file mode 100644 index 0000000..364859c --- /dev/null +++ b/Code/Westergaard/postProcess.m @@ -0,0 +1,18 @@ +function[Mesh, Solution]=postProcess(Mesh, Data, Solution) + +% postProcess: General post-processing function used to compute derived +% quantities +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% OUTPUT: +% Mesh: Updated mesh structure with coordinates of nodes where stresses +% and strains were evaluated +% Solution: Updated solution structure with derived quantities + + +if any(contains(Data.PostProcessing.quantity, {'stress', 'strain'})) + [Mesh, Solution]=postProcessDisp(Mesh, Data, Solution); +end +end \ No newline at end of file diff --git a/Code/Westergaard/postProcessDisp.m b/Code/Westergaard/postProcessDisp.m new file mode 100644 index 0000000..f1f6613 --- /dev/null +++ b/Code/Westergaard/postProcessDisp.m @@ -0,0 +1,147 @@ +function[Mesh, Solution]=postProcessDisp(Mesh, Data, Solution) + +% postProcessDisp: Function used to compute strains and stresses once the +% displacements are known. +% INPUT: +% Mesh: Structure containing all mesh parameters +% Data: Structure containing all data parameters +% Solution: Structure containing the computed solutions +% OUTPUT: +% Mesh: Updated mesh structure with evaluation points coordinates +% Solution: Updated solution structure including strains and stresses + + +% Finite element order +order=Mesh.order; +% Number of local degrees of freedom +d=Mesh.nb_dof_local; +% Number of elements +ne=Mesh.nb_elem; +% Number of nodes per element +nne=Mesh.nb_nodes_elem; +% Coordinate matrix +coord=Mesh.coord_mat; +% Connectivity matrix +connect=Mesh.connect_mat; +% Retrieve basis functions +[Functions]=getFunctions(order); +% Retrieve Jacobian matrix +J_phi=Functions.J_phi; +% Number of evaluation points per element +nq=Data.PostProcessing.eval_points; +% Retrieve evaluation points +[points, ~] = getQuadrature(nq, 'interpolation'); +% Number of independent strain and stress components +Mesh.nb_comp_ind=1/2*d*(d+1); +% Mu +f1=Data.Coefficient.mu.function; +% Lambda +f2=Data.Coefficient.lambda.function; +% Retrieve solution +U_sort=Solution.U_sort; +% Compute stresses and strains using current position +coord=coord+U_sort; + +% Define permutation matrix +Sdd=zeros(d*d); +Id1=eye(d); +Id2=eye(d^2); + +for k=1:d + Sdd=Sdd+kron(Id1(:,k)', kron(Id1, Id1(:,k))); +end + +% Reduce size of matrices to avoid unnecessary computations +% sigma_xx, sigma_xy, sigma_yy +h=[1 2 4]; + +I1=(Id2+Sdd); +I2=Id1(:); + +% Truncate the matrices +I1=I1(h,:); +I2=I2(h); + +% Initialization +Q=zeros(nq*d^2, nne*d); + +% Loop over the Gauss points +for i = 1:nq + L=kron(J_phi(points(i,:))', Id1); + Q((i-1)*d^2+1:i*d^2, :)=L; +end +Q=sparse(Q); +T=connect'; +G=T(:); + +% Coordinates of the nodes of the elements +coord_nodes=coord(G,:); +% Displacements of the nodes of the elements +W=reshape(U_sort(G,:)', nne*d, ne); +% Vertices of the elements +a=coord_nodes(1:nne:end,:); +b=coord_nodes(2:nne:end,:); +c=coord_nodes(3:nne:end,:); +% Interpolated coordinates +P=a(:)+[b(:)-a(:) c(:)-a(:)]*points'; +X(:,:,1)=P(1:ne,:); +X(:,:,2)=P(ne+1:end,:); +coord_inter=reshape(P', nq*ne, d); +% Update mesh +Mesh.coord_inter=coord_inter; + +% Computation of geometric quantities +% Determinants of Jacobian matrices +detJK=(b(:,1)-a(:,1)).*(c(:,2)-a(:,2))-(b(:,2)-a(:,2)).*(c(:,1)-a(:,1)); + +JK_invT=zeros(d, d*ne); +JK_invT(:,1:d:d*ne)=1./detJK'.*[c(:,2)-a(:,2) -(c(:,1)-a(:,1))]'; +JK_invT(:,2:d:d*ne)=1./detJK'.*[-(b(:,2)-a(:,2)) b(:,1)-a(:,1)]'; + +vec_JK_invT=reshape(JK_invT, 1, d^2*ne); + +% Coefficient evaluation +F1=f1(X); +F2=f2(X); +if size(F1,1)>1 +F1=reshape(F1, 1, nq, ne); +end +if size(F2,1)>1 +F2=reshape(F2, 1, nq, ne); +end + +% Matrix product computations +W=reshape(Q*W, d^2, nq, ne); + +B1=reshape(product(JK_invT,repmat(Id1, 1, ne),d,ne), d^2, d^2, ne); +B2=reshape(vec_JK_invT, 1, d^2, ne); + +% Computation of strains +if any(contains(Data.PostProcessing.quantity, 'strain')) + E=pagemtimes(B1, W); + E=0.5*I1*reshape(E, d^2, nq*ne); + Solution.epsilon=E; +end + +% Computation of stresses +if any(contains(Data.PostProcessing.quantity, 'stress')) + S1=pagemtimes(B1, F1.*W); + S2=pagemtimes(B2, F2.*W); + S1=I1*reshape(S1, d^2, nq*ne); + S2=I2*reshape(S2, 1, nq*ne); + S=S1+S2; + Solution.sigma=S; +end + + + function[M]=product(A,B,d,ne) + + % !! Only for a two-dimensional case !! + M=zeros(d^2, d^2*ne); + + M(:,1:d^2:end)=kr(A(:,1:d:end), B(:,1:d:end)); + M(:,2:d^2:end)=kr(A(:,1:d:end), B(:,2:d:end)); + M(:,3:d^2:end)=kr(A(:,2:d:end), B(:,1:d:end)); + M(:,4:d^2:end)=kr(A(:,2:d:end), B(:,2:d:end)); + end +end \ No newline at end of file diff --git a/Code/Westergaard/readGMSH.m b/Code/Westergaard/readGMSH.m new file mode 100755 index 0000000..21425ca --- /dev/null +++ b/Code/Westergaard/readGMSH.m @@ -0,0 +1,86 @@ +function [Mesh] = readGMSH(file_name) + +% readGMSH: Reads a GMSH mesh file (.msh) and constructs the coordinate, +% connectivity, boundary nodes, material tag and boundary tag matrices +% INPUT: +% file_name: GMSH mesh file (.msh) +% OUTPUT: +% Mesh: Structure containing all fields related to the geometry, boundary +% conditions and material tags +% coord: Matrix of node coordinates +% connectivites: Connectivity matrix of the system +% boundary_nodes: Matrix containing the boundary nodes (both displacements +% and external forces) +% material_tag: Vector containing the material tag of the elements +% boundary_tag: Vector containing the boundary tags of the elements +% forming boundaries (enables to make the distinction between Dirichlet +% and Neumann boundary conditions) + +fileID = fopen(file_name); +text = textscan(fileID, '%s', 'delimiter', '\n'); +fclose(fileID); + +text=text{1}; +% Construction of the coordinate matrix +nodes_start = findPos(text, '$Nodes') + 1; + +fileID = fopen(file_name); +% Node coordinates +coord = textscan(fileID, '%f %f %f %f', 'HeaderLines', nodes_start); +% Elements +connect = textscan(fileID, [repmat('%f', 1, 20) '%*[^\n]'], 'HeaderLines', 3, 'EndOfLine', '\n', 'CollectOutput', 1); +fclose(fileID); + +% Coordinate matrix +coord=cell2mat(coord); +% Ignore z component +coord=coord(:,2:end-1); + +% Connectivity matrix +connect=cell2mat(connect); + +I=sum(~isnan(connect), 2); +v=unique(I); + +% Boundary elements +B=connect(I==v(1), 1:v(1)); +% Elements +E=connect(I==v(2), 1:v(2)); + +% Boundary tags +B_tags=B(:,4); +% Element tags +E_tags=E(:,4); + +% Boundary elements connectivity matrix +B_connect=B(:,6:end); +% Elements connectivity matrix +E_connect=E(:,6:end); + +% Initialize mesh structure +Mesh.coord_mat=coord; +Mesh.BC_nodes=B_connect; +Mesh.connect_mat=E_connect; +Mesh.BC_tag=B_tags; +Mesh.material_tag=E_tags; + +% Identification of the type of element in GMSH +% 2 -> T3 +% 9 -> T6 +% 21 -> T10 +% 1 -> line +% 8 -> quadratic curve +% 26 -> cubic curve +end + + +function[start]=findPos(text, string) +lines = size(text, 1); +start = 1; +for i = 1:lines + if strcmp(text{i}, string) + start = i; + break + end +end +end \ No newline at end of file diff --git a/Code/Westergaard/removeTraction.m b/Code/Westergaard/removeTraction.m new file mode 100644 index 0000000..e4c5c16 --- /dev/null +++ b/Code/Westergaard/removeTraction.m @@ -0,0 +1,73 @@ +function[Data]=removeTraction(Mesh, Data, Solution, n) + +% removeTraction: Checks for convergence and updates the interface properties +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% n: Current iteration number +% OUTPUT: +% Data: Updated data structure with updated interface properties + +% Compute the normals based on deformed structure +% Normals for initial interface +[Data]=computeNormals(Mesh, Data, Solution); + +normal1=Data.body{1}.normal; +normal2=Data.body{2}.normal; + +% Retrieve Lagrange multipliers +lambda1=Solution.lambda; +lambda_sort1=Solution.lambda_sort; + +% Project the lambdas on interface 2 +lambda2=-Data.R21*lambda1; +lambda_sort2=reshape(lambda2, Mesh.nb_dof_local, [])'; + +% Computes the scalar products +scalar1=sum(lambda_sort1.*normal1(Data.body{1}.active_set,:),2); +scalar2=sum(lambda_sort2.*normal2(Data.body{2}.active_set,:),2); + +% Detect nodes to be removed from the interfaces +dump_nodes1=Data.body{1}.interface_nodes(scalar1>0); +dump_nodes2=Data.body{2}.interface_nodes(scalar2>0); + +% Update the interfaces +% If only compression test penetration, otherwise remove nodes in traction +if isempty(dump_nodes1) && isempty(dump_nodes2) + % Gap verification + [add_nodes1, add_nodes2]=detectGaps(Mesh, Data, Solution); + + [Data, nodediff1]=updateInterface(Mesh, Data, 1, add_nodes1, 'add'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, add_nodes2, 'add'); + +else + % Skip gap verification + [Data, nodediff1]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data, nodediff2]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); +end + +fprintf('Iteration %d: \n', n) +fprintf('%+d nodes on interface 1 \n', nodediff1) +fprintf('%+d nodes on interface 2 \n', nodediff2) +fprintf('----------------------------------- \n') + +% Check for convergence failure +if isempty(Data.body{1}.interface_nodes) || isempty(Data.body{2}.interface_nodes) + msg=['The contact algorithm stopped prematurely because at least one of ' ... + 'the sets of nodes of the potential contact interface is empty. '... + 'Potential causes for this error are:\n']; + + causes=['1) The boundary conditions are incorrect \n'... + '2) The potential contact interface was misidentified \n'... + '3) The mesh is too coarse \n']; + + error(sprintf([msg, causes])) +end + +% Check for convergence +if abs(nodediff1)+abs(nodediff2)==0 % Stop the iterations + Data.Contact.iterate=0; + disp('Successfully converged') +end +end \ No newline at end of file diff --git a/Code/Westergaard/setBoundaryConditions.m b/Code/Westergaard/setBoundaryConditions.m new file mode 100644 index 0000000..9b514dd --- /dev/null +++ b/Code/Westergaard/setBoundaryConditions.m @@ -0,0 +1,324 @@ +function[Data]=setBoundaryConditions(Mesh, Data, varargin) + +% setBoundaryConditions: Function which initializes the boundary data +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% OPTIONAL INPUT: +% Any type of boundary condition. If no Dirichlet boundary conditions are +% specified, the program returns an error +% Types of boundary conditions: +% Dirichlet +% Neumann +% OUTPUT: +% Data: Updated data structure with boundary conditions + + +%% Set boundary conditions +if mod(length(varargin{1}), 2)~=0 + error('Missing a name or value') +end + +% Retrieve boundary nodes +BC_tags=unique(Mesh.BC_tag); + +% Dirichlet type +D_types={'dirichlet'}; +% Neumann types +N_types={'neumann'}; +% Available types of boundary conditions +available_types=union(D_types, N_types); +% Count to detect duplicate boundaries +count=[]; +% Count number of Dirichlet and Neumann boundaries specified +nb_D_BC=0; +nb_N_BC=0; + +% Retrieve labels and values entered by the user +labels_in=varargin{1}(1:2:end); +values_in=varargin{1}(2:2:end); + +% Initilization +Data.BC=[]; + +% Checking for boundary conditions +c1=strcmp(labels_in, 'BC'); + +if any(c1) + BC=values_in{c1}; + nb_BC=length(BC); + Data.BC=cell(nb_BC,1); + + for i=1:nb_BC + bc=BC{i}; + l=length(bc); + + if mod(l,2)~=0 + error('Missing a name or value') + end + + for j=1:2:l + arg=lower(bc{j}); + val=bc{j+1}; + + switch arg + + case 'type' + if any(ismember(available_types, lower(val))) + Data.BC{i}.(arg)=lower(val); + + if any(ismember(D_types, lower(val))) + nb_D_BC=nb_D_BC+1; + else + nb_N_BC=nb_N_BC+1; + end + else + error('Unrecognized boundary condition') + end + + case 'edges' + + edges=intersect(BC_tags, val); + + if isempty(edges) + error('One of the edges specified does not exist') + elseif ~isempty(intersect(count, val)) || length(unique(val))~=length(val) + error('Duplicate edges detected') + end + count=[count val]; + Data.BC{i}.(arg)=val; + + case {'function'} + + if isa(val,'double') + Data.BC{i}.label='const'; + Data.BC{i}.(arg)= @(x) val; + + elseif isa(val,'function_handle') + Data.BC{i}.label=detectDependency(val); + Data.BC{i}.(arg)=val; + else + error('Input parameters must be either constants or function handles') + end + + case 'axis' + + if strcmp(val, 'x') || strcmp(val, 'y') || strcmp(val, 'all') + Data.BC{i}.(arg)=val; + else + error('Unrecognized axis') + end + + + otherwise + error('Unrecognized argument') + end + + end + + + end + +end + +%% Post-Processing: Partitioning the structure BC into Dirichlet BC and Neumann BC and checking for missing data + +if nb_D_BC==0 + error('Dirichlet boundary conditions must be prescribed') +end + +BC=Data.BC; +l=length(BC); + +% Initialize cells +D_BC=cell(nb_D_BC,1); +N_BC=cell(nb_N_BC,1); +% Set of Dirichlet and Neumann tags +D_tags=[]; +N_tags=[]; +% Initialize counters +d=1; +n=1; + +for i=1:l + bc=BC{i}; + type=bc.type; + + if ~isfield(bc, 'edges') + error('The edges must be specified') + end + + if any(ismember(D_types, type)) + D_tags=[D_tags bc.edges]; + D_BC{d}.type=type; + D_BC{d}.edges=bc.edges; + + if isfield(bc, 'function') + D_BC{d}.function=bc.function; + D_BC{d}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + D_BC{d}.function=@(x) [0; 0]; + D_BC{d}.label='const'; + end + + if isfield(bc, 'axis') + D_BC{d}.axis=bc.axis; + else + D_BC{d}.axis='all'; + end + + d=d+1; + + elseif any(ismember(N_types, type)) + N_tags=[N_tags bc.edges]; + N_BC{n}.type=type; + N_BC{n}.edges=bc.edges; + + + if isfield(bc, 'function') + N_BC{n}.function=bc.function; + N_BC{n}.label=bc.label; + else + warning('A function has not been prescribed and will be assumed zero') + N_BC{n}.function=@(x) [0; 0]; + N_BC{n}.label='const'; + end + + if isfield(bc, 'axis') + N_BC{n}.axis=bc.axis; + else + N_BC{n}.axis='all'; + end + + n=n+1; + end +end + +Data.D_BC=D_BC; +Data.N_BC=N_BC; + +Data.D_tags=D_tags; +Data.N_tags=N_tags; + +%% Sorting nodes +% If there is a node belonging to conflicting boundary conditions, it is +% set using the following priority list: Neumann, Dirichlet, Interior +BC_nodes=Mesh.BC_nodes; +BC_tag=Mesh.BC_tag; +nb_nodes=Mesh.nb_nodes; +Dof_set=Mesh.Dof_set; + +set_D_nodes=cell(nb_D_BC,1); +set_N_nodes=cell(nb_N_BC,1); + +set_Nd=cell(nb_D_BC,1); +set_Nn=cell(nb_N_BC,1); + +for k=1:nb_D_BC + nodes=BC_nodes(BC_tag==D_BC{k}.edges,:); + set_D_nodes{k}=unique(nodes); + + if strcmp(D_BC{k}.axis, 'x') + set_Nd{k}=Dof_set(1,set_D_nodes{k})'; + + elseif strcmp(D_BC{k}.axis, 'y') + set_Nd{k}=Dof_set(2,set_D_nodes{k})'; + + else + dof=Dof_set(:,set_D_nodes{k}); + set_Nd{k}=dof(:); + end +end +for k=1:nb_N_BC + nodes=BC_nodes(BC_tag==N_BC{k}.edges,:); + set_N_nodes{k}=unique(nodes); + + if strcmp(N_BC{k}.axis, 'x') + set_Nn{k}=Dof_set(1,set_N_nodes{k})'; + + elseif strcmp(N_BC{k}.axis, 'y') + set_Nn{k}=Dof_set(2,set_N_nodes{k})'; + + else + dof=Dof_set(:,set_N_nodes{k}); + set_Nn{k}=dof(:); + end +end +% Define the sets of nodes +bc_nodes=unique(BC_nodes); +% Neumann nodes +Nodes_N=unique(cell2mat(set_N_nodes)); +% Dirichlet nodes +Nodes_D=unique(cell2mat(set_D_nodes)); +Nodes_D=setdiff(Nodes_D, Nodes_N); +Nodes_N=setdiff(bc_nodes, Nodes_D); +% Interior nodes +Nodes_I=setdiff(1:nb_nodes, union(Nodes_D, Nodes_N)); +% Free nodes +Nodes_F=union(Nodes_I, Nodes_N); + +% Define the sets of degrees of freedom +% Neumann nodes +Nn=unique(cell2mat(set_Nn)); +% Dirichlet nodes +Nd=unique(cell2mat(set_Nd)); +Nd=setdiff(Nd, Nn); +dof_n=Dof_set(:,bc_nodes); +dof_n=dof_n(:); +Nn=setdiff(dof_n, Nd); +% Interior nodes +Ni=setdiff(Dof_set(:), union(Nd, Nn)); +% Free nodes +Nf=union(Ni, Nn); + +% Correct the set for Dirichlet nodes +for k=1:nb_D_BC + set_D_nodes{k}=setdiff(set_D_nodes{k}, Nodes_N); + set_Nd{k}=setdiff(set_Nd{k}, Nn); +end + + +% Update data +Data.Nodes_I=Nodes_I; +Data.Nodes_N=Nodes_N; +Data.Nodes_D=Nodes_D; +Data.Nodes_F=Nodes_F; + +Data.Ni=Ni; +Data.Nd=Nd; +Data.Nn=Nn; +Data.Nf=Nf; + +Data.set_D_nodes=set_D_nodes; +Data.set_N_nodes=set_N_nodes; + +Data.set_Nd=set_Nd; +Data.set_Nn=set_Nn; + +%% Auxiliary function + + function[label]=detectDependency(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end + +end diff --git a/Code/Westergaard/setCoefficient.m b/Code/Westergaard/setCoefficient.m new file mode 100644 index 0000000..d15a956 --- /dev/null +++ b/Code/Westergaard/setCoefficient.m @@ -0,0 +1,103 @@ +function[Data]=setCoefficient(Data, varargin) + +% setCoefficient: Initializes the model coefficients +% OPTIONAL INPUT: +% rho: Specific mass +% mu: Lamé constant +% lambda: Lamé constant +% f: Function "f" of the PDE +% OUTPUT: +% Data: Data structure with initialized coefficients + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +available_coeff={'rho', 'lambda', 'mu', 'f'}; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + if any(ismember(available_coeff, arg)) + + + switch arg + case {'rho', 'lambda', 'mu'} % Only space dependent + if isa(val,'double') + + if val ~= 0 + Data.Coefficient.(arg).label='const'; + else + Data.Coefficient.(arg).label='zero'; + end + Data.Coefficient.(arg).function=@(x) val; + + elseif isa(val,'function_handle') + Data.Coefficient.(arg).label=detectDependency1(val); + Data.Coefficient.(arg).function=val; + else + error('Input parameters must be either constants or function handles') + end + + case 'f' + if isa(val,'double') % Only space dependent + Data.Coefficient.f.label='const'; + Data.Coefficient.f.function=@(x) val; + elseif isa(val,'function_handle') + Data.Coefficient.f.label=detectDependency2(val); + Data.Coefficient.f.function=@(x) val(x); + else + error('Input parameters must be either constants or function handles') + end + + end + + else + error('Unrecognized coefficient') + end +end + + + function[label]=detectDependency1(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Coefficient can only be constant or space dependent') + end + + if contains(string(5:end), 'x') + label='space'; + elseif contains(string(5), '0') + label='zero'; + else + label='const'; + end + end + + function[label]=detectDependency2(func) + + string=func2str(func); + + if ~strcmp(string(1:4), '@(x)') + error('Function handle must contain space variables') + end + string=string(5:end); + + if contains(string, 'x') && contains(string, 't') + label='space_time'; + elseif contains(string, 'x') + label='space'; + elseif contains(string, 't') + label='time'; + elseif contains(string(1), '0') + label='zero'; + else + label='const'; + end + + end +end \ No newline at end of file diff --git a/Code/Westergaard/setModel.m b/Code/Westergaard/setModel.m new file mode 100644 index 0000000..58c27a3 --- /dev/null +++ b/Code/Westergaard/setModel.m @@ -0,0 +1,75 @@ +function[Data]=setModel(varargin) + +% setModel: Initializes the numerical model +% OPTIONAL INPUT: +% model: Specifies which model should be used +% Default: transient_heat +% submodel: Specifies which submodel from the specified model should be +% used +% Default: standard +% type: Specifies whether the model is linear or nonlinear +% Default: linear +% OUTPUT: +% Data: Data structure with initialized model + +if mod(length(varargin), 2)~=0 + error('Missing a name or value') +end +labels_in=varargin(1:2:end); +values_in=varargin(2:2:end); + +models={'transient_heat', 'static_solid', 'dynamic_solid'}; +n=length(models); +submodels=cell(n,1); +submodels{1}={'standard'}; +submodels{2}={'standard', 'contact'}; +submodels{3}={'standard'}; +types={'linear', 'nonlinear'}; + +% Set default Parameters +Data.Model.name=models{1}; +Data.Model.submodel=submodels{1}; +Data.Model.type=types{1}; + +% Define constants +% Stefan Boltzman constant W/(m^2*K^4) +Data.Constants.sigma=5.670373e-8; + +for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case 'model' + if any(ismember(models, val)) + Data.Model.name=val; + else + error('Unrecognized model') + end + + case 'submodel' + + if isfield(Data.Model, 'name') + index=strcmp(Data.Model.name, models); + else + error('Submodel must be defined after the model') + end + if any(ismember(submodels{index}, val)) + Data.Model.submodel=val; + else + error('Unrecognized submodel') + end + + case 'type' + if any(ismember(types, val)) + Data.Model.type=val; + else + error('Unrecognized type') + end + + otherwise + error('Unrecognized argument') + end + +end +end \ No newline at end of file diff --git a/Code/Westergaard/setNumericalParameters.m b/Code/Westergaard/setNumericalParameters.m new file mode 100644 index 0000000..48a7fff --- /dev/null +++ b/Code/Westergaard/setNumericalParameters.m @@ -0,0 +1,229 @@ +function[Data]=setNumericalParameters(Mesh, Data, varargin) + +% setNumericalParameters: Initializes the numerical parameters of the +% solver +% OPTIONAL INPUT: +% Contact: +% iterate: Parameter controlling whether iterations should +% take place (1) or not (0) +% Default: 1 +% maxiter: Maximum number of iterations for solving the +% contact problem +% Default: 10 +% rescaling: Rescaling parameter for the stiffness matrix +% Default: 1 +% c: Number of nearest neighbors for computing the +% radius of support of the radial basis functions +% Default: 1 +% radius: Intersection radius used to define the initial +% potential contact interface. +% Default: infinity +% gamma: Value of gamma for preventing quick loss of diagonal +% dominance of the matrices PhiMM +% Default: 0.5 +% Gamma: Value of Gamma to avoid very small nonzero entries +% in the matrices PhiNM +% Default: 0.95 +% d0: Measure of tolerance for contact detection. +% Default: 0.05 +% tol: Tolerance for gap detection +% Default: 1e-1 +% average: Parameter controlling whether a simple average (1) +% or a weighted average (0) of the normal vectors +% should be used. +% Default: 1 +% Solver: +% name: Name of the solver to be used. +% Default: direct +% PostProcessing: +% quantity: Additional quantities to be computed (for example +% stresses, strains, fluxes,...) +% Default: none +% ampl_fact: Value of amplification factor to apply to +% displacements +% Default: auto +% eval_points: Number of evaluation points used for computing +% stresses or strains +% Default: 3 (P1 finite elements) +% 6 (P2 or higher order finite elements) + +if nargin>2 +if mod(length(varargin)-1, 2)~=0 + error('Missing a name or value') +end + +id=lower(varargin{1}); +labels_in=varargin(2:2:end); +values_in=varargin(3:2:end); +end + +% Set default Parameters +if ~isfield(Data, 'Contact') + % Boolean indicator for contact algorithm iterations + Data.Contact.iterate=true; + % Maximum number of iterations of the contact algorithm + Data.Contact.maxiter=10; + % Rescaling parameter for the INTERNODES matrix + Data.Contact.rescaling=1; + % Number of nearest neighbors + Data.Contact.c=1; + % Intersection radius + Data.Contact.radius=inf; + % gamma value + Data.Contact.gamma=0.5; + % Gamma value + Data.Contact.Gamma=0.95; + % Contact tolerance + Data.Contact.d0=0.05; + % Gap tolerance + Data.Contact.tol=1e-1; + % Average for normal vectors + Data.Contact.average=1; +end +if ~isfield(Data, 'Solver') + % Solver name + Data.Solver.name='direct'; +end +if ~isfield(Data, 'PostProcessing') + % Additional quantities to be computed + Data.PostProcessing.quantity='none'; + % Amplification factor + Data.PostProcessing.amplification='auto'; + % Number of evaluation points per element for computing stresses/strains + eval_points={3,6}; + + if Mesh.order==1 + Data.PostProcessing.eval_points=eval_points{1}; + else + Data.PostProcessing.eval_points=eval_points{2}; + end + +end + +% Override the defaults +if exist('id', 'var') + switch id + case 'contact' + %% Contact algorithm specific parameters + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=lower(values_in{i}); + + switch arg + case {'iterate', 'average'} + if isa(val, 'double') + + if val<0 || val>1 + error([arg ' parameter must be a boolean']) + else + Data.Contact.(arg)=logical(val); + end + + elseif isa(val, 'logical') + Data.Contact.(arg)=val; + else + error([arg ' parameter must be a boolean']) + end + + case {'maxiter', 'c', 'radius', 'd0'} + if val<=0 + error(['Parameter ' arg ' must be strictly greater than zero']) + else + Data.Contact.(arg)=val; + end + + case 'rescaling' + if val==0 + error(['Parameter ' arg ' must be different from zero']) + else + Data.Contact.(arg)=val; + end + + case {'gamma', 'Gamma', 'tol'} + if val<=0 || val>=1 + error([arg ' must be strictly greater than 0 and strictly smaller than 1']) + else + Data.Contact.(arg)=val; + end + + otherwise + error('Unrecognized argument') + end + end + + case 'solver' + %% Solver parameters + names={'direct'}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'name' + if any(ismember(names, val)) + Data.Solver.(arg)=val; + else + error('Unrecognized solver name') + end + + otherwise + error('Unrecognized argument') + end + + end + + case 'postprocessing' + %% Post-processing parameters + quantities={'none', 'flux', 'error', 'stress', 'strain'}; + eval_points={3,6}; + + for i=1:length(labels_in) + arg=lower(labels_in{i}); + val=values_in{i}; + + switch arg + case 'quantity' + if any(ismember(quantities, val)) + Data.PostProcessing.quantity=val; + else + error('Unrecognized quantity') + end + + case 'eval_points' + if any(ismember(eval_points, val)) + Data.PostProcessing.eval_points=val; + else + error('Invalid number of evaluation points') + end + + case 'ampl_fact' + + if isa(val, 'char') + if ~strcmp(val, 'auto') + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + + elseif isa(val, 'double') + if val <= 0 + error('Invalid amplification factor') + else + Data.PostProcessing.amplification=val; + end + else + error('Invalid amplification factor') + end + + otherwise + error('Unrecognized argument') + end + end + + otherwise + error('Unrecognized argument') + end +end +end \ No newline at end of file diff --git a/Code/Westergaard/solve.m b/Code/Westergaard/solve.m new file mode 100644 index 0000000..12638e4 --- /dev/null +++ b/Code/Westergaard/solve.m @@ -0,0 +1,93 @@ +function[Mesh, Data, Solution, A, b]=solve(Mesh, Data, it) + +% solve: Solves the contact problem +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% it: Cell array containing the boundary tags of the interfaces (master +% interface and slave interface) +% OUTPUT: +% Mesh: Updated structure containing all the mesh parameters +% Data: Updated structure containing all the data parameters +% Solution: Structure containing the solution +% A: Global INTERNODES matrix +% b: Global right-hand side + + +% Initialization of the system +[Data, Solution, A, b, K]=initializeSystem(Mesh, Data, it); +% Initialize count +n=1; + +while Data.Contact.iterate && n<= Data.Contact.maxiter + + % Solve the system of equations + [Solution]=solveSystem(Mesh, Data, Solution, A, b); + % Update the system + [Data, A, b]=updateSystem(Mesh, Data, Solution); + n=n+1; +end + +if n > Data.Contact.maxiter && Data.Contact.iterate + warning('Failed to converge within the specified number of iterations') +end + + function[Solution]=solveSystem(Mesh, Data, Solution, A, b) + + % solveSystem: Solves the linear system of equations + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Initialized solution structure with Dirichlet BC + % A: INTERNODES matrix + % b: Right-hand side vector + % OUTPUT: + % Solution: Updated solution structure with computed displacements + % and Lagrange multipliers + + + % Retrieve data parameters + % Free degrees of freedom + Nf=Data.Nf; + % Number of free degrees of freedom + nf=length(Nf); + % Number of constraints + nc=length(Data.body{1}.interface_dof); + + % Retrieve mesh parameters + d=Mesh.nb_dof_local; + % Retrieve solution + U=Solution.U; + % Direct method + x=A\b; + % Retrieve the displacements + U(Nf)=x(1:nf); + + % Update solution + Solution.U=U; + Solution.U_sort=reshape(U, d, [])'; + Solution.lambda=Data.Contact.rescaling*x(nf+1:nf+nc); + Solution.lambda_sort=reshape(Solution.lambda, d, [])'; + end + + function[Data, A, b]=updateSystem(Mesh, Data, Solution) + + % updateSystem: Updates all quantities which have changed + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Solution structure with computed displacements + % OUPUT: + % Data: Data structure with updated interface properties + % A: New INTERNODES matrix + % b: New right-hand side + + + % Check for convergence + [Data]=removeTraction(Mesh, Data, Solution, n); + % Reassemble the blocks which have changed + [Data]=subAssemble(Mesh, Data, Solution, 'updating'); + % Reassemble the entire internodes matrix + [A,b]=assembleInternodesMat(Mesh, Data, Solution, K); + end +end \ No newline at end of file diff --git a/Code/Westergaard/subAssemble.m b/Code/Westergaard/subAssemble.m new file mode 100644 index 0000000..26e78d1 --- /dev/null +++ b/Code/Westergaard/subAssemble.m @@ -0,0 +1,202 @@ +function[Data]=subAssemble(Mesh, Data, Solution, operation) + +% subAssemble: Assembles the part of the internodes matrix which is +% changing from one iteration to the next +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% Solution: Structure containing the solution +% operation: Specifies the type of operation (initialization or +% updating) +% OUTPUT: +% Data: Updated structure containing all the data parameters + +if strcmp(operation, 'initialization') + [Data]=computeBoundaryMass(Mesh, Data, 'initialization'); +end + +% Assemble interpolation matrices +[Data]=assembleRs(Mesh, Data, Solution); +% Assemble boundary mass matrices +[Data]=computeBoundaryMass(Mesh, Data, 'updating'); + + function[Data]=assembleRs(Mesh, Data, Solution) + + % assembleRs: Constructs the interpolation matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % Solution: Structure containing the solution + % OUTPUT: + % Data: Updated data structure with interpolation matrices + + % Retrieve mesh parameters + nb_dof_local=Mesh.nb_dof_local; + + % Retrieve nodes making up the interfaces + nodes1=Data.body{1}.interface_nodes; + nodes2=Data.body{2}.interface_nodes; + + % Retrieve coordinates of nodes making up the interfaces + coord1=Mesh.coord_mat(nodes1,:)+Solution.U_sort(nodes1,:); + coord2=Mesh.coord_mat(nodes2,:)+Solution.U_sort(nodes2,:); + + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + + % Check for distant bodies + if all(i1) || all(i2) + warning('One of the bodies will be translated because they are too far away from each other. Boundary conditions might be affected') + [Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + % Check for isolated nodes + while any(i1) || any(i2) + + dump_nodes1=nodes1(i1); + dump_nodes2=nodes2(i2); + + % Updating + [Data]=updateInterface(Mesh, Data, 1, dump_nodes1, 'dump'); + [Data]=updateInterface(Mesh, Data, 2, dump_nodes2, 'dump'); + + nodes1=nodes1(~i1); + nodes2=nodes2(~i2); + + coord1=coord1(~i1,:); + coord2=coord2(~i2,:); + + % Recompute radiuses + [radiuses1, ~, ~, nnzR21, ~]=getRadius(Data, nodes1, nodes2, coord1, coord2); + [radiuses2, ~, ~, nnzR12, ~]=getRadius(Data, nodes2, nodes1, coord2, coord1); + + i1=nnzR12==0; + i2=nnzR21==0; + end + + [Phi11] = assembleInterpMat(coord1, coord1, radiuses1); + [Phi21] = assembleInterpMat(coord1, coord2, radiuses1); + [Phi22] = assembleInterpMat(coord2, coord2, radiuses2); + [Phi12] = assembleInterpMat(coord2, coord1, radiuses2); + + % Interpolation matrices + R12=Phi12/Phi22; + R21=Phi21/Phi11; + + s1=sum(R12,2); + s2=sum(R21,2); + + % Rescaling + R12=R12./s1; + R21=R21./s2; + + Data.R12_normal=R12; + Data.R21_normal=R21; + + % Expansion to full size + Data.R12=kron(R12, speye(nb_dof_local)); + Data.R21=kron(R21, speye(nb_dof_local)); + + end + + function[Data]=computeBoundaryMass(Mesh, Data, operation) + + % computeBoundaryMass: Assembles the boundary mass matrices + % INPUT: + % Mesh: Structure containing all the mesh parameters + % Data: Structure containing all the data parameters + % operation: Specifies the type of operation (initialization or + % updating) + % OUTPUT: + % Data: Updated data structure with boundary mass matrices + + + % Retrieve mesh parameters + % Number of local degrees of freedom + nb_dof_local=Mesh.nb_dof_local; + % Coordinate matrix + coord=Mesh.coord_mat; + % Order + order=Mesh.order; + % Get quadrature nodes and weights + [points, weights]=getQuadrature(3, 'boundary'); + % Number of quadrature points + nb_points=length(weights); + + % Retrive functions + [Functions]=getFunctions(order); + % Retrieve boundary basis functions + phi_boundary=Functions.phi_boundary; + + % Retrieve bodies + body=Data.body; + % Number of bodies + nb_bodies=length(body); + + if strcmp(operation, 'initialization') + + for n=1:nb_bodies + % Retrieve data + % Retriveve edges making up the interface + Edges=body{n}.interface_connect; + % Retrieve nodes making up the interface + nodes=body{n}.interface_nodes; + nb_nodes=length(nodes); + s=order+1; + + % Initialization + Phi=zeros(s, nb_points); + + % Map global nodes to local nodes + func=@(x) find(nodes==x); + connect=arrayfun(func, Edges); + T=connect'; + G=T(:); + + I=repmat(connect, [1 s])'; + J=repmat(G', [s 1]); + + % Loop over the Gauss points + for i = 1:nb_points + Phi(:,i)=phi_boundary(points(i,:)); + end + + Tr=Edges'; + Gr=Tr(:); + % Coordinates of the nodes of the elements + coord_nodes=coord(Gr,:); + % Endpoints of the edges + a=coord_nodes(1:s:end,:); + b=coord_nodes(2:s:end,:); + bk=b-a; + norm_bk=vecnorm(bk,2,2); + + lambda=weights.*norm_bk'; + + M=kr(Phi, Phi)*lambda; + M=sparse(I(:), J(:), M(:), nb_nodes, nb_nodes); + + Data.body{n}.iinterface_mass=M; + % Expansion to full size + M=kron(M, speye(nb_dof_local)); + Data.body{n}.interface_mass=M; + end + else + for n=1:nb_bodies + active_set=Data.body{n}.active_set; + Data.body{n}.interface_mass=kron(Data.body{n}.iinterface_mass(active_set, active_set), speye(nb_dof_local)); + end + end + end + +end \ No newline at end of file diff --git a/Code/Westergaard/translateBodies.m b/Code/Westergaard/translateBodies.m new file mode 100644 index 0000000..f5a398f --- /dev/null +++ b/Code/Westergaard/translateBodies.m @@ -0,0 +1,60 @@ +function[Mesh, coord1, coord2]=translateBodies(Mesh, Data, radiuses1, radiuses2, nodes1, nodes2, coord1, coord2) + +% translateBodies: Performs a translation of one of the bodies in case the +% initial potential contact interface is empty (because the bodies are too +% far away from each other) +% INPUT: +% Mesh: Structure containing all the mesh parameters +% Data: Structure containing all the data parameters +% radiusesk: Radiuses of support of the radial basis functions (k=1,2) +% nodesk: Nodes on the potential contact interface of body k (k=1,2) +% coordk: Coordinates of the nodes on the potential contact interface +% of body k (k=1,2) +% OUTPUT: +% Mesh: Mesh structure with updated coordinate matrix +% coordk: New coordinates of the nodes on the potential contact +% interface of body k (k=1,2) + +n1=length(radiuses1); + +dist=inf; +node1=0; +node2=0; + +for k=1:n1 + + [d, i]=min(vecnorm(coord1(k,:)-coord2,2,2)); + + if d