#ifndef SIMULATIONSTRUCTS_HPP #define SIMULATIONSTRUCTS_HPP namespace Eigen { template void write_binary(const std::string &filename, const Matrix &matrix) { std::ofstream out(filename, std::ios::out | std::ios::binary | std::ios::trunc); typename Matrix::Index rows = matrix.rows(), cols = matrix.cols(); out.write((char *)(&rows), sizeof(typename Matrix::Index)); out.write((char *)(&cols), sizeof(typename Matrix::Index)); out.write((char *)matrix.data(), rows * cols * sizeof(typename Matrix::Scalar)); out.close(); } template void read_binary(const std::string &filename, Matrix &matrix) { std::ifstream in(filename, std::ios::in | std::ios::binary); typename Matrix::Index rows = 0, cols = 0; in.read((char *)(&rows), sizeof(typename Matrix::Index)); in.read((char *)(&cols), sizeof(typename Matrix::Index)); matrix.resize(rows, cols); in.read((char *)matrix.data(), rows * cols * sizeof(typename Matrix::Scalar)); in.close(); } } // namespace Eigen #include "simulationmesh.hpp" #include "nlohmann/json.hpp" #include #include struct SimulationHistory { SimulationHistory() {} size_t numberOfSteps{0}; std::string label; std::vector residualForcesLog; std::vector kineticEnergy; std::vector potentialEnergies; std::vector redMarks; std::vector greenMarks; std::vector residualForcesMovingAverage; std::vector sumOfNormalizedDisplacementNorms; // std::vector residualForcesMovingAverageDerivativesLog; void markRed(const size_t &stepNumber) { redMarks.push_back(stepNumber); } void markGreen(const size_t &stepNumber) { greenMarks.push_back(stepNumber); } void stepPulse(const SimulationMesh &mesh) { kineticEnergy.push_back(log(mesh.currentTotalKineticEnergy)); // potentialEnergy.push_back(mesh.totalPotentialEnergykN); residualForcesLog.push_back(std::log(mesh.totalResidualForcesNorm)); residualForcesMovingAverage.push_back(mesh.residualForcesMovingAverage); sumOfNormalizedDisplacementNorms.push_back(mesh.sumOfNormalizedDisplacementNorms); // residualForcesMovingAverageDerivativesLog.push_back( // std::log(mesh.residualForcesMovingAverageDerivativeNorm)); } void clear() { residualForcesLog.clear(); kineticEnergy.clear(); potentialEnergies.clear(); residualForcesMovingAverage.clear(); sumOfNormalizedDisplacementNorms.clear(); // residualForcesMovingAverageDerivativesLog.clear(); } }; namespace nlohmann { template <> struct adl_serializer> { static void to_json(json &j, const std::unordered_map &value) { // calls the "to_json" method in T's namespace } static void from_json(const nlohmann::json &j, std::unordered_map &m) { std::cout << "Entered." << std::endl; for (const auto &p : j) { m.emplace(p.at(0).template get(), p.at(1).template get>()); } } }; } // namespace nlohmann class SimulationJob { // const std::unordered_map nodalForcedNormals; // json labels struct JSONLabels { inline static std::string meshFilename{"mesh filename"}; inline static std::string forcedDisplacements{"forced displacements"}; inline static std::string constrainedVertices{"fixed vertices"}; inline static std::string nodalForces{"forces"}; inline static std::string label{"label"}; inline static std::string meshLabel{ "meshLabel"}; // TODO: should be in the savePly function of the // simulation mesh class } jsonLabels; public: std::shared_ptr pMesh; std::string label{"empty_job"}; std::unordered_map> constrainedVertices; std::unordered_map nodalExternalForces; std::unordered_map nodalForcedDisplacements; SimulationJob( const std::shared_ptr &m, const std::string &label, const std::unordered_map> &cv, const std::unordered_map &ef = {}, const std::unordered_map &fd = {}) : pMesh(m), label(label), constrainedVertices(cv), nodalExternalForces(ef), nodalForcedDisplacements(fd) {} SimulationJob() {} SimulationJob(const std::string &jsonFilename) { load(jsonFilename); } void remap(const std::unordered_map &thisToOtherViMap, SimulationJob &simulationJobMapped) const { assert(simulationJobMapped.pMesh->VN() != 0); std::unordered_map> reducedModelFixedVertices; for (auto fullModelFixedVertex : this->constrainedVertices) { reducedModelFixedVertices[thisToOtherViMap.at(fullModelFixedVertex.first)] = fullModelFixedVertex.second; } std::unordered_map reducedModelNodalForces; for (auto fullModelNodalForce : this->nodalExternalForces) { reducedModelNodalForces[thisToOtherViMap.at(fullModelNodalForce.first)] = fullModelNodalForce.second; } std::unordered_map reducedNodalForcedDisplacements; for (auto fullForcedDisplacement : this->nodalForcedDisplacements) { reducedNodalForcedDisplacements[thisToOtherViMap.at(fullForcedDisplacement.first)] = fullForcedDisplacement.second; } simulationJobMapped.constrainedVertices = reducedModelFixedVertices; simulationJobMapped.nodalExternalForces = reducedModelNodalForces; simulationJobMapped.label = this->getLabel(); simulationJobMapped.nodalForcedDisplacements = reducedNodalForcedDisplacements; } SimulationJob getCopy() const { SimulationJob jobCopy; jobCopy.pMesh = std::make_shared(); jobCopy.pMesh->copy(*pMesh); jobCopy.label = label; jobCopy.constrainedVertices = constrainedVertices; jobCopy.nodalExternalForces = nodalExternalForces; jobCopy.nodalForcedDisplacements = nodalForcedDisplacements; return jobCopy; } std::string getLabel() const { return label; } std::string toString() const { nlohmann::json json; if (!constrainedVertices.empty()) { json[jsonLabels.constrainedVertices] = constrainedVertices; } if (!nodalExternalForces.empty()) { std::unordered_map> arrForces; for (const auto &f : nodalExternalForces) { arrForces[f.first] = f.second; } json[jsonLabels.nodalForces] = arrForces; } return json.dump(); } void clear() { label = "empty_job"; constrainedVertices.clear(); nodalExternalForces.clear(); nodalForcedDisplacements.clear(); if (pMesh.use_count() == 1) { std::cout << "Job mesh is deleted" << std::endl; } pMesh.reset(); } bool load(const std::string &jsonFilename, const bool &shouldLoadMesh = true) { label = "empty_job"; constrainedVertices.clear(); nodalExternalForces.clear(); nodalForcedDisplacements.clear(); const bool beVerbose = false; if (std::filesystem::path(jsonFilename).extension() != ".json") { std::cerr << "A json file is expected as input. The given file has the " "following extension:" << std::filesystem::path(jsonFilename).extension() << std::endl; assert(false); return false; } if (!std::filesystem::exists(std::filesystem::path(jsonFilename))) { std::cerr << "The json file does not exist. Json file provided:" << jsonFilename << std::endl; assert(false); return false; } if (beVerbose) { std::cout << "Loading json file:" << jsonFilename << std::endl; } nlohmann::json json; std::ifstream ifs(jsonFilename); ifs >> json; if (shouldLoadMesh) { pMesh.reset(); pMesh = std::make_shared(); if (json.contains(jsonLabels.meshFilename)) { const std::string relativeFilepath = json[jsonLabels.meshFilename]; const auto meshFilepath = std::filesystem::path( std::filesystem::path(jsonFilename).parent_path()) .append(relativeFilepath); pMesh->load(meshFilepath.string()); pMesh->setLabel(json[jsonLabels.meshLabel]); // FIXME: This should be exported using // nanoply but nanoply might not be able // to write a string(??) } if (json.contains(jsonLabels.meshLabel)) { pMesh->setLabel(json[jsonLabels.meshLabel]); } } if (json.contains(jsonLabels.constrainedVertices)) { constrainedVertices = // auto conV = json[jsonLabels.constrainedVertices] .get>>(); if (beVerbose) { std::cout << "Loaded constrained vertices. Number of constrained " "vertices found:" << constrainedVertices.size() << std::endl; } } if (json.contains(jsonLabels.nodalForces)) { auto f = json[jsonLabels.nodalForces].get>>(); for (const auto &forces : f) { nodalExternalForces[forces.first] = Vector6d(forces.second); } if (beVerbose) { std::cout << "Loaded forces. Number of forces found:" << nodalExternalForces.size() << std::endl; } } if (json.contains(jsonLabels.forcedDisplacements)) { // auto conV = std::unordered_map> forcedDisp = json[jsonLabels.forcedDisplacements] .get>>(); for (const auto &fd : forcedDisp) { nodalForcedDisplacements[fd.first] = Eigen::Vector3d(fd.second[0], fd.second[1], fd.second[2]); } if (beVerbose) { std::cout << "Loaded forced displacements. Number of forced displaced" "vertices found:" << nodalForcedDisplacements.size() << std::endl; } } if (json.contains(jsonLabels.label)) { label = json[jsonLabels.label]; } return true; } bool save(const std::string &folderDirectory) const { const std::filesystem::path pathFolderDirectory(folderDirectory); if (!std::filesystem::is_directory(pathFolderDirectory)) { std::cerr << "A folder directory is expected for saving the simulation " "job. Exiting.." << std::endl; return false; } bool returnValue = true; std::string jsonFilename( std::filesystem::path(pathFolderDirectory) // .append(label + "_" + pMesh->getLabel() + "_simulationJob.json") .append("SimulationJob.json") .string()); const std::string meshFilename = std::filesystem::absolute( std::filesystem::canonical( std::filesystem::path(pathFolderDirectory))) .append(pMesh->getLabel() + ".ply") .string(); returnValue = pMesh->save(meshFilename); nlohmann::json json; json[jsonLabels.meshFilename] = std::filesystem::relative(std::filesystem::path(meshFilename), std::filesystem::path( std::filesystem::path(jsonFilename).parent_path())); json[jsonLabels.meshLabel] = pMesh->getLabel(); // FIXME: This should be exported using nanoply but // nanoply might not be able to write a string(??) if (!constrainedVertices.empty()) { json[jsonLabels.constrainedVertices] = constrainedVertices; } if (!nodalExternalForces.empty()) { std::unordered_map> arrForces; for (const auto &f : nodalExternalForces) { arrForces[f.first] = f.second; } json[jsonLabels.nodalForces] = arrForces; } if (!nodalForcedDisplacements.empty()) { std::unordered_map> forcedDisp; for (const auto &fd : nodalForcedDisplacements) { forcedDisp[fd.first] = {fd.second[0], fd.second[1], fd.second[2]}; } json[jsonLabels.forcedDisplacements] = forcedDisp; } if (!label.empty()) { json[jsonLabels.label] = label; } if (!pMesh->getLabel().empty()) { json[jsonLabels.meshLabel] = pMesh->getLabel(); } std::ofstream jsonFile(jsonFilename); jsonFile << json; // std::cout << "Saved simulation job as:" << jsonFilename << std::endl; return returnValue; } #ifdef POLYSCOPE_DEFINED void registerForDrawing(const std::string &meshLabel, const bool &shouldEnable = false) const { PolyscopeInterface::init(); if (meshLabel.empty()) { assert(false); std::cerr << "Expects a mesh label on which to draw the simulation job." << std::endl; return; } if (!polyscope::hasCurveNetwork(meshLabel)) { assert(false); std::cerr << "Expects mesh already being registered to draw the " "simulation job. No struct named " + meshLabel << std::endl; return; } std::vector> nodeColors(pMesh->VN()); for (auto fixedVertex : constrainedVertices) { const bool hasRotationalDoFConstrained = fixedVertex.second.contains(3) || fixedVertex.second.contains(4) || fixedVertex.second.contains(5); const bool hasTranslationalDoFConstrained = fixedVertex.second.contains(0) || fixedVertex.second.contains(1) || fixedVertex.second.contains(2); if (hasTranslationalDoFConstrained && !hasRotationalDoFConstrained) { nodeColors[fixedVertex.first] = {0, 0, 1}; } else if (!hasTranslationalDoFConstrained && hasRotationalDoFConstrained) { nodeColors[fixedVertex.first] = {0, 1, 0}; } else { nodeColors[fixedVertex.first] = {0, 1, 1}; } } if (!nodalForcedDisplacements.empty()) { for (std::pair viDisplPair : nodalForcedDisplacements) { const VertexIndex vi = viDisplPair.first; nodeColors[vi][0] += 1; nodeColors[vi][0] /= 2; nodeColors[vi][1] += 0; nodeColors[vi][1] /= 2; nodeColors[vi][2] += 0; nodeColors[vi][2] /= 2; } } std::for_each(nodeColors.begin(), nodeColors.end(), [](std::array &color) { const double norm = sqrt(std::pow(color[0], 2) + std::pow(color[1], 2) + std::pow(color[2], 2)); if (norm > std::pow(10, -7)) { color[0] /= norm; color[1] /= norm; color[2] /= norm; } }); if (!nodeColors.empty()) { polyscope::getCurveNetwork(meshLabel) ->addNodeColorQuantity("Boundary conditions_" + label, nodeColors) ->setEnabled(shouldEnable); } // per node external forces std::vector> externalForces(pMesh->VN()); for (const auto &forcePair : nodalExternalForces) { auto index = forcePair.first; auto force = forcePair.second; externalForces[index] = {force[0], force[1], force[2]}; } if (!externalForces.empty()) { polyscope::getCurveNetwork(meshLabel) ->addNodeVectorQuantity("External force_" + label, externalForces) ->setEnabled(shouldEnable); } // per node external moments std::vector> externalMoments(pMesh->VN()); for (const auto &forcePair : nodalExternalForces) { auto index = forcePair.first; auto load = forcePair.second; externalMoments[index] = {load[3], load[4], load[5]}; } if (!externalMoments.empty()) { polyscope::getCurveNetwork(meshLabel) ->addNodeVectorQuantity("External moment_" + label, externalMoments) ->setEnabled(shouldEnable); } } void unregister(const std::string &meshLabel) { if (polyscope::getCurveNetwork(meshLabel) == nullptr) { return; } polyscope::getCurveNetwork(meshLabel)->removeQuantity("External force_" + label); polyscope::getCurveNetwork(meshLabel)->removeQuantity("Boundary conditions_" + label); } #endif // POLYSCOPE_DEFINED }; struct SimulationResults { /*TODO: remove rotationalDisplacementQuaternion since the last three components of the displacments * vector contains the same info using euler angles */ bool converged{true}; std::shared_ptr job; SimulationHistory history; std::vector displacements; std::vector> rotationalDisplacementQuaternion; //per vertex double internalPotentialEnergy{0}; double executionTime{0}; std::string labelPrefix{"deformed"}; inline static char deliminator{' '}; SimulationResults() { job = std::make_shared(); } std::vector getTranslationalDisplacements() const { std::vector translationalDisplacements(displacements.size()); std::transform(displacements.begin(), displacements.end(), translationalDisplacements.begin(), [&](const Vector6d &d) { return VectorType(d[0], d[1], d[2]); }); return translationalDisplacements; } void setLabelPrefix(const std::string &lp) { labelPrefix += deliminator + lp; } std::string getLabel() const { return labelPrefix + deliminator + job->pMesh->getLabel() + deliminator + job->getLabel(); } void saveDeformedModel(const std::string &outputFolder = std::string()) { VCGEdgeMesh m; vcg::tri::Append::MeshCopy(m, *job->pMesh); for (int vi = 0; vi < m.VN(); vi++) { m.vert[vi].P() = m.vert[vi].P() + CoordType(displacements[vi][0], displacements[vi][1], displacements[vi][2]); } m.save(std::filesystem::path(outputFolder).append(getLabel() + ".ply").string()); } void save(const std::string &outputFolder = std::string()) { const std::filesystem::path outputFolderPath = outputFolder.empty() ? std::filesystem::current_path() : std::filesystem::path(outputFolder); std::filesystem::path simulationJobOutputFolderPath = std::filesystem::path(outputFolderPath).append("SimulationJob"); std::filesystem::create_directories(simulationJobOutputFolderPath); job->save(simulationJobOutputFolderPath); const std::string filename(getLabel() + "_displacements.eigenBin"); Eigen::MatrixXd m = Utilities::toEigenMatrix(displacements); Eigen::write_binary(std::filesystem::path(outputFolderPath).append(filename).string(), m); saveDeformedModel(outputFolderPath); } // The comparison of the results happens comparing the 6-dof nodal // displacements bool isEqual(const Eigen::MatrixXd &nodalDisplacements, double &error) { assert(nodalDisplacements.cols() == 6); Eigen::MatrixXd eigenDisplacements = Utilities::toEigenMatrix(this->displacements); const double errorNorm = (eigenDisplacements - nodalDisplacements).norm(); error = errorNorm; return errorNorm < 1e-10; // return eigenDisplacements.isApprox(nodalDisplacements); } void load(const std::filesystem::path &loadFromPath, const std::filesystem::path &loadJobFrom) { //load job job->load(std::filesystem::path(loadJobFrom).append("SimulationJob.json").string()); //Use the first .eigenBin file for loading the displacements for (auto const &entry : std::filesystem::recursive_directory_iterator(loadFromPath)) { if (filesystem::is_regular_file(entry) && entry.path().extension() == ".eigenBin") { Eigen::MatrixXd displacements_eigen; Eigen::read_binary(entry.path().string(), displacements_eigen); displacements = Utilities::fromEigenMatrix(displacements_eigen); break; } } rotationalDisplacementQuaternion.resize(job->pMesh->VN()); for (int vi = 0; vi < job->pMesh->VN(); vi++) { rotationalDisplacementQuaternion[vi] = Eigen::AngleAxisd(displacements[vi][3], Eigen::Vector3d::UnitX()) * Eigen::AngleAxisd(displacements[vi][4], Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(displacements[vi][5], Eigen::Vector3d::UnitZ()); } } #ifdef POLYSCOPE_DEFINED void unregister() const { if (!polyscope::hasCurveNetwork(getLabel())) { std::cerr << "No curve network registered with a name: " << getLabel() << std::endl; std::cerr << "Nothing to remove." << std::endl; return; } job->unregister(getLabel()); polyscope::removeCurveNetwork(getLabel()); } void registerForDrawing(const std::optional> &desiredColor = std::nullopt, const bool &shouldEnable = true, const bool &shouldShowFrames = false) const { PolyscopeInterface::init(); const std::shared_ptr &mesh = job->pMesh; polyscope::CurveNetwork *polyscopeHandle_deformedEdmeMesh; if (!polyscope::hasStructure(getLabel())) { polyscopeHandle_deformedEdmeMesh = polyscope::registerCurveNetwork(getLabel(), mesh->getEigenVertices(), mesh->getEigenEdges()); } polyscopeHandle_deformedEdmeMesh->setEnabled(shouldEnable); polyscopeHandle_deformedEdmeMesh->setRadius(0.002, true); if (desiredColor.has_value()) { const glm::vec3 desiredColor_glm(desiredColor.value()[0], desiredColor.value()[1], desiredColor.value()[2]); polyscopeHandle_deformedEdmeMesh->setColor(desiredColor_glm); } Eigen::MatrixX3d nodalDisplacements(mesh->VN(), 3); Eigen::MatrixX3d framesX(mesh->VN(), 3); Eigen::MatrixX3d framesY(mesh->VN(), 3); Eigen::MatrixX3d framesZ(mesh->VN(), 3); for (VertexIndex vi = 0; vi < mesh->VN(); vi++) { const Vector6d &nodalDisplacement = displacements[vi]; nodalDisplacements.row(vi) = Eigen::Vector3d(nodalDisplacement[0], nodalDisplacement[1], nodalDisplacement[2]); auto deformedNormal = rotationalDisplacementQuaternion[vi] * Eigen::Vector3d(0, 0, 1); auto deformedFrameY = rotationalDisplacementQuaternion[vi] * Eigen::Vector3d(0, 1, 0); auto deformedFrameX = rotationalDisplacementQuaternion[vi] * Eigen::Vector3d(1, 0, 0); framesX.row(vi) = Eigen::Vector3d(deformedFrameX[0], deformedFrameX[1], deformedFrameX[2]); framesY.row(vi) = Eigen::Vector3d(deformedFrameY[0], deformedFrameY[1], deformedFrameY[2]); framesZ.row(vi) = Eigen::Vector3d(deformedNormal[0], deformedNormal[1], deformedNormal[2]); } polyscopeHandle_deformedEdmeMesh->updateNodePositions(mesh->getEigenVertices() + nodalDisplacements); //if(showFramesOn.contains(vi)){ auto polyscopeHandle_frameX = polyscopeHandle_deformedEdmeMesh ->addNodeVectorQuantity("FrameX", framesX); polyscopeHandle_frameX->setVectorLengthScale(0.01); polyscopeHandle_frameX->setVectorRadius(0.01); polyscopeHandle_frameX->setVectorColor( /*polyscope::getNextUniqueColor()*/ glm::vec3(1, 0, 0)); auto polyscopeHandle_frameY = polyscopeHandle_deformedEdmeMesh ->addNodeVectorQuantity("FrameY", framesY); polyscopeHandle_frameY->setVectorLengthScale(0.01); polyscopeHandle_frameY->setVectorRadius(0.01); polyscopeHandle_frameY->setVectorColor( /*polyscope::getNextUniqueColor()*/ glm::vec3(0, 1, 0)); auto polyscopeHandle_frameZ = polyscopeHandle_deformedEdmeMesh ->addNodeVectorQuantity("FrameZ", framesZ); polyscopeHandle_frameZ->setVectorLengthScale(0.01); polyscopeHandle_frameZ->setVectorRadius(0.01); polyscopeHandle_frameZ->setVectorColor( /*polyscope::getNextUniqueColor()*/ glm::vec3(0, 0, 1)); //} job->registerForDrawing(getLabel()); static bool wasExecuted = false; if (!wasExecuted) { std::function callback = [&]() { static bool showFrames = shouldShowFrames; if (ImGui::Checkbox("Show Frames", &showFrames) && showFrames) { polyscopeHandle_frameX->setEnabled(showFrames); polyscopeHandle_frameY->setEnabled(showFrames); polyscopeHandle_frameZ->setEnabled(showFrames); } }; PolyscopeInterface::addUserCallback(callback); wasExecuted = true; } } #endif }; #endif // SIMULATIONHISTORY_HPP