From e9dbf9b5c1948c28fdeea6028386303c9f1e50c2 Mon Sep 17 00:00:00 2001 From: Bill Senior Date: Mon, 9 Dec 2024 12:50:55 +0100 Subject: [PATCH] GRIDEDIT-1548 Passing to other computer --- .../include/MeshKernel/CasulliRefinement.hpp | 6 +- libs/MeshKernel/include/MeshKernel/Mesh2D.hpp | 2 +- .../include/MeshKernel/SampleInterpolator.hpp | 58 +++++- libs/MeshKernel/src/CasulliRefinement.cpp | 8 +- libs/MeshKernel/src/Mesh2D.cpp | 2 +- libs/MeshKernel/src/MeshTriangulation.cpp | 63 +----- libs/MeshKernel/src/SampleInterpolator.cpp | 187 ++++++++++++++++-- .../tests/src/MeshRefinementTests.cpp | 25 +-- 8 files changed, 248 insertions(+), 103 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/CasulliRefinement.hpp b/libs/MeshKernel/include/MeshKernel/CasulliRefinement.hpp index 7e2e20ec2..dd8aa73f1 100644 --- a/libs/MeshKernel/include/MeshKernel/CasulliRefinement.hpp +++ b/libs/MeshKernel/include/MeshKernel/CasulliRefinement.hpp @@ -60,7 +60,7 @@ namespace meshkernel /// @param [in] polygon Area within which the mesh will be refined [[nodiscard]] static std::unique_ptr Compute(Mesh2D& mesh, const Polygons& polygon, - const SampleTriangulationInterpolator& interpolator, + const SampleInterpolator& interpolator, const int propertyId, const MeshRefinementParameters& refinementParameters); @@ -108,13 +108,13 @@ namespace meshkernel static std::vector InitialiseDepthBasedNodeMask(const Mesh2D& mesh, const Polygons& polygon, - const SampleTriangulationInterpolator& interpolator, + const SampleInterpolator& interpolator, const int propertyId, const MeshRefinementParameters& refinementParameters, bool& refinementRequested); static void RefineNodeMaskBasedOnDepths(const Mesh2D& mesh, - const SampleTriangulationInterpolator& interpolator, + const SampleInterpolator& interpolator, const int propertyId, const MeshRefinementParameters& refinementParameters, std::vector& nodeMask, diff --git a/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp b/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp index a302040b4..2ae64fbc5 100644 --- a/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp +++ b/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp @@ -254,7 +254,7 @@ namespace meshkernel /// @param[in] node The node index /// @param[in] enlargementFactor The factor by which the dual face is enlarged /// @param[out] dualFace The dual face to be calculated - void MakeDualFace(UInt node, double enlargementFactor, std::vector& dualFace); + void MakeDualFace(UInt node, double enlargementFactor, std::vector& dualFace) const; /// @brief Sorts the faces around a node, sorted in counter clock wise order /// @param[in] node The node index diff --git a/libs/MeshKernel/include/MeshKernel/SampleInterpolator.hpp b/libs/MeshKernel/include/MeshKernel/SampleInterpolator.hpp index 0688c8d00..feaf46857 100644 --- a/libs/MeshKernel/include/MeshKernel/SampleInterpolator.hpp +++ b/libs/MeshKernel/include/MeshKernel/SampleInterpolator.hpp @@ -39,6 +39,7 @@ #include "MeshKernel/Definitions.hpp" #include "MeshKernel/Entities.hpp" #include "MeshKernel/Exceptions.hpp" +#include "MeshKernel/Mesh2D.hpp" #include "MeshKernel/MeshTriangulation.hpp" #include "MeshKernel/Operations.hpp" #include "MeshKernel/Point.hpp" @@ -62,6 +63,7 @@ namespace meshkernel SetDataSpan(propertyId, sampleData); } + /// @brief Set sample data contained in an std::span object virtual void SetDataSpan(const int propertyId, const std::span& sampleData) = 0; /// @brief Interpolate the sample data set at the interpolation nodes. @@ -73,8 +75,21 @@ namespace meshkernel InterpolateSpan(propertyId, spanInterpolationNodes, spanResult); } + /// @brief Interpolate the sample data set at the interpolation nodes. virtual void InterpolateSpan(const int propertyId, const std::span& iterpolationNodes, std::span& result) const = 0; + /// @brief Interpolate the sample data set at the interpolation nodes. + template + void Interpolate(const int propertyId, const Mesh2D& mesh, const Location location, ScalarVectorType& result) const + { + std::span spanResult(result.data(), result.size()); + InterpolateSpan(propertyId, mesh, location, spanResult); + } + + /// @brief Interpolate the sample data. + // TODO may need a polygon too: const Polygons& polygon + virtual void InterpolateSpan(const int propertyId, const Mesh2D& mesh, const Location location, std::span& result) const = 0; + /// @brief Interpolate the sample data set at a single interpolation point. /// /// If interpolation at multiple points is required then better performance @@ -126,6 +141,11 @@ namespace meshkernel // void Interpolate(const int propertyId, const PointVectorType& iterpolationNodes, ScalarVectorType& result) const override; void InterpolateSpan(const int propertyId, const std::span& iterpolationNodes, std::span& result) const override; + // DOes nothing at the moment. + void InterpolateSpan(const int propertyId [[maybe_unused]], const Mesh2D& mesh [[maybe_unused]], const Location location [[maybe_unused]], std::span& result [[maybe_unused]]) const override + { + } + /// @brief Interpolate the sample data set at a single interpolation point. /// /// If interpolation at multiple points is required then better performance @@ -201,6 +221,8 @@ namespace meshkernel /// @brief Interpolate the sample data set at the interpolation nodes. void InterpolateSpan(const int propertyId, const std::span& iterpolationNodes, std::span& result) const override; + void InterpolateSpan(const int propertyId, const Mesh2D& mesh, const Location location, std::span& result) const override; + /// @brief Interpolate the sample data set at a single interpolation point. /// /// If interpolation at multiple points is required then better performance @@ -211,10 +233,12 @@ namespace meshkernel bool Contains(const int propertyId) const override; private: + static constexpr UInt MaximumNumberOfEdgesPerNode = 16; ///< Maximum number of edges per node + template static std::vector CombineCoordinates(const VectorType& xNodes, const VectorType& yNodes) { - std::vector result; + std::vector result(xNodes.size()); for (size_t i = 0; i < xNodes.size(); ++i) { @@ -228,8 +252,36 @@ namespace meshkernel /// @brief Interpolate the sample data on the element at the interpolation point. double InterpolateOnElement(const UInt elementId, const Point& interpolationPoint, const std::vector& sampleValues) const; + double ComputeOnPolygon(const int propertyId, + std::vector& polygon, + const Point& interpolationPoint, + const double relativeSearchRadius, + const bool useClosestSampleIfNoneAvailable, + const Projection projection, + std::vector& sampleCache) const; + + double GetSearchRadiusSquared(const std::vector& searchPolygon, + const Point& interpolationPoint, + const Projection projection) const; + + void GenerateSearchPolygon(const double relativeSearchRadius, + const Point& interpolationPoint, + std::vector& polygon, + const Projection projection) const; + + double GetSampleValueFromRTree(const int propertyId, const UInt index) const; + + double ComputeInterpolationResultFromNeighbors(const int propertyId, + const Point& interpolationPoint, + const std::vector& searchPolygon, + const Projection projection, + std::vector& sampleCache) const; + + std::vector m_samplePoints; + // SHould use the m_projection from the mesh in the interpolate function or + // needs to be passed to the interpolate function in the no mesh version Projection m_projection = Projection::cartesian; ///< The projection used InterpolationParameters m_interpolationParameters; @@ -238,6 +290,10 @@ namespace meshkernel /// @brief Map from sample id (int) to sample data. std::map> m_sampleData; + + ///< RTree of mesh nodes + std::unique_ptr m_nodeRTree; + }; } // namespace meshkernel diff --git a/libs/MeshKernel/src/CasulliRefinement.cpp b/libs/MeshKernel/src/CasulliRefinement.cpp index d9a9e083a..e82cc4cb7 100644 --- a/libs/MeshKernel/src/CasulliRefinement.cpp +++ b/libs/MeshKernel/src/CasulliRefinement.cpp @@ -57,7 +57,7 @@ std::unique_ptr meshkernel::CasulliRefinement::Compute(M std::unique_ptr meshkernel::CasulliRefinement::Compute(Mesh2D& mesh, const Polygons& polygon, - const SampleTriangulationInterpolator& interpolator, + const SampleInterpolator& interpolator, const int propertyId, const MeshRefinementParameters& refinementParameters) { @@ -221,14 +221,14 @@ void meshkernel::CasulliRefinement::InitialiseFaceNodes(const Mesh2D& mesh, std: } void meshkernel::CasulliRefinement::RefineNodeMaskBasedOnDepths(const Mesh2D& mesh, - const SampleTriangulationInterpolator& interpolator, + const SampleInterpolator& interpolator, const int propertyId, const MeshRefinementParameters& refinementParameters, std::vector& nodeMask [[maybe_unused]], bool& refinementRequested) { std::vector interpolatedDepth(mesh.m_edgesCenters.size()); - interpolator.Interpolate(propertyId, mesh.m_edgesCenters, interpolatedDepth); + interpolator.Interpolate(propertyId, mesh, Location::Nodes, interpolatedDepth); const double maxDtCourant = refinementParameters.max_courant_time; refinementRequested = false; @@ -296,7 +296,7 @@ void meshkernel::CasulliRefinement::RegisterNodesInsidePolygon(const Mesh2D& mes std::vector meshkernel::CasulliRefinement::InitialiseDepthBasedNodeMask(const Mesh2D& mesh, const Polygons& polygon, - const SampleTriangulationInterpolator& interpolator, + const SampleInterpolator& interpolator, const int propertyId, const MeshRefinementParameters& refinementParameters, bool& refinementRequested) diff --git a/libs/MeshKernel/src/Mesh2D.cpp b/libs/MeshKernel/src/Mesh2D.cpp index d41972e64..58fe67267 100644 --- a/libs/MeshKernel/src/Mesh2D.cpp +++ b/libs/MeshKernel/src/Mesh2D.cpp @@ -1501,7 +1501,7 @@ std::unique_ptr Mesh2D::TriangulateFaces() return triangulationAction; } -void Mesh2D::MakeDualFace(UInt node, double enlargementFactor, std::vector& dualFace) +void Mesh2D::MakeDualFace(UInt node, double enlargementFactor, std::vector& dualFace) const { const auto sortedFacesIndices = SortedFacesAroundNode(node); const auto numEdges = m_nodesNumEdges[node]; diff --git a/libs/MeshKernel/src/MeshTriangulation.cpp b/libs/MeshKernel/src/MeshTriangulation.cpp index be8c17502..8e4c10f37 100644 --- a/libs/MeshKernel/src/MeshTriangulation.cpp +++ b/libs/MeshKernel/src/MeshTriangulation.cpp @@ -188,36 +188,13 @@ void meshkernel::MeshTriangulation::Compute(const std::span& xNode meshkernel::UInt meshkernel::MeshTriangulation::FindNearestFace(const Point& pnt) const { m_elementCentreRTree->SearchNearestPoint(pnt); - // return m_elementCentreRTree->HasQueryResults() ? m_elementCentreRTree->GetQueryResult(0) : constants::missing::uintValue; - - bool printIt = 728900.0 <= pnt.x && pnt.x <= 729100.0 && -5.60855e6 <= pnt.y && pnt.y <= -5.60845e6; if (m_elementCentreRTree->HasQueryResults()) { UInt faceId = m_elementCentreRTree->GetQueryResult(0); - if (printIt) - { - std::cout << "face id = " << faceId << std::endl; - } - if (PointIsInElement(pnt, faceId)) { - - if (printIt) - { - auto nodes = GetNodes(faceId); - - std::cout << "face nodes: {" - << nodes[0].x << ", " << nodes[0].y << "} {" - << nodes[1].x << ", " << nodes[1].y << "} {" - << nodes[2].x << ", " << nodes[2].y << "} " << std::endl; - - auto nodeIds = GetNodeIds(faceId); - - std::cout << "face node ids: " << nodeIds[0] << ", " << nodeIds[1] << ", " << nodeIds[2] << std::endl; - } - return faceId; } else @@ -227,20 +204,10 @@ meshkernel::UInt meshkernel::MeshTriangulation::FindNearestFace(const Point& pnt BoundedArray<3 * MaximumNumberOfEdgesPerNode> elementsChecked; elementsChecked.push_back(faceId); - // TODO collect the 3 neighbour elements ids, so that later - // if we have to check the elements that are connected to a - // node then we do not have to check those elements we have - // checked already - for (UInt i = 0; i < edgeIds.size(); ++i) { const auto [neighbour1, neighbour2] = GetFaceIds(edgeIds[i]); - if (printIt) - { - std::cout << "neighbours = " << neighbour1 << " " << neighbour2 << std::endl; - } - if (neighbour1 != faceId && neighbour1 != constants::missing::uintValue) { if (PointIsInElement(pnt, neighbour1)) @@ -279,11 +246,6 @@ meshkernel::UInt meshkernel::MeshTriangulation::FindNearestFace(const Point& pnt { const auto faces = GetFaceIds(m_nodesEdges[nodeId][n]); - if (printIt) - { - std::cout << "faces = " << faces[0] << " " << faces[1] << std::endl; - } - if (faces[0] != constants::missing::uintValue && !elementsChecked.contains(faces[0])) { @@ -310,30 +272,7 @@ meshkernel::UInt meshkernel::MeshTriangulation::FindNearestFace(const Point& pnt } } - // else - { - return constants::missing::uintValue; - } - - // if (m_elementCentreRTree->HasQueryResults()) - // { - - // for (UInt i = 0; i < m_elementCentreRTree->GetQueryResultSize(); ++i ) - // { - // UInt id = m_elementCentreRTree->GetQueryResult(i); - - // if (id < NumberOfFaces() && PointIsInElement (pnt, id)) - // { - // return id; - // } - - // } - // } - - // // else - // { - // return constants::missing::uintValue; - // } + return constants::missing::uintValue; } std::array meshkernel::MeshTriangulation::GetNodes(const UInt faceId) const diff --git a/libs/MeshKernel/src/SampleInterpolator.cpp b/libs/MeshKernel/src/SampleInterpolator.cpp index e59a0b817..d4580faa8 100644 --- a/libs/MeshKernel/src/SampleInterpolator.cpp +++ b/libs/MeshKernel/src/SampleInterpolator.cpp @@ -87,7 +87,6 @@ double meshkernel::SampleTriangulationInterpolator::InterpolateOnElement(const U sampleValues[id2] == constants::missing::doubleValue || sampleValues[id3] == constants::missing::doubleValue) [[unlikely]] { - std::cout << " invalid sample " << std::endl; return result; } @@ -106,7 +105,6 @@ double meshkernel::SampleTriangulationInterpolator::InterpolateOnElement(const U if (std::abs(det) < 1e-12) [[unlikely]] { - std::cout << " det = " << det << std::endl; return result; } @@ -130,22 +128,13 @@ double meshkernel::SampleTriangulationInterpolator::InterpolateValue(const int p if (!evaluationPoint.IsValid()) { - std::cout << " invalid point " << std::endl; return result; } - bool printIt = 728900.0 <= evaluationPoint.x && evaluationPoint.x <= 729100.0 && -5.60855e6 <= evaluationPoint.y && evaluationPoint.y <= -5.60845e6; - - if (printIt) - { - std::cout << "interpolation point: " << evaluationPoint.x << ", " << evaluationPoint.y << std::endl; - } - const UInt elementId = m_triangulation.FindNearestFace(evaluationPoint); if (elementId == constants::missing::uintValue) { - std::cout << " element invalid " << std::endl; return result; } @@ -153,15 +142,6 @@ double meshkernel::SampleTriangulationInterpolator::InterpolateValue(const int p { const std::vector& propertyValues = m_sampleData.at(propertyId); result = InterpolateOnElement(elementId, evaluationPoint, propertyValues); - - if (printIt) - { - std::cout << "result = " << result << std::endl; - } - } - else - { - std::cout << " point not in element " << std::endl; } return result; @@ -188,6 +168,173 @@ void meshkernel::SampleAveragingInterpolator::InterpolateSpan(const int property } } + +double meshkernel::SampleAveragingInterpolator::GetSearchRadiusSquared(const std::vector& searchPolygon, + const Point& interpolationPoint, + const Projection projection) const +{ + double result = std::numeric_limits::lowest(); + + for (const auto& value : searchPolygon) + { + auto const squaredDistance = ComputeSquaredDistance(interpolationPoint, value, projection); + result = std::max(result, squaredDistance); + } + + return result; +} + +void meshkernel::SampleAveragingInterpolator::GenerateSearchPolygon(const double relativeSearchRadius, + const Point& interpolationPoint, + std::vector& polygon, + const Projection projection) const +{ + std::ranges::transform(std::begin(polygon), + std::end(polygon), + std::begin(polygon), + [&](const Point& p) + { return p * relativeSearchRadius + interpolationPoint * (1.0 - relativeSearchRadius); }); + + if (projection == Projection::spherical) + { + const auto boundingBox = BoundingBox(polygon); + const auto lowerLeft = boundingBox.lowerLeft(); + const auto upperRight = boundingBox.upperRight(); + + if (upperRight.x - lowerLeft.x <= 180.0) + { + return; + } + + auto const x_mean = 0.5 * (upperRight.x + lowerLeft.x); + + for (auto& value : polygon) + { + if (value.x < x_mean) + { + value.x = value.x + 360.0; + } + } + } + +} + +double meshkernel::SampleAveragingInterpolator::GetSampleValueFromRTree(const int propertyId, const UInt index) const +{ + UInt sampleIndex = m_nodeRTree->GetQueryResult(index); + return m_sampleData.at(propertyId)[sampleIndex]; +} + +double meshkernel::SampleAveragingInterpolator::ComputeInterpolationResultFromNeighbors(const int propertyId, + const Point& interpolationPoint, + const std::vector& searchPolygon, + const Projection projection, + std::vector& sampleCache) const +{ + sampleCache.clear(); + + const std::vector& propertyData (m_sampleData.at(propertyId)); + + Point polygonCentre{constants::missing::doubleValue, constants::missing::doubleValue}; + + if (projection == Projection::sphericalAccurate) + { + polygonCentre = ComputeAverageCoordinate(searchPolygon, projection); + } + + for (UInt i = 0; i < m_nodeRTree->GetQueryResultSize(); ++i) + { + auto const sampleIndex = m_nodeRTree->GetQueryResult(i); + auto const sampleValue = propertyData[sampleIndex]; + + if (sampleValue == constants::missing::doubleValue) + { + continue; + } + + const Point& samplePoint (m_samplePoints[sampleIndex]); + + if (IsPointInPolygonNodes(samplePoint, searchPolygon, projection, polygonCentre)) + { + sampleCache.emplace_back(samplePoint.x, samplePoint.y, sampleValue); + } + } + + return m_strategy->Calculate(interpolationPoint, sampleCache); +} + +double meshkernel::SampleAveragingInterpolator::ComputeOnPolygon(const int propertyId, + std::vector& polygon, + const Point& interpolationPoint, + const double relativeSearchRadius, + const bool useClosestSampleIfNoneAvailable, + const Projection projection, + std::vector& sampleCache) const +{ + + if (!interpolationPoint.IsValid()) + { + throw ConstraintError("Invalid interpolation point"); + } + + GenerateSearchPolygon(relativeSearchRadius, interpolationPoint, polygon, projection); + + double const searchRadiusSquared = GetSearchRadiusSquared(polygon, interpolationPoint, projection); + + if (searchRadiusSquared <= 0.0) + { + throw ConstraintError("Search radius: {} <= 0", searchRadiusSquared); + } + + m_nodeRTree->SearchPoints(interpolationPoint, searchRadiusSquared); + + if (!m_nodeRTree->HasQueryResults() && useClosestSampleIfNoneAvailable) + { + m_nodeRTree->SearchNearestPoint(interpolationPoint); + return m_nodeRTree->HasQueryResults() ? GetSampleValueFromRTree(propertyId, 0) : constants::missing::doubleValue; + } + else if (m_nodeRTree->HasQueryResults()) + { + return ComputeInterpolationResultFromNeighbors(propertyId, interpolationPoint, polygon, projection, sampleCache); + } + + return constants::missing::doubleValue; +} + + +void meshkernel::SampleAveragingInterpolator::InterpolateSpan(const int propertyId, const Mesh2D& mesh, const Location location, std::span& result) const +{ + std::ranges::fill(result, constants::missing::doubleValue); + + std::cout << "SampleAveragingInterpolator::InterpolateSpan " << std::endl; + + if (location == Location::Nodes) + { + std::cout << " location nodes: " << mesh.GetNumNodes() << std::endl; + std::vector dualFacePolygon; + dualFacePolygon.reserve (MaximumNumberOfEdgesPerNode); + std::vector sampleCache; + sampleCache.reserve (100); + + for (UInt n = 0; n < mesh.GetNumNodes(); ++n) + { + mesh.MakeDualFace(n, m_interpolationParameters.m_relativeSearchRadius, dualFacePolygon); + const double resultValue = ComputeOnPolygon(propertyId, + dualFacePolygon, + mesh.Node(n), + m_interpolationParameters.m_relativeSearchRadius, + m_interpolationParameters.m_useClosestIfNoneFound, + mesh.m_projection, + sampleCache); + result [n] = resultValue; + std::cout << "averaging interpoaltion: " << mesh.Node (n).x << " " << mesh.Node (n).y << " " << resultValue << std::endl; + } + + + } + +} + double meshkernel::SampleAveragingInterpolator::InterpolateValue(const int propertyId [[maybe_unused]], const Point& evaluationPoint [[maybe_unused]]) const { return constants::missing::doubleValue; diff --git a/libs/MeshKernel/tests/src/MeshRefinementTests.cpp b/libs/MeshKernel/tests/src/MeshRefinementTests.cpp index 9dd508c6c..f74933874 100644 --- a/libs/MeshKernel/tests/src/MeshRefinementTests.cpp +++ b/libs/MeshKernel/tests/src/MeshRefinementTests.cpp @@ -2808,7 +2808,7 @@ TEST(MeshRefinement, CasulliRefinementBasedOnDepthReal) std::vector yNodes(MaxSize); std::vector depths(MaxSize); - std::string fileName = "/home/wcs1/MeshKernel/MeshKernel/build_deb/stpete.xyz"; + std::string fileName = "/home/wcs1/MeshKernel/MeshKernel06/build_deb/stpete.xyz"; std::ifstream asciiFile; asciiFile.open(fileName.c_str()); @@ -2826,6 +2826,7 @@ TEST(MeshRefinement, CasulliRefinementBasedOnDepthReal) // asciiFile >> xNodes[i]; // asciiFile >> yNodes[i]; + asciiFile >> pnt.x; asciiFile >> pnt.y; asciiFile >> depths[i]; @@ -2849,18 +2850,20 @@ TEST(MeshRefinement, CasulliRefinementBasedOnDepthReal) // 6.959936535738328e+05 -5.595510022644172e+06 - for (size_t i = 0; i < MaxSize; ++i) - { + // for (size_t i = 0; i < MaxSize; ++i) + // { - if (yNodes[i] >= -5.595510022644172e+06) - { - std::cout << xNodes[i] << " " << yNodes[i] << " " << depths[i] << std::endl; - } - } + // // if (yNodes[i] >= -5.595510022644172e+06) + // { + // std::cout << i << " -- " << xNodes[i] << " " << yNodes[i] << " " << depths[i] << std::endl; + // } + // } - return; + std::cout << "range: " << minX << " " << maxX << " "<< minY << " " << maxY << " " << minD << " " << maxD << " " << std::endl; - mk::SampleTriangulationInterpolator depthInterpolator(xNodes, yNodes, mk::Projection::cartesian); + mk::InterpolationParameters interpolationParameters; + mk::SampleAveragingInterpolator depthInterpolator(xNodes, yNodes, mk::Projection::cartesian, interpolationParameters); + mk::SampleTriangulationInterpolator depthInterpolatorForMesh(xNodes, yNodes, mk::Projection::cartesian); depthInterpolator.SetData(1, depths); mk::Polygons polygon; @@ -2879,7 +2882,7 @@ TEST(MeshRefinement, CasulliRefinementBasedOnDepthReal) { std::cout << "checkint point " << i + 1 << " of " << nodes.size() << std::endl; - if (depthInterpolator.InterpolateValue(1, nodes[i]) == mk::constants::missing::doubleValue) + if (depthInterpolatorForMesh.InterpolateValue(1, nodes[i]) == mk::constants::missing::doubleValue) { nodes[i].SetInvalid(); }