#include "beamformfinder.hpp" #include "polyscope/curve_network.h" #include "polyscope/histogram.h" #include "polyscope/polyscope.h" #include "simulationhistoryplotter.hpp" #include #include #include #include void FormFinder::runUnitTests() { const std::filesystem::path groundOfTruthFolder{ "/home/iason/Coding/Libraries/MySources/formFinder_unitTestFiles"}; FormFinder formFinder; // First example of the paper VCGEdgeMesh beam; // const size_t spanGridSize = 11; // mesh.createSpanGrid(spanGridSize); beam.loadPly( std::filesystem::path(groundOfTruthFolder).append("simpleBeam.ply").string()); std::unordered_map> fixedVertices; fixedVertices[0] = std::unordered_set{0, 1, 2, 3}; fixedVertices[beam.VN() - 1] = std::unordered_set{1, 2}; std::unordered_map nodalForces{ {beam.VN() / 2, Vector6d({0, 1000, 1000, 0, 0, 0})}}; // Forced displacements std::unordered_map nodalForcedDisplacements; nodalForcedDisplacements.insert({beam.VN() - 1, Eigen::Vector3d(-0.2, 0, 0)}); SimulationJob beamSimulationJob{ std::make_shared(beam), "First paper example", // SimulationJob::constructFixedVerticesSpanGrid(spanGridSize, // mesh.VN()), fixedVertices, nodalForces, nodalForcedDisplacements}; beamSimulationJob.pMesh->setBeamMaterial(0.3, 200 * 1e9); assert(CrossSectionType::name == CylindricalBeamDimensions::name); beamSimulationJob.pMesh->setBeamCrossSection(CrossSectionType{0.03, 0.026}); Settings settings; settings.Dtini = 0.1; settings.xi = 0.9969; settings.maxDRMIterations = 0; settings.totalResidualForcesNormThreshold = 1; // settings.shouldDraw = true; settings.beVerbose = true; SimulationResults simpleBeam_simulationResults = formFinder.executeSimulation( std::make_shared(beamSimulationJob), settings); simpleBeam_simulationResults.save(); const std::string simpleBeamGroundOfTruthBinaryFilename = std::filesystem::path(groundOfTruthFolder) .append("SimpleBeam_displacements.eigenBin").string(); assert(std::filesystem::exists( std::filesystem::path(simpleBeamGroundOfTruthBinaryFilename))); Eigen::MatrixXd simpleBeam_groundOfTruthDisplacements; Eigen::read_binary(simpleBeamGroundOfTruthBinaryFilename, simpleBeam_groundOfTruthDisplacements); if (!simpleBeam_simulationResults.isEqual( simpleBeam_groundOfTruthDisplacements)) { std::cerr << "Error:Beam simulation produces wrong results." << std::endl; // return; } // Second example of the paper VCGEdgeMesh shortSpanGrid; // const size_t spanGridSize = 11; // mesh.createSpanGrid(spanGridSize); shortSpanGrid.loadPly(std::filesystem::path(groundOfTruthFolder) .append("shortSpanGridshell.ply") .string()); fixedVertices.clear(); //// Corner nodes fixedVertices[0] = std::unordered_set{2}; fixedVertices[4] = std::unordered_set{2}; fixedVertices[16] = std::unordered_set{2}; fixedVertices[20] = std::unordered_set{2}; //// Center node fixedVertices[10] = std::unordered_set{0, 1, 3, 4, 5}; nodalForcedDisplacements.clear(); nodalForcedDisplacements.insert({{0, Eigen::Vector3d(0.1, 0.1, 0)}, {4, Eigen::Vector3d(-0.1, 0.1, 0)}, {16, Eigen::Vector3d(0.1, -0.1, 0)}, {20, Eigen::Vector3d(-0.1, -0.1, 0)}}); SimulationJob shortSpanGridshellSimulationJob{ std::make_shared(shortSpanGrid), "Short span gridshell", fixedVertices, {}, nodalForcedDisplacements}; shortSpanGridshellSimulationJob.pMesh->setBeamMaterial(0.3, 200 * 1e9); assert(typeid(CrossSectionType) == typeid(CylindricalBeamDimensions)); shortSpanGridshellSimulationJob.pMesh->setBeamCrossSection( CrossSectionType{0.03, 0.026}); SimulationResults shortSpanGridshellSimulationResults = formFinder.executeSimulation( std::make_shared(shortSpanGridshellSimulationJob), settings); shortSpanGridshellSimulationResults.save(); const std::string groundOfTruthBinaryFilename = std::filesystem::path(groundOfTruthFolder) .append("ShortSpanGridshell_displacements.eigenBin") .string(); assert(std::filesystem::exists( std::filesystem::path(groundOfTruthBinaryFilename))); Eigen::MatrixXd shortSpanGridshell_groundOfTruthDisplacements; Eigen::read_binary(groundOfTruthBinaryFilename, shortSpanGridshell_groundOfTruthDisplacements); // shortSpanGridshellSimulationResults.registerForDrawing( // shortSpanGridshellSimulationJob); // polyscope::show(); assert(shortSpanGridshellSimulationResults.isEqual( shortSpanGridshell_groundOfTruthDisplacements)); if (!shortSpanGridshellSimulationResults.isEqual( shortSpanGridshell_groundOfTruthDisplacements)) { std::cerr << "Error:Short span simulation produces wrong results." << std::endl; return; } // Third example of the paper VCGEdgeMesh longSpanGrid; longSpanGrid.loadPly(std::filesystem::path(groundOfTruthFolder) .append("longSpanGridshell.ply") .string()); const size_t spanGridSize = 11; fixedVertices.clear(); for (size_t vi = 0; vi < spanGridSize - 1; vi++) { fixedVertices[vi] = {0, 2}; } for (size_t vi = longSpanGrid.VN() - 1 - (spanGridSize - 2); vi < longSpanGrid.VN(); vi++) { fixedVertices[vi] = {0, 2}; } for (size_t vi = spanGridSize - 1; vi < longSpanGrid.VN() - 1 - (spanGridSize - 2) - spanGridSize + 1; vi += spanGridSize + 1) { fixedVertices[vi] = {1, 2}; fixedVertices[vi + spanGridSize] = {1, 2}; } nodalForcedDisplacements.clear(); const size_t horizontalOffset = std::floor((spanGridSize - 2) / 2); nodalForcedDisplacements.insert( {{horizontalOffset, Eigen::Vector3d(0, 0.3, 0)}, {horizontalOffset + 1, Eigen::Vector3d(0, 0.3, 0)}, {spanGridSize * (spanGridSize + 1) - 2 + horizontalOffset, Eigen::Vector3d(0, -0.3, 0)}, {spanGridSize * (spanGridSize + 1) - 2 + horizontalOffset + 1, Eigen::Vector3d(0, -0.3, 0)}, {std::floor(spanGridSize / 2) * (spanGridSize + 1) - 2, Eigen::Vector3d(0.3, 0, 0)}, {(std::floor(spanGridSize / 2) + 1) * (spanGridSize + 1) - 2, Eigen::Vector3d(0.3, 0, 0)}, {std::floor(spanGridSize / 2) * (spanGridSize + 1) - 2 + spanGridSize, Eigen::Vector3d(-0.3, 0, 0)}, {(std::floor(spanGridSize / 2) + 1) * (spanGridSize + 1) - 2 + spanGridSize, Eigen::Vector3d(-0.3, 0, 0)}}); SimulationJob longSpanGridshell_simulationJob{ std::make_shared(longSpanGrid), "long span gridshell", fixedVertices, {}, nodalForcedDisplacements}; longSpanGridshell_simulationJob.pMesh->setBeamMaterial(0.3, 200 * 1e9); if (typeid(CrossSectionType) != typeid(CylindricalBeamDimensions)) { std::cerr << "A cylindrical cross section is expected for running the " "paper examples." << std::endl; } longSpanGridshell_simulationJob.pMesh->setBeamCrossSection( CrossSectionType{0.03, 0.026}); SimulationResults longSpanGridshell_simulationResults = formFinder.executeSimulation( std::make_shared(longSpanGridshell_simulationJob), settings); longSpanGridshell_simulationResults.save(); const std::string longSpan_groundOfTruthBinaryFilename = std::filesystem::path(groundOfTruthFolder) .append("LongSpanGridshell_displacements.eigenBin") .string(); assert(std::filesystem::exists( std::filesystem::path(longSpan_groundOfTruthBinaryFilename))); Eigen::MatrixXd longSpanGridshell_groundOfTruthDisplacements; Eigen::read_binary(longSpan_groundOfTruthBinaryFilename, longSpanGridshell_groundOfTruthDisplacements); assert(longSpanGridshell_simulationResults.isEqual( longSpanGridshell_groundOfTruthDisplacements)); if (!longSpanGridshell_simulationResults.isEqual( longSpanGridshell_groundOfTruthDisplacements)) { std::cerr << "Error:Long span simulation produces wrong results." << std::endl; return; } std::cout << "Form finder unit tests succesufully passed." << std::endl; // polyscope::show(); } void FormFinder::reset() { mCurrentSimulationStep = 0; history.clear(); constrainedVertices.clear(); rigidSupports.clear(); pMesh.reset(); plotYValues.clear(); plotHandle.reset(); checkedForMaximumMoment = false; mSettings.shouldUseTranslationalKineticEnergyThreshold = false; externalMomentsNorm = 0; mSettings.drawingStep = 1; Dt = mSettings.Dtini; numOfDampings=0; } VectorType FormFinder::computeDisplacementDifferenceDerivative( const EdgeType &e, const DifferentiateWithRespectTo &dui) const { VectorType displacementDiffDeriv(0, 0, 0); const DoFType &dofi = dui.dofi; const bool differentiateWithRespectToANonEdgeNode = e.cV(0) != &dui.v && e.cV(1) != &dui.v; if (differentiateWithRespectToANonEdgeNode || dofi > 2) { return displacementDiffDeriv; } if (e.cV(0) == &dui.v) { displacementDiffDeriv[dofi] = -1; } else if (e.cV(1) == &dui.v) { displacementDiffDeriv[dofi] = 1; } return displacementDiffDeriv; } VectorType FormFinder::computeDerivativeOfNormal( const VertexType &v, const DifferentiateWithRespectTo &dui) const { const size_t vi = pMesh->getIndex(v); VectorType normalDerivative(0, 0, 0); if (&dui.v != &v || (dui.dofi == 0 || dui.dofi == 1 || dui.dofi == 2 || dui.dofi == 5)) { return normalDerivative; } const VectorType &n = v.cN(); const double &nx = n[0], ny = n[1]; const double nxnyMagnitude = std::pow(nx, 2) + std::pow(ny, 2); if (dui.dofi == 3) { if (nxnyMagnitude + 1e-5 >= 1) { const double normalDerivativeX = 1 / sqrt(nxnyMagnitude) - std::pow(nx, 2) / std::pow(nxnyMagnitude, 1.5); const double normalDerivativeY = -nx * ny / std::pow(nxnyMagnitude, 1.5); const double normalDerivativeZ = 0; normalDerivative = VectorType(normalDerivativeX, normalDerivativeY, normalDerivativeZ); } else { const double normalDerivativeX = 1; const double normalDerivativeY = 0; const double normalDerivativeZ = -nx / std::sqrt(1 - nxnyMagnitude); normalDerivative = VectorType(normalDerivativeX, normalDerivativeY, normalDerivativeZ); } } else if (dui.dofi == 4) { if (nxnyMagnitude + 1e-5 >= 1) { const double normalDerivativeX = -nx * ny / std::pow(nxnyMagnitude, 1.5); const double normalDerivativeY = 1 / sqrt(nxnyMagnitude) - std::pow(ny, 2) / std::pow(nxnyMagnitude, 1.5); const double normalDerivativeZ = 0; normalDerivative = VectorType(normalDerivativeX, normalDerivativeY, normalDerivativeZ); } else { const double normalDerivativeX = 0; const double normalDerivativeY = 1; const double normalDerivativeZ = -ny / std::sqrt(1 - nxnyMagnitude); normalDerivative = VectorType(normalDerivativeX, normalDerivativeY, normalDerivativeZ); } } const bool normalDerivativeIsFinite = std::isfinite(normalDerivative[0]) && std::isfinite(normalDerivative[1]) && std::isfinite(normalDerivative[2]); assert(normalDerivativeIsFinite); const bool shouldBreak = mCurrentSimulationStep == 118 && vi == 1 && dui.dofi == 3; return normalDerivative; } double FormFinder::computeDerivativeElementLength( const EdgeType &e, const DifferentiateWithRespectTo &dui) const { if (e.cV(0) != &dui.v && e.cV(1) != &dui.v) { return 0; } const VectorType &X_j = e.cP(0); const VectorType &X_jplus1 = e.cP(1); const VectorType positionVectorDiff = X_jplus1 - X_j; const VectorType displacementDiffDeriv = computeDisplacementDifferenceDerivative(e, dui); const double edgeLength = pMesh->elements[e].length; const double L_kDeriv = positionVectorDiff * displacementDiffDeriv / edgeLength; return L_kDeriv; } double FormFinder::computeDerivativeOfNorm(const VectorType &x, const VectorType &derivativeOfX) { return x.dot(derivativeOfX) / x.Norm(); } VectorType FormFinder::computeDerivativeOfCrossProduct( const VectorType &a, const VectorType &derivativeOfA, const VectorType &b, const VectorType &derivativeOfB) { const auto firstTerm = Cross(derivativeOfA, b); const auto secondTerm = Cross(a, derivativeOfB); return firstTerm + secondTerm; } VectorType FormFinder::computeDerivativeOfR(const EdgeType &e, const DifferentiateWithRespectTo &dui) const { const VertexType &v_j = *e.cV(0); const VertexType &v_jplus1 = *e.cV(1); const VectorType normal_j = v_j.cN(); const VectorType normal_jplus1 = v_jplus1.cN(); const VectorType derivativeOfNormal_j = &v_j == &dui.v && dui.dofi > 2 ? pMesh->nodes[v_j].derivativeOfNormal[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeOfNormal_jplus1 = &v_jplus1 == &dui.v && dui.dofi > 2 ? pMesh->nodes[v_jplus1].derivativeOfNormal[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeOfSumOfNormals = derivativeOfNormal_j + derivativeOfNormal_jplus1; const VectorType sumOfNormals = normal_j + normal_jplus1; const double normOfSumOfNormals = sumOfNormals.Norm(); const double derivativeOfNormOfSumOfNormals = computeDerivativeOfNorm(sumOfNormals, derivativeOfSumOfNormals); const VectorType derivativeOfR_firstTerm = -sumOfNormals * derivativeOfNormOfSumOfNormals / std::pow(normOfSumOfNormals, 2); const VectorType derivativeOfR_secondTerm = derivativeOfSumOfNormals / normOfSumOfNormals; const VectorType derivativeOfR = derivativeOfR_firstTerm + derivativeOfR_secondTerm; assert(std::isfinite(derivativeOfR[0]) && std::isfinite(derivativeOfR[1]) && std::isfinite(derivativeOfR[2])); return derivativeOfR; } VectorType FormFinder::computeDerivativeT1(const EdgeType &e, const DifferentiateWithRespectTo &dui) const { const VectorType &X_j = e.cP(0); const VectorType &X_jplus1 = e.cP(1); const VectorType edgeVector = X_jplus1 - X_j; const double L_kDerivative = computeDerivativeElementLength(e, dui); const double edgeLength = pMesh->elements[e].length; const VectorType firstTerm = -edgeVector * L_kDerivative / std::pow(edgeLength, 2); const VectorType secondTerm = computeDisplacementDifferenceDerivative(e, dui) / edgeLength; const VectorType t1Derivative = firstTerm + secondTerm; return t1Derivative; } VectorType FormFinder::computeDerivativeT2(const EdgeType &e, const DifferentiateWithRespectTo &dui) const { const DoFType dofi = dui.dofi; const VertexType &v_j = *e.cV(0); const size_t vi_j = pMesh->getIndex(v_j); const VertexType &v_jplus1 = *e.cV(1); const size_t vi_jplus1 = pMesh->getIndex(v_jplus1); const VectorType r = (v_j.cN() + v_jplus1.cN()).Normalize(); const VectorType derivativeR_j = dofi > 2 && &v_j == &dui.v ? pMesh->elements[e].derivativeR_j[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeR_jplus1 = dofi > 2 && &v_jplus1 == &dui.v ? pMesh->elements[e].derivativeR_jplus1[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeR = derivativeR_j + derivativeR_jplus1; const VectorType &t1 = pMesh->elements[e].frame.t1; const VectorType derivativeT1_j = dofi < 3 && &v_j == &dui.v ? pMesh->elements[e].derivativeT1_j[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_jplus1 = dofi < 3 && &v_jplus1 == &dui.v ? pMesh->elements[e].derivativeT1_jplus1[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1 = derivativeT1_j + derivativeT1_jplus1; const VectorType derivativeOfRCrossT1 = computeDerivativeOfCrossProduct(r, derivativeR, t1, derivativeT1); const VectorType rCrossT1 = Cross(r, t1); const double normOfRCrossT1 = rCrossT1.Norm(); const double derivativeNormRCrossT1 = computeDerivativeOfNorm(rCrossT1, derivativeOfRCrossT1); const VectorType t2Deriv_firstTerm = -(rCrossT1 * derivativeNormRCrossT1) / std::pow(normOfRCrossT1, 2); const VectorType t2Deriv_secondTerm = derivativeOfRCrossT1 / normOfRCrossT1; const VectorType t2Deriv = t2Deriv_firstTerm + t2Deriv_secondTerm; const double t2DerivNorm = t2Deriv.Norm(); assert(std::isfinite(t2DerivNorm)); const bool shouldBreak = mCurrentSimulationStep == 118 && (vi_jplus1 == 1 || vi_j == 1) && dofi == 3; return t2Deriv; } VectorType FormFinder::computeDerivativeT3(const EdgeType &e, const DifferentiateWithRespectTo &dui) const { const Element &element = pMesh->elements[e]; const VectorType &t1 = element.frame.t1; const VectorType &t2 = element.frame.t2; const VectorType t1CrossT2 = Cross(t1, t2); const VertexType &v_j = *e.cV(0); const size_t vi_j = pMesh->getIndex(v_j); const VertexType &v_jplus1 = *e.cV(1); const size_t vi_jplus1 = pMesh->getIndex(v_jplus1); const VectorType derivativeT1_j = dui.dofi < 3 && &v_j == &dui.v ? pMesh->elements[e].derivativeT1_j[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_jplus1 = dui.dofi < 3 && &v_jplus1 == &dui.v ? pMesh->elements[e].derivativeT1_jplus1[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1 = derivativeT1_j + derivativeT1_jplus1; // const VectorType derivativeOfT2 = computeDerivativeT2(e, dui); // const VectorType derivativeT2_j = // &v_j == &dui.v // ? mesh->elements[e].derivativeT2_j[dui.dofi] // : VectorType(0, 0, 0); // const VectorType derivativeT2_jplus1 = // &v_jplus1 == &dui.v // ? mesh->elements[e].derivativeT2_jplus1[dui.dofi] // : VectorType(0, 0, 0); VectorType derivativeT2(0, 0, 0); if (&v_j == &dui.v) { derivativeT2 = pMesh->elements[e].derivativeT2_j[dui.dofi]; } else if (&v_jplus1 == &dui.v) { derivativeT2 = pMesh->elements[e].derivativeT2_jplus1[dui.dofi]; } const VectorType derivativeT1CrossT2 = computeDerivativeOfCrossProduct(t1, derivativeT1, t2, derivativeT2); const double derivativeOfNormT1CrossT2 = computeDerivativeOfNorm(t1CrossT2, derivativeT1CrossT2); const double normT1CrossT2 = t1CrossT2.Norm(); const VectorType t3Deriv_firstTerm = -(t1CrossT2 * derivativeOfNormT1CrossT2) / std::pow(normT1CrossT2, 2); const VectorType t3Deriv_secondTerm = derivativeT1CrossT2 / normT1CrossT2; const VectorType t3Deriv = t3Deriv_firstTerm + t3Deriv_secondTerm; assert(std::isfinite(t3Deriv[0]) && std::isfinite(t3Deriv[1]) && std::isfinite(t3Deriv[2])); return t3Deriv; } double FormFinder::computeDerivativeTheta1(const EdgeType &e, const VertexIndex &evi, const VertexIndex &dwrt_evi, const DoFType &dwrt_dofi) const { const VertexType &v = *e.cV(evi); const size_t vi = pMesh->getIndex(v); const Element &element = pMesh->elements[e]; const VectorType derivativeT1 = element.derivativeT1[dwrt_evi][dwrt_dofi]; const VectorType derivativeT3 = element.derivativeT3[dwrt_evi][dwrt_dofi]; const VectorType nDerivative = evi != dwrt_evi ? VectorType(0, 0, 0) : pMesh->nodes[v].derivativeOfNormal[dwrt_dofi]; const VectorType n = v.cN(); const VectorType &t1 = element.frame.t1; const VectorType &t3 = element.frame.t3; const double theta1Derivative = derivativeT1 * Cross(t3, n) + t1 * (Cross(derivativeT3, n) + Cross(t3, nDerivative)); const bool shouldBreak = mCurrentSimulationStep == 118 && vi == 1 && dwrt_dofi == 3; return theta1Derivative; } double FormFinder::computeDerivativeTheta2(const EdgeType &e, const VertexIndex &evi, const VertexIndex &dwrt_evi, const DoFType &dwrt_dofi) const { const VertexType &v = *e.cV(evi); const Element &element = pMesh->elements[e]; const VectorType derivativeT2 = element.derivativeT2[dwrt_evi][dwrt_dofi]; const VectorType derivativeT3 = element.derivativeT3[dwrt_evi][dwrt_dofi]; const VectorType n = v.cN(); const VectorType &t2 = element.frame.t2; const VectorType &t3 = element.frame.t3; const VectorType nDerivative = dwrt_evi == evi ? pMesh->nodes[v].derivativeOfNormal[dwrt_dofi] : VectorType(0, 0, 0); const double theta2Derivative = derivativeT2 * Cross(t3, n) + t2 * (Cross(derivativeT3, n) + Cross(t3, nDerivative)); return theta2Derivative; } double FormFinder::computeTheta3(const EdgeType &e, const VertexType &v) { const VertexIndex &vi = pMesh->nodes[v].vi; // assert(e.cV(0) == &v || e.cV(1) == &v); const Element &elem = pMesh->elements[e]; const EdgeIndex &ei = elem.ei; const Element::LocalFrame &ef = elem.frame; const VectorType &t1 = ef.t1; const VectorType &n = v.cN(); const Node &node = pMesh->nodes[v]; const double &nR = node.nR; // TODO: This makes the function non-const. // Should be refactored in the future double theta3; if (&e == node.referenceElement) { // Use nR as theta3 only for the first star edge return nR; } // assert(node.alphaAngles.contains(mesh->getIndex(e))); double alphaAngle = node.alphaAngles.find(elem.ei)->second; const EdgeType &refElem = *node.referenceElement; const double rotationAngle = nR + alphaAngle; // const VectorType &t1_k = computeT1Vector(refElem); const VectorType &t1_k = pMesh->elements[refElem].frame.t1; const VectorType f1 = (t1_k - (n * (t1_k * n))).Normalize(); vcg::Matrix44 rotationMatrix; rotationMatrix.SetRotateRad(rotationAngle, n); const double cosRotationAngle = cos(rotationAngle); const double sinRotationAngle = sin(rotationAngle); const VectorType f2 = (f1 * cosRotationAngle + Cross(n, f1) * sinRotationAngle).Normalize(); const VectorType &t1Current = t1; const VectorType f3 = Cross(t1Current, f2); Element &element = pMesh->elements[e]; // Save for computing theta3Derivative if (&v == e.cV(0)) { element.f1_j = f1; element.f2_j = f2; element.f3_j = f3; element.cosRotationAngle_j = cosRotationAngle; element.sinRotationAngle_j = sinRotationAngle; } else { element.f1_jplus1 = f1; element.f2_jplus1 = f2; element.f3_jplus1 = f3; element.cosRotationAngle_jplus1 = cosRotationAngle; element.sinRotationAngle_jplus1 = sinRotationAngle; } theta3 = f3.dot(n); return theta3; } double FormFinder::computeDerivativeTheta3( const EdgeType &e, const VertexType &v, const DifferentiateWithRespectTo &dui) const { const Node &node = pMesh->nodes[v]; const VertexIndex &vi = pMesh->nodes[v].vi; const bool isRigidSupport = rigidSupports.contains(vi); if (&e == node.referenceElement && !isRigidSupport) { if (dui.dofi == DoF::Nr && &dui.v == &v) { return 1; } else { return 0; } } // assert(e.cV(0) == &v || e.cV(1) == &v); const Element &element = pMesh->elements[e]; const Element::LocalFrame &ef = element.frame; const VectorType &t1 = ef.t1; const VectorType &n = v.cN(); const DoFType &dofi = dui.dofi; const VertexPointer &vp_j = e.cV(0); const VertexPointer &vp_jplus1 = e.cV(1); double derivativeTheta3_dofi = 0; if (isRigidSupport) { const VectorType &t1Initial = computeT1Vector(pMesh->nodes[vp_j].initialLocation, pMesh->nodes[vp_jplus1].initialLocation); VectorType g1 = Cross(t1, t1Initial); const VectorType derivativeInitialT1_dofi(0, 0, 0); const VectorType derivativeT1_j = dui.dofi < 3 && vp_j == &dui.v ? element.derivativeT1_j[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_jplus1 = dui.dofi < 3 && vp_jplus1 == &dui.v ? element.derivativeT1_jplus1[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_dofi = derivativeT1_j + derivativeT1_jplus1; // VectorType derivativeT1_dofi(0, 0, 0); // if (dui.dofi < 3) { // if (&v_j == &dui.v) { // derivativeT1_dofi = mesh->elements[e].derivativeT1_j[dui.dofi]; // } else if (&v_jplus1 == &dui.v) { // derivativeT1_dofi = // mesh->elements[e].derivativeT1_jplus1[dui.dofi]; // } // } const VectorType derivativeG1_firstTerm = Cross(derivativeT1_dofi, t1Initial); const VectorType derivativeG1_secondTerm = Cross(t1, derivativeInitialT1_dofi); const VectorType derivativeG1 = derivativeG1_firstTerm + derivativeG1_secondTerm; const VectorType derivativeNormal = &v == &dui.v && dui.dofi > 2 ? node.derivativeOfNormal[dui.dofi] : VectorType(0, 0, 0); derivativeTheta3_dofi = derivativeG1 * n + g1 * derivativeNormal; return derivativeTheta3_dofi; } EdgeType &refElem = *node.referenceElement; const VectorType &t1_k = pMesh->elements[refElem].frame.t1; VectorType f1, f2, f3; double cosRotationAngle, sinRotationAngle; if (e.cV(0) == &v) { f1 = element.f1_j; cosRotationAngle = element.cosRotationAngle_j; sinRotationAngle = element.sinRotationAngle_j; f2 = element.f2_j; f3 = element.f3_j; } else { f1 = element.f1_jplus1; cosRotationAngle = element.cosRotationAngle_jplus1; sinRotationAngle = element.sinRotationAngle_jplus1; f2 = element.f2_jplus1; f3 = element.f3_jplus1; } const VectorType &t1_kplus1 = t1; const VectorType f1Normalized = f1 / f1.Norm(); VectorType derivativeF1(0, 0, 0); VectorType derivativeF2(0, 0, 0); VectorType derivativeF3(0, 0, 0); if (dui.dofi < 3) { const VectorType derivativeT1_kplus1_j = vp_j == &dui.v ? element.derivativeT1_j[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_kplus1_jplus1 = vp_jplus1 == &dui.v ? element.derivativeT1_jplus1[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_kplus1 = derivativeT1_kplus1_j + derivativeT1_kplus1_jplus1; const VectorType derivativeT1_k_j = refElem.cV(0) == &dui.v ? pMesh->elements[refElem].derivativeT1_j[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_k_jplus1 = refElem.cV(1) == &dui.v ? pMesh->elements[refElem].derivativeT1_jplus1[dui.dofi] : VectorType(0, 0, 0); const VectorType derivativeT1_k = derivativeT1_k_j + derivativeT1_k_jplus1; derivativeF1 = derivativeT1_k - (n * (derivativeT1_k * n)); const double f1Norm = f1.Norm(); const VectorType derivativeF1Normalized = -f1 * (f1 * derivativeF1) / (f1Norm * f1Norm * f1Norm) + derivativeF1 / f1Norm; derivativeF2 = derivativeF1Normalized * cosRotationAngle + Cross(n, derivativeF1Normalized) * sinRotationAngle; const VectorType derivativeF3_firstTerm = Cross(derivativeT1_kplus1, f2); const VectorType derivativeF3_secondTerm = Cross(t1_kplus1, derivativeF2); derivativeF3 = derivativeF3_firstTerm + derivativeF3_secondTerm; derivativeTheta3_dofi = derivativeF3 * n; } else if (dui.dofi == DoF::Nr && &dui.v == &v) { derivativeF2 = f1Normalized * (-sinRotationAngle) + Cross(n, f1Normalized) * cosRotationAngle; derivativeF3 = Cross(t1_kplus1, derivativeF2); derivativeTheta3_dofi = derivativeF3 * n; } else { // 2vert) { const Node &node = pMesh->nodes[v]; if (!node.force.hasExternalForce()) { continue; } const auto nodeDisplacement = v.cP() - node.initialLocation; const SimulationMesh::CoordType externalForce( node.force.external[0], node.force.external[1], node.force.external[2]); totalExternalPotentialEnergy += externalForce.dot(nodeDisplacement); } double totalInternalPotentialEnergy = 0; for (const SimulationMesh::EdgeType &e : pMesh->edge) { const Element &element = pMesh->elements[e]; const EdgeIndex ei = pMesh->getIndex(e); const double e_k = element.length - element.initialLength; const double axialPE = pow(e_k, 2) * element.material.youngsModulus * element.A / (2 * element.initialLength); const double theta1Diff = element.rotationalDisplacements_jplus1.theta1 - element.rotationalDisplacements_j.theta1; const double torsionalPE = element.G * element.J * pow(theta1Diff, 2) / (2 * element.initialLength); const double &theta2_j = element.rotationalDisplacements_j.theta2; const double &theta2_jplus1 = element.rotationalDisplacements_jplus1.theta2; const double firstBendingPE_inBracketsTerm = 4 * pow(theta2_j, 2) + 4 * theta2_j * theta2_jplus1 + 4 * pow(theta2_jplus1, 2); const double firstBendingPE = firstBendingPE_inBracketsTerm * element.material.youngsModulus * element.I2 / (2 * element.initialLength); const double &theta3_j = element.rotationalDisplacements_j.theta3; const double &theta3_jplus1 = element.rotationalDisplacements_jplus1.theta3; const double secondBendingPE_inBracketsTerm = 4 * pow(theta3_j, 2) + 4 * theta3_j * theta3_jplus1 + 4 * pow(theta3_jplus1, 2); const double secondBendingPE = secondBendingPE_inBracketsTerm * 2 * element.material.youngsModulus * element.I3 / element.initialLength; totalInternalPotentialEnergy += axialPE + torsionalPE + firstBendingPE + secondBendingPE; } return totalInternalPotentialEnergy - totalExternalPotentialEnergy; } void FormFinder::updateResidualForcesOnTheFly( const std::unordered_map> &fixedVertices) { // std::vector internalForcesParallel(mesh->vert.size()); std::vector>> internalForcesContributionsFromEachEdge( pMesh->EN(), std::vector>(4, {-1, Vector6d()})); // omp_lock_t writelock; // omp_init_lock(&writelock); //#pragma omp parallel for //schedule(static) num_threads(8) for (int ei = 0; ei < pMesh->EN(); ei++) { const EdgeType &e = pMesh->edge[ei]; const SimulationMesh::VertexType &ev_j = *e.cV(0); const SimulationMesh::VertexType &ev_jplus1 = *e.cV(1); const Element &element = pMesh->elements[e]; const Element::LocalFrame &ef = element.frame; const VectorType t3CrossN_j = Cross(ef.t3, ev_j.cN()); const VectorType t3CrossN_jplus1 = Cross(ef.t3, ev_jplus1.cN()); const double theta1_j = ef.t1.dot(t3CrossN_j); const double theta1_jplus1 = ef.t1.dot(t3CrossN_jplus1); const double theta2_j = ef.t2.dot(t3CrossN_j); const double theta2_jplus1 = ef.t2.dot(t3CrossN_jplus1); const double theta3_j = computeTheta3(e, ev_j); const double theta3_jplus1 = computeTheta3(e, ev_jplus1); // element.rotationalDisplacements_j.theta1 = theta1_j; // element.rotationalDisplacements_jplus1.theta1 = theta1_jplus1; // element.rotationalDisplacements_j.theta2 = theta2_j; // element.rotationalDisplacements_jplus1.theta2 = theta2_jplus1; // element.rotationalDisplacements_j.theta3 = theta3_j; // element.rotationalDisplacements_jplus1.theta3 = theta3_jplus1; std::vector> internalForcesContributionFromThisEdge(4, {-1, Vector6d()}); for (VertexIndex evi = 0; evi < 2; evi++) { const SimulationMesh::VertexType &ev = *e.cV(evi); const Node &edgeNode = pMesh->nodes[ev]; internalForcesContributionFromThisEdge[evi].first = edgeNode.vi; const VertexPointer &rev_j = edgeNode.referenceElement->cV(0); const VertexPointer &rev_jplus1 = edgeNode.referenceElement->cV(1); // refElemOtherVertex can be precomputed const VertexPointer &refElemOtherVertex = rev_j == &ev ? rev_jplus1 : rev_j; const Node &refElemOtherVertexNode = pMesh->nodes[refElemOtherVertex]; if (edgeNode.referenceElement != &e) { internalForcesContributionFromThisEdge[evi + 2].first = refElemOtherVertexNode.vi; } const size_t vi = edgeNode.vi; // #pragma omp parallel for schedule(static) num_threads(6) for (DoFType dofi = DoF::Ux; dofi < DoF::NumDoF; dofi++) { const bool isDofConstrainedFor_ev = fixedVertices.contains(edgeNode.vi) && fixedVertices.at(edgeNode.vi).contains(dofi); if (!isDofConstrainedFor_ev) { DifferentiateWithRespectTo dui{ev, dofi}; // Axial force computation const double e_k = element.length - element.initialLength; const double e_kDeriv = computeDerivativeElementLength(e, dui); const double axialForce_dofi = e_kDeriv * e_k * element.rigidity.axial; // Torsional force computation const double theta1_j_deriv = computeDerivativeTheta1(e, 0, evi, dofi); const double theta1_jplus1_deriv = computeDerivativeTheta1(e, 1, evi, dofi); const double theta1Diff = theta1_jplus1 - theta1_j; const double theta1DiffDerivative = theta1_jplus1_deriv - theta1_j_deriv; const double torsionalForce_dofi = theta1DiffDerivative * theta1Diff * element.rigidity.torsional; // First bending force computation ////theta2_j derivative const double theta2_j_deriv = computeDerivativeTheta2(e, 0, evi, dofi); ////theta2_jplus1 derivative const double theta2_jplus1_deriv = computeDerivativeTheta2(e, 1, evi, dofi); ////1st in bracket term const double firstBendingForce_inBracketsTerm_0 = theta2_j_deriv * 2 * theta2_j; ////2nd in bracket term const double firstBendingForce_inBracketsTerm_1 = theta2_jplus1_deriv * theta2_j; ////3rd in bracket term const double firstBendingForce_inBracketsTerm_2 = theta2_j_deriv * theta2_jplus1; ////4th in bracket term const double firstBendingForce_inBracketsTerm_3 = 2 * theta2_jplus1_deriv * theta2_jplus1; // 3rd term computation const double firstBendingForce_inBracketsTerm = firstBendingForce_inBracketsTerm_0 + firstBendingForce_inBracketsTerm_1 + firstBendingForce_inBracketsTerm_2 + firstBendingForce_inBracketsTerm_3; const double firstBendingForce_dofi = firstBendingForce_inBracketsTerm * element.rigidity.firstBending; // Second bending force computation ////theta2_j derivative const double theta3_j_deriv = computeDerivativeTheta3(e, ev_j, dui); ////theta2_jplus1 derivative const double theta3_jplus1_deriv = computeDerivativeTheta3(e, ev_jplus1, dui); ////1st in bracket term const double secondBendingForce_inBracketsTerm_0 = theta3_j_deriv * 2 * theta3_j; ////2nd in bracket term const double secondBendingForce_inBracketsTerm_1 = theta3_jplus1_deriv * theta3_j; ////3rd in bracket term const double secondBendingForce_inBracketsTerm_2 = theta3_j_deriv * theta3_jplus1; ////4th in bracket term const double secondBendingForce_inBracketsTerm_3 = 2 * theta3_jplus1_deriv * theta3_jplus1; // 3rd term computation const double secondBendingForce_inBracketsTerm = secondBendingForce_inBracketsTerm_0 + secondBendingForce_inBracketsTerm_1 + secondBendingForce_inBracketsTerm_2 + secondBendingForce_inBracketsTerm_3; double secondBendingForce_dofi = secondBendingForce_inBracketsTerm * element.rigidity.secondBending; const bool shouldBreak = mCurrentSimulationStep == 118 && edgeNode.vi == 1 && dofi == 3; internalForcesContributionFromThisEdge[evi].second[dofi] = axialForce_dofi + firstBendingForce_dofi + secondBendingForce_dofi + torsionalForce_dofi; } if (edgeNode.referenceElement != &e) { const bool isDofConstrainedFor_refElemOtherVertex = fixedVertices.contains(refElemOtherVertexNode.vi) && fixedVertices.at(refElemOtherVertexNode.vi).contains(dofi); if (!isDofConstrainedFor_refElemOtherVertex) { DifferentiateWithRespectTo dui{*refElemOtherVertex, dofi}; ////theta3_j derivative const double theta3_j_deriv = computeDerivativeTheta3(e, ev_j, dui); ////theta3_jplus1 derivative const double theta3_jplus1_deriv = computeDerivativeTheta3(e, ev_jplus1, dui); ////1st in bracket term const double secondBendingForce_inBracketsTerm_0 = theta3_j_deriv * 2 * theta3_j; ////2nd in bracket term const double secondBendingForce_inBracketsTerm_1 = theta3_jplus1_deriv * theta3_j; ////3rd in bracket term const double secondBendingForce_inBracketsTerm_2 = theta3_j_deriv * theta3_jplus1; ////4th in bracket term const double secondBendingForce_inBracketsTerm_3 = theta3_jplus1_deriv * 2 * theta3_jplus1; // 4th term computation const double secondBendingForce_inBracketsTerm = secondBendingForce_inBracketsTerm_0 + secondBendingForce_inBracketsTerm_1 + secondBendingForce_inBracketsTerm_2 + secondBendingForce_inBracketsTerm_3; const double secondBendingForce_dofi = secondBendingForce_inBracketsTerm * element.rigidity.secondBending; internalForcesContributionFromThisEdge[evi + 2].second[dofi] = secondBendingForce_dofi; } } } } internalForcesContributionsFromEachEdge[ei] = internalForcesContributionFromThisEdge; } //#pragma omp parallel for schedule(static) num_threads(8) for (size_t vi = 0; vi < pMesh->VN(); vi++) { Node::Forces &force = pMesh->nodes[vi].force; force.residual = force.external; force.internal = 0; } double totalResidualForcesNorm = 0; for (size_t ei = 0; ei < pMesh->EN(); ei++) { for (int i = 0; i < 4; i++) { std::pair internalForcePair = internalForcesContributionsFromEachEdge[ei][i]; int vi = internalForcePair.first; if (i > 1 && vi == -1) { continue; } Node::Forces &force = pMesh->nodes[vi].force; force.internal = force.internal + internalForcePair.second; force.residual = force.residual + (internalForcePair.second * -1); } } Vector6d sumOfResidualForces(0); for (size_t vi = 0; vi < pMesh->VN(); vi++) { Node::Forces &force = pMesh->nodes[vi].force; const Vector6d &nodeResidualForce = force.residual; // sumOfResidualForces = sumOfResidualForces + nodeResidualForce; const double residualForceNorm = nodeResidualForce.norm(); const bool shouldTrigger = std::isnan(residualForceNorm); totalResidualForcesNorm += residualForceNorm; } pMesh->previousTotalResidualForcesNorm = pMesh->totalResidualForcesNorm; pMesh->totalResidualForcesNorm = totalResidualForcesNorm; // mesh->totalResidualForcesNorm = sumOfResidualForces.norm(); // plotYValues.push_back(totalResidualForcesNorm); // auto xPlot = matplot::linspace(0, plotYValues.size(), plotYValues.size()); // plotHandle = matplot::scatter(xPlot, plotYValues); } void FormFinder::updateNodalExternalForces( const std::unordered_map &nodalForces, const std::unordered_map> &fixedVertices) { externalMomentsNorm = 0; for (const std::pair &nodalForce : nodalForces) { const VertexIndex nodeIndex = nodalForce.first; const bool isNodeConstrained = fixedVertices.contains(nodeIndex); Node &node = pMesh->nodes[nodeIndex]; Vector6d nodalExternalForce(0); for (DoFType dofi = 0; dofi < 6; dofi++) { const bool isDofConstrained = isNodeConstrained && fixedVertices.at(nodeIndex).contains(dofi); if (isDofConstrained) { continue; } nodalExternalForce[dofi] = nodalForce.second[dofi]; } externalMomentsNorm += std::sqrt(pow(nodalExternalForce[3], 2) + pow(nodalExternalForce[4], 2)); // CoordType v = (mesh->vert[nodeIndex].cP() - // mesh->vert[0].cP()).Normalize(); const double forceMagnitude = 0.1; // nodalExternalForce[3] = v[0] * forceMagnitude; // nodalExternalForce[4] = v[1] * forceMagnitude; node.force.external = nodalExternalForce; } } void FormFinder::updateResidualForces() { pMesh->totalResidualForcesNorm = 0; for (const VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; node.force.residual = node.force.external - node.force.internal; const double residualForceNorm = (node.force.residual).norm(); pMesh->totalResidualForcesNorm += residualForceNorm; } } void FormFinder::computeRigidSupports() { for (const VertexType &v : pMesh->vert) { const VertexIndex vi = pMesh->nodes[v].vi; const bool isVertexConstrained = constrainedVertices.contains(vi); if (isVertexConstrained) { auto constrainedDoFType = constrainedVertices.at(vi); const bool hasAllDoFTypeConstrained = constrainedDoFType.contains(DoF::Ux) && constrainedDoFType.contains(DoF::Uy) && constrainedDoFType.contains(DoF::Uz) && constrainedDoFType.contains(DoF::Nx) && constrainedDoFType.contains(DoF::Ny) && constrainedDoFType.contains(DoF::Nr); if (hasAllDoFTypeConstrained) { rigidSupports.insert(vi); } } } } void FormFinder::updateNormalDerivatives() { for (const VertexType &v : pMesh->vert) { for (DoFType dofi = DoF::Nx; dofi < DoF::NumDoF; dofi++) { DifferentiateWithRespectTo dui{v, dofi}; pMesh->nodes[v].derivativeOfNormal[dofi] = computeDerivativeOfNormal(v, dui); } } } void FormFinder::updateT1Derivatives() { for (const EdgeType &e : pMesh->edge) { for (DoFType dofi = DoF::Ux; dofi < DoF::Nx; dofi++) { DifferentiateWithRespectTo dui_v0{*e.cV(0), dofi}; Element &element = pMesh->elements[e]; element.derivativeT1_j[dofi] = computeDerivativeT1(e, dui_v0); DifferentiateWithRespectTo dui_v1{*e.cV(1), dofi}; element.derivativeT1_jplus1[dofi] = computeDerivativeT1(e, dui_v1); element.derivativeT1[0][dofi] = element.derivativeT1_j[dofi]; element.derivativeT1[1][dofi] = element.derivativeT1_jplus1[dofi]; } } } void FormFinder::updateT2Derivatives() { for (const EdgeType &e : pMesh->edge) { for (DoFType dofi = DoF::Ux; dofi < DoF::NumDoF; dofi++) { DifferentiateWithRespectTo dui_v0{*e.cV(0), dofi}; pMesh->elements[e].derivativeT2_j[dofi] = computeDerivativeT2(e, dui_v0); DifferentiateWithRespectTo dui_v1{*e.cV(1), dofi}; pMesh->elements[e].derivativeT2_jplus1[dofi] = computeDerivativeT2(e, dui_v1); Element &element = pMesh->elements[e]; element.derivativeT2[0][dofi] = element.derivativeT2_j[dofi]; element.derivativeT2[1][dofi] = element.derivativeT2_jplus1[dofi]; } } } void FormFinder::updateT3Derivatives() { for (const EdgeType &e : pMesh->edge) { for (DoFType dofi = DoF::Ux; dofi < DoF::NumDoF; dofi++) { DifferentiateWithRespectTo dui_v0{*e.cV(0), dofi}; Element &element = pMesh->elements[e]; element.derivativeT3_j[dofi] = computeDerivativeT3(e, dui_v0); DifferentiateWithRespectTo dui_v1{*e.cV(1), dofi}; element.derivativeT3_jplus1[dofi] = computeDerivativeT3(e, dui_v1); element.derivativeT3[0][dofi] = element.derivativeT3_j[dofi]; element.derivativeT3[1][dofi] = element.derivativeT3_jplus1[dofi]; } } } void FormFinder::updateRDerivatives() { for (const EdgeType &e : pMesh->edge) { for (DoFType dofi = DoF::Nx; dofi < DoF::NumDoF; dofi++) { DifferentiateWithRespectTo dui_v0{*e.cV(0), dofi}; pMesh->elements[e].derivativeR_j[dofi] = computeDerivativeOfR(e, dui_v0); DifferentiateWithRespectTo dui_v1{*e.cV(1), dofi}; pMesh->elements[e].derivativeR_jplus1[dofi] = computeDerivativeOfR(e, dui_v1); } } } void FormFinder::updateElementalLengths() { pMesh->updateElementalLengths(); } FormFinder::FormFinder() {} void FormFinder::updateNodalMasses() { const double gamma = 0.8; for (VertexType &v : pMesh->vert) { const size_t vi = pMesh->getIndex(v); double translationalSumSk = 0; double rotationalSumSk_I2 = 0; double rotationalSumSk_I3 = 0; double rotationalSumSk_J = 0; for (const EdgePointer &ep : pMesh->nodes[v].incidentElements) { const size_t ei = pMesh->getIndex(ep); const Element &elem = pMesh->elements[ep]; const double SkTranslational = elem.material.youngsModulus * elem.A / elem.length; translationalSumSk += SkTranslational; const double lengthToThe3 = std::pow(elem.length, 3); const long double SkRotational_I2 = elem.material.youngsModulus * elem.I2 / lengthToThe3; // TODO: I2->t2,I3->t3,t1->polar inertia const long double SkRotational_I3 = elem.material.youngsModulus * elem.I3 / lengthToThe3; // TODO: I2->t2,I3->t3,t1->polar inertia const long double SkRotational_J = elem.material.youngsModulus * elem.J / lengthToThe3; // TODO: I2->t2,I3->t3,t1->polar inertia rotationalSumSk_I2 += SkRotational_I2; rotationalSumSk_I3 += SkRotational_I3; rotationalSumSk_J += SkRotational_J; assert(rotationalSumSk_I2 != 0); assert(rotationalSumSk_I3 != 0); assert(rotationalSumSk_J != 0); } pMesh->nodes[v].translationalMass = gamma * pow(mSettings.Dtini, 2) * 2 * translationalSumSk; pMesh->nodes[v].rotationalMass_I2 = gamma * pow(mSettings.Dtini, 2) * 8 * rotationalSumSk_I2; pMesh->nodes[v].rotationalMass_I3 = gamma * pow(mSettings.Dtini, 2) * 8 * rotationalSumSk_I3; pMesh->nodes[v].rotationalMass_J = gamma * pow(mSettings.Dtini, 2) * 8 * rotationalSumSk_J; assert(std::pow(mSettings.Dtini, 2.0) * translationalSumSk / pMesh->nodes[v].translationalMass < 2); assert(std::pow(mSettings.Dtini, 2.0) * rotationalSumSk_I2 / pMesh->nodes[v].rotationalMass_I2 < 2); assert(std::pow(mSettings.Dtini, 2.0) * rotationalSumSk_I3 / pMesh->nodes[v].rotationalMass_I3 < 2); assert(std::pow(mSettings.Dtini, 2.0) * rotationalSumSk_J / pMesh->nodes[v].rotationalMass_J < 2); } } void FormFinder::updateNodalAccelerations() { for (VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; const VertexIndex vi = pMesh->getIndex(v); for (DoFType dofi = DoF::Ux; dofi < DoF::NumDoF; dofi++) { if (dofi == DoF::Ux || dofi == DoF::Uy || dofi == DoF::Uz) { node.acceleration[dofi] = node.force.residual[dofi] / node.translationalMass; } else if (dofi == DoF::Nx) { node.acceleration[dofi] = node.force.residual[dofi] / node.rotationalMass_J; } else if (dofi == DoF::Ny) { node.acceleration[dofi] = node.force.residual[dofi] / node.rotationalMass_I3; } else if (dofi == DoF::Nr) { node.acceleration[dofi] = node.force.residual[dofi] / node.rotationalMass_I2; } } } } void FormFinder::updateNodalVelocities() { for (VertexType &v : pMesh->vert) { const VertexIndex vi = pMesh->getIndex(v); Node &node = pMesh->nodes[v]; node.velocity = node.velocity + node.acceleration * Dt; } updateKineticEnergy(); } void FormFinder::updateNodalDisplacements() { for (VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; node.displacements = node.displacements + node.velocity * Dt; // if (mSettings.beVerbose) { // std::cout << "Node " << node.vi << ":" << endl; // std::cout << node.displacements[0] << " " << node.displacements[0] // << " " // << node.displacements[1] << " " << node.displacements[2] // << " " // << node.displacements[3] << " " << node.displacements[4] // << " " // << node.displacements[5] << std::endl; // } } } void FormFinder::updateNodePosition( VertexType &v, const std::unordered_map> &fixedVertices) { Node &node = pMesh->nodes[v]; CoordType previousLocation = v.cP(); const VertexIndex &vi = pMesh->nodes[v].vi; VectorType displacementVector(0, 0, 0); if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(0)) { displacementVector += VectorType(node.displacements[0], 0, 0); } if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(1)) { displacementVector += VectorType(0, node.displacements[1], 0); } if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(2)) { displacementVector += VectorType(0, 0, node.displacements[2]); } node.previousLocation = previousLocation; v.P() = node.initialLocation + displacementVector; if (shouldApplyInitialDistortion && mCurrentSimulationStep < 40) { VectorType desiredInitialDisplacement(0, 0, 0.01); v.P() = v.P() + desiredInitialDisplacement; } } void FormFinder::updateNodeNormal( VertexType &v, const std::unordered_map> &fixedVertices) { Node &node = pMesh->nodes[v]; const VertexIndex &vi = node.vi; // if (vi == 1) { // std::cout << "PRE:" << mesh->vert[1].N()[0] << " " << // mesh->vert[1].N()[1] // << " " << mesh->vert[1].N()[2] << std::endl; // } VectorType normalDisplacementVector(0, 0, 0); if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(3)) { normalDisplacementVector += VectorType(node.displacements[3], 0, 0); } if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(4)) { normalDisplacementVector += VectorType(0, node.displacements[4], 0); } const double nxnyMagnitudePre = std::pow(v.N()[0], 2) + std::pow(v.N()[1], 2); v.N() = node.initialNormal + normalDisplacementVector; const double &nx = v.N()[0], ny = v.N()[1], nz = v.N()[2]; const double nxnyMagnitude = std::pow(nx, 2) + std::pow(ny, 2); VectorType n = v.N(); const bool shouldBreak = mCurrentSimulationStep == 118 && vi == 3; if (nxnyMagnitude > 1) { VectorType newNormal(nx / std::sqrt(nxnyMagnitude), ny / std::sqrt(nxnyMagnitude), 0); v.N() = newNormal; /*If an external moment caused the normal to lay on the xy plane this means * that in order to disable its effect a greater internal force is needed * than what is possible (the constraint on the z of the normals imposes a * constraint on the maximum internal force). Because of that the * totalResidualForcesNorm can't drop below the magnitude of external moment * applied on vertex vi. In order to allow termination of the simulation * when the described phenomenon happens we allow the termination of the * algorithm if the kinetic energy of the system drops below the set * threshold. * */ const bool viHasMoments = node.force.external[3] != 0 || node.force.external[4] != 0; if (!checkedForMaximumMoment && viHasMoments) { mSettings.shouldUseTranslationalKineticEnergyThreshold = true; if (mSettings.beVerbose) { std::cout << "Maximum moment reached.The Kinetic energy of the system will " "be used as a convergence criterion" << std::endl; } checkedForMaximumMoment = true; } } else { const double nzSquared = 1.0 - nxnyMagnitude; const double nz = std::sqrt(nzSquared); VectorType newNormal(nx, ny, nz); v.N() = newNormal; } if (!rigidSupports.contains(vi)) { node.nR = node.displacements[5]; } else { const EdgePointer &refElem = node.referenceElement; const VectorType &refT1 = pMesh->elements[refElem].frame.t1; const VectorType &t1Initial = computeT1Vector(pMesh->nodes[refElem->cV(0)].initialLocation, pMesh->nodes[refElem->cV(1)].initialLocation); VectorType g1 = Cross(refT1, t1Initial); node.nR = g1.dot(v.cN()); } // if (vi == 1) { // std::cout << "AFTER:" << mesh->vert[1].N()[0] << " " << // mesh->vert[1].N()[1] // << " " << mesh->vert[1].N()[2] << std::endl; // } } void FormFinder::applyDisplacements( const std::unordered_map> &fixedVertices) { for (VertexType &v : pMesh->vert) { updateNodePosition(v, fixedVertices); updateNodeNormal(v, fixedVertices); } updateElementalFrames(); if (mSettings.shouldDraw) { pMesh->updateEigenEdgeAndVertices(); } } void FormFinder::updateElementalFrames() { for (const EdgeType &e : pMesh->edge) { const VectorType elementNormal = (e.cV(0)->cN() + e.cV(1)->cN()).Normalize(); pMesh->elements[e].frame = computeElementFrame(e.cP(0), e.cP(1), elementNormal); } } void FormFinder::applyForcedDisplacements( const std::unordered_map nodalForcedDisplacements) { for (const std::pair vertexIndexDisplacementPair : nodalForcedDisplacements) { const VertexIndex vi = vertexIndexDisplacementPair.first; const Eigen::Vector3d vertexDisplacement = vertexIndexDisplacementPair.second; Node &node = pMesh->nodes[vi]; VectorType displacementVector(vertexDisplacement(0), vertexDisplacement(1), vertexDisplacement(2)); pMesh->vert[vi].P() = node.initialLocation + displacementVector; node.displacements = Vector6d( {vertexDisplacement(0), vertexDisplacement(1), vertexDisplacement(2), node.displacements[3], node.displacements[4], node.displacements[5]}); } if (mSettings.shouldDraw) { pMesh->updateEigenEdgeAndVertices(); } } void FormFinder::applyForcedNormals( const std::unordered_map nodalForcedRotations) { for (const std::pair vertexIndexDesiredNormalPair : nodalForcedRotations) { const VertexIndex vi = vertexIndexDesiredNormalPair.first; Node &node = pMesh->nodes[vi]; pMesh->vert[vi].N() = vertexIndexDesiredNormalPair.second; node.displacements = Vector6d( {node.displacements[0], node.displacements[1], node.displacements[2], vertexIndexDesiredNormalPair.second[0] - node.initialNormal[0], vertexIndexDesiredNormalPair.second[1] - node.initialNormal[1], node.displacements[5]}); } } // NOTE: Is this correct? Should the kinetic energy be computed like that? void FormFinder::updateKineticEnergy() { pMesh->previousTotalKineticEnergy = pMesh->currentTotalKineticEnergy; pMesh->currentTotalKineticEnergy = 0; pMesh->currentTotalTranslationalKineticEnergy = 0; for (const VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; node.kineticEnergy = 0; const double translationalVelocityNorm = std::sqrt( std::pow(node.velocity[0], 2) + std::pow(node.velocity[1], 2) + std::pow(node.velocity[2], 2)); const double nodeTranslationalKineticEnergy = 0.5 * node.translationalMass * pow(translationalVelocityNorm, 2); const double nodeRotationalKineticEnergy = 0.5 * (node.rotationalMass_J * pow(node.velocity[3], 2) + +node.rotationalMass_I3 * pow(node.velocity[4], 2) + +node.rotationalMass_I2 * pow(node.velocity[5], 2)); node.kineticEnergy += nodeTranslationalKineticEnergy /*+ nodeRotationalKineticEnergy*/; assert(node.kineticEnergy < 10000000000000000000); pMesh->currentTotalKineticEnergy += node.kineticEnergy; pMesh->currentTotalTranslationalKineticEnergy += nodeTranslationalKineticEnergy; } // assert(mesh->currentTotalKineticEnergy < 100000000000000); } void FormFinder::resetVelocities() { for (const VertexType &v : pMesh->vert) { pMesh->nodes[v].velocity = 0; // mesh->nodes[v].force.residual * 0.5 * Dt / // mesh->nodes[v].mass; // NOTE: Do I reset the previous // velocity? // reset // current to 0 or this? } updateKineticEnergy(); } void FormFinder::updatePositionsOnTheFly( const std::unordered_map> &fixedVertices) { const double gamma = 0.8; for (VertexType &v : pMesh->vert) { double translationalSumSk = 0; double rotationalSumSk_I2 = 0; double rotationalSumSk_I3 = 0; double rotationalSumSk_J = 0; for (const EdgePointer &ep : pMesh->nodes[v].incidentElements) { const Element &elem = pMesh->elements[ep]; const double SkTranslational = elem.material.youngsModulus * elem.A / elem.length; translationalSumSk += SkTranslational; const double lengthToThe3 = std::pow(elem.length, 3); const double SkRotational_I2 = elem.material.youngsModulus * elem.I2 / lengthToThe3; // TODO: I2->t2,I3->t3,t1->polar inertia const double SkRotational_I3 = elem.material.youngsModulus * elem.I3 / lengthToThe3; // TODO: I2->t2,I3->t3,t1->polar inertia const double SkRotational_J = elem.material.youngsModulus * elem.J / lengthToThe3; // TODO: I2->t2,I3->t3,t1->polar inertia rotationalSumSk_I2 += SkRotational_I2; rotationalSumSk_I3 += SkRotational_I3; rotationalSumSk_J += SkRotational_J; // assert(rotationalSumSk_I2 != 0); // assert(rotationalSumSk_I3 != 0); // assert(rotationalSumSk_J != 0); } pMesh->nodes[v].translationalMass = gamma * pow(mSettings.Dtini, 2) * 2 * translationalSumSk; pMesh->nodes[v].rotationalMass_I2 = gamma * pow(mSettings.Dtini, 2) * 8 * rotationalSumSk_I2; pMesh->nodes[v].rotationalMass_I3 = gamma * pow(mSettings.Dtini, 2) * 8 * rotationalSumSk_I3; pMesh->nodes[v].rotationalMass_J = gamma * pow(mSettings.Dtini, 2) * 8 * rotationalSumSk_J; // assert(std::pow(mSettings.Dtini, 2.0) * translationalSumSk / // mesh->nodes[v].translationalMass < // 2); // assert(std::pow(mSettings.Dtini, 2.0) * rotationalSumSk_I2 / // mesh->nodes[v].rotationalMass_I2 < // 2); // assert(std::pow(mSettings.Dtini, 2.0) * rotationalSumSk_I3 / // mesh->nodes[v].rotationalMass_I3 < // 2); // assert(std::pow(mSettings.Dtini, 2.0) * rotationalSumSk_J / // mesh->nodes[v].rotationalMass_J < // 2); } for (VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; for (DoFType dofi = DoF::Ux; dofi < DoF::NumDoF; dofi++) { if (dofi == DoF::Ux || dofi == DoF::Uy || dofi == DoF::Uz) { node.acceleration[dofi] = node.force.residual[dofi] / node.translationalMass; } else if (dofi == DoF::Nx) { node.acceleration[dofi] = node.force.residual[dofi] / node.rotationalMass_J; } else if (dofi == DoF::Ny) { node.acceleration[dofi] = node.force.residual[dofi] / node.rotationalMass_I3; } else if (dofi == DoF::Nr) { node.acceleration[dofi] = node.force.residual[dofi] / node.rotationalMass_I2; } } } for (VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; node.velocity = node.velocity + node.acceleration * Dt; } updateKineticEnergy(); for (VertexType &v : pMesh->vert) { Node &node = pMesh->nodes[v]; node.displacements = node.displacements + node.velocity * Dt; } for (VertexType &v : pMesh->vert) { updateNodePosition(v, fixedVertices); Node &node = pMesh->nodes[v]; const VertexIndex &vi = node.vi; VectorType normalDisplacementVector(0, 0, 0); if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(3)) { normalDisplacementVector += VectorType(node.displacements[3], 0, 0); } if (!fixedVertices.contains(vi) || !fixedVertices.at(vi).contains(4)) { normalDisplacementVector += VectorType(0, node.displacements[4], 0); } v.N() = node.initialNormal + normalDisplacementVector; const double &nx = v.N()[0], ny = v.N()[1]; const double nxnyMagnitude = std::pow(nx, 2) + std::pow(ny, 2); if (nxnyMagnitude > 1) { v.N() = VectorType(nx / std::sqrt(nxnyMagnitude), ny / std::sqrt(nxnyMagnitude), 0); } else { const double nzSquared = 1.0 - nxnyMagnitude; const double nz = std::sqrt(nzSquared); VectorType newNormal(nx, ny, nz); v.N() = newNormal; } if (!rigidSupports.contains(vi)) { node.nR = node.displacements[5]; } else { } } updateElementalFrames(); } SimulationResults FormFinder::computeResults(const std::shared_ptr &pJob) { std::vector displacements(pMesh->VN()); for (size_t vi = 0; vi < pMesh->VN(); vi++) { displacements[vi] = pMesh->nodes[vi].displacements; } history.numberOfSteps = mCurrentSimulationStep; return SimulationResults{true, pJob, history, displacements}; } void FormFinder::draw(const std::string &screenshotsFolder = {}) { // update positions // polyscope::getCurveNetwork("Undeformed edge mesh")->setEnabled(false); polyscope::getCurveNetwork(meshPolyscopeLabel) ->updateNodePositions(pMesh->getEigenVertices()); // Vertex quantities std::vector kineticEnergies(pMesh->VN()); std::vector> nodeNormals(pMesh->VN()); std::vector> internalForces(pMesh->VN()); std::vector> externalForces(pMesh->VN()); std::vector> externalMoments(pMesh->VN()); std::vector internalForcesNorm(pMesh->VN()); std::vector> internalAxialForces(pMesh->VN()); std::vector> internalFirstBendingTranslationForces( pMesh->VN()); std::vector> internalFirstBendingRotationForces( pMesh->VN()); std::vector> internalSecondBendingTranslationForces( pMesh->VN()); std::vector> internalSecondBendingRotationForces( pMesh->VN()); std::vector nRs(pMesh->VN()); std::vector theta2(pMesh->VN()); std::vector theta3(pMesh->VN()); std::vector> residualForces(pMesh->VN()); std::vector residualForcesNorm(pMesh->VN()); std::vector accelerationX(pMesh->VN()); for (const VertexType &v : pMesh->vert) { kineticEnergies[pMesh->getIndex(v)] = pMesh->nodes[v].kineticEnergy; const VectorType n = v.cN(); nodeNormals[pMesh->getIndex(v)] = {n[0], n[1], n[2]}; // per node internal forces const Vector6d nodeforce = pMesh->nodes[v].force.internal * (-1); internalForces[pMesh->getIndex(v)] = {nodeforce[0], nodeforce[1], nodeforce[2]}; internalForcesNorm[pMesh->getIndex(v)] = nodeforce.norm(); // External force const Vector6d nodeExternalForce = pMesh->nodes[v].force.external; externalForces[pMesh->getIndex(v)] = { nodeExternalForce[0], nodeExternalForce[1], nodeExternalForce[2]}; externalMoments[pMesh->getIndex(v)] = {nodeExternalForce[3], nodeExternalForce[4], 0}; internalAxialForces[pMesh->getIndex(v)] = {nodeforce[0], nodeforce[1], nodeforce[2]}; const Node &node = pMesh->nodes[v]; const Vector6d nodeForceFirst = node.force.internalFirstBending * (-1); internalFirstBendingTranslationForces[pMesh->getIndex(v)] = { nodeForceFirst[0], nodeForceFirst[1], nodeForceFirst[2]}; internalFirstBendingRotationForces[pMesh->getIndex(v)] = { nodeForceFirst[3], nodeForceFirst[4], 0}; const Vector6d nodeForceSecond = node.force.internalSecondBending * (-1); internalSecondBendingTranslationForces[pMesh->getIndex(v)] = { nodeForceSecond[0], nodeForceSecond[1], nodeForceSecond[2]}; internalSecondBendingRotationForces[pMesh->getIndex(v)] = { nodeForceSecond[3], nodeForceSecond[4], 0}; nRs[pMesh->getIndex(v)] = node.nR; const Vector6d nodeResidualForce = pMesh->nodes[v].force.residual; residualForces[pMesh->getIndex(v)] = { nodeResidualForce[0], nodeResidualForce[1], nodeResidualForce[2]}; residualForcesNorm[pMesh->getIndex(v)] = nodeResidualForce.norm(); accelerationX[pMesh->getIndex(v)] = pMesh->nodes[v].acceleration[0]; } polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeScalarQuantity("Kinetic Energy", kineticEnergies) ->setEnabled(false); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeVectorQuantity("Node normals", nodeNormals) ->setEnabled(true); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeVectorQuantity("Internal force", internalForces) ->setEnabled(false); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeVectorQuantity("External force", externalForces) ->setEnabled(true); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeVectorQuantity("External Moments", externalMoments) ->setEnabled(true); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeScalarQuantity("Internal force norm", internalForcesNorm) ->setEnabled(true); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeVectorQuantity("Internal Axial force", internalAxialForces) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeVectorQuantity("First bending force-Translation", // internalFirstBendingTranslationForces) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeVectorQuantity("First bending force-Rotation", // internalFirstBendingRotationForces) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeVectorQuantity("Second bending force-Translation", // internalSecondBendingTranslationForces) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeVectorQuantity("Second bending force-Rotation", // internalSecondBendingRotationForces) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeScalarQuantity("nR", nRs) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeScalarQuantity("theta3", theta3) // ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeScalarQuantity("theta2", theta2) // ->setEnabled(false); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeVectorQuantity("Residual force", residualForces) ->setEnabled(false); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addNodeScalarQuantity("Residual force norm", residualForcesNorm) ->setEnabled(false); // polyscope::getCurveNetwork(meshPolyscopeLabel) // ->addNodeScalarQuantity("Node acceleration x", accelerationX); // Edge quantities std::vector A(pMesh->EN()); std::vector J(pMesh->EN()); std::vector I2(pMesh->EN()); std::vector I3(pMesh->EN()); for (const EdgeType &e : pMesh->edge) { const size_t ei = pMesh->getIndex(e); A[ei] = pMesh->elements[e].A; J[ei] = pMesh->elements[e].J; I2[ei] = pMesh->elements[e].I2; I3[ei] = pMesh->elements[e].I3; } polyscope::getCurveNetwork(meshPolyscopeLabel)->addEdgeScalarQuantity("A", A); polyscope::getCurveNetwork(meshPolyscopeLabel)->addEdgeScalarQuantity("J", J); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addEdgeScalarQuantity("I2", I2); polyscope::getCurveNetwork(meshPolyscopeLabel) ->addEdgeScalarQuantity("I3", I3); // Specify the callback polyscope::state::userCallback = [&]() { // Since options::openImGuiWindowForUserCallback == true by default, // we can immediately start using ImGui commands to build a UI ImGui::PushItemWidth(100); // Make ui elements 100 pixels wide, // instead of full width. Must have // matching PopItemWidth() below. ImGui::InputInt("Simulation drawing step", &mSettings.drawingStep); // set a int variable ImGui::Checkbox("Enable drawing", &mSettings.shouldDraw); // set a int variable ImGui::Text("Number of simulation steps: %zu", mCurrentSimulationStep); ImGui::PopItemWidth(); }; if (!screenshotsFolder.empty()) { static bool firstDraw = true; if (firstDraw) { for (const auto &entry : std::filesystem::directory_iterator(screenshotsFolder)) std::filesystem::remove_all(entry.path()); // polyscope::view::processZoom(5); firstDraw = false; } polyscope::screenshot( std::filesystem::path(screenshotsFolder) .append(std::to_string(mCurrentSimulationStep) + ".png") .string(), false); } else { polyscope::show(); } } void FormFinder::printCurrentState() const { std::cout << "Simulation steps executed:" << mCurrentSimulationStep << std::endl; std::cout << "Residual forces norm: " << pMesh->totalResidualForcesNorm << std::endl; std::cout << "Kinetic energy:" << pMesh->currentTotalKineticEnergy << std::endl; } void FormFinder::printDebugInfo() const { std::cout << pMesh->elements[0].rigidity.toString() << std::endl; std::cout << "Number of dampings:" << numOfDampings << endl; printCurrentState(); } SimulationResults FormFinder::executeSimulation(const std::shared_ptr &pJob, const Settings &settings) { assert(pJob->pMesh.operator bool()); auto t1 = std::chrono::high_resolution_clock::now(); reset(); mSettings = settings; // if (!pJob->nodalExternalForces.empty()) { // double externalForcesNorm = 0; // for (const auto &externalForce : pJob->nodalExternalForces) { // externalForcesNorm += externalForce.second.norm(); // } // mSettings.totalResidualForcesNormThreshold = externalForcesNorm * 1e-2; // } constrainedVertices = pJob->constrainedVertices; if (!pJob->nodalForcedDisplacements.empty()) { for (std::pair viDisplPair : pJob->nodalForcedDisplacements) { const VertexIndex vi = viDisplPair.first; constrainedVertices[vi].insert({0, 1, 2}); } } // if (!pJob->nodalForcedNormals.empty()) { // for (std::pair viNormalPair : // pJob->nodalForcedDisplacements) { // assert(viNormalPair3second[2] >= 0); // } // } pMesh.reset(); pMesh = std::make_unique(*pJob->pMesh); if (mSettings.beVerbose ) { std::cout << "Executing simulation for mesh with:" << pMesh->VN() << " nodes and " << pMesh->EN() << " elements." << std::endl; } computeRigidSupports(); if (mSettings.shouldDraw ) { initPolyscope(); polyscope::registerCurveNetwork( meshPolyscopeLabel, pMesh->getEigenVertices(), pMesh->getEigenEdges()); // registerWorldAxes(); } for (auto fixedVertex : pJob->constrainedVertices) { assert(fixedVertex.first < pMesh->VN()); } updateElementalFrames(); updateNodalMasses(); if (!pJob->nodalForcedDisplacements.empty() && pJob->nodalExternalForces.empty()) { shouldApplyInitialDistortion = true; } updateNodalExternalForces(pJob->nodalExternalForces, constrainedVertices); while (settings.maxDRMIterations == 0 || mCurrentSimulationStep < settings.maxDRMIterations) { // while (true) { updateNormalDerivatives(); updateT1Derivatives(); updateRDerivatives(); updateT2Derivatives(); updateT3Derivatives(); updateResidualForcesOnTheFly(constrainedVertices); // TODO: write parallel function for updating positions // TODO: Make a single function out of updateResidualForcesOnTheFly // updatePositionsOnTheFly // updatePositionsOnTheFly(constrainedVertices); updateNodalMasses(); updateNodalAccelerations(); updateNodalVelocities(); updateNodalDisplacements(); applyDisplacements(constrainedVertices); if (!pJob->nodalForcedDisplacements.empty()) { applyForcedDisplacements( pJob->nodalForcedDisplacements); } // if (!pJob->nodalForcedNormals.empty()) { // applyForcedNormals(pJob->nodalForcedNormals); // } updateElementalLengths(); pMesh->previousTotalPotentialEnergykN = pMesh->currentTotalPotentialEnergykN; pMesh->currentTotalPotentialEnergykN = computeTotalPotentialEnergy() / 1000; if (mCurrentSimulationStep != 0) { history.stepPulse(*pMesh); } if (std::isnan(pMesh->currentTotalKineticEnergy)) { std::time_t now = time(0); std::tm *ltm = std::localtime(&now); std::string dir = "../ProblematicSimulationJobs/" + std::to_string(ltm->tm_mday) + "_" + std::to_string(1 + ltm->tm_mon) + "_" + std::to_string(1900 + ltm->tm_year); const std::string subDir = std::filesystem::path(dir) .append(std::to_string(ltm->tm_hour) + ":" + std::to_string(ltm->tm_min)) .string(); std::filesystem::create_directories(subDir); pJob->save(subDir); std::cout << "Infinite kinetic energy detected.Exiting.." << std::endl; FormFinder debug; FormFinder::Settings settings; settings.shouldDraw = true; settings.beVerbose = true; debug.executeSimulation(pJob, settings); std::terminate(); break; } if (mSettings.shouldDraw && mCurrentSimulationStep % mSettings.drawingStep == 0) /* && currentSimulationStep > maxDRMIterations*/ { // std::string saveTo = std::filesystem::current_path() // .append("Debugging_files") // .append("Screenshots") // .string(); // draw(saveTo); draw(); // yValues = history.kineticEnergy; // plotHandle = matplot::scatter(xPlot, yValues); // label = "Log of Kinetic energy"; // plotHandle->legend_string(label); // shouldUseKineticEnergyThreshold = true; } if (mSettings.shouldCreatePlots && mCurrentSimulationStep % 10 == 0) { printCurrentState(); SimulationResultsReporter::createPlot( "Number of Steps", "Log of Kinetic energy", history.kineticEnergy); } // t = t + Dt; mCurrentSimulationStep++; // std::cout << "Kinetic energy:" << mesh.currentTotalKineticEnergy // << std::endl; // std::cout << "Residual forces norm:" << mesh.totalResidualForcesNorm // << std::endl; // Kinetic energy convergence if ((mSettings.shouldUseTranslationalKineticEnergyThreshold || mCurrentSimulationStep > 100000) && pMesh->currentTotalTranslationalKineticEnergy < mSettings.totalTranslationalKineticEnergyThreshold) { if (mSettings.beVerbose) { std::cout << "Simulation converged." << std::endl; printCurrentState(); std::cout << "Total potential:" << pMesh->currentTotalPotentialEnergykN << " kNm" << std::endl; std::cout << "Warning: The kinetic energy of the system was " " used as a convergence criterion" << std::endl; } break; } // Residual forces norm convergence if (pMesh->previousTotalKineticEnergy > pMesh->currentTotalKineticEnergy /*|| mesh->previousTotalPotentialEnergykN > mesh->currentTotalPotentialEnergykN*/ /*|| mesh->currentTotalPotentialEnergykN < minPotentialEnergy*/) { if (pMesh->totalResidualForcesNorm < mSettings.totalResidualForcesNormThreshold) { if (mSettings.beVerbose) { std::cout << "Simulation converged." << std::endl; printCurrentState(); std::cout << "Total potential:" << pMesh->currentTotalPotentialEnergykN << " kNm" << std::endl; } break; // } } // for (VertexType &v : mesh->vert) { // Node &node = mesh->nodes[v]; // node.displacements = node.displacements - node.velocity * Dt; // } // applyDisplacements(constrainedVertices); // if (!pJob->nodalForcedDisplacements.empty()) { // applyForcedDisplacements( // pJob->nodalForcedDisplacements); // } // updateElementalLengths(); resetVelocities(); Dt = Dt * mSettings.xi; ++numOfDampings; } if (mSettings.debugMessages) { printDebugInfo(); } } SimulationResults results = computeResults(pJob); if (mCurrentSimulationStep == settings.maxDRMIterations && mCurrentSimulationStep != 0) { std::cout << "Did not reach equilibrium before reaching the maximum number " "of DRM steps (" << settings.maxDRMIterations << "). Breaking simulation" << std::endl; results.converged = false; } else if (std::isnan(pMesh->currentTotalKineticEnergy)) { results.converged = false; } else if (mSettings.beVerbose ) { auto t2 = std::chrono::high_resolution_clock::now(); double simulationDuration = std::chrono::duration_cast(t2 - t1).count(); simulationDuration /= 1000; std::cout << "Simulation converged after " << simulationDuration << "s" << std::endl; std::cout << "Time/(node*iteration) " << simulationDuration / (static_cast(mCurrentSimulationStep) * pMesh->VN()) << "s" << std::endl; std::cout << "Number of dampings:" << numOfDampings << endl; } // mesh.printVertexCoordinates(mesh.VN() / 2); if (results.converged) { if (mSettings.shouldCreatePlots) { SimulationResultsReporter reporter; reporter.reportResults({results}, "Results", pJob->pMesh->getLabel()); } if (mSettings.shouldDraw) { draw(); } } if (polyscope::hasCurveNetwork(meshPolyscopeLabel)) { polyscope::removeCurveNetwork(meshPolyscopeLabel); } return results; }