Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testFEMOwnership.cpp
Go to the documentation of this file.
1#include <FiniteElement.h>
3#include <Eigen/SparseCholesky>
4#include <filesystem>
5#include <iostream>
6#include <optional>
7
8using namespace model;
12
13void require(bool ok,const std::string& message)
14{
15 if(!ok)
16 {
17 throw std::runtime_error(message);
18 }
19}
20template<class F>
21void rejects(F&& f,const std::string& message)
22{
23 bool threw=false;
24 try
25 {
26 f();
27 }
28 catch(const std::exception&)
29 {
30 threw=true;
31 }
32 require(threw,message);
33}
34template<class Form>
35Eigen::MatrixXd matrix(const Form& form)
36{
37 Eigen::SparseMatrix<double> result(form->field().gSize(),form->field().gSize());
38 const auto entries=form->globalTriplets();
39 result.setFromTriplets(entries.begin(),entries.end());
40 return result;
41}
42std::shared_ptr<const Mesh> makeMesh(const std::filesystem::path& root,double scale=1.0)
43{
44 return std::make_shared<const Mesh>((root/"Library/Meshes/bicrystal_48.msh").string(),scale*Eigen::Matrix3d::Identity(),Eigen::Vector3d::Zero(),std::set<int>{-1});
45}
46
47void independenceAndAssembly(const std::filesystem::path& root)
48{
49 auto mesh=makeMesh(root);
50 auto fe=std::make_shared<Space>(mesh);
51 auto other=std::make_shared<Space>(makeMesh(root,2.0));
52 auto u=std::make_shared<Field>(*fe);
53 *u=2.0;
54 auto v=std::make_shared<Field>(*fe);
55 auto w=std::make_shared<Field>(*other);
56 *v=7.0;
57 *w=11.0;
58 const auto& ele=fe->elements().begin()->second;
59 const Eigen::Vector4d bary=Eigen::Vector4d::Constant(.25);
60 require(std::abs((*eval(u))(ele,bary)(0)-2.0)<1.e-12,"Second same-type field overwrote first field");
61 require(std::abs((*eval(v))(ele,bary)(0)-7.0)<1.e-12,"Independent field values lost");
62 require(&u->fe()==fe.get() && &w->fe()==other.get(),"FEM pointer shared across instances");
63 u->dirichletConditions().emplace(0,2.0);
64 u->dirichletNodeMap()[0][0]=true;
65 require(v->dirichletConditions().empty() && w->dirichletNodeMap().empty(),"Constraints leaked across fields");
66 auto alias=u;
67 *alias=3.0;
68 require(std::abs((*eval(u))(ele,bary)(0)-3.0)<1.e-12,"Copy handle lost field identity");
69 auto beforeAssignment=eval(u);
70 *u=*v;
71 *v=9.0;
72 require(std::abs((*beforeAssignment)(ele,bary)(0)-7.0)<1.e-12,"Assignment rebound an existing expression");
73 auto savedGradient=[&]()
74 {
75 auto local=2.0*grad(u);
76 return eval(local);
77 }();
78 require((*savedGradient)(ele,bary).norm()<1.e-11,"Saved intermediate expression lost ownership");
79 std::weak_ptr<Field> coefficientField;
80 auto coefficient=[&]()
81 {
82 auto c=std::make_shared<Field>(*fe);
83 *c=4.0;
84 coefficientField=c;
85 return eval(c)*u;
86 }();
87 require(std::abs((*eval(coefficient))(ele,bary)(0)-28.0)<1.e-11,"Evaluated coefficient did not retain its field");
88
89 require(!coefficientField.expired(),"Coefficient field was not retained");
90 auto sum=u+u;
91 static_assert(std::is_same_v<decltype(sum),std::shared_ptr<TrialSum<Field,Field>>>);
92 static_assert(!std::is_copy_constructible_v<Field>);
93 require(sum->op1.get()==u.get() && sum->op2.get()==u.get(),"Sum copied its operands");
94 require(std::abs((*eval(sum))(ele,bary)(0)-14.0)<1.e-12,"Trial sum evaluation changed");
95 rejects([&]() { auto invalid=u+v; },"Sum accepted independent fields");
96 rejects([&]() { auto invalid=grad(std::shared_ptr<Field>{}); },"Null operand accepted");
97 require((*eval(grad(2.0*u)))(ele,bary).norm()<1.e-11,"Gradient of scalar product changed");
98
99 auto dV=fe->domain<EntireDomain,4,GaussLegendre>();
100 auto mass=(test(u),u)*dV;
101 auto stiffness=(test(grad(u)),grad(u))*dV;
102 auto load=(test(u),make_constant(1.0))*dV;
103 auto M=matrix(mass);
104 auto K=matrix(stiffness);
105 Eigen::MatrixXd expectedM=Eigen::MatrixXd::Zero(u->gSize(),u->gSize());
106 Eigen::MatrixXd expectedK=expectedM;
107 Eigen::VectorXd expectedLoad=Eigen::VectorXd::Zero(u->gSize());
108 for(const auto& entry : fe->elements())
109 {
110 const auto& e=entry.second;
111 Eigen::Matrix4d coordinates;
112 for(int i=0;i<4;++i)
113 {
114 coordinates(i,0)=1.0;
115 coordinates.row(i).tail<3>()=e.node(i).P0.transpose();
116 }
117 const Eigen::Matrix<double,3,4> gradients=coordinates.inverse().bottomRows<3>();
118 for(int i=0;i<4;++i)
119 {
120 const size_t I=e.node(i).gID;
121 expectedLoad(I)+=e.simplex.vol0/4.0;
122 for(int j=0;j<4;++j)
123 {
124 const size_t J=e.node(j).gID;
125 expectedM(I,J)+=e.simplex.vol0*(i==j ? 2.0 : 1.0)/20.0;
126 expectedK(I,J)+=e.simplex.vol0*gradients.col(i).dot(gradients.col(j));
127 }
128 }
129 }
130 require((M-expectedM).norm()<1.e-11*expectedM.norm(),"Mass assembly changed");
131 require((K-expectedK).norm()<1.e-11*expectedK.norm(),"Diffusion assembly changed");
132 require((load->globalVector()-expectedLoad).norm()<1.e-11*expectedLoad.norm(),"Load assembly changed");
133 require((matrix(mass+mass)-2*M).norm()<1.e-11*M.norm(),"Bilinear sum changed");
134 require(((load+load-load)->globalVector()-expectedLoad).norm()<1.e-11*expectedLoad.norm(),"Linear sum/difference changed");
135 rejects([&]() { matrix((test(u),v)*dV); },"Mixed field bilinear form accepted");
136 rejects([&]() { matrix(mass+(test(v),v)*dV); },"Mixed field bilinear sum accepted");
137 rejects([&]() { (load+(test(v),make_constant(1.0))*dV)->globalVector(); },"Mixed field load sum accepted");
138
139 rejects([&]() { matrix((test(w),w)*dV); },"Foreign FEM integration domain accepted");
140
141 auto retainedMass=[&]()
142 {
143 auto localDomain=fe->domain<EntireDomain,4,GaussLegendre>();
144 auto localForm=(test(u),u);
145 return localForm*localDomain;
146 }();
147 require((matrix(retainedMass)-M).norm()<1.e-12,"Temporary integration domain was borrowed");
148 auto boundary=[&]()
149 {
150 auto localDomain=fe->boundary<ExternalBoundary,4,GaussLegendre>();
151 return (test(u),make_constant(1.0))*localDomain;
152 }();
153 double area=0.0;
154 for(const auto& e : fe->boundary<ExternalBoundary,4,GaussLegendre>())
155 {
156 area+=e.first->simplex.child(e.second).vol0;
157 }
158 require(std::abs(boundary->globalVector().sum()-area)<1.e-10*area,"Boundary assembly changed");
159
160 FixedDirichletSolver<Eigen::SimplicialLLT<Eigen::SparseMatrix<double>>,Eigen::ConjugateGradient<Eigen::SparseMatrix<double>>> solver(true,1.e-12);
161 u->dirichletConditions()[0]=2.0;
162 solver.compute(mass);
163 Eigen::VectorXd desired=Eigen::VectorXd::Constant(u->gSize(),2.0);
164 require((solver.solve(M*desired)-desired).norm()<1.e-10,"Fixed Dirichlet solve changed");
165 // Constraints are intentionally live after compute; their set stays fixed.
166 u->dirichletConditions()[0]=5.0;
167 desired.setConstant(5.0);
168 require((solver.solve(M*desired)-desired).norm()<1.e-10,"Solver lost instance-owned constraints");
169 require(!fe->periodicNodePairs().empty(),"Periodic node pairing lost");
170}
171
172void lifetime(const std::filesystem::path& root)
173{
174 std::weak_ptr<const Mesh> weakMesh;
175 std::weak_ptr<Space> weakSpace;
176 std::weak_ptr<const void> weakField;
177 {
178 auto expression=[&]()
179 {
180 auto mesh=makeMesh(root);
181 auto fe=std::make_shared<Space>(mesh);
182 auto u=std::make_shared<Field>(*fe);
183 *u=6.0;
184 weakMesh=mesh;
185 weakSpace=fe;
186 weakField=u;
187 auto intermediate=2.0*u;
188 return eval(intermediate);
189 }();
190 require(!weakField.expired() && !weakSpace.expired() && !weakMesh.expired(),"Expression failed to retain field/space/mesh");
191 const auto& e=expression->field().fe().elements().begin()->second;
192 require(std::abs((*expression)(e,Eigen::Vector4d::Constant(.25))(0)-12.0)<1.e-11,"Detached expression evaluates incorrectly");
193 auto copy=expression;
194 require(std::abs((*copy)(e,Eigen::Vector4d::Constant(.25))(0)-12.0)<1.e-11,"Copied evaluator evaluates incorrectly");
195 }
196 require(weakField.expired() && weakSpace.expired() && weakMesh.expired(),"Shared ownership cycle leaked FEM objects");
197 {
198 auto retainedDomain=[&]()
199 {
200 auto fe=std::make_shared<Space>(makeMesh(root));
201 weakSpace=fe;
202 return fe->domain<EntireDomain,4,GaussLegendre>();
203 }();
204 require(!weakSpace.expired(),"Domain did not retain its FEM space");
205 require(retainedDomain.element(0).simplex.vol0>0,"Retained domain is invalid");
206 }
207 require(weakSpace.expired(),"Domain leaked its FEM space");
208 {
209 FixedDirichletSolver<Eigen::SimplicialLLT<Eigen::SparseMatrix<double>>,Eigen::ConjugateGradient<Eigen::SparseMatrix<double>>> solver(true,1.e-12);
210 Eigen::VectorXd rhs;
211 {
212 auto fe=std::make_shared<Space>(makeMesh(root));
213 auto u=std::make_shared<Field>(*fe);
214 *u=3.0;
215 u->dirichletConditions()[0]=3.0;
216 weakField=u->ownership();
217 weakSpace=fe;
218 const auto form=(test(u),u)*fe->domain<EntireDomain,4,GaussLegendre>();
219 rhs=matrix(form)*u->dofVector();
220 solver.compute(form);
221 }
222 require(!weakField.expired() && !weakSpace.expired(),"Solver failed to retain field state");
223 require((solver.solve(rhs).array()-3.0).matrix().norm()<1.e-10,"Detached solver lost constraints or DOFs");
224 }
225 require(weakField.expired() && weakSpace.expired(),"Solver leaked its field state");
226 auto mesh=makeMesh(root);
227 auto expired=[&]()
228 {
229 Space stackSpace(*mesh);
230 auto u=std::make_shared<Field>(stackSpace);
231 return eval(u);
232 }();
233 rejects([&]() { (*expired)(Eigen::Vector3d::Zero()); },"Expired borrowed FEM space was dereferenced");
234 auto borrowedMesh=[&]()
235 {
236 auto temporaryMesh=makeMesh(root);
237 auto fe=std::make_shared<Space>(*temporaryMesh);
238 auto u=std::make_shared<Field>(*fe);
239 return eval(u);
240 }();
241 rejects([&]() { (*borrowedMesh)(Eigen::Vector3d::Zero()); },"Expired borrowed mesh was dereferenced");
242}
243
244template<int order>
245void affine(const std::filesystem::path& root)
246{
248 auto fe=std::make_shared<FE>(makeMesh(root));
249 auto u=fe->template trial<3>();
250 const Eigen::Matrix3d A=(Eigen::Matrix3d()<<1,2,3,4,5,6,7,8,9).finished();
251 for(size_t n=0;n<fe->nodeSize();++n)
252 {
253 u->dofVector().template segment<3>(3*n)=A*fe->node(n).P0+Eigen::Vector3d(2,3,4);
254 }
255 auto gradient=eval(grad(u));
256 auto sumGradient=eval(grad(u+u));
257 auto scaledGradient=eval(grad(2.0*u));
258 auto sumStrain=eval(def(u+u));
259 auto strain=eval(def(u));
260 Eigen::Matrix<double,6,1> expectedStrain;
261 expectedStrain<<1,5,9,6,14,10;
262 for(const auto& entry : fe->elements())
263 {
264 const auto& e=entry.second;
265 const Eigen::Vector4d bary=Eigen::Vector4d::Constant(.25);
266 require(((*eval(u))(e,bary)-A*e.position(bary)-Eigen::Vector3d(2,3,4)).norm()<1.e-10,"Affine interpolation changed");
267 const Eigen::Matrix<double,9,1> expected=A.transpose().reshaped();
268 require(((*gradient)(e,bary)-expected).norm()<1.e-10,"Affine gradient changed");
269 require(((*sumGradient)(e,bary)-2*expected).norm()<1.e-10,"Gradient of trial sum changed");
270 require(((*scaledGradient)(e,bary)-2*expected).norm()<1.e-10,"Gradient of scaled trial changed");
271 require(((*sumStrain)(e,bary)-2*expectedStrain).norm()<1.e-10,"Strain of trial sum changed");
272 require(((*strain)(e,bary)-expectedStrain).norm()<1.e-10,"Engineering strain changed");
273 }
274 auto dV=fe->template domain<EntireDomain,4,GaussLegendre>();
275 const auto energy=matrix((test(def(u)),def(u))*dV);
276 const double integrated=u->dofVector().dot(energy*u->dofVector());
277 const double exact=fe->mesh.volume()*expectedStrain.squaredNorm();
278 require(std::abs(integrated-exact)<1.e-10*exact,"Elastic strain energy changed");
279}
280int main(int argc,char** argv)
281{
282 try
283 {
284 require(argc==2,"Expected repository root");
286 lifetime(argv[1]);
287 affine<1>(argv[1]);
288 affine<2>(argv[1]);
289 affine<3>(argv[1]);
290 std::cout<<"PASS FEM field ownership, lifetimes, mass/diffusion/load/elastic assembly and constraints"<<std::endl;
291 }
292 catch(const std::exception& e)
293 {
294 std::cerr<<e.what()<<std::endl;
295 return 1;
296 }
297}
Eigen::Matrix3d M
void compute(const std::shared_ptr< BilinearWeakFormType > &bWF)
Eigen::VectorXd solve(const Eigen::VectorXd &b) const
The q vector I
function infiniteDisplacement syms Ax Ay Az Bx By Bz qx qy qz u L x y z real A
c
Definition int_R.m:15
function int_R syms a b c u real f
Definition int_R.m:4
auto def(const std::shared_ptr< T > &x)
Definition TrialDef.h:64
auto grad(const std::shared_ptr< T > &x)
Definition TrialGrad.h:66
auto eval(const std::shared_ptr< T > &t)
auto make_constant(const Eigen::Matrix< double, rows, cols > &c)
Definition Constant.h:44
Class template defining the GaussLegendre rules for determination of quadrature abscissas and weights...
std::shared_ptr< const Mesh > makeMesh(const std::filesystem::path &root, double scale=1.0)
void independenceAndAssembly(const std::filesystem::path &root)
void require(bool ok, const std::string &message)
FiniteElement< LagrangeElement< 3, 1 > > Space
Eigen::MatrixXd matrix(const Form &form)
void rejects(F &&f, const std::string &message)
void affine(const std::filesystem::path &root)
TrialFunction< 'c', 1, Space > Field
void lifetime(const std::filesystem::path &root)
SimplicialMesh< 3 > Mesh
int j
REAL w[16]
void argv
REAL u[4]
void b int argc
int i
int e
REAL area
result
REAL v[4]