// Write void cohesiveLawFvPatchVectorField::write(Ostream& os) const { fvPatchVectorField::write(os); traction_.writeEntry("traction", os); os.writeKeyword("cohesiveLaw") << law().type() << token::END_STATEMENT << nl; os.writeKeyword("relaxationFactor") << relaxationFactor_ << token::END_STATEMENT << nl; law().writeDict(os); writeEntry("value", os); }
int main(void) { Form paper("Piece of paper", 150, 150); Bureaucrat barneyStinson("Barney Stinson", 2); std::cout << "Printing \"paper(\"Piece of paper\", 150, 150);\": " << paper; barneyStinson.signForm(paper); std::cout << "Printing \"paper(\"Piece of paper\", 150, 150);\": " << paper; Form law("Law", 1, 1); std::cout << "Printing \"paper(\"Law\", 1, 1);\": " << law; barneyStinson.signForm(law); std::cout << "Printing \"paper(\"Law\", 1, 1);\": " << law; try { std::cout << "Trying to create \"something(\"something\", 151, 151);\"" << std::endl; Form something("something", 151, 151); } catch (Form::GradeTooLowException & e) { std::cout << "Exception: " << e.what() << std::endl; } try { std::cout << "Trying to create \"something(\"something\", 0, 0);\"" << std::endl; Form something("something", 0, 0); } catch (Form::GradeTooHighException & e) { std::cout << "Exception: " << e.what() << std::endl; } }
// with stretch (and stretch-rate) independent contraction models the implicit and explicit schemes // are identical void TestCompareImplicitAndExplicitWithStretchIndependentContractionModel() throw(Exception) { QuadraticMesh<2> mesh(0.25, 1.0, 1.0); MooneyRivlinMaterialLaw<2> law(1); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh,0,0.0); ElectroMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetContractionModel(NONPHYSIOL1,0.01); problem_defn.SetMechanicsSolveTimestep(0.01); //This is only set to make ElectroMechanicsProblemDefinition::Validate pass //The following lines are not relevant to this test but need to be there TetrahedralMesh<2,2>* p_fine_mesh = new TetrahedralMesh<2,2>();//unused in this test p_fine_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); TetrahedralMesh<2,2>* p_coarse_mesh = new TetrahedralMesh<2,2>();//unused in this test p_coarse_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); FineCoarseMeshPair<2>* p_pair = new FineCoarseMeshPair<2>(*p_fine_mesh, *p_coarse_mesh);//also unused in this test p_pair->SetUpBoxesOnFineMesh(); ///////////////////////////////////////////////////////////////////// // NONPHYSIOL 1 - contraction model is of the form sin(t) IncompressibleExplicitSolver2d expl_solver(mesh,problem_defn,""/*"TestCompareExplAndImplCardiacSolvers_Exp"*/); p_pair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(expl_solver.GetQuadratureRule()), false); p_pair->DeleteFineBoxCollection(); expl_solver.SetFineCoarseMeshPair(p_pair); expl_solver.Initialise(); IncompressibleImplicitSolver2d impl_solver(mesh,problem_defn,""/*"TestCompareExplAndImplCardiacSolvers_Imp"*/); impl_solver.SetFineCoarseMeshPair(p_pair); impl_solver.Initialise(); double dt = 0.25; for(double t=0; t<3; t+=dt) { expl_solver.Solve(t,t+dt,dt); impl_solver.Solve(t,t+dt,dt); // computations should be identical TS_ASSERT_EQUALS(expl_solver.GetNumNewtonIterations(), impl_solver.GetNumNewtonIterations()); for(unsigned i=0; i<mesh.GetNumNodes(); i++) { TS_ASSERT_DELTA(expl_solver.rGetDeformedPosition()[i](0), impl_solver.rGetDeformedPosition()[i](0), 1e-9); TS_ASSERT_DELTA(expl_solver.rGetDeformedPosition()[i](1), impl_solver.rGetDeformedPosition()[i](1), 1e-9); } } //in need of deletion even if all these 3 have no influence at all on this test delete p_fine_mesh; delete p_coarse_mesh; delete p_pair; }
void TestWithSimpleContractionModel() throw(Exception) { QuadraticMesh<2> mesh(0.25, 1.0, 1.0); MooneyRivlinMaterialLaw<2> law(1); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh,0,0.0); ElectroMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetContractionModel(NONPHYSIOL1,0.01); problem_defn.SetMechanicsSolveTimestep(0.01); //This is only set to make ElectroMechanicsProblemDefinition::Validate pass // NONPHYSIOL1 => NonphysiologicalContractionModel 1 IncompressibleExplicitSolver2d solver(mesh,problem_defn,"TestExplicitCardiacMech"); //The following lines are not relevant to this test but need to be there TetrahedralMesh<2,2>* p_fine_mesh = new TetrahedralMesh<2,2>();//unused in this test p_fine_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); TetrahedralMesh<2,2>* p_coarse_mesh = new TetrahedralMesh<2,2>();//unused in this test p_coarse_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); FineCoarseMeshPair<2>* p_pair = new FineCoarseMeshPair<2>(*p_fine_mesh, *p_coarse_mesh);//also unused in this test p_pair->SetUpBoxesOnFineMesh(); p_pair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(solver.GetQuadratureRule()), false); p_pair->DeleteFineBoxCollection(); solver.SetFineCoarseMeshPair(p_pair); /////////////////////////////////////////////////////////////////////////// solver.Initialise(); // coverage QuadraturePointsGroup<2> quad_points(mesh, *(solver.GetQuadratureRule())); std::vector<double> calcium_conc(solver.GetTotalNumQuadPoints(), 0.0); std::vector<double> voltages(solver.GetTotalNumQuadPoints(), 0.0); solver.SetCalciumAndVoltage(calcium_conc, voltages); // solve UP TO t=0. So Ta(lam_n,t_{n+1})=5*sin(0)=0, ie no deformation solver.Solve(-0.01,0.0,0.01); TS_ASSERT_EQUALS(solver.GetNumNewtonIterations(),0u); solver.Solve(0.24,0.25,0.01); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[4](0), 0.9732, 1e-2); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[4](1), -0.0156, 1e-2); //in need of deletion even if all these 3 have no influence at all on this test delete p_fine_mesh; delete p_coarse_mesh; delete p_pair; }
/* == Sliding boundary conditions == * * It is common to require a Dirichlet boundary condition where the displacement/position in one dimension * is fixed, but the displacement/position in the others are free. This can be easily done when * collecting the new locations for the fixed nodes, as shown in the following example. Here, we * take a unit square, apply gravity downward, and suppose the Y=0 surface is like a frictionless boundary, * so that, for the nodes on Y=0, we specify y=0 but leave x free (Here (X,Y)=old position, (x,y)=new position). * (Note though that this wouldn't be enough to uniquely specify the final solution - an arbitrary * translation in the Y direction could be added a solution to obtain another valid solution, so we * fully fix the node at the origin.) */ void TestWithSlidingDirichletBoundaryConditions() throw(Exception) { QuadraticMesh<2> mesh; mesh.ConstructRegularSlabMesh(0.1 /*stepsize*/, 1.0 /*width*/, 1.0 /*height*/); ExponentialMaterialLaw<2> law(1.0, 0.5); // First parameter is 'a', second 'b', in W=a*exp(b(I1-3)) /* Create fixed nodes and locations... */ std::vector<unsigned> fixed_nodes; std::vector<c_vector<double,2> > locations; /* Fix node 0 (the node at the origin) */ fixed_nodes.push_back(0); locations.push_back(zero_vector<double>(2)); /* For the rest, if the node is on the Y=0 surface.. */ for (unsigned i=1; i<mesh.GetNumNodes(); i++) { if ( fabs(mesh.GetNode(i)->rGetLocation()[1])<1e-6) { /* ..add it to the list of fixed nodes.. */ fixed_nodes.push_back(i); /* ..and define y to be 0 but x is fixed */ c_vector<double,2> new_location; new_location(0) = SolidMechanicsProblemDefinition<2>::FREE; new_location(1) = 0.0; locations.push_back(new_location); } } /* Set the material law and fixed nodes, add some gravity, and solve */ SolidMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetFixedNodes(fixed_nodes, locations); c_vector<double,2> gravity = zero_vector<double>(2); gravity(1) = -0.5; problem_defn.SetBodyForce(gravity); IncompressibleNonlinearElasticitySolver<2> solver(mesh, problem_defn, "ElasticitySlidingBcsExample"); solver.Solve(); solver.CreateCmguiOutput(); /* Check the node at (1,0) has moved but has stayed on Y=0 */ TS_ASSERT_LESS_THAN(1.0, solver.rGetDeformedPosition()[10](0)); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[10](1), 0.0, 1e-3); }
// Update the coefficients associated with the patch field void cohesiveLawFvPatchVectorField::updateCoeffs() { if (updated()) { return; } // Looking up rheology const fvPatchField<scalar>& mu = patch().lookupPatchField<volScalarField, scalar>("mu"); const fvPatchField<scalar>& lambda = patch().lookupPatchField<volScalarField, scalar>("lambda"); vectorField n = patch().nf(); const fvPatchField<tensor>& gradU = patch().lookupPatchField<volTensorField, tensor>("grad(U)"); // Patch displacement const vectorField& U = *this; // Patch stress tensorField sigma = mu*(gradU + gradU.T()) + I*(lambda*tr(gradU)); // Normal stress component scalarField sigmaN = (n & (n & sigma)); scalarField delta = -(n & U); label sizeByTwo = patch().size()/2; for(label i = 0; i < sizeByTwo; i++) { scalar tmp = delta[i]; delta[i] += delta[sizeByTwo + i]; delta[sizeByTwo + i] += tmp; } forAll (traction_, faceI) { if (delta[faceI] < 0) { // Return from traction to symmetryPlane?? traction_[faceI] = law().sigmaMax().value()*n[faceI]; } else if(delta[faceI] > law().deltaC().value()) { // Traction free traction_[faceI] = vector::zero; } else { // Calculate cohesive traction from cohesive zone model traction_[faceI] = law().traction(delta[faceI])*n[faceI]; } } gradient() = ( traction_ - (n & (mu*gradU.T() - (mu + lambda)*gradU)) - n*lambda*tr(gradU) )/(2.0*mu + lambda); fixedGradientFvPatchVectorField::updateCoeffs(); }
/* == Incompressible deformation: non-zero displacement boundary conditions, functional tractions == * * We now consider a more complicated example. We prescribe particular new locations for the nodes * on the Dirichlet boundary, and also show how to prescribe a traction that is given in functional form * rather than prescribed for each boundary element. */ void TestIncompressibleProblemMoreComplicatedExample() throw(Exception) { /* Create a mesh */ QuadraticMesh<2> mesh; mesh.ConstructRegularSlabMesh(0.1 /*stepsize*/, 1.0 /*width*/, 1.0 /*height*/); /* Use a different material law this time, an exponential material law. * The material law needs to inherit from `AbstractIncompressibleMaterialLaw`, * and there are a few implemented, see `continuum_mechanics/src/problem/material_laws` */ ExponentialMaterialLaw<2> law(1.0, 0.5); // First parameter is 'a', second 'b', in W=a*exp(b(I1-3)) /* Now specify the fixed nodes, and their new locations. Create `std::vector`s for each. */ std::vector<unsigned> fixed_nodes; std::vector<c_vector<double,2> > locations; /* Loop over the mesh nodes */ for (unsigned i=0; i<mesh.GetNumNodes(); i++) { /* If the node is on the Y=0 surface (the LHS) */ if ( fabs(mesh.GetNode(i)->rGetLocation()[1])<1e-6) { /* Add it to the list of fixed nodes */ fixed_nodes.push_back(i); /* and define a new position x=(X,0.1*X^2^) */ c_vector<double,2> new_location; double X = mesh.GetNode(i)->rGetLocation()[0]; new_location(0) = X; new_location(1) = 0.1*X*X; locations.push_back(new_location); } } /* Now collect all the boundary elements on the top surface, as before, except * here we don't create the tractions for each element */ std::vector<BoundaryElement<1,2>*> boundary_elems; for (TetrahedralMesh<2,2>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); ++iter) { /* If Y=1, have found a boundary element */ if (fabs((*iter)->CalculateCentroid()[1] - 1.0)<1e-6) { BoundaryElement<1,2>* p_element = *iter; boundary_elems.push_back(p_element); } } /* Create a problem definition object, and this time calling `SetFixedNodes` * which takes in the new locations of the fixed nodes. */ SolidMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetFixedNodes(fixed_nodes, locations); /* Now call `SetTractionBoundaryConditions`, which takes in a vector of * boundary elements as in the previous test. However this time the second argument * is a ''function pointer'' (just the name of the function) to a * function returning traction in terms of position (and time [see below]). * This function is defined above, before the tests. It has to take in a `c_vector` (position) * and a double (time), and returns a `c_vector` (traction), and will only be called * using points in the boundary elements being passed in. The function `MyTraction` * (defined above, before the tests) above defines a horizontal traction (ie a shear stress, since it is * applied to the top surface) which increases in magnitude across the object. */ problem_defn.SetTractionBoundaryConditions(boundary_elems, MyTraction); /* Note: You can also call `problem_defn.SetBodyForce(MyBodyForce)`, passing in a function * instead of a vector, although isn't really physically useful, it is only really useful * for constructing problems with exact solutions. * * Create the solver as before */ IncompressibleNonlinearElasticitySolver<2> solver(mesh, problem_defn, "IncompressibleElasticityMoreComplicatedExample"); /* Call `Solve()` */ solver.Solve(); /* Another quick check */ TS_ASSERT_EQUALS(solver.GetNumNewtonIterations(), 6u); /* Visualise as before. * * '''Advanced:''' Note that the function `MyTraction` takes in time, which it didn't use. In the above it would have been called * with t=0. The current time can be set using `SetCurrentTime()`. The idea is that the user may want to solve a * sequence of static problems with time-dependent tractions (say), for which they should allow `MyTraction` to * depend on time, and put the solve inside a time-loop, for example: */ //for (double t=0; t<T; t+=dt) //{ // solver.SetCurrentTime(t); // solver.Solve(); //} /* In this the current time would be passed through to `MyTraction` * * Create Cmgui output */ solver.CreateCmguiOutput(); /* This is just to check that nothing has been accidentally changed in this test */ TS_ASSERT_DELTA(solver.rGetDeformedPosition()[98](0), 1.4543, 1e-3); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[98](1), 0.5638, 1e-3); }
/* == Compressible deformation, and other bits and pieces == * * In this test, we will show the (very minor) changes required to solve a compressible nonlinear * elasticity problem, we will describe and show how to specify 'pressure on deformed body' * boundary conditions, we illustrate how a quadratic mesh can be generated using a linear mesh * input files, and we also illustrate how `Solve()` can be called repeatedly, with loading * changing between the solves. * * Note: for other examples of compressible solves, including problems with an exact solution, see the * file `continuum_mechanics/test/TestCompressibleNonlinearElasticitySolver.hpp` */ void TestSolvingCompressibleProblem() throw (Exception) { /* All mechanics problems must take in quadratic meshes, but the mesh files for * (standard) linear meshes in Triangles/Tetgen can be automatically converted * to quadratic meshes, by simply doing the following. (The mesh loaded here is a disk * centred at the origin with radius 1). */ QuadraticMesh<2> mesh; TrianglesMeshReader<2,2> reader("mesh/test/data/disk_522_elements"); mesh.ConstructFromLinearMeshReader(reader); /* Compressible problems require a compressible material law, ie one that * inherits from `AbstractCompressibleMaterialLaw`. The `CompressibleMooneyRivlinMaterialLaw` * is one such example; instantiate one of these */ CompressibleMooneyRivlinMaterialLaw<2> law(1.0, 0.5); /* For this problem, we fix the nodes on the surface for which Y < -0.9 */ std::vector<unsigned> fixed_nodes; for ( TetrahedralMesh<2,2>::BoundaryNodeIterator iter = mesh.GetBoundaryNodeIteratorBegin(); iter != mesh.GetBoundaryNodeIteratorEnd(); ++iter) { double Y = (*iter)->rGetLocation()[1]; if(Y < -0.9) { fixed_nodes.push_back((*iter)->GetIndex()); } } /* We will (later) apply Neumann boundary conditions to surface elements which lie below Y=0, * and these are collected here. (Minor, subtle, comment: we don't bother here checking Y>-0.9, * so the surface elements collected here include the ones on the Dirichlet boundary. This doesn't * matter as the Dirichlet boundary conditions to the nonlinear system essentially overwrite * an Neumann-related effects). */ std::vector<BoundaryElement<1,2>*> boundary_elems; for (TetrahedralMesh<2,2>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); ++iter) { BoundaryElement<1,2>* p_element = *iter; if(p_element->CalculateCentroid()[1]<0.0) { boundary_elems.push_back(p_element); } } assert(boundary_elems.size()>0); /* Create the problem definition class, and set the law again, this time * stating that the law is compressible */ SolidMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(COMPRESSIBLE,&law); /* Set the fixed nodes and gravity */ problem_defn.SetZeroDisplacementNodes(fixed_nodes); /* The elasticity solvers have two nonlinear solvers implemented, one hand-coded and one which uses PETSc's SNES * solver. The latter is not the default but can be more robust (and will probably be the default in later * versions). This is how it can be used. (This option can also be called if the compiled binary is run from * the command line (see ChasteGuides/RunningBinariesFromCommandLine) using the option "-mech_use_snes"). */ problem_defn.SetSolveUsingSnes(); /* This line tells the solver to output info about the nonlinear solve as it progresses, and can be used with * or without the SNES option above. The corresponding command line option is "-mech_verbose" */ problem_defn.SetVerboseDuringSolve(); c_vector<double,2> gravity; gravity(0) = 0; gravity(1) = 0.1; problem_defn.SetBodyForce(gravity); /* Declare the compressible solver, which has the same interface as the incompressible * one, and call `Solve()` */ CompressibleNonlinearElasticitySolver<2> solver(mesh, problem_defn, "CompressibleSolidMechanicsExample"); solver.Solve(); /* Now we call add additional boundary conditions, and call `Solve() again. Firstly: these * Neumann conditions here are not specified traction boundary conditions (such BCs are specified * on the undeformed body), but instead, the (more natural) specification of a pressure * exactly in the ''normal direction on the deformed body''. We have to provide a set of boundary * elements of the mesh, and a pressure to act on those elements. The solver will automatically * compute the deformed normal directions on which the pressure acts. Note: with this type of * BC, the ordering of the nodes on the boundary elements needs to be consistent, otherwise some * normals will be computed to be inward and others outward. The solver will check this on the * original mesh and throw an exception if this is not the case. (The required ordering is such that: * in 2D, surface element nodes are ordered anticlockwise (looking at the whole mesh); in 3D, looking * at any surface element from outside the mesh, the three nodes are ordered anticlockwise. (Triangle/tetgen * automatically create boundary elements like this)). */ double external_pressure = -0.4; // negative sign => inward pressure problem_defn.SetApplyNormalPressureOnDeformedSurface(boundary_elems, external_pressure); /* Call `Solve()` again, so now solving with fixed nodes, gravity, and pressure. The solution from * the previous solve will be used as the initial guess. Although at the moment the solution from the * previous call to `Solve()` will be over-written, calling `Solve()` repeatedly may be useful for * some problems: sometimes, Newton's method will fail to converge for given force/pressures etc, and it can * be (very) helpful to ''increment'' the loading. For example, set the gravity to be (0,-9.81/3), solve, * then set it to be (0,-2*9.81/3), solve again, and finally set it to be (0,-9.81) and solve for a * final time */ solver.Solve(); solver.CreateCmguiOutput(); }
/* == Incompressible deformation: 2D shape hanging under gravity with a balancing traction == * * We now repeat the above test but include a traction on the bottom surface (Y=0). We apply this * in the inward direction so that is counters (somewhat) the effect of gravity. We also show how stresses * and strains can be written to file. */ void TestIncompressibleProblemWithTractions() throw(Exception) { /* All of this is exactly as above */ QuadraticMesh<2> mesh; mesh.ConstructRegularSlabMesh(0.1 /*stepsize*/, 0.8 /*width*/, 1.0 /*height*/); MooneyRivlinMaterialLaw<2> law(1.0); c_vector<double,2> body_force; body_force(0) = 0.0; body_force(1) = -2.0; std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh, 1, 1.0); /* Now the traction boundary conditions. We need to collect all the boundary elements on the surface which we want to * apply non-zero tractions, put them in a `std::vector`, and create a corresponding `std::vector` of the tractions * for each of the boundary elements. Note that the each traction is a 2D vector with dimensions of pressure. * * First, declare the data structures: */ std::vector<BoundaryElement<1,2>*> boundary_elems; std::vector<c_vector<double,2> > tractions; /* Create a constant traction */ c_vector<double,2> traction; traction(0) = 0; traction(1) = 1.0; // this choice of sign corresponds to an inward force (if applied to the bottom surface) /* Loop over boundary elements */ for (TetrahedralMesh<2,2>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); ++iter) { /* If the centre of the element has Y value of 0.0, it is on the surface we need */ if (fabs((*iter)->CalculateCentroid()[1] - 0.0) < 1e-6) { /* Put the boundary element and the constant traction into the stores. */ BoundaryElement<1,2>* p_element = *iter; boundary_elems.push_back(p_element); tractions.push_back(traction); } } /* A quick check */ assert(boundary_elems.size() == 8u); /* Now create the problem definition object, setting the material law, fixed nodes and body force as * before (this time not calling `SetDensity()`, so using the default density of 1.0, * and also calling a method for setting tractions, which takes in the boundary elements * and tractions for each of those elements. */ SolidMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetBodyForce(body_force); problem_defn.SetTractionBoundaryConditions(boundary_elems, tractions); /* Create solver as before */ IncompressibleNonlinearElasticitySolver<2> solver(mesh, problem_defn, "IncompressibleElasticityWithTractionsTutorial"); /* In this test we also output the stress and strain. For the former, we have to tell the solver to store * the stresses that are computed during the solve. */ solver.SetComputeAverageStressPerElementDuringSolve(); /* Call `Solve()` */ solver.Solve(); /* If VTK output is written (discussed above) strains can be visualised. Alternatively, we can create text files * for strains and stresses by doing the following. * * Write the final deformation gradients to file. The i-th line of this file provides the deformation gradient F, * written as 'F(0,0) F(0,1) F(1,0) F(1,1)', evaluated at the centroid of the i-th element. The first variable * can also be DEFORMATION_TENSOR_C or LAGRANGE_STRAIN_E to write C or E. The second parameter is the file name. */ solver.WriteCurrentStrains(DEFORMATION_GRADIENT_F,"deformation_grad"); /* Since we called `SetComputeAverageStressPerElementDuringSolve`, we can write the stresses to file too. However, * note that for each element this is not the stress evaluated at the centroid, but the mean average of the stresses * evaluated at the quadrature points - for technical cardiac electromechanics reasons, it is difficult to * define the stress at non-quadrature points. */ solver.WriteCurrentAverageElementStresses("2nd_PK_stress"); /* Another quick check */ TS_ASSERT_EQUALS(solver.GetNumNewtonIterations(), 4u); /* Visualise as before by going to the output directory and doing * `x=load('solution.nodes'); plot(x(:,1),x(:,2),'m*')` in Matlab/octave, or by using Cmgui. * The effect of the traction should be clear (especially when compared to * the results of the first test). * * Create Cmgui output */ solver.CreateCmguiOutput(); /* This is just to check that nothing has been accidentally changed in this test */ TS_ASSERT_DELTA(solver.rGetDeformedPosition()[8](0), 0.8561, 1e-3); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[8](1), 0.0310, 1e-3); }
/* In the first test we use INCOMPRESSIBLE nonlinear elasticity. For incompressible elasticity, there * is a constraint on the deformation, which results in a pressure field (a Lagrange multiplier) * which is solved for together with the deformation. * * All the mechanics solvers solve for the deformation using the finite element method with QUADRATIC * basis functions for the deformation. This necessitates the use of a `QuadraticMesh` - such meshes have * extra nodes that aren't vertices of elements, in this case midway along each edge. (The displacement * is solved for at ''each node'' in the mesh (including internal [non-vertex] nodes), whereas the pressure * is only solved for at each vertex - in FEM terms, quadratic interpolation for displacement, linear * interpolation for pressure, which is required for stability. The pressure at internal nodes is computed * by linear interpolation). * */ void TestSimpleIncompressibleProblem() throw(Exception) { /* First, define the geometry. This should be specified using the `QuadraticMesh` class, which inherits from `TetrahedralMesh` * and has mostly the same interface. Here we define a 0.8 by 1 rectangle, with elements 0.1 wide. * (`QuadraticMesh`s can also be read in using `TrianglesMeshReader`; see next tutorial/rest of code base for examples of this). */ QuadraticMesh<2> mesh; mesh.ConstructRegularSlabMesh(0.1 /*stepsize*/, 0.8 /*width*/, 1.0 /*height*/); /* We use a Mooney-Rivlin material law, which applies to isotropic materials and has two parameters. * Restricted to 2D however, it only has one parameter, which can be thought of as the total * stiffness. We declare a Mooney-Rivlin law, setting the parameter to 1. */ MooneyRivlinMaterialLaw<2> law(1.0); /* Next, the body force density. In realistic problems this will either be * acceleration due to gravity (ie b=(0,-9.81)) or zero if the effect of gravity can be neglected. * In this problem we apply a gravity-like downward force. */ c_vector<double,2> body_force; body_force(0) = 0.0; body_force(1) = -2.0; /* Two types of boundary condition are required: displacement and traction. As with the other PDE solvers, * the displacement (Dirichlet) boundary conditions are specified at nodes, whereas traction (Neumann) boundary * conditions are specified on boundary elements. * * In this test we apply displacement boundary conditions on one surface of the mesh, the upper (Y=1.0) surface. * We are going to specify zero-displacement at these nodes. * We do not specify any traction boundary conditions, which means that (effectively) zero-traction boundary * conditions (ie zero pressures) are applied on the three other surfaces. * * We need to get a `std::vector` of all the node indices that we want to fix. The `NonlinearElasticityTools` * has a static method for helping do this: the following gets all the nodes for which Y=1.0. The second * argument (the '1') indicates Y . (So, for example, `GetNodesByComponentValue(mesh, 0, 10)` would get the nodes on X=10). */ std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh, 1, 1.0); /* * Before creating the solver we create a `SolidMechanicsProblemDefinition` object, which contains * everything that defines the problem: mesh, material law, body force, * the fixed nodes and their locations, any traction boundary conditions, and the density * (which multiplies the body force, otherwise isn't used). */ SolidMechanicsProblemDefinition<2> problem_defn(mesh); /* Set the material problem on the problem definition object, saying that the problem, and * the material law, is incompressible. All material law files can be found in * `continuum_mechanics/src/problem/material_laws`. */ problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); /* Set the fixed nodes, choosing zero displacement for these nodes (see later for how * to provide locations for the fixed nodes). */ problem_defn.SetZeroDisplacementNodes(fixed_nodes); /* Set the body force and the density. (Note that the second line isn't technically * needed, as internally the density is initialised to 1) */ problem_defn.SetBodyForce(body_force); problem_defn.SetDensity(1.0); /* Now we create the (incompressible) solver, passing in the mesh, problem definition * and output directory */ IncompressibleNonlinearElasticitySolver<2> solver(mesh, problem_defn, "SimpleIncompressibleElasticityTutorial"); /* .. and to compute the solution, just call `Solve()` */ solver.Solve(); /* '''Visualisation'''. Go to the folder `SimpleIncompressibleElasticityTutorial` in your test-output directory. * There should be 2 files, initial.nodes and solution.nodes. These are the original nodal positions and the deformed * positions. Each file has two columns, the x and y locations of each node. To visualise the solution in say * Matlab or Octave, you could do: `x=load('solution.nodes'); plot(x(:,1),x(:,2),'k*')`. For Cmgui output, see below. * * To get the actual solution from the solver, use these two methods. Note that the first * gets the deformed position (ie the new location, not the displacement). They are both of size * num_total_nodes. */ std::vector<c_vector<double,2> >& r_deformed_positions = solver.rGetDeformedPosition(); std::vector<double>& r_pressures = solver.rGetPressures(); /* Let us obtain the values of the new position, and the pressure, at the bottom right corner node. */ unsigned node_index = 8; assert( fabs(mesh.GetNode(node_index)->rGetLocation()[0] - 0.8) < 1e-6); // check that X=0.8, ie that we have the correct node, assert( fabs(mesh.GetNode(node_index)->rGetLocation()[1] - 0.0) < 1e-6); // check that Y=0.0, ie that we have the correct node, std::cout << "New position: " << r_deformed_positions[node_index](0) << " " << r_deformed_positions[node_index](1) << "\n"; std::cout << "Pressure: " << r_pressures[node_index] << "\n"; /* HOW_TO_TAG Continuum mechanics * Visualise nonlinear elasticity problems solutions, including visualisng strains */ /* One visualiser is Cmgui. This method can be used to convert all the output files to Cmgui format. * They are placed in `[OUTPUT_DIRECTORY]/cmgui`. A script is created to easily load the data: in a * terminal cd to this directory and call `cmgui LoadSolutions.com`. (In this directory, the initial position is given by * solution_0.exnode, the deformed by solution_1.exnode). */ solver.CreateCmguiOutput(); /* The recommended visualiser is Paraview, for which Chaste must be installed with VTK. With paraview, strains (and in the future * stresses) can be visualised on the undeformed/deformed geometry). We can create VTK output using * the `VtkNonlinearElasticitySolutionWriter` class. The undeformed geometry, solution displacement, and pressure (if incompressible * problem) are written to file, and below we also choose to write the deformation tensor C for each element. */ VtkNonlinearElasticitySolutionWriter<2> vtk_writer(solver); vtk_writer.SetWriteElementWiseStrains(DEFORMATION_TENSOR_C); // other options are DEFORMATION_GRADIENT_F and LAGRANGE_STRAIN_E vtk_writer.Write(); /* These are just to check that nothing has been accidentally changed in this test. * Newton's method (with damping) was used to solve the nonlinear problem, and we check that * 4 iterations were needed to converge. */ TS_ASSERT_DELTA(r_deformed_positions[node_index](0), 0.7980, 1e-3); TS_ASSERT_DELTA(r_deformed_positions[node_index](1), -0.1129, 1e-3); TS_ASSERT_EQUALS(solver.GetNumNewtonIterations(), 4u); }
// Solve a problem where some pressure is applied in the outward direction on the bottom (Z=0) surface // // Returns the number of newton iterations taken unsigned SolvePressureOnUnderside(QuadraticMesh<3>& rMesh, std::string outputDirectory, std::vector<double>& rSolution, bool useSolutionAsGuess, double scaleFactor = 1.0) { MooneyRivlinMaterialLaw<3> law(1.0, 0.0); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<3>::GetNodesByComponentValue(rMesh, 0, 0.0); std::vector<BoundaryElement<2,3>*> boundary_elems; double pressure = 0.001; for (TetrahedralMesh<3,3>::BoundaryElementIterator iter = rMesh.GetBoundaryElementIteratorBegin(); iter != rMesh.GetBoundaryElementIteratorEnd(); ++iter) { BoundaryElement<2,3>* p_element = *iter; double Z = p_element->CalculateCentroid()[2]; if(fabs(Z)<1e-6) { boundary_elems.push_back(p_element); } } SolidMechanicsProblemDefinition<3> problem_defn(rMesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetApplyNormalPressureOnDeformedSurface(boundary_elems, pressure*scaleFactor); problem_defn.SetVerboseDuringSolve(); IncompressibleNonlinearElasticitySolver<3> solver(rMesh,problem_defn,outputDirectory); solver.SetWriteOutputEachNewtonIteration(); // cover the SetTakeFullFirstNewtonStep() method, and the SetUsePetscDirectSolve() method solver.SetTakeFullFirstNewtonStep(); solver.SetUsePetscDirectSolve(); if(useSolutionAsGuess) { if(solver.rGetCurrentSolution().size()!=rSolution.size()) { EXCEPTION("Badly-sized input"); } for(unsigned i=0; i<rSolution.size(); i++) { solver.rGetCurrentSolution()[i] = rSolution[i]; } } if(scaleFactor < 1.0) { try { solver.Solve(); } catch (Exception& e) { // not final Solve, so don't quit WARNING(e.GetMessage()); } } else { solver.Solve(); solver.CreateCmguiOutput(); VtkNonlinearElasticitySolutionWriter<3> vtk_writer(solver); vtk_writer.SetWriteElementWiseStrains(DEFORMATION_TENSOR_C); vtk_writer.Write(); } rSolution.clear(); rSolution.resize(solver.rGetCurrentSolution().size()); for(unsigned i=0; i<rSolution.size(); i++) { rSolution[i] = solver.rGetCurrentSolution()[i]; } return solver.GetNumNewtonIterations(); }
// cover all other contraction model options which are allowed but not been used in a test so far void TestCoverage() throw(Exception) { QuadraticMesh<2> mesh(1.0, 1.0, 1.0); MooneyRivlinMaterialLaw<2> law(1); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh,0,0.0); ElectroMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetContractionModel(NONPHYSIOL3,0.01); problem_defn.SetMechanicsSolveTimestep(0.01); //This is only set to make ElectroMechanicsProblemDefinition::Validate pass TS_ASSERT_THROWS_THIS(problem_defn.SetApplyAnisotropicCrossFibreTension(true,1.0,1.0), "You can only apply anisotropic cross fibre tensions in a 3D simulation."); TetrahedralMesh<2,2>* p_fine_mesh = new TetrahedralMesh<2,2>(); p_fine_mesh->ConstructRegularSlabMesh(1.0, 1.0, 1.0); TetrahedralMesh<2,2>* p_coarse_mesh = new TetrahedralMesh<2,2>(); p_coarse_mesh->ConstructRegularSlabMesh(1.0, 1.0, 1.0); FineCoarseMeshPair<2>* p_pair = new FineCoarseMeshPair<2>(*p_fine_mesh, *p_coarse_mesh); p_pair->SetUpBoxesOnFineMesh(); TetrahedralMesh<2,2>* p_coarse_mesh_big = new TetrahedralMesh<2,2>(); p_coarse_mesh_big->ConstructRegularSlabMesh(1.0, 3.0, 3.0); FineCoarseMeshPair<2>* p_pair_wrong = new FineCoarseMeshPair<2>(*p_fine_mesh, *p_coarse_mesh_big); // Some strange memory thing happens if we don't have the test with the // exceptions below in a separate block. I have no idea why. { IncompressibleExplicitSolver2d expl_solver(mesh,problem_defn,""); p_pair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(expl_solver.GetQuadratureRule()), false); p_pair->DeleteFineBoxCollection(); expl_solver.SetFineCoarseMeshPair(p_pair); expl_solver.Initialise(); // Prevent an assertion being thrown about setting the cell factory more than once. delete problem_defn.mpContractionCellFactory; problem_defn.mpContractionCellFactory = NULL; problem_defn.SetContractionModel(NASH2004,0.01); IncompressibleExplicitSolver2d expl_solver_with_nash(mesh,problem_defn,""); expl_solver_with_nash.SetFineCoarseMeshPair(p_pair); expl_solver_with_nash.Initialise(); // Prevent an assertion being thrown about setting the cell factory more than once. delete problem_defn.mpContractionCellFactory; problem_defn.mpContractionCellFactory = NULL; problem_defn.SetContractionModel(KERCHOFFS2003,0.01); IncompressibleExplicitSolver2d expl_solver_with_kerchoffs(mesh,problem_defn,""); expl_solver_with_kerchoffs.SetFineCoarseMeshPair(p_pair); expl_solver_with_kerchoffs.Initialise(); } { // bad contraction model ElectroMechanicsProblemDefinition<2> problem_defn2(mesh); problem_defn2.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn2.SetContractionModel(NHS,0.01); IncompressibleExplicitSolver2d solver(mesh,problem_defn2,""); TS_ASSERT_THROWS_THIS(solver.SetFineCoarseMeshPair(p_pair_wrong), "When setting a mesh pair into the solver, the coarse mesh of the mesh pair must be the same as the quadratic mesh"); solver.SetFineCoarseMeshPair(p_pair); TS_ASSERT_THROWS_THIS(solver.Initialise(), "stretch-rate-dependent contraction model requires an IMPLICIT cardiac mechanics solver."); } delete p_fine_mesh; delete p_coarse_mesh; delete p_pair; delete p_coarse_mesh_big; delete p_pair_wrong; }
// with stretch-dependent contraction models the implicit and explicit schemes can be similar void TestCompareImplicitAndExplicitWithStretchDependentContractionModel() throw(Exception) { QuadraticMesh<2> mesh(0.25, 1.0, 1.0); MooneyRivlinMaterialLaw<2> law(1); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh,0,0.0); ElectroMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetContractionModel(NONPHYSIOL2,0.01); problem_defn.SetMechanicsSolveTimestep(0.01); //This is only set to make ElectroMechanicsProblemDefinition::Validate pass //The following lines are not relevant to this test but need to be there TetrahedralMesh<2,2>* p_fine_mesh = new TetrahedralMesh<2,2>();//unused in this test p_fine_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); TetrahedralMesh<2,2>* p_coarse_mesh = new TetrahedralMesh<2,2>();//unused in this test p_coarse_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); FineCoarseMeshPair<2>* p_pair = new FineCoarseMeshPair<2>(*p_fine_mesh, *p_coarse_mesh);//also unused in this test p_pair->SetUpBoxesOnFineMesh(); ///////////////////////////////////////////////////////////////////// // NONPHYSIOL 2 - contraction model is of the form lam*sin(t) IncompressibleExplicitSolver2d expl_solver(mesh,problem_defn,"TestCompareExplAndImplCardiacSolversStretch_Exp"); p_pair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(expl_solver.GetQuadratureRule()), false); p_pair->DeleteFineBoxCollection(); expl_solver.SetFineCoarseMeshPair(p_pair); expl_solver.Initialise(); IncompressibleImplicitSolver2d impl_solver(mesh,problem_defn,"TestCompareExplAndImplCardiacSolversStretch_Imp"); impl_solver.SetFineCoarseMeshPair(p_pair); impl_solver.Initialise(); expl_solver.WriteCurrentSpatialSolution("solution","nodes",0); impl_solver.WriteCurrentSpatialSolution("solution","nodes",0); unsigned counter = 1; double t0 = 0.0; double t1 = 0.25; // to be quite quick (min stretch ~=0.88), make this 5/4 (?) say for min stretch < 0.7 double dt = 0.025; for(double t=t0; t<t1; t+=dt) { //std::cout << "\n **** t = " << t << " ****\n" << std::flush; expl_solver.SetWriteOutput(false); expl_solver.Solve(t,t+dt,dt); expl_solver.SetWriteOutput(); expl_solver.WriteCurrentSpatialSolution("solution","nodes",counter); impl_solver.SetWriteOutput(false); impl_solver.Solve(t,t+dt,dt); impl_solver.SetWriteOutput(); impl_solver.WriteCurrentSpatialSolution("solution","nodes",counter); // the solutions turn out to be very close to each other for(unsigned i=0; i<mesh.GetNumNodes(); i++) { TS_ASSERT_DELTA(expl_solver.rGetDeformedPosition()[i](0), impl_solver.rGetDeformedPosition()[i](0), 2e-3); TS_ASSERT_DELTA(expl_solver.rGetDeformedPosition()[i](1), impl_solver.rGetDeformedPosition()[i](1), 2e-3); } // visualisation in matlab or octave // run from CHASTETESTOUTPUT // // close all; figure; hold on // for i=0:10, x1 = load(['TestCompareExplAndImplCardiacSolversStretch_Exp/solution_',num2str(i),'.nodes']); plot(i,x1(5,1),'b*'); end // for i=0:10, x2 = load(['TestCompareExplAndImplCardiacSolversStretch_Imp/solution_',num2str(i),'.nodes']); plot(i,x2(5,1),'r*'); end // // close all; figure; hold on // for i=0:10, x2 = load(['TestCompareExplAndImplCardiacSolversStretch_Imp/solution_',num2str(i),'.nodes']); plot(x2(:,1),x2(:,2),'r*'); end counter++; } // check there was significant deformation - node 4 is (1,0) TS_ASSERT_DELTA(mesh.GetNode(4)->rGetLocation()[0], 1.0, 1e-12); TS_ASSERT_LESS_THAN(impl_solver.rGetDeformedPosition()[4](0), 0.9); //in need of deletion even if all these 3 have no influence at all on this test delete p_fine_mesh; delete p_coarse_mesh; delete p_pair; }
/** * Solve a problem in which the traction boundary condition is a normal pressure * applied to a surface of the deformed body. * * The initial body is the unit cube. The deformation is given by x=Rx', where * R is a rotation matrix and x'=[X/(lambda^2) lambda*Y lambda*Z], ie simple stretching * followed by a rotation. * * Ignoring R for the time being, the deformation gradient would be * F=diag(1/lambda^2, lambda, lambda). * Assuming a Neo-Hookean material law, the first Piola-Kirchoff tensor is * * S = 2c F^T - p F^{-1} * = diag ( 2c lam^{-2} - p lam^2, 2c lam - p lam^{-1}, 2c lam - p lam^{-1} * * We choose the internal pressure (p) to be 2clam^2, so that * * S = diag( 2c (lam^{-2} - lam^4), 0, 0) * * To obtain this deformation as the solution, we can specify fixed location boundary conditions * on X=0 and specify a traction of t=[2c(lam^{-2} - lam^4) 0 0] on the X=1 surface. * * Now, including the rotation matrix, we can specify appropriate fixed location boundary conditions * on the X=0 surface, and specify that a normal pressure should act on the *deformed* X=1 surface, * with value P = 2c(lam^{-2} - lam^4)/(lambda^2) [divided by lambda^2 as the deformed surface * has a smaller area than the undeformed surface. * */ void TestWithPressureOnDeformedSurfaceBoundaryCondition3d() throw (Exception) { double lambda = 0.85; unsigned num_elem_each_dir = 5; QuadraticMesh<3> mesh(1.0/num_elem_each_dir, 1.0, 1.0, 1.0); // Neo-Hookean material law double c1 = 1.0; MooneyRivlinMaterialLaw<3> law(c1, 0.0); // anything will do here c_matrix<double,3,3> rotation_matrix = identity_matrix<double>(3); rotation_matrix(0,0)=1.0/sqrt(2.0); rotation_matrix(0,1)=-1.0/sqrt(2.0); rotation_matrix(1,0)=1.0/sqrt(2.0); rotation_matrix(1,1)=1.0/sqrt(2.0); // Define displacement boundary conditions std::vector<unsigned> fixed_nodes; std::vector<c_vector<double,3> > locations; for (unsigned i=0; i<mesh.GetNumNodes(); i++) { double X = mesh.GetNode(i)->rGetLocation()[0]; double Y = mesh.GetNode(i)->rGetLocation()[1]; double Z = mesh.GetNode(i)->rGetLocation()[2]; // if X=0 if ( fabs(X)<1e-6) { fixed_nodes.push_back(i); c_vector<double,3> new_position_before_rotation; new_position_before_rotation(0) = 0.0; new_position_before_rotation(1) = lambda*Y; new_position_before_rotation(2) = lambda*Z; locations.push_back(prod(rotation_matrix, new_position_before_rotation)); } } assert(fixed_nodes.size()==(2*num_elem_each_dir+1)*(2*num_elem_each_dir+1)); // Define pressure boundary conditions X=1 surface std::vector<BoundaryElement<2,3>*> boundary_elems; double pressure_bc = 2*c1*((pow(lambda,-2.0)-pow(lambda,4.0)))/(lambda*lambda); for (TetrahedralMesh<3,3>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); ++iter) { if (fabs((*iter)->CalculateCentroid()[0]-1)<1e-6) { BoundaryElement<2,3>* p_element = *iter; boundary_elems.push_back(p_element); } } assert(boundary_elems.size()==2*num_elem_each_dir*num_elem_each_dir); SolidMechanicsProblemDefinition<3> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetFixedNodes(fixed_nodes, locations); problem_defn.SetApplyNormalPressureOnDeformedSurface(boundary_elems, pressure_bc); IncompressibleNonlinearElasticitySolver<3> solver(mesh, problem_defn, "nonlin_elas_3d_pressure_on_deformed"); solver.Solve(); // Compare std::vector<c_vector<double,3> >& r_solution = solver.rGetDeformedPosition(); for (unsigned i=0; i<mesh.GetNumNodes(); i++) { double X = mesh.GetNode(i)->rGetLocation()[0]; double Y = mesh.GetNode(i)->rGetLocation()[1]; double Z = mesh.GetNode(i)->rGetLocation()[2]; c_vector<double,3> new_position_before_rotation; new_position_before_rotation(0) = X/(lambda*lambda); new_position_before_rotation(1) = lambda*Y; new_position_before_rotation(2) = lambda*Z; c_vector<double,3> new_position = prod(rotation_matrix, new_position_before_rotation); TS_ASSERT_DELTA(r_solution[i](0), new_position(0), 1e-2); TS_ASSERT_DELTA(r_solution[i](1), new_position(1), 1e-2); TS_ASSERT_DELTA(r_solution[i](2), new_position(2), 1e-2); } // test the pressures std::vector<double>& r_pressures = solver.rGetPressures(); for (unsigned i=0; i<r_pressures.size(); i++) { TS_ASSERT_DELTA(r_pressures[i], 2*c1*lambda*lambda, 0.05); } }
/** * Solve 3D nonlinear elasticity problem with an exact solution. * * For full details see Pathmanathan, Gavaghan, Whiteley "A comparison of numerical * methods used in finite element modelling of soft tissue deformation", J. Strain * Analysis, to appear. * * We solve a 3d problem on a cube with a Neo-Hookean material, assuming the solution * will be * x = X+aX^2/2 * y = Y+bY^2/2 * z = Z/(1+aX)(1+bY) * with p=2c (c the material parameter), * which, note, has been constructed to be an incompressible. We assume displacement * boundary conditions on X=0 and traction boundary conditions on the remaining 5 surfaces. * It is then possible to determine the body force and surface tractions required for * this deformation, and they are defined in the above class. */ void TestSolve3d() throw(Exception) { unsigned num_runs=4; double l2_errors[4]; unsigned num_elem_each_dir[4] = {1,2,5,10}; for(unsigned run=0; run<num_runs; run++) { QuadraticMesh<3> mesh(1.0/num_elem_each_dir[run], 1.0, 1.0, 1.0); // Neo-Hookean material law MooneyRivlinMaterialLaw<3> law(ThreeDimensionalModelProblem::c1, 0.0); // Define displacement boundary conditions std::vector<unsigned> fixed_nodes; std::vector<c_vector<double,3> > locations; for (unsigned i=0; i<mesh.GetNumNodes(); i++) { double X = mesh.GetNode(i)->rGetLocation()[0]; double Y = mesh.GetNode(i)->rGetLocation()[1]; double Z = mesh.GetNode(i)->rGetLocation()[2]; // if X=0 if ( fabs(X)<1e-6) { fixed_nodes.push_back(i); c_vector<double,3> new_position; new_position(0) = 0.0; new_position(1) = Y + Y*Y*ThreeDimensionalModelProblem::b/2.0; new_position(2) = Z/((1+X*ThreeDimensionalModelProblem::a)*(1+Y*ThreeDimensionalModelProblem::b)); locations.push_back(new_position); } } assert(fixed_nodes.size()==(2*num_elem_each_dir[run]+1)*(2*num_elem_each_dir[run]+1)); // Define traction boundary conditions on all boundary elems that are not on X=0 std::vector<BoundaryElement<2,3>*> boundary_elems; for (TetrahedralMesh<3,3>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); ++iter) { if (fabs((*iter)->CalculateCentroid()[0])>1e-6) { BoundaryElement<2,3>* p_element = *iter; boundary_elems.push_back(p_element); } } assert(boundary_elems.size()==10*num_elem_each_dir[run]*num_elem_each_dir[run]); SolidMechanicsProblemDefinition<3> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetFixedNodes(fixed_nodes, locations); problem_defn.SetBodyForce(ThreeDimensionalModelProblem::GetBodyForce); problem_defn.SetTractionBoundaryConditions(boundary_elems, ThreeDimensionalModelProblem::GetTraction); IncompressibleNonlinearElasticitySolver<3> solver(mesh, problem_defn, "nonlin_elas_3d_10"); solver.Solve(1e-12); // note newton tolerance of 1e-12 needed for convergence to occur solver.CreateCmguiOutput(); // Compare l2_errors[run] = 0; std::vector<c_vector<double,3> >& r_solution = solver.rGetDeformedPosition(); for (unsigned i=0; i<mesh.GetNumNodes(); i++) { double X = mesh.GetNode(i)->rGetLocation()[0]; double Y = mesh.GetNode(i)->rGetLocation()[1]; double Z = mesh.GetNode(i)->rGetLocation()[2]; double exact_x = X + X*X*ThreeDimensionalModelProblem::a/2.0; double exact_y = Y + Y*Y*ThreeDimensionalModelProblem::b/2.0; double exact_z = Z/((1+X*ThreeDimensionalModelProblem::a)*(1+Y*ThreeDimensionalModelProblem::b)); c_vector<double,3> error; error(0) = r_solution[i](0) - exact_x; error(1) = r_solution[i](1) - exact_y; error(2) = r_solution[i](2) - exact_z; l2_errors[run] += norm_2(error); if(num_elem_each_dir[run]==5u) { TS_ASSERT_DELTA(r_solution[i](0), exact_x, 1e-2); TS_ASSERT_DELTA(r_solution[i](1), exact_y, 1e-2); TS_ASSERT_DELTA(r_solution[i](2), exact_z, 1e-2); } } l2_errors[run] /= mesh.GetNumNodes(); std::vector<double>& r_pressures = solver.rGetPressures(); for (unsigned i=0; i<r_pressures.size(); i++) { TS_ASSERT_DELTA(r_pressures[i]/(2*ThreeDimensionalModelProblem::c1), 1.0, 2e-1); } } //for(unsigned run=0; run<num_runs; run++) //{ // std::cout << 1.0/num_elem_each_dir[run] << " " << l2_errors[run] << "\n"; //} TS_ASSERT_LESS_THAN(l2_errors[0], 0.0005) TS_ASSERT_LESS_THAN(l2_errors[1], 5e-5); TS_ASSERT_LESS_THAN(l2_errors[2], 2.1e-6); TS_ASSERT_LESS_THAN(l2_errors[3], 4e-7); }
/** * This time we will make x (fibres) and z (sheet-normal) contract, * and y will not contract (so nodes will expand out for y, shrink in for x,z). */ void TestAnisotropicCrossFibreTensions() throw(Exception) { /* * Expected resulting deformed location of Nodes 4, 24, 104, 124: * 4: 1, 0, 0 * 24: 1, 1, 0 * 104: 1, 0, 1 * 124: 1, 1, 1 */ c_vector<double, 4> x; c_vector<double, 4> y; c_vector<double, 4> z; x[0] = 0.9905; x[1] = 0.9908; x[2] = 0.9904; x[3] = 0.9907; y[0] = -0.0113; y[1] = 1.0105; y[2] = -0.0113; y[3] = 1.0105; z[0] = 0.0056; z[1] = 0.0056; z[2] = 0.9946; z[3] = 0.9946; QuadraticMesh<3> mesh(0.25, 1.0, 1.0, 1.0); MooneyRivlinMaterialLaw<3> law(1,1); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<3>::GetNodesByComponentValue(mesh,0,0.0); ElectroMechanicsProblemDefinition<3> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetContractionModel(NONPHYSIOL1,0.01); problem_defn.SetMechanicsSolveTimestep(0.01); //This is only set to make ElectroMechanicsProblemDefinition::Validate pass double sheet_tension_fraction=0; double sheet_normal_tension_fraction=1; problem_defn.SetApplyAnisotropicCrossFibreTension(true,sheet_tension_fraction, sheet_normal_tension_fraction); // NONPHYSIOL1 => NonphysiologicalContractionModel 1 IncompressibleExplicitSolver3d solver(mesh,problem_defn,"TestAnisotropicCrossFibreExplicit"); // The following lines are not relevant to this test but need to be there // as the solver is expecting an electrics node to be paired up with each mechanics node. TetrahedralMesh<3,3>* p_fine_mesh = new TetrahedralMesh<3,3>();//electrics ignored in this test p_fine_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0, 1.0); FineCoarseMeshPair<3>* p_pair = new FineCoarseMeshPair<3>(*p_fine_mesh, mesh); p_pair->SetUpBoxesOnFineMesh(); p_pair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(solver.GetQuadratureRule()), false); p_pair->DeleteFineBoxCollection(); solver.SetFineCoarseMeshPair(p_pair); /////////////////////////////////////////////////////////////////////////// solver.Initialise(); // coverage QuadraturePointsGroup<3> quad_points(mesh, *(solver.GetQuadratureRule())); std::vector<double> calcium_conc(solver.GetTotalNumQuadPoints(), 0.0); std::vector<double> voltages(solver.GetTotalNumQuadPoints(), 0.0); solver.SetCalciumAndVoltage(calcium_conc, voltages); // solve UP TO t=0. So Ta(lam_n,t_{n+1})=5*sin(0)=0, ie no deformation solver.Solve(-0.01,0.0,0.01); TS_ASSERT_EQUALS(solver.GetNumNewtonIterations(),0u); solver.Solve(0.24,0.25,0.01); std::vector<unsigned> nodes; nodes.push_back(4); nodes.push_back(24); nodes.push_back(104); nodes.push_back(124); for (unsigned node=0; node<4; node++) { std::cout << "Node: " << nodes[node] << "\n"; TS_ASSERT_DELTA(solver.rGetDeformedPosition()[nodes[node]](0), x[node], 1e-3); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[nodes[node]](1), y[node], 1e-3); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[nodes[node]](2), z[node], 1e-3); } // tidy up memory delete p_fine_mesh; delete p_pair; }
void TestCrossFibreTensionWithSimpleContractionModel() throw(Exception) { QuadraticMesh<2> mesh(0.25, 1.0, 1.0); MooneyRivlinMaterialLaw<2> law(1); std::vector<unsigned> fixed_nodes = NonlinearElasticityTools<2>::GetNodesByComponentValue(mesh,0,0.0); ElectroMechanicsProblemDefinition<2> problem_defn(mesh); problem_defn.SetMaterialLaw(INCOMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetContractionModel(NONPHYSIOL1,0.01); problem_defn.SetMechanicsSolveTimestep(0.01); //This is only set to make ElectroMechanicsProblemDefinition::Validate pass //Cross fibre tension fractions to be tested c_vector<double, 4> tension_fractions; tension_fractions[0] = 0.0; tension_fractions[1] = 0.1; tension_fractions[2] = 0.5; tension_fractions[3] = 1.0; //Expected resulting deformed location of Node 4. c_vector<double, 4> x; c_vector<double, 4> y; x[0] = 0.9732; x[1] = 0.9759; x[2] = 0.9865; x[3] = 1.0; //Note, for cross_tension == 1.0 there should be no deformation of the tissue square (tensions balance) y[0] = -0.0156; y[1] = -0.0140; y[2] = -0.0077; y[3] = 0.0; for(unsigned i=0; i < tension_fractions.size();i++) { problem_defn.SetApplyIsotropicCrossFibreTension(true,tension_fractions[i]); TS_ASSERT_EQUALS(problem_defn.GetApplyCrossFibreTension(), true); TS_ASSERT_DELTA(problem_defn.GetSheetTensionFraction(),tension_fractions[i], 1e-6); TS_ASSERT_DELTA(problem_defn.GetSheetNormalTensionFraction(),tension_fractions[i], 1e-6); // NONPHYSIOL1 => NonphysiologicalContractionModel 1 IncompressibleExplicitSolver2d solver(mesh,problem_defn,"TestExplicitCardiacMech"); // The following lines are not relevant to this test but need to be there // as the solver is expecting an electrics node to be paired up with each mechanics node. TetrahedralMesh<2,2>* p_fine_mesh = new TetrahedralMesh<2,2>();//electrics ignored in this test p_fine_mesh->ConstructRegularSlabMesh(0.25, 1.0, 1.0); FineCoarseMeshPair<2>* p_pair = new FineCoarseMeshPair<2>(*p_fine_mesh, mesh); p_pair->SetUpBoxesOnFineMesh(); p_pair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(solver.GetQuadratureRule()), false); p_pair->DeleteFineBoxCollection(); solver.SetFineCoarseMeshPair(p_pair); /////////////////////////////////////////////////////////////////////////// solver.Initialise(); // coverage QuadraturePointsGroup<2> quad_points(mesh, *(solver.GetQuadratureRule())); std::vector<double> calcium_conc(solver.GetTotalNumQuadPoints(), 0.0); std::vector<double> voltages(solver.GetTotalNumQuadPoints(), 0.0); solver.SetCalciumAndVoltage(calcium_conc, voltages); // solve UP TO t=0. So Ta(lam_n,t_{n+1})=5*sin(0)=0, ie no deformation solver.Solve(-0.01,0.0,0.01); TS_ASSERT_EQUALS(solver.GetNumNewtonIterations(),0u); solver.Solve(0.24,0.25,0.01); //// These fail due to changes from #2185 but no point fixing as these numbers will change later anyway (#2180) TS_ASSERT_DELTA(solver.rGetDeformedPosition()[4](0), x[i], 1e-3); TS_ASSERT_DELTA(solver.rGetDeformedPosition()[4](1), y[i], 1e-3); //in need of deletion even if all these 3 have no influence at all on this test delete p_fine_mesh; delete p_pair; } }