34 Eigen::SparseMatrix<double> K(F.size(),F.size()),Z(F.size(),3*freeCount);
35 K.setFromTriplets(triplets.begin(),triplets.end());
36 Z.setFromTriplets(
z.begin(),
z.end());
37 const Eigen::SparseMatrix<double> reduced(Z.transpose()*K*Z);
38 const Eigen::VectorXd force(Z.transpose()*F);
39 Eigen::VectorXd diagonal=Eigen::VectorXd::Zero(Z.cols());
40 for(
int k=0;
k<reduced.outerSize();++
k)
42 for(Eigen::SparseMatrix<double>::InnerIterator it(reduced,
k);it;++it)
44 if(it.row()==it.col())
46 diagonal(it.row())+=it.value();
50 diagonal(it.row())+=0.5*it.value();
51 diagonal(it.col())+=0.5*it.value();
55 Eigen::VectorXd velocity=Eigen::VectorXd::Zero(Z.cols());
56 for(Eigen::Index
k=0;
k<diagonal.size();++
k)
58 if(std::abs(diagonal(
k))>FLT_EPSILON)
60 velocity(
k)=force(
k)/diagonal(
k);
64 require(std::abs(force(
k))<=FLT_EPSILON,
"Force on a zero-mass degree of freedom");
74 const auto loopCount=network.
loops().size();
75 std::map<size_t,std::pair<Eigen::Vector3d,Eigen::Vector3d>> positions;
76 for(
const auto& entry : network.
loopNodes())
78 const auto ln=entry.second.lock();
79 positions.emplace(ln->sID,std::make_pair(ln->get_P(),ln->networkNode->get_P()));
81 std::map<std::pair<size_t,size_t>,std::pair<Eigen::Vector3d,std::set<size_t>>> topology;
84 const auto link=entry.second.lock();
85 std::set<size_t> loops;
86 for(
const auto* ll : link->loopLinks())
88 loops.insert(ll->loop->sID);
90 topology.emplace(entry.first,std::make_pair(link->burgers(),loops));
91 link->createQuadraturePoints(
false);
93 const auto capture=[&]()
95 std::vector<Eigen::Matrix<double,18,1>> values;
98 for(
const auto& qp : entry.second.lock()->quadraturePoints())
100 Eigen::Matrix<double,18,1> value;
101 value.head<9>()=Eigen::Map<
const Eigen::Matrix<double,9,1>>(qp.stress.data());
102 value.segment<3>(9)=qp.pkForce;
103 value.segment<3>(12)=qp.lineTensionForce;
104 value.tail<3>()=qp.velocity;
105 values.push_back(value);
110 const auto t0=Clock::now();
112#pragma omp parallel for
118 it->second.lock()->updateQuadraturePoints(
false);
120 const double oldFields=std::chrono::duration<double>(Clock::now()-
t0).count();
121 const auto oldValues=capture();
122 require(!oldValues.empty(),
"Fixture has no quadrature points");
124 Eigen::VectorXd oldF=Eigen::VectorXd::Zero(3*nodeCount);
125 const auto t1=Clock::now();
128 const auto link=entry.second.lock();
129 if(!link->isBoundarySegment())
131 link->addToGlobalAssembly(oldK,oldF);
134 const double oldAssembly=std::chrono::duration<double>(Clock::now()-
t1).count();
140 entry.second.lock()->createQuadraturePoints(
false);
142 const auto t2=Clock::now();
143 std::vector<std::shared_ptr<Network::NetworkLinkType>> links;
147 links.push_back(entry.second.lock());
150#pragma omp parallel for
152 for(
size_t k=0;
k<links.size();++
k)
154 links[
k]->updateQuadraturePoints(
false);
156 const double newFields=std::chrono::duration<double>(Clock::now()-t2).count();
157 const auto newValues=capture();
158 require(newValues.size()==oldValues.size(),
"Snapshot changed the quadrature count");
160 for(
size_t i=0;
i<oldValues.size();++
i)
162 require(newValues[
i].allFinite(),
"Nonfinite quadrature result");
163 fieldError=std::max(fieldError,(newValues[
i]-oldValues[
i]).cwiseAbs().maxCoeff());
165 require(fieldError==0,
"Snapshot changed a quadrature field, force or velocity");
166 const auto t3=Clock::now();
167 std::map<size_t,size_t> indices;
170 indices.emplace(entry.second.lock()->sID,indices.size());
173 Eigen::VectorXd newF=Eigen::VectorXd::Zero(3*nodeCount);
174 for(
const auto& link : links)
176 if(!link->isBoundarySegment())
178 link->addToGlobalAssembly(newK,newF,indices.at(link->source->sID),indices.at(link->sink->sID));
181 const double newAssembly=std::chrono::duration<double>(Clock::now()-t3).count();
182 require(oldK.size()==newK.size(),
"Indexed assembly changed matrix sparsity");
183 for(
size_t k=0;
k<oldK.size();++
k)
185 require(oldK[
k].row()==newK[
k].row() && oldK[
k].col()==newK[
k].col() && oldK[
k].value()==newK[
k].value(),
186 "Indexed assembly changed a matrix entry");
188 require((oldF.array()==newF.array()).all(),
"Indexed assembly changed the force vector");
189 const Eigen::VectorXd velocity=network.
glideSolver->getNodeVelocities();
190 const double velocityError=(velocity-oldVelocity).cwiseAbs().maxCoeff();
191 require(velocityError==0,
"Production solver differs from legacy assembly");
193 network.
networkLinks().size()==topology.size(),
"Field evaluation changed topology");
196 const auto link=entry.second.lock();
197 std::set<size_t> loops;
198 for(
const auto* ll : link->loopLinks())
200 loops.insert(ll->loop->sID);
202 require(loops==topology.at(entry.first).second &&
203 (link->burgers().array()==topology.at(entry.first).first.array()).all(),
"Junction sharing changed");
205 for(
const auto& entry : network.
loopNodes())
207 const auto ln=entry.second.lock();
208 require((ln->get_P().array()==positions.at(ln->sID).first.array()).all() &&
209 (ln->networkNode->get_P().array()==positions.at(ln->sID).second.array()).all(),
210 "Field evaluation changed the periodic loop/network positions");
212 std::cout<<
"\nCACHE BENCHMARK nodes="<<nodeCount<<
" links="<<links.size()<<
" qpoints="<<oldValues.size()
213 <<
" fields="<<oldFields<<
" -> "<<newFields<<
" assembly="<<oldAssembly<<
" -> "<<newAssembly
214 <<
" fieldError="<<fieldError<<
" velocityError="<<velocityError<<
'\n';
222 const std::filesystem::path root(
argv[1]);
223 if(
argc==3 && std::filesystem::is_directory(
argv[2]))
226 omp_set_num_threads(12);
230 saved.
read(base.simulationParameters.runID);
234 require(
bool(network) && !network->computeDDinteractions,
"Benchmark requires DD with interactions disabled");
238 const auto folder = std::filesystem::temp_directory_path() /
239 (
"glide-index-caches-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
240 const auto input = folder /
"inputFiles";
241 std::filesystem::create_directories(input);
242 std::filesystem::create_directories(folder /
"F");
243 std::filesystem::copy_file(root /
"Library/Materials/W.txt", input /
"W.txt");
245 std::filesystem::copy_file(root /
"Library/Meshes/bicrystal_48.msh", input /
"mesh.msh");
246 std::filesystem::copy_file(root /
"Library/DislocationDynamics/DD.txt", input /
"DD.txt");
247 std::filesystem::create_directories(folder /
"config");
248 const double length=3671.9477120460206;
250 std::ofstream
f(input /
"DefectiveCrystal.txt");
251 f <<
"physics=DislocationDynamics;\nuseFEM=0;\nNsteps=0;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=0 0 0;\nEwaldLengthFactor=0;\n";
252 std::ofstream
p(input /
"polycrystal.txt");
253 p << std::setprecision(17) <<
"materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\nC2G1=1 0 0 0 1 0 0 0 1;\n"
254 <<
"C2G2=0.8 0 0.6 0 1 0 -0.6 0 0.8;\nF=" << length <<
" 0 0 0 " << length <<
" 0 0 0 " << length
255 <<
";\nX0=0 0 0;\nperiodicFaceIDs=-1;\n";
261 std::ifstream stream(
argc==3 ? std::filesystem::path(
argv[2]) : root/
"tests/data/bicrystalSharedConstraints1915.txt");
262 require(
bool(stream),
"Missing bicrystal junction regression fixture");
267 omp_set_num_threads(
argc==3 ? 12 : 2);
272 std::shared_ptr<Network::NetworkLinkType> shared;
275 const auto link=entry.second.lock();
276 if(link->loopLinks().size()>1 && link->chordLength()>1 && !link->isBoundarySegment())
282 require(
bool(shared),
"Missing shared junction for topology-change test");
283 const auto memberships=shared->loopLinks().size();
287 require(inserted->loopNodes().size()==memberships,
"Expansion lost junction sharing");
293 std::filesystem::remove_all(folder);
294 std::cout<<
"PASS: exact cached-index equivalence before and after shared junction topology changes\n";
297 catch(
const std::exception& error)
299 std::cerr<<error.what()<<
'\n';