#ifndef SIMULATIONSTRUCTS_HPP #define SIMULATIONSTRUCTS_HPP #include "simulationmesh.hpp" #include "nlohmann/json.hpp" struct SimulationHistory { SimulationHistory() {} size_t numberOfSteps{0}; std::string label; std::vector residualForces; std::vector kineticEnergy; std::vector potentialEnergies; std::vector redMarks; std::vector greenMarks; 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); residualForces.push_back(mesh.totalResidualForcesNorm); } void clear() { residualForces.clear(); kineticEnergy.clear(); potentialEnergies.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 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); } 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(); } bool load(const std::string &jsonFilename) { 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; 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.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 = std::unordered_map>( json[jsonLabels.nodalForces]); 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.label)) { label = json[jsonLabels.label]; } if (json.contains(jsonLabels.meshLabel)) { pMesh->setLabel(json[jsonLabels.meshLabel]); } 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") .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 (!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 { initPolyscope(); if (meshLabel.empty()) { assert(false); std::cerr << "Expects a mesh label on which to draw the simulation job." << std::endl; return; } auto structs = polyscope::state::structures; 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) { nodeColors[fixedVertex.first] = {0, 0, 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(false); } // 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(false); } } #endif // POLYSCOPE_DEFINED }; 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 struct SimulationResults { 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{' '}; 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() { 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(getLabel() + ".ply"); } void save() { const std::string filename(getLabel() + "_displacements.eigenBin"); Eigen::MatrixXd m = Utilities::toEigenMatrix(displacements); Eigen::write_binary(filename, m); } // The comparison of the results happens comparing the 6-dof nodal // displacements bool isEqual(const Eigen::MatrixXd &nodalDisplacements) { assert(nodalDisplacements.cols() == 6); Eigen::MatrixXd eigenDisplacements = Utilities::toEigenMatrix(this->displacements); const double errorNorm = (eigenDisplacements - nodalDisplacements).norm(); return errorNorm < 1e-10; // return eigenDisplacements.isApprox(nodalDisplacements); } #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; } polyscope::removeCurveNetwork(getLabel()); } void registerForDrawing(const std::optional> &desiredColor = std::nullopt, const bool &shouldEnable = true, const bool &showFrames = false) const { polyscope::options::groundPlaneEnabled = false; polyscope::view::upDir = polyscope::view::UpDir::ZUp; if (!polyscope::state::initialized) { polyscope::init(); } /* else { polyscope::removeAllStructures(); }*/ // const std::string undeformedMeshName = "Undeformed_" + label; // polyscope::registerCurveNetwork( // undeformedMeshName, mesh->getEigenVertices(), // mesh->getEigenEdges()); const std::shared_ptr &mesh = job->pMesh; auto 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); polyscopeHandle_deformedEdmeMesh->addNodeVectorQuantity("FrameX", framesX) ->setVectorLengthScale(0.1) ->setVectorRadius(0.01) ->setVectorColor(/*polyscope::getNextUniqueColor()*/ glm::vec3(1, 0, 0)) ->setEnabled(showFrames); polyscopeHandle_deformedEdmeMesh->addNodeVectorQuantity("FrameY", framesY) ->setVectorLengthScale(0.1) ->setVectorRadius(0.01) ->setVectorColor(/*polyscope::getNextUniqueColor()*/ glm::vec3(0, 1, 0)) ->setEnabled(showFrames); polyscopeHandle_deformedEdmeMesh->addNodeVectorQuantity("FrameZ", framesZ) ->setVectorLengthScale(0.1) ->setVectorRadius(0.01) ->setVectorColor(/*polyscope::getNextUniqueColor()*/ glm::vec3(0, 0, 1)) ->setEnabled(showFrames); job->registerForDrawing(getLabel()); } #endif }; #endif // SIMULATIONHISTORY_HPP