/* == 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); }
void TestAssembler2d() throw (Exception) { QuadraticMesh<2> mesh; TrianglesMeshReader<2,2> mesh_reader("mesh/test/data/canonical_triangle_quadratic", 2, 2, false); mesh.ConstructFromMeshReader(mesh_reader); ContinuumMechanicsProblemDefinition<2> problem_defn(mesh); double t1 = 2.6854233; double t2 = 3.2574578; // for the boundary element on the y=0 surface, create a traction std::vector<BoundaryElement<1,2>*> boundary_elems; std::vector<c_vector<double,2> > tractions; c_vector<double,2> traction; traction(0) = t1; traction(1) = t2; for (TetrahedralMesh<2,2>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); ++iter) { if (fabs((*iter)->CalculateCentroid()[1])<1e-4) { BoundaryElement<1,2>* p_element = *iter; boundary_elems.push_back(p_element); tractions.push_back(traction); } } assert(boundary_elems.size()==1); problem_defn.SetTractionBoundaryConditions(boundary_elems, tractions); ContinuumMechanicsNeumannBcsAssembler<2> assembler(&mesh, &problem_defn); TS_ASSERT_THROWS_THIS(assembler.AssembleVector(), "Vector to be assembled has not been set"); Vec bad_sized_vec = PetscTools::CreateVec(2); assembler.SetVectorToAssemble(bad_sized_vec, true); TS_ASSERT_THROWS_THIS(assembler.AssembleVector(), "Vector provided to be assembled has size 2, not expected size of 18 ((dim+1)*num_nodes)"); Vec vec = PetscTools::CreateVec(3*mesh.GetNumNodes()); assembler.SetVectorToAssemble(vec, true); assembler.AssembleVector(); ReplicatableVector vec_repl(vec); // Note: on a 1d boundary element, intgl phi_i dx = 1/6 for the bases on the vertices // and intgl phi_i dx = 4/6 for the basis at the interior node. (Here the integrals // are over the canonical 1d element, [0,1], which is also the physical element for this // mesh. // node 0 is on the surface, and is a vertex TS_ASSERT_DELTA(vec_repl[0], t1/6.0, 1e-8); TS_ASSERT_DELTA(vec_repl[1], t2/6.0, 1e-8); // node 1 is on the surface, and is a vertex TS_ASSERT_DELTA(vec_repl[3], t1/6.0, 1e-8); TS_ASSERT_DELTA(vec_repl[4], t2/6.0, 1e-8); // nodes 2, 3, 4 are not on the surface for(unsigned i=2; i<5; i++) { TS_ASSERT_DELTA(vec_repl[3*i], 0.0, 1e-8); TS_ASSERT_DELTA(vec_repl[3*i], 0.0, 1e-8); } // node 5 is on the surface, and is an interior node TS_ASSERT_DELTA(vec_repl[15], 4*t1/6.0, 1e-8); TS_ASSERT_DELTA(vec_repl[16], 4*t2/6.0, 1e-8); // the rest of the vector is the pressure block and should be zero. TS_ASSERT_DELTA(vec_repl[2], 0.0, 1e-8); TS_ASSERT_DELTA(vec_repl[5], 0.0, 1e-8); TS_ASSERT_DELTA(vec_repl[8], 0.0, 1e-8); TS_ASSERT_DELTA(vec_repl[11], 0.0, 1e-8); TS_ASSERT_DELTA(vec_repl[14], 0.0, 1e-8); TS_ASSERT_DELTA(vec_repl[17], 0.0, 1e-8); PetscTools::Destroy(vec); PetscTools::Destroy(bad_sized_vec); }
// Similar to above but uses a compressible material unsigned SolvePressureOnUndersideCompressible(QuadraticMesh<3>& rMesh, std::string outputDirectory, std::vector<double>& rSolution, bool useSolutionAsGuess, double scaleFactor = 1.0) { CompressibleExponentialLaw<3> law; 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(COMPRESSIBLE,&law); problem_defn.SetZeroDisplacementNodes(fixed_nodes); problem_defn.SetApplyNormalPressureOnDeformedSurface(boundary_elems, pressure*scaleFactor); problem_defn.SetVerboseDuringSolve(); CompressibleNonlinearElasticitySolver<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(); }