From 7f58576ec86ef7d645e656fd2431d4899b022adb Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 26 Apr 2023 22:15:15 +0200 Subject: [PATCH 01/89] shared_ptr as holder and splinelist based functions WIP --- cpp/splinepy/py/py_spline_list.hpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 0e506e39a..d1f271c70 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -12,12 +12,32 @@ namespace splinepy::py { namespace py = pybind11; +using PySplineList = std::vector>; + + +inline py::array_t Evaluate(const PySplineList& splist, const int nthreads) { +} + +inline py::array_t EvaluateUsingSameBasis(const PySplineList& splist, const int nthreads) { +} + +inline py::array_t Derivative(const PySplineList& splist, const int nthreads) { +} + +inline py::array_t DerivativeUsingSameBasis(const PySplineList& splist, const int nthreads) { +} + +inline py::array_t Sample(const PySplineList& splist, const int nthreads) { +} + + + /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have inline void add_spline_list_pyclass(py::module& m) { - using PySplineList = std::vector>; - py::bind_vector(m, "SplineList"); + // use shared_ptr as holder + py::bind_vector>(m, "SplineList"); m.def("reserve_list", [](PySplineList& splist, py::ssize_t size) { splist.reserve(size); }); From 21e001bd0ff9f08f76971126221d65d6516dd55c Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 11:13:30 +0200 Subject: [PATCH 02/89] format --- cpp/splinepy/py/py_spline_list.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index d1f271c70..c89208b2a 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -24,8 +24,8 @@ inline py::array_t EvaluateUsingSameBasis(const PySplineList& splist, co inline py::array_t Derivative(const PySplineList& splist, const int nthreads) { } -inline py::array_t DerivativeUsingSameBasis(const PySplineList& splist, const int nthreads) { -} +inline py::array_t Sample(const PySplineList& splist, + const py::array const int nthreads) {} inline py::array_t Sample(const PySplineList& splist, const int nthreads) { } From a83b089c8cebf074ac1c12094cf761819cfddb62 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 11:14:53 +0200 Subject: [PATCH 03/89] format --- cpp/splinepy/py/py_spline_list.hpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index c89208b2a..cd081a2d1 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -6,7 +6,7 @@ #include -PYBIND11_MAKE_OPAQUE(std::vector>) +// PYBIND11_MAKE_OPAQUE(std::vector>); namespace splinepy::py { @@ -15,22 +15,26 @@ namespace py = pybind11; using PySplineList = std::vector>; -inline py::array_t Evaluate(const PySplineList& splist, const int nthreads) { -} - -inline py::array_t EvaluateUsingSameBasis(const PySplineList& splist, const int nthreads) { -} +inline py::array_t +Evaluate(const PySplineList& splist, + const py::array_t& queries, + const int nthreads) {} -inline py::array_t Derivative(const PySplineList& splist, const int nthreads) { -} +inline py::array_t +Derivative(const PySplineList& splist, + const py::array_t& queries, + const py::array_t& orders, + const int nthreads) {} inline py::array_t Sample(const PySplineList& splist, - const py::array const int nthreads) {} - -inline py::array_t Sample(const PySplineList& splist, const int nthreads) { -} + const py::array_t resolutions, + const int nthreads) {} +inline std::shared_ptr +ExtractBoundarySplines(const PySplineList& splist, const int nthreads) {} +inline py::array_t BoundaryCenters(const PySplineList& splist, + const int nthreads) {} /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have From 0e98ae994dc7210e4cf49e022239ae677ae6e6d5 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 11:15:43 +0200 Subject: [PATCH 04/89] fix warning --- cpp/splinepy/splines/helpers/properties.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/splines/helpers/properties.hpp b/cpp/splinepy/splines/helpers/properties.hpp index ddcc22718..dcb44318b 100644 --- a/cpp/splinepy/splines/helpers/properties.hpp +++ b/cpp/splinepy/splines/helpers/properties.hpp @@ -83,11 +83,11 @@ inline void GetGrevilleAbscissae(const SplineType& spline, for (int j{}; j < cmr; ++j) { if constexpr (SplineType::kHasKnotVectors) { // + using IndexType = splinelib::Index; const auto& knot_vectors = spline.GetKnotVectors()[i_para_dim]; double factor{}; for (int k{0}; k < degrees[i_para_dim]; ++k) { - factor += - knot_vectors->operator[](typename splinelib::Index(k + j + 1)); + factor += knot_vectors->operator[](IndexType(k + j + 1)); } greville_abscissae[j] = static_cast(inv_factor * factor); } else { From 0a31c9040067b1278b0300d30cf245e0693bd0ff Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 10 May 2023 16:51:19 +0200 Subject: [PATCH 05/89] rough draft of even-ish load --- cpp/splinepy/py/py_spline_list.hpp | 64 +++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index cd081a2d1..2dd858d23 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -1,10 +1,13 @@ #pragma once +#include + // pybind #include #include #include +#include // PYBIND11_MAKE_OPAQUE(std::vector>); @@ -14,17 +17,58 @@ namespace py = pybind11; using PySplineList = std::vector>; +// evaluates splines of same para_dim and dim. +inline py::array_t Evaluate(const PySplineList& splist, + const py::array_t& queries, + const int nthreads) { + const int& para_dim = splist[0]->para_dim_; + const int& dim = splist[0]->dim_; + + CheckPyArrayShape(queries, {-1, para_dim}, true); + + // prepare input and output + double* queries_ptr = static_cast(queries.request().ptr); + const int n_splines = splist.size(); + const int n_queries = queries.size(); + const int n_total = n_splines * n_queries; + py::array_t evaluated({n_total, dim}); + double* evaluated_ptr = static_cast(evaluated.request().ptr); + + // each thread evaluates similar amount of queries from each spline + auto evaluate = [&](int begin, int end) { + const int n_common = std::div(end - begin, n_splines).quot; // floor + const int spline_start = begin % n_splines; + const int spline_end = (end - 1) % n_splines; + const int query_offset = std::div(begin, n_splines).quot; + + // loop splines + for (int i{}; i < n_splines; ++i) { + const auto& core = *splist[i]->Core(); + const int output_offset = i * n_queries * dim; + int query_start = query_offset; + + int n_additional{}; + if (i < spline_start) { + n_additional -= 1; + query_start += 1; + } + if (i <= spline_end) { + n_additional += 1; + } + + for (int j{query_start}; j < (query_start + n_common + n_additional); + ++j) { + core.Evaluate(&queries_ptr[j * para_dim], + &evaluated_ptr[output_offset + (j * dim)]); + } + } + } +} -inline py::array_t -Evaluate(const PySplineList& splist, - const py::array_t& queries, - const int nthreads) {} - -inline py::array_t -Derivative(const PySplineList& splist, - const py::array_t& queries, - const py::array_t& orders, - const int nthreads) {} +inline py::array_t Derivative(const PySplineList& splist, + const py::array_t& queries, + const py::array_t& orders, + const int nthreads) {} inline py::array_t Sample(const PySplineList& splist, const py::array_t resolutions, From d9192e134518538967a94d3718262f356dd9fe60 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 16 May 2023 18:19:17 +0200 Subject: [PATCH 06/89] use min of (total, nthread) --- cpp/splinepy/utils/nthreads.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/splinepy/utils/nthreads.hpp b/cpp/splinepy/utils/nthreads.hpp index acf2a5f05..35b0e5918 100644 --- a/cpp/splinepy/utils/nthreads.hpp +++ b/cpp/splinepy/utils/nthreads.hpp @@ -27,6 +27,8 @@ void NThreadExecution(const Func& f, return; } + nthread = std::min(total, nthread); + // get chunk size and prepare threads // make sure it rounds up const IndexT chunk_size = std::div((total + nthread - 1), nthread).quot; From bc967cb2ce7068c5a7576dbdd2973c19a8908661 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 16 May 2023 18:19:36 +0200 Subject: [PATCH 07/89] add fill query option --- cpp/splinepy/utils/grid_points.hpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cpp/splinepy/utils/grid_points.hpp b/cpp/splinepy/utils/grid_points.hpp index 144368aae..d50622b33 100644 --- a/cpp/splinepy/utils/grid_points.hpp +++ b/cpp/splinepy/utils/grid_points.hpp @@ -193,6 +193,9 @@ class CStyleArrayPointerGridPoints { } } + /// @brief given global id, returns grid point coordinate + /// @param id in + /// @param grid_point out void IdToGridPoint(const int& id, double* grid_point) const { int tmp{id}; for (int i{0}; i < dim_; ++i) { @@ -201,6 +204,27 @@ class CStyleArrayPointerGridPoints { } } + /// @brief fills array with full set of grid points + /// @param grid_point_to_fill + void Fill(double* grid_point_to_fill) const { + int i{}, tile{len_}, repeat{1}; + for (const auto& entry : entries_) { + const int entry_size = entry.size(); + tile /= entry_size; + int output_id{i}; + for (int j{}; j < tile; ++j) { + for (const auto& e : entry) { + for (int k{}; k < repeat; ++k) { + grid_point_to_fill[output_id] = e; + output_id += dim_; + } + } + } + ++i; + repeat *= entry_size; + } + } + int Size() const { return len_; } protected: From 206ff7d864e115e3a35c675301f9321ef9919014 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 16 May 2023 18:20:01 +0200 Subject: [PATCH 08/89] add CheckParaDimAndDim --- cpp/splinepy/py/py_spline_extensions.hpp | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cpp/splinepy/py/py_spline_extensions.hpp b/cpp/splinepy/py/py_spline_extensions.hpp index a1dbce43e..38678f059 100644 --- a/cpp/splinepy/py/py_spline_extensions.hpp +++ b/cpp/splinepy/py/py_spline_extensions.hpp @@ -14,6 +14,33 @@ namespace splinepy::py { namespace py = pybind11; +/// checks if given PySpline has specified para_dim and dim +/// optionally raise if they don't match +inline bool CheckParaDimAndDim(const PySpline& spline, + const int& para_dim, + const int& dim, + const bool throw_ = true) { + if (para_dim != spline.para_dim_ || dim != spline.dim_) { + if (throw_) { + splinepy::utils::PrintAndThrowError( + "Spline is expected to have (para_dim", + para_dim, + ", dim", + dim, + ".", + "But it has", + "(", + spline.para_dim_, + ",", + spline.dim_, + ")."); + } + return false; + } + + return true; +} + /// (multiple) knot insertion, single dimension inline py::list InsertKnots(std::shared_ptr& spline, int para_dim, From 658d35c325307b1fd79fe4aa728756a68d695e2e Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 16 May 2023 18:23:07 +0200 Subject: [PATCH 09/89] add EvaluateList and SampleList --- cpp/splinepy/py/py_spline_list.hpp | 315 ++++++++++++++++++++++++++--- 1 file changed, 283 insertions(+), 32 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 2dd858d23..de465f1e6 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -1,12 +1,15 @@ #pragma once #include +#include +#include // pybind #include #include #include +#include #include // PYBIND11_MAKE_OPAQUE(std::vector>); @@ -17,62 +20,297 @@ namespace py = pybind11; using PySplineList = std::vector>; -// evaluates splines of same para_dim and dim. -inline py::array_t Evaluate(const PySplineList& splist, - const py::array_t& queries, - const int nthreads) { +struct PySplineListNThreadExecutionHelper { + // thread level info + int n_common_, n_rem_, spline_start_, spline_end_, query_offset_, n_queries_, + dim_; + // spline level info + int query_start_, query_end_, output_offset_; + + PySplineListNThreadExecutionHelper(const int& begin_id, + const int& end_id, + const int& n_splines, + const int& n_queries, + const int& dim) { + const int thread_load = end_id - begin_id; + + // number of queries common to all splines + n_common_ = thread_load / n_splines; + + // number of remaining queries. + n_rem_ = thread_load % n_splines; + + // beginning spline id of this load + spline_start_ = begin_id % n_splines; + + // ending spline id of this load + spline_end_ = (end_id - 1) % n_splines; + + // offset to the very first query for this thread + query_offset_ = begin_id / n_splines; + + // copy n_queries and dim + n_queries_ = n_queries; + dim_ = dim; + } + + /// computes information required for each spline within the thread. + void SetSplineId(const int& spline_id) { + + // output ptr offset + output_offset_ = spline_id * n_queries_ * dim_; + + // init start + query_start_ = query_offset_; + + // get query start and end + int n_additional{}; + if (spline_id < spline_start_) { + n_additional -= 1; + query_start_ += 1; + } + if (n_rem_ != 0 && spline_id <= spline_end_) { + n_additional += 1; + } + query_end_ = query_start_ + n_common_ + n_additional; + } +}; + +inline void RaiseIfElementsHaveUnequalParaDimOrDim(const PySplineList& splist, + const int nthreads) { + // use first spline as guide line const int& para_dim = splist[0]->para_dim_; const int& dim = splist[0]->dim_; + auto check_dims = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + CheckParaDimAndDim(*splist[i], para_dim, dim, true); + } + }; + + splinepy::utils::NThreadExecution(check_dims, + static_cast(splist.size()), + nthreads); +} + +/// evaluates splines of same para_dim and dim. +inline py::array_t EvaluateList(const PySplineList& splist, + const py::array_t& queries, + const int nthreads, + const bool check_dims) { + // use first spline as dimension guide line + const int& para_dim = splist[0]->para_dim_; + const int& dim = splist[0]->dim_; + + // query dim check CheckPyArrayShape(queries, {-1, para_dim}, true); + // check dims if wanted. Else it will assume that every spline has same + // dimension as the first entry's + if (check_dims) { + RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); + } + // prepare input and output double* queries_ptr = static_cast(queries.request().ptr); const int n_splines = splist.size(); - const int n_queries = queries.size(); + const int n_queries = queries.shape(0); const int n_total = n_splines * n_queries; py::array_t evaluated({n_total, dim}); double* evaluated_ptr = static_cast(evaluated.request().ptr); // each thread evaluates similar amount of queries from each spline auto evaluate = [&](int begin, int end) { - const int n_common = std::div(end - begin, n_splines).quot; // floor - const int spline_start = begin % n_splines; - const int spline_end = (end - 1) % n_splines; - const int query_offset = std::div(begin, n_splines).quot; + auto thread_helper = PySplineListNThreadExecutionHelper(begin, + end, + n_splines, + n_queries, + dim); // loop splines for (int i{}; i < n_splines; ++i) { - const auto& core = *splist[i]->Core(); - const int output_offset = i * n_queries * dim; - int query_start = query_offset; - - int n_additional{}; - if (i < spline_start) { - n_additional -= 1; - query_start += 1; - } - if (i <= spline_end) { - n_additional += 1; - } + const auto& spl = *splist[i]; + const auto& core = *spl.Core(); - for (int j{query_start}; j < (query_start + n_common + n_additional); + // compute start and end of query, and output ptr offset. + thread_helper.SetSplineId(i); + + // queries for splines + for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; ++j) { - core.Evaluate(&queries_ptr[j * para_dim], - &evaluated_ptr[output_offset + (j * dim)]); + core.SplinepyEvaluate( + &queries_ptr[j * para_dim], + &evaluated_ptr[thread_helper.output_offset_ + (j * dim)]); } } - } + }; + + // exe + splinepy::utils::NThreadExecution(evaluate, n_total, nthreads); + + return evaluated; } -inline py::array_t Derivative(const PySplineList& splist, - const py::array_t& queries, - const py::array_t& orders, - const int nthreads) {} +inline py::array_t DerivativeList(const PySplineList& splist, + const py::array_t& queries, + const py::array_t& orders, + const int nthreads) {} -inline py::array_t Sample(const PySplineList& splist, - const py::array_t resolutions, - const int nthreads) {} +/// Samples equal resoltions for each para dim +inline py::array_t SampleList(const PySplineList& splist, + const int resolution, + const int nthreads, + const bool same_parametric_bounds, + const bool check_dims) { + // use first spline as dimension guide line + const auto& first_spline = *splist[0]; + const int& para_dim = first_spline.para_dim_; + const int& dim = first_spline.dim_; + + // dim check first + if (check_dims) { + RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); + } + + // n_queries, and n_splines; + int n_queries{1}; + const int n_splines = splist.size(); + + // prepare resolutions + int* resolutions = new int[para_dim]; + for (int i{}; i < para_dim; ++i) { + resolutions[i] = resolution; + n_queries *= resolution; + } + + // prepare input / output + const int n_total = n_splines * n_queries; + py::array_t sampled({n_total, dim}); + double* sampled_ptr = static_cast(sampled.request().ptr); + + // queries - will only be filled if same_parametric_bounds=true + double* queries; + + // create variable for lambda - needs different ones based on + // same_parametric_bounds + std::function thread_func; + + // GridPoints for same_parametric_bounds=true, this will have size=1. + std::vector grid_points; + + // if you know all the queries have same parametric bounds + // you don't need to re-compute queries + if (same_parametric_bounds) { + // get para bounds + double* para_bounds = new double[2 * para_dim]; + first_spline.Core()->SplinepyParametricBounds(para_bounds); + + // create grid points helper + grid_points.emplace_back( + splinepy::utils::CStyleArrayPointerGridPoints(para_dim, + para_bounds, + resolutions)); + const auto& gp_generator = grid_points[0]; + + // assign queries + queries = new double[n_queries * para_dim]; + + gp_generator.Fill(queries); + + // create lambda for nthread exe + thread_func = [&](int begin, int end) { + auto thread_helper = PySplineListNThreadExecutionHelper(begin, + end, + n_splines, + n_queries, + dim); + + // loop splines + for (int i{}; i < n_splines; ++i) { + const auto& core = *splist[i]->Core(); + + // compute start and end of query, and output ptr offset. + thread_helper.SetSplineId(i); + + // queries for splines + for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; + ++j) { + core.SplinepyEvaluate( + &queries[j * para_dim], + &sampled_ptr[thread_helper.output_offset_ + (j * dim)]); + } + } + }; + + // release + delete[] para_bounds; + + } else { + // we assume each spline has different parametric bounds + + // resize grid points to have same size as input spline list + grid_points.resize(n_splines); + + // create grid_points + auto create_grid_points = [&](int begin, int end) { + double* para_bounds = new double[2 * para_dim]; + + for (int i{begin}; i < end; ++i) { + // get para_bounds + splist[i]->Core()->SplinepyParametricBounds(para_bounds); + // setup grid points helper + grid_points[i].SetUp(para_dim, para_bounds, resolutions); + } + delete[] para_bounds; + }; + + // pre compute entries + splinepy::utils::NThreadExecution(create_grid_points, n_splines, nthreads); + + // similar to the one with same_parametric_bounds, except it computes query + // on the fly + thread_func = [&](int begin, int end) { + auto thread_helper = PySplineListNThreadExecutionHelper(begin, + end, + n_splines, + n_queries, + dim); + // each thread needs just one query array + double* q_ptr = new double[para_dim]; + // loop splines + for (int i{}; i < n_splines; ++i) { + // get spline core and grid point helper + const auto& core = *splist[i]->Core(); + const auto& gp_helper = grid_points[i]; + + // compute start and end of query, and output ptr offset. + thread_helper.SetSplineId(i); + + // queries for splines + for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; + ++j) { + gp_helper.IdToGridPoint(j, q_ptr); + + core.SplinepyEvaluate( + q_ptr, + &sampled_ptr[thread_helper.output_offset_ + (j * dim)]); + } + } + delete[] q_ptr; + }; + } + + // nthread exe + splinepy::utils::NThreadExecution(thread_func, n_total, nthreads); + + // release + delete[] resolutions; + if (same_parametric_bounds) { + delete[] queries; + } + + return sampled; +} inline std::shared_ptr ExtractBoundarySplines(const PySplineList& splist, const int nthreads) {} @@ -96,6 +334,19 @@ inline void add_spline_list_pyclass(py::module& m) { m.def("swap_list", [](PySplineList& splist_a, PySplineList& splist_b) { splist_a.swap(splist_b); }); + m.def("evaluate_list", + &EvaluateList, + py::arg("spline_list"), + py::arg("queries"), + py::arg("nthreads"), + py::arg("check_dims")); + m.def("sample_list", + &SampleList, + py::arg("spline_list"), + py::arg("resolution"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")); } } // namespace splinepy::py From c93d932e243186e1421782ffb74c7bda8e40d860 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 17 May 2023 12:53:57 +0200 Subject: [PATCH 10/89] replace new with D I vector --- cpp/splinepy/py/py_spline_list.hpp | 37 ++++++++++++++---------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index de465f1e6..71c94cf6a 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -10,15 +10,17 @@ #include #include +#include #include -// PYBIND11_MAKE_OPAQUE(std::vector>); - namespace splinepy::py { namespace py = pybind11; +// alias using PySplineList = std::vector>; +using IntVector = splinepy::utils::DefaultInitializationVector; +using DoubleVector = splinepy::utils::DefaultInitializationVector; struct PySplineListNThreadExecutionHelper { // thread level info @@ -177,7 +179,8 @@ inline py::array_t SampleList(const PySplineList& splist, const int n_splines = splist.size(); // prepare resolutions - int* resolutions = new int[para_dim]; + IntVector resolutions_vector(para_dim); + int* resolutions = resolutions_vector.data(); for (int i{}; i < para_dim; ++i) { resolutions[i] = resolution; n_queries *= resolution; @@ -189,6 +192,7 @@ inline py::array_t SampleList(const PySplineList& splist, double* sampled_ptr = static_cast(sampled.request().ptr); // queries - will only be filled if same_parametric_bounds=true + DoubleVector queries_vector; double* queries; // create variable for lambda - needs different ones based on @@ -202,7 +206,8 @@ inline py::array_t SampleList(const PySplineList& splist, // you don't need to re-compute queries if (same_parametric_bounds) { // get para bounds - double* para_bounds = new double[2 * para_dim]; + DoubleVector para_bounds_vector(2 * para_dim); + double* para_bounds = para_bounds_vector.data(); first_spline.Core()->SplinepyParametricBounds(para_bounds); // create grid points helper @@ -213,7 +218,8 @@ inline py::array_t SampleList(const PySplineList& splist, const auto& gp_generator = grid_points[0]; // assign queries - queries = new double[n_queries * para_dim]; + queries_vector.resize(n_queries * para_dim); + queries = queries_vector.data(); gp_generator.Fill(queries); @@ -242,9 +248,6 @@ inline py::array_t SampleList(const PySplineList& splist, } }; - // release - delete[] para_bounds; - } else { // we assume each spline has different parametric bounds @@ -253,7 +256,8 @@ inline py::array_t SampleList(const PySplineList& splist, // create grid_points auto create_grid_points = [&](int begin, int end) { - double* para_bounds = new double[2 * para_dim]; + DoubleVector para_bounds_vector(2 * para_dim); + double* para_bounds = para_bounds_vector.data(); for (int i{begin}; i < end; ++i) { // get para_bounds @@ -261,7 +265,6 @@ inline py::array_t SampleList(const PySplineList& splist, // setup grid points helper grid_points[i].SetUp(para_dim, para_bounds, resolutions); } - delete[] para_bounds; }; // pre compute entries @@ -276,7 +279,8 @@ inline py::array_t SampleList(const PySplineList& splist, n_queries, dim); // each thread needs just one query array - double* q_ptr = new double[para_dim]; + DoubleVector thread_query_vector(para_dim); + double* thread_query = thread_query_vector.data(); // loop splines for (int i{}; i < n_splines; ++i) { // get spline core and grid point helper @@ -289,26 +293,19 @@ inline py::array_t SampleList(const PySplineList& splist, // queries for splines for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; ++j) { - gp_helper.IdToGridPoint(j, q_ptr); + gp_helper.IdToGridPoint(j, thread_query); core.SplinepyEvaluate( - q_ptr, + thread_query, &sampled_ptr[thread_helper.output_offset_ + (j * dim)]); } } - delete[] q_ptr; }; } // nthread exe splinepy::utils::NThreadExecution(thread_func, n_total, nthreads); - // release - delete[] resolutions; - if (same_parametric_bounds) { - delete[] queries; - } - return sampled; } From 16668adf36fba9c89c0cb07fcad9dfac6e862d65 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 17 May 2023 13:55:01 +0200 Subject: [PATCH 11/89] add boundary extract and prepend list_ for splinelist funcs --- cpp/splinepy/py/py_spline_list.hpp | 76 +++++++++++++++++++++++++++--- tests/test_spline_list.py | 2 +- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 71c94cf6a..3267f1076 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -309,8 +309,65 @@ inline py::array_t SampleList(const PySplineList& splist, return sampled; } +// extracts boundary splines from splist. inline std::shared_ptr -ExtractBoundarySplines(const PySplineList& splist, const int nthreads) {} +ListExtractBoundaries(const PySplineList& splist, + const int nthreads, + const bool same_para_dims) { + const int n_splines = splist.size(); + // to accumulate + int n_boundaries{}; + // gather offsets for boundary add one last so that we can always find out + // n_boundary for each spline + IntVector boundary_offsets{}; + boundary_offsets.reserve(n_splines + 1); + + // in case of same para dim, it'd be equidistance + if (same_para_dims) { + // compute n_boundary + const int& n_boundary = splist[0]->para_dim_ * 2; + + // fill offset + int offset{}; + for (int i{}; i < n_splines; ++i) { + boundary_offsets.push_back(offset); + offset += n_boundary; + } + + // set n_boundaries + n_boundaries = offset; + } else { + // for un-equal boundary sizes, we lookup each one of them + int offset{}; + for (int i{}; i < n_splines; ++i) { + boundary_offsets.push_back(offset); + offset += splist[i]->para_dim_ * 2; + } + // set n_boundareis + n_boundaries = offset; + } + + // prepare output + auto out_boundaries = std::make_shared(n_boundaries); + + // prepare lambda + auto boundary_extract = + [&](int begin, int end) { + auto& ob_deref = *out_boundaries; + for (int i{begin}; i < end; ++i) { + auto& core = *splist[i]->Core(); + for (int j{}; j < boundary_offsets[i + 1] - boundary_offsets[i]; + ++i) { + ob_deref[i + j] = + std::make_shared(core.SplinepyExtractBoundary(j)); + } + } + }; + + splinepy::utils::NThreadExecution(boundary_extract, n_splines, nthreads); + + return out_boundaries; +} inline py::array_t BoundaryCenters(const PySplineList& splist, const int nthreads) {} @@ -322,28 +379,33 @@ inline void add_spline_list_pyclass(py::module& m) { // use shared_ptr as holder py::bind_vector>(m, "SplineList"); - m.def("reserve_list", + m.def("list_reserve", [](PySplineList& splist, py::ssize_t size) { splist.reserve(size); }); - m.def("resize_list", + m.def("list_resize", [](PySplineList& splist, py::ssize_t size) { splist.resize(size); }); - m.def("shrink_to_fit_list", + m.def("list_shrink_to_fit", [](PySplineList& splist) { splist.shrink_to_fit(); }); - m.def("swap_list", [](PySplineList& splist_a, PySplineList& splist_b) { + m.def("list_swap", [](PySplineList& splist_a, PySplineList& splist_b) { splist_a.swap(splist_b); }); - m.def("evaluate_list", + m.def("list_evaluate", &EvaluateList, py::arg("spline_list"), py::arg("queries"), py::arg("nthreads"), py::arg("check_dims")); - m.def("sample_list", + m.def("list_sample", &SampleList, py::arg("spline_list"), py::arg("resolution"), py::arg("nthreads"), py::arg("same_parametric_bounds"), py::arg("check_dims")); + m.def("list_extract_boundaries", + &ListExtractBoundaries, + py::arg("spline_list"), + py::arg("n_threads"), + py::arg("same_para_dims")); } } // namespace splinepy::py diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 69e81e386..ff6507eed 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -54,7 +54,7 @@ def test_spline_list(self): splist4 = SplineList() # should fill with nullptr - c.splinepy.splinepy_core.resize_list(splist4, len(splines)) + c.splinepy.splinepy_core.list_resize(splist4, len(splines)) assert len(splist4) == len(splines) for s4 in splist4: From 4eeaa23498fadf55208c83dc4630f49899e32952 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 17 May 2023 14:58:48 +0200 Subject: [PATCH 12/89] separate calc boundary center --- .../splines/helpers/scalar_type_wrapper.hpp | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp b/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp index 0b4857da5..e5ff60c54 100644 --- a/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp +++ b/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp @@ -33,21 +33,17 @@ void ScalarTypeEvaluate(const SplineType& spline, } } -/// @brief Evaluate Splines at boundary face centers -/// output should have size of 2 * para_dim * dim +/// @brief Computes parametric coordinate of boundary centers +/// @tparam SplineType +/// @tparam OutputType +/// @param spline +/// @param output should have size of 2 * para_dim * dim template -void ScalarTypeEvaluateBoundaryCenters(const SplineType& spline, - OutputType* output) { - +void ScalarTypeBoundaryCenters(const SplineType& spline, OutputType* output) { using DoubleVector = splinepy::utils::DefaultInitializationVector; // Prepare inputs const int para_dim = spline.SplinepyParaDim(); - const int dim = spline.SplinepyDim(); - const int n_faces = 2 * para_dim; - - DoubleVector queries_vector(n_faces * para_dim); - double* queries = queries_vector.data(); // get parametric bounds // They are given back in the order [min_0, min_1,...,max_0, max_1...] @@ -59,15 +55,35 @@ void ScalarTypeEvaluateBoundaryCenters(const SplineType& spline, for (int i{}; i < para_dim; ++i) { for (int j{}; j < para_dim; ++j) { if (i == j) { - queries[2 * i * para_dim + j] = bounds[j]; - queries[(2 * i + 1) * para_dim + j] = bounds[j + para_dim]; + output[2 * i * para_dim + j] = bounds[j]; + output[(2 * i + 1) * para_dim + j] = bounds[j + para_dim]; } else { const auto q = .5 * (bounds[j] + bounds[j + para_dim]); - queries[2 * i * para_dim + j] = q; - queries[(2 * i + 1) * para_dim + j] = q; + output[2 * i * para_dim + j] = q; + output[(2 * i + 1) * para_dim + j] = q; } } } +} + +/// @brief Evaluate Splines at boundary face centers +/// output should have size of 2 * para_dim * dim +template +void ScalarTypeEvaluateBoundaryCenters(const SplineType& spline, + OutputType* output) { + + using DoubleVector = splinepy::utils::DefaultInitializationVector; + + // Prepare inputs + const int para_dim = spline.SplinepyParaDim(); + const int dim = spline.SplinepyDim(); + const int n_faces = 2 * para_dim; + + DoubleVector queries_vector(n_faces * para_dim); + double* queries = queries_vector.data(); + + // compute queries + ScalarTypeBoundaryCenters(spline, queries); // Evaluate for (int i{}; i < n_faces; ++i) { From a3a05bb1d3ec5f1e84d0b6fcbb2f037c1a6ad11d Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 17 May 2023 21:54:23 +0200 Subject: [PATCH 13/89] edit boundary extract and add boundary center --- cpp/splinepy/py/py_spline_list.hpp | 120 +++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 22 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 3267f1076..f66292788 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -156,7 +157,9 @@ inline py::array_t EvaluateList(const PySplineList& splist, inline py::array_t DerivativeList(const PySplineList& splist, const py::array_t& queries, const py::array_t& orders, - const int nthreads) {} + const int nthreads) { + return py::array_t(); +} /// Samples equal resoltions for each para dim inline py::array_t SampleList(const PySplineList& splist, @@ -323,54 +326,121 @@ ListExtractBoundaries(const PySplineList& splist, boundary_offsets.reserve(n_splines + 1); // in case of same para dim, it'd be equidistance + int offset{}; // offset counter if (same_para_dims) { // compute n_boundary - const int& n_boundary = splist[0]->para_dim_ * 2; + const int n_boundary = splist[0]->para_dim_ * 2; // fill offset - int offset{}; + for (int i{}; i < n_splines; ++i) { boundary_offsets.push_back(offset); offset += n_boundary; } - // set n_boundaries - n_boundaries = offset; } else { // for un-equal boundary sizes, we lookup each one of them - int offset{}; for (int i{}; i < n_splines; ++i) { boundary_offsets.push_back(offset); offset += splist[i]->para_dim_ * 2; } - // set n_boundareis - n_boundaries = offset; } + // current offset value should equal to total boundary count + boundary_offsets.push_back(offset); + n_boundaries = offset; // prepare output auto out_boundaries = std::make_shared(n_boundaries); // prepare lambda - auto boundary_extract = - [&](int begin, int end) { - auto& ob_deref = *out_boundaries; - for (int i{begin}; i < end; ++i) { - auto& core = *splist[i]->Core(); - for (int j{}; j < boundary_offsets[i + 1] - boundary_offsets[i]; - ++i) { - ob_deref[i + j] = - std::make_shared(core.SplinepyExtractBoundary(j)); - } - } - }; + auto boundary_extract = [&](int begin, int end) { + // deref + auto& ob_deref = *out_boundaries; + for (int i{begin}; i < end; ++i) { + // get core + auto& core = *splist[i]->Core(); + // start of the offset + auto const& this_offset = boundary_offsets[i]; + // end of the offset + auto const& next_offset = boundary_offsets[i + 1]; + for (int j{}; j < next_offset - this_offset; ++j) { + ob_deref[this_offset + j] = + std::make_shared(core.SplinepyExtractBoundary(j)); + } + } + }; splinepy::utils::NThreadExecution(boundary_extract, n_splines, nthreads); return out_boundaries; } -inline py::array_t BoundaryCenters(const PySplineList& splist, - const int nthreads) {} +// computes boundary centers for splines of same para_dim and dim +inline py::array_t +ListBoundaryCenters(const PySplineList& splist, + const int nthreads, + const bool same_parametric_bounds, + const bool check_dims) { + // dim check first + if (check_dims) { + RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); + } + + // prepare output + const int n_splines = splist.size(); + const int& para_dim = splist[0]->para_dim_; + const int& dim = splist[0]->dim_; + const int n_queries = 2 * para_dim; + const int n_total = n_queries * n_splines; + py::array_t boundary_centers({n_total, dim}); + double* boundary_centers_ptr = + static_cast(boundary_centers.request().ptr); + + // we assume from here that all the splines have the same para_dim and dim + auto calc_boundary_centers = [&](int begin, int end) { + // each thread needs one query + DoubleVector queries_vector(2 * para_dim * para_dim); + double* queries = queries_vector.data(); + + // pre compute boundary centers if para bounds are the same + if (same_parametric_bounds) { + splinepy::splines::helpers::ScalarTypeBoundaryCenters(*splist[0]->Core(), + queries); + } + + // prepare thread helper + auto thread_helper = PySplineListNThreadExecutionHelper(begin, + end, + n_splines, + n_queries, + dim); + // loop splines + for (int i{}; i < n_splines; ++i) { + // get core spline (SplinepyBase) + const auto& core = *splist[i]->Core(); + + // compute start and end of query, and output ptr offset. + thread_helper.SetSplineId(i); + + // in case para_bounds are assumed to be different, compute query + if (!same_parametric_bounds) { + splinepy::splines::helpers::ScalarTypeBoundaryCenters(core, queries); + } + + // queries for splines + for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; + ++j) { + core.SplinepyEvaluate( + &queries[j * para_dim], + &boundary_centers_ptr[thread_helper.output_offset_ + (j * dim)]); + } + } + }; + + splinepy::utils::NThreadExecution(calc_boundary_centers, n_total, nthreads); + + return boundary_centers; +} /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have @@ -406,6 +476,12 @@ inline void add_spline_list_pyclass(py::module& m) { py::arg("spline_list"), py::arg("n_threads"), py::arg("same_para_dims")); + m.def("list_boundary_centers", + &ListBoundaryCenters, + py::arg("spline_list"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")); } } // namespace splinepy::py From b51ea4df160c6c7bfbedcb1ef855ae460172b6b4 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 17 May 2023 22:02:09 +0200 Subject: [PATCH 14/89] fix function name and avoid update ListSample --- cpp/splinepy/py/py_spline_list.hpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index f66292788..fee8b9aeb 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -97,7 +97,7 @@ inline void RaiseIfElementsHaveUnequalParaDimOrDim(const PySplineList& splist, } /// evaluates splines of same para_dim and dim. -inline py::array_t EvaluateList(const PySplineList& splist, +inline py::array_t ListEvaluate(const PySplineList& splist, const py::array_t& queries, const int nthreads, const bool check_dims) { @@ -154,7 +154,7 @@ inline py::array_t EvaluateList(const PySplineList& splist, return evaluated; } -inline py::array_t DerivativeList(const PySplineList& splist, +inline py::array_t ListDerivative(const PySplineList& splist, const py::array_t& queries, const py::array_t& orders, const int nthreads) { @@ -162,7 +162,7 @@ inline py::array_t DerivativeList(const PySplineList& splist, } /// Samples equal resoltions for each para dim -inline py::array_t SampleList(const PySplineList& splist, +inline py::array_t ListSample(const PySplineList& splist, const int resolution, const int nthreads, const bool same_parametric_bounds, @@ -202,8 +202,10 @@ inline py::array_t SampleList(const PySplineList& splist, // same_parametric_bounds std::function thread_func; - // GridPoints for same_parametric_bounds=true, this will have size=1. - std::vector grid_points; + // GridPoints for same_parametric_bounds=true, else, won't be used + splinepy::utils::DefaultInitializationVector< + splinepy::utils::CStyleArrayPointerGridPoints> + grid_points; // if you know all the queries have same parametric bounds // you don't need to re-compute queries @@ -213,13 +215,9 @@ inline py::array_t SampleList(const PySplineList& splist, double* para_bounds = para_bounds_vector.data(); first_spline.Core()->SplinepyParametricBounds(para_bounds); - // create grid points helper - grid_points.emplace_back( - splinepy::utils::CStyleArrayPointerGridPoints(para_dim, + splinepy::utils::CStyleArrayPointerGridPoints gp_generator(para_dim, para_bounds, - resolutions)); - const auto& gp_generator = grid_points[0]; - + resolutions); // assign queries queries_vector.resize(n_queries * para_dim); queries = queries_vector.data(); @@ -459,13 +457,13 @@ inline void add_spline_list_pyclass(py::module& m) { splist_a.swap(splist_b); }); m.def("list_evaluate", - &EvaluateList, + &ListEvaluate, py::arg("spline_list"), py::arg("queries"), py::arg("nthreads"), py::arg("check_dims")); m.def("list_sample", - &SampleList, + &ListSample, py::arg("spline_list"), py::arg("resolution"), py::arg("nthreads"), From ec1bd8faa0dbbf514c2e2c65c590071281d9e03d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 20:07:26 +0000 Subject: [PATCH 15/89] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cpp/splinepy/py/py_spline_list.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index fee8b9aeb..bd9af7276 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -216,8 +216,8 @@ inline py::array_t ListSample(const PySplineList& splist, first_spline.Core()->SplinepyParametricBounds(para_bounds); splinepy::utils::CStyleArrayPointerGridPoints gp_generator(para_dim, - para_bounds, - resolutions); + para_bounds, + resolutions); // assign queries queries_vector.resize(n_queries * para_dim); queries = queries_vector.data(); From b8b704a906a80a0ad6ca8637dd16d4c80e9a7710 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 17 May 2023 22:10:20 +0200 Subject: [PATCH 16/89] fix comment --- cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp b/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp index e5ff60c54..1424c2628 100644 --- a/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp +++ b/cpp/splinepy/splines/helpers/scalar_type_wrapper.hpp @@ -37,7 +37,7 @@ void ScalarTypeEvaluate(const SplineType& spline, /// @tparam SplineType /// @tparam OutputType /// @param spline -/// @param output should have size of 2 * para_dim * dim +/// @param output should have size of 2 * para_dim * para_dim template void ScalarTypeBoundaryCenters(const SplineType& spline, OutputType* output) { using DoubleVector = splinepy::utils::DefaultInitializationVector; From 5a2aad58df277055011a86bc0be0185568ca5f0e Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Thu, 18 May 2023 14:03:57 +0200 Subject: [PATCH 17/89] list functions are now member functions --- cpp/splinepy/py/py_spline_list.hpp | 73 ++++++++++++++++-------------- tests/test_spline_list.py | 2 +- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index bd9af7276..37394a9b8 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -440,46 +440,49 @@ ListBoundaryCenters(const PySplineList& splist, return boundary_centers; } +inline std::shared_ptr ListCompose() {} +inline std::shared_ptr ListCompositionDerivative() {} + /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have inline void add_spline_list_pyclass(py::module& m) { // use shared_ptr as holder - py::bind_vector>(m, "SplineList"); - - m.def("list_reserve", - [](PySplineList& splist, py::ssize_t size) { splist.reserve(size); }); - m.def("list_resize", - [](PySplineList& splist, py::ssize_t size) { splist.resize(size); }); - m.def("list_shrink_to_fit", - [](PySplineList& splist) { splist.shrink_to_fit(); }); - m.def("list_swap", [](PySplineList& splist_a, PySplineList& splist_b) { - splist_a.swap(splist_b); - }); - m.def("list_evaluate", - &ListEvaluate, - py::arg("spline_list"), - py::arg("queries"), - py::arg("nthreads"), - py::arg("check_dims")); - m.def("list_sample", - &ListSample, - py::arg("spline_list"), - py::arg("resolution"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")); - m.def("list_extract_boundaries", - &ListExtractBoundaries, - py::arg("spline_list"), - py::arg("n_threads"), - py::arg("same_para_dims")); - m.def("list_boundary_centers", - &ListBoundaryCenters, - py::arg("spline_list"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")); + auto klasse = py::bind_vector>( + m, + "SplineList"); + + klasse + .def("reserve", + [](PySplineList& splist, py::ssize_t size) { splist.reserve(size); }) + .def("resize", + [](PySplineList& splist, py::ssize_t size) { splist.resize(size); }) + .def("shrink_to_fit", + [](PySplineList& splist) { splist.shrink_to_fit(); }) + .def("swap", + [](PySplineList& splist_a, PySplineList& splist_b) { + splist_a.swap(splist_b); + }) + .def("evaluate", + &ListEvaluate, + py::arg("queries"), + py::arg("nthreads"), + py::arg("check_dims")) + .def("sample", + &ListSample, + py::arg("resolution"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")) + .def("extract_boundaries", + &ListExtractBoundaries, + py::arg("n_threads"), + py::arg("same_para_dims")) + .def("boundary_centers", + &ListBoundaryCenters, + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")); } } // namespace splinepy::py diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index ff6507eed..c42532c15 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -54,7 +54,7 @@ def test_spline_list(self): splist4 = SplineList() # should fill with nullptr - c.splinepy.splinepy_core.list_resize(splist4, len(splines)) + splist4.resize(len(splines)) assert len(splist4) == len(splines) for s4 in splist4: From 65288d2bf9bb4cb4413439a14456d33f1358fbc1 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Thu, 18 May 2023 14:21:41 +0200 Subject: [PATCH 18/89] add raise_dim_mismatch --- cpp/splinepy/py/py_spline_extensions.hpp | 1 - cpp/splinepy/py/py_spline_list.hpp | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline_extensions.hpp b/cpp/splinepy/py/py_spline_extensions.hpp index 38678f059..ee04b1648 100644 --- a/cpp/splinepy/py/py_spline_extensions.hpp +++ b/cpp/splinepy/py/py_spline_extensions.hpp @@ -316,7 +316,6 @@ inline void add_spline_extensions(py::module& m) { m.def("core_ref_count", &splinepy::py::CoreRefCount, py::arg("spline")); m.def("have_core", &splinepy::py::HaveCore, py::arg("spline")); m.def("annul_core", &splinepy::py::AnnulCore, py::arg("spline")); - ; } } // namespace splinepy::py diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 37394a9b8..9575a1925 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -79,6 +79,9 @@ struct PySplineListNThreadExecutionHelper { } }; +/// @brief raises if elements' para_dim and dims aren't equal +/// @param splist +/// @param nthreads inline void RaiseIfElementsHaveUnequalParaDimOrDim(const PySplineList& splist, const int nthreads) { // use first spline as guide line @@ -463,6 +466,9 @@ inline void add_spline_list_pyclass(py::module& m) { [](PySplineList& splist_a, PySplineList& splist_b) { splist_a.swap(splist_b); }) + .def("raise_dim_mismatch", + &RaiseIfElementsHaveUnequalParaDimOrDim, + py::arg("nthreads")) .def("evaluate", &ListEvaluate, py::arg("queries"), From 72828e6d9e79899e3e133758fde873c2fc6f216c Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 12:11:07 +0200 Subject: [PATCH 19/89] use nthread step query instead of NThreadExecution helper --- cpp/splinepy/py/py_spline_list.hpp | 264 ++++++++++------------------- cpp/splinepy/utils/nthreads.hpp | 50 ++++-- 2 files changed, 125 insertions(+), 189 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 9575a1925..b4146a930 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -23,62 +23,6 @@ using PySplineList = std::vector>; using IntVector = splinepy::utils::DefaultInitializationVector; using DoubleVector = splinepy::utils::DefaultInitializationVector; -struct PySplineListNThreadExecutionHelper { - // thread level info - int n_common_, n_rem_, spline_start_, spline_end_, query_offset_, n_queries_, - dim_; - // spline level info - int query_start_, query_end_, output_offset_; - - PySplineListNThreadExecutionHelper(const int& begin_id, - const int& end_id, - const int& n_splines, - const int& n_queries, - const int& dim) { - const int thread_load = end_id - begin_id; - - // number of queries common to all splines - n_common_ = thread_load / n_splines; - - // number of remaining queries. - n_rem_ = thread_load % n_splines; - - // beginning spline id of this load - spline_start_ = begin_id % n_splines; - - // ending spline id of this load - spline_end_ = (end_id - 1) % n_splines; - - // offset to the very first query for this thread - query_offset_ = begin_id / n_splines; - - // copy n_queries and dim - n_queries_ = n_queries; - dim_ = dim; - } - - /// computes information required for each spline within the thread. - void SetSplineId(const int& spline_id) { - - // output ptr offset - output_offset_ = spline_id * n_queries_ * dim_; - - // init start - query_start_ = query_offset_; - - // get query start and end - int n_additional{}; - if (spline_id < spline_start_) { - n_additional -= 1; - query_start_ += 1; - } - if (n_rem_ != 0 && spline_id <= spline_end_) { - n_additional += 1; - } - query_end_ = query_start_ + n_common_ + n_additional; - } -}; - /// @brief raises if elements' para_dim and dims aren't equal /// @param splist /// @param nthreads @@ -126,33 +70,20 @@ inline py::array_t ListEvaluate(const PySplineList& splist, double* evaluated_ptr = static_cast(evaluated.request().ptr); // each thread evaluates similar amount of queries from each spline - auto evaluate = [&](int begin, int end) { - auto thread_helper = PySplineListNThreadExecutionHelper(begin, - end, - n_splines, - n_queries, - dim); - - // loop splines - for (int i{}; i < n_splines; ++i) { - const auto& spl = *splist[i]; - const auto& core = *spl.Core(); - - // compute start and end of query, and output ptr offset. - thread_helper.SetSplineId(i); - - // queries for splines - for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; - ++j) { - core.SplinepyEvaluate( - &queries_ptr[j * para_dim], - &evaluated_ptr[thread_helper.output_offset_ + (j * dim)]); - } + auto evaluate_step = [&](int begin, int total_) { + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + splist[i_spline]->Core()->SplinepyEvaluate( + &queries_ptr[i_query * para_dim], + &evaluated_ptr[(i_spline * n_queries + i_query) * dim]); } }; // exe - splinepy::utils::NThreadExecution(evaluate, n_total, nthreads); + splinepy::utils::NThreadExecution(evaluate_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); return evaluated; } @@ -197,66 +128,49 @@ inline py::array_t ListSample(const PySplineList& splist, py::array_t sampled({n_total, dim}); double* sampled_ptr = static_cast(sampled.request().ptr); - // queries - will only be filled if same_parametric_bounds=true - DoubleVector queries_vector; - double* queries; - - // create variable for lambda - needs different ones based on - // same_parametric_bounds - std::function thread_func; - - // GridPoints for same_parametric_bounds=true, else, won't be used - splinepy::utils::DefaultInitializationVector< - splinepy::utils::CStyleArrayPointerGridPoints> - grid_points; - // if you know all the queries have same parametric bounds // you don't need to re-compute queries if (same_parametric_bounds) { + // get para bounds DoubleVector para_bounds_vector(2 * para_dim); double* para_bounds = para_bounds_vector.data(); first_spline.Core()->SplinepyParametricBounds(para_bounds); + // prepare queries + DoubleVector queries_vector(n_queries * para_dim); + double* queries = queries_vector.data(); + + // use grid point generator to fill queries splinepy::utils::CStyleArrayPointerGridPoints gp_generator(para_dim, para_bounds, resolutions); - // assign queries - queries_vector.resize(n_queries * para_dim); - queries = queries_vector.data(); - gp_generator.Fill(queries); // create lambda for nthread exe - thread_func = [&](int begin, int end) { - auto thread_helper = PySplineListNThreadExecutionHelper(begin, - end, - n_splines, - n_queries, - dim); - - // loop splines - for (int i{}; i < n_splines; ++i) { - const auto& core = *splist[i]->Core(); - - // compute start and end of query, and output ptr offset. - thread_helper.SetSplineId(i); - - // queries for splines - for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; - ++j) { - core.SplinepyEvaluate( - &queries[j * para_dim], - &sampled_ptr[thread_helper.output_offset_ + (j * dim)]); - } + auto sample_same_bounds_step = [&](int begin, int total_) { + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + splist[i_spline]->Core()->SplinepyEvaluate( + &queries[i_query * para_dim], + &sampled_ptr[(i_spline * n_queries + i_query) * dim]); } }; + splinepy::utils::NThreadExecution(sample_same_bounds_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + } else { - // we assume each spline has different parametric bounds + // here, we will execute 2 times: + // first, to create grid point helpers for each spline + // second, to sample - // resize grid points to have same size as input spline list - grid_points.resize(n_splines); + // create a container to hold grid point helper. + splinepy::utils::DefaultInitializationVector< + splinepy::utils::CStyleArrayPointerGridPoints> + grid_points(n_splines); // create grid_points auto create_grid_points = [&](int begin, int end) { @@ -271,44 +185,32 @@ inline py::array_t ListSample(const PySplineList& splist, } }; - // pre compute entries + // pre compute entries -> this one is a chunk query splinepy::utils::NThreadExecution(create_grid_points, n_splines, nthreads); // similar to the one with same_parametric_bounds, except it computes query // on the fly - thread_func = [&](int begin, int end) { - auto thread_helper = PySplineListNThreadExecutionHelper(begin, - end, - n_splines, - n_queries, - dim); + auto sample_step = [&](int begin, int total_) { // each thread needs just one query array DoubleVector thread_query_vector(para_dim); double* thread_query = thread_query_vector.data(); - // loop splines - for (int i{}; i < n_splines; ++i) { - // get spline core and grid point helper - const auto& core = *splist[i]->Core(); - const auto& gp_helper = grid_points[i]; - - // compute start and end of query, and output ptr offset. - thread_helper.SetSplineId(i); - - // queries for splines - for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; - ++j) { - gp_helper.IdToGridPoint(j, thread_query); - - core.SplinepyEvaluate( - thread_query, - &sampled_ptr[thread_helper.output_offset_ + (j * dim)]); - } + + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + const auto& gp_helper = grid_points[i_spline]; + gp_helper.IdToGridPoint(i_query, thread_query); + splist[i_spline]->Core()->SplinepyEvaluate( + thread_query, + &sampled_ptr[(i_spline * n_queries + i_query) * dim]); } }; - } - // nthread exe - splinepy::utils::NThreadExecution(thread_func, n_total, nthreads); + // exe - this one is step + splinepy::utils::NThreadExecution(sample_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + } return sampled; } @@ -388,6 +290,7 @@ ListBoundaryCenters(const PySplineList& splist, } // prepare output + // from here we assume that all the splines have the same para_dim and dim const int n_splines = splist.size(); const int& para_dim = splist[0]->para_dim_; const int& dim = splist[0]->dim_; @@ -397,48 +300,59 @@ ListBoundaryCenters(const PySplineList& splist, double* boundary_centers_ptr = static_cast(boundary_centers.request().ptr); - // we assume from here that all the splines have the same para_dim and dim - auto calc_boundary_centers = [&](int begin, int end) { + // pre-compute boundary centers + DoubleVector para_bounds; + double* para_bounds_ptr; + if (!same_parametric_bounds) { + para_bounds.resize(n_total * para_dim); + para_bounds_ptr = para_bounds.data(); + const int stride = n_queries * para_dim; + + auto calc_para_bounds = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + splinepy::splines::helpers::ScalarTypeBoundaryCenters( + *splist[i]->Core(), + ¶_bounds_ptr[stride * i]); + } + }; + + // exe + splinepy::utils::NThreadExecution(calc_para_bounds, n_splines, nthreads); + } + + auto calc_boundary_centers_step = [&](int begin, int total_) { // each thread needs one query - DoubleVector queries_vector(2 * para_dim * para_dim); - double* queries = queries_vector.data(); + DoubleVector queries_vector; /* unused if same_parametric_bounds=true*/ + double* queries; // pre compute boundary centers if para bounds are the same if (same_parametric_bounds) { + queries_vector.resize(2 * para_dim * para_dim); + queries = queries_vector.data(); splinepy::splines::helpers::ScalarTypeBoundaryCenters(*splist[0]->Core(), queries); } - // prepare thread helper - auto thread_helper = PySplineListNThreadExecutionHelper(begin, - end, - n_splines, - n_queries, - dim); - // loop splines - for (int i{}; i < n_splines; ++i) { - // get core spline (SplinepyBase) - const auto& core = *splist[i]->Core(); - - // compute start and end of query, and output ptr offset. - thread_helper.SetSplineId(i); + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + const auto& core = *splist[i_spline]->Core(); - // in case para_bounds are assumed to be different, compute query + // get ptr start if (!same_parametric_bounds) { - splinepy::splines::helpers::ScalarTypeBoundaryCenters(core, queries); + queries = ¶_bounds_ptr[i_spline * n_queries * para_dim]; } - // queries for splines - for (int j{thread_helper.query_start_}; j < thread_helper.query_end_; - ++j) { - core.SplinepyEvaluate( - &queries[j * para_dim], - &boundary_centers_ptr[thread_helper.output_offset_ + (j * dim)]); - } + // eval + core.SplinepyEvaluate( + &queries[i_query * para_dim], + &boundary_centers_ptr[(i_spline * n_queries + i_query) * dim]); } }; - splinepy::utils::NThreadExecution(calc_boundary_centers, n_total, nthreads); + splinepy::utils::NThreadExecution(calc_boundary_centers_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); return boundary_centers; } diff --git a/cpp/splinepy/utils/nthreads.hpp b/cpp/splinepy/utils/nthreads.hpp index 35b0e5918..ffd8946a9 100644 --- a/cpp/splinepy/utils/nthreads.hpp +++ b/cpp/splinepy/utils/nthreads.hpp @@ -5,13 +5,22 @@ namespace splinepy::utils { +enum class NThreadQueryType : int { + // split queries into chunks that can allows contiguous visit + Chunk = 0, + // visit queries with nthread steps. similar to python's + // `queries[start::nthread]` + Step = 1 +}; + /// N-Thread execution. Queries will be splitted into chunks and each thread /// will execute those. template -void NThreadExecution(const Func& f, - const IndexT& total, - IndexT nthread /* copy */ -) { +void NThreadExecution( + const Func& f, + const IndexT& total, + IndexT nthread /* copy */, + const NThreadQueryType query_type = NThreadQueryType::Chunk) { // For any negative value, std::thread::hardware_concurrency() will be taken // If you are not satisfied with returned value, use positive value. // For more info: @@ -27,21 +36,34 @@ void NThreadExecution(const Func& f, return; } + // we don't want nthread to exceed total nthread = std::min(total, nthread); - // get chunk size and prepare threads - // make sure it rounds up - const IndexT chunk_size = std::div((total + nthread - 1), nthread).quot; + // reserve thread pool std::vector thread_pool; thread_pool.reserve(nthread); - for (int i{0}; i < (nthread - 1); i++) { - thread_pool.emplace_back( - std::thread{f, i * chunk_size, (i + 1) * chunk_size}); - } - { - // last one - thread_pool.emplace_back(std::thread{f, (nthread - 1) * chunk_size, total}); + if (query_type == NThreadQueryType::Chunk) { + // get chunk size and prepare threads + // make sure it rounds up + const IndexT chunk_size = std::div((total + nthread - 1), nthread).quot; + + for (int i{}; i < (nthread - 1); ++i) { + thread_pool.emplace_back( + std::thread{f, i * chunk_size, (i + 1) * chunk_size}); + } + { + // last one + thread_pool.emplace_back( + std::thread{f, (nthread - 1) * chunk_size, total}); + } + + } else if (query_type == NThreadQueryType::Step) { + for (int i{}; i < nthread; ++i) { + // strictly, (most of the time) we don't need total for lambda funcs + // keep it for conformity + thread_pool.emplace_back(std::thread{f, i, total}); + } } for (auto& t : thread_pool) { From ee0a165983afedfc231b8a68cd0f581f18d71a2c Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 12:15:12 +0200 Subject: [PATCH 20/89] edit comments --- cpp/splinepy/utils/nthreads.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/utils/nthreads.hpp b/cpp/splinepy/utils/nthreads.hpp index ffd8946a9..78807db15 100644 --- a/cpp/splinepy/utils/nthreads.hpp +++ b/cpp/splinepy/utils/nthreads.hpp @@ -8,8 +8,10 @@ namespace splinepy::utils { enum class NThreadQueryType : int { // split queries into chunks that can allows contiguous visit Chunk = 0, - // visit queries with nthread steps. similar to python's - // `queries[start::nthread]` + // useful if you want to visit queries with nthread steps, + // similar to python's `queries[start::nthread]`. + // practical difference is that it passes (i_thread, total) to the function f, + // instead of (chunk_begin, chunk_end) Step = 1 }; From f3f3e82be3fb09370afc6b2988fcf9a6725e9392 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 15:03:19 +0200 Subject: [PATCH 21/89] fix typo --- cpp/splinepy/py/py_spline_list.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index b4146a930..4c22a6428 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -396,7 +396,7 @@ inline void add_spline_list_pyclass(py::module& m) { py::arg("check_dims")) .def("extract_boundaries", &ListExtractBoundaries, - py::arg("n_threads"), + py::arg("nthreads"), py::arg("same_para_dims")) .def("boundary_centers", &ListBoundaryCenters, From a3511748b5a7699415b7bf92e8cfb0b85ee9dd2f Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 15:03:56 +0200 Subject: [PATCH 22/89] add all test spline creation and add to_derived --- tests/common.py | 108 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 33 deletions(-) diff --git a/tests/common.py b/tests/common.py index 4a96e06f8..4f203e0bf 100644 --- a/tests/common.py +++ b/tests/common.py @@ -104,39 +104,76 @@ def r2p2d(): r2P2D = r2p2d() + +def all2p2d(): + z = splinepy.Bezier(**z2p2d()) + r = splinepy.RationalBezier(**r2p2d()) + b = splinepy.BSpline(**b2p2d()) + n = splinepy.NURBS(**n2p2d()) + return [z, r, b, n] + + # 3D -z3P3D = dict( - degrees=[1, 1, 1], - control_points=[ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], - [1.0, 1.0, 0.0], - [0.0, -1.0, 1.0], - [1.0, 0.0, 1.0], - [-1.0, 1.0, 2.0], - [2.0, 2.0, 2.0], - ], -) - -r3P3D = dict( - **z3P3D, - weights=[1.0] * len(z3P3D["control_points"]), -) - -b3P3D = dict( - **z3P3D, - knot_vectors=[ - [0.0, 0.0, 1.0, 1.0], - [0.0, 0.0, 1.0, 1.0], - [0.0, 0.0, 1.0, 1.0], - ], -) - -n3P3D = dict( - **r3P3D, - knot_vectors=b3P3D["knot_vectors"], -) +def z3p3d(): + return dict( + degrees=[1, 1, 1], + control_points=[ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, -1.0, 1.0], + [1.0, 0.0, 1.0], + [-1.0, 1.0, 2.0], + [2.0, 2.0, 2.0], + ], + ) + + +z3P3D = z3p3d() + + +def r3p3d(): + return dict( + **z3P3D, + weights=[1.0] * len(z3P3D["control_points"]), + ) + + +r3P3D = r3p3d() + + +def b3p3d(): + return dict( + **z3P3D, + knot_vectors=[ + [0.0, 0.0, 1.0, 1.0], + [0.0, 0.0, 1.0, 1.0], + [0.0, 0.0, 1.0, 1.0], + ], + ) + + +b3P3D = b3p3d() + + +def n3p3d(): + return dict( + **r3P3D, + knot_vectors=b3P3D["knot_vectors"], + ) + + +n3P3D = n3p3d() + + +def all3p3d(): + z = splinepy.Bezier(**z3p3d()) + r = splinepy.RationalBezier(**r3p3d()) + b = splinepy.BSpline(**b3p3d()) + n = splinepy.NURBS(**n3p3d()) + return [z, r, b, n] + # query points q2D = [ @@ -192,11 +229,16 @@ def to_tmpf(tmpd): return os.path.join(tmpd, "nqv248p90") +def to_derived(spline): + """initialize to derived types in python""" + return splinepy.settings.NAME_TO_TYPE[spline.name](spline=spline) + + def are_splines_equal(a, b): """returns True if Splines are equivalent""" if not a.whatami == b.whatami: return False - for req_prop in a.current_core_properties(): + for req_prop in a.required_properties: if req_prop == "knot_vectors": for aa, bb in zip(a.knot_vectors, b.knot_vectors): if not np.allclose(aa, bb): From 5cbfcacc90f3dcf207396baf2096212a73f8b996 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 15:04:16 +0200 Subject: [PATCH 23/89] add spline list func tests --- tests/test_spline_list.py | 151 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 6 deletions(-) diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index c42532c15..45ba13b28 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -12,12 +12,8 @@ def test_spline_list(self): so, minimal test to make sure it works in our context """ # Define some splines - z = c.splinepy.Bezier(**c.z2p2d()) - r = c.splinepy.RationalBezier(**c.r2p2d()) - b = c.splinepy.BSpline(**c.b2p2d()) - n = c.splinepy.NURBS(**c.n2p2d()) - splines = [z, r, b, n] - b_id = splines.index(b) + splines = c.all2p2d() + b_id = splines.index(splines[2]) # this should be b_spline # short cut SplineList = c.splinepy.splinepy_core.SplineList @@ -65,6 +61,149 @@ def test_spline_list(self): splist4[i] = s assert splist0 == splist4 + def test_spline_list_evaluate(self): + """list evaluation""" + # get list of splines + splines = c.all2p2d() + + # create SplineList + slist = c.splinepy.splinepy_core.SplineList(splines) + + # get reference solution + ref_solutions = c.np.vstack([s.evaluate(c.q2D) for s in splines]) + + # call eval, don't check dim + list_solutions = slist.evaluate(c.q2D, nthreads=2, check_dims=False) + assert c.np.allclose(ref_solutions, list_solutions) + + # call eval, check dim + list_solutions = slist.evaluate(c.q2D, nthreads=2, check_dims=True) + assert c.np.allclose(ref_solutions, list_solutions) + + # call eval, single thread + list_solutions = slist.evaluate(c.q2D, nthreads=1, check_dims=True) + assert c.np.allclose(ref_solutions, list_solutions) + + def test_spline_list_sample(self): + """spline sample""" + splines = c.all2p2d() + slist = c.splinepy.splinepy_core.SplineList(splines) + + # set resolution. this is int, + # because it is implemented for equal resolution only + resolution = 3 + + ref_solutions = c.np.vstack( + [s.sample([resolution] * splines[0].para_dim) for s in splines] + ) + + # check everything and compute + list_samples = slist.sample( + resolution, + nthreads=2, + same_parametric_bounds=True, + check_dims=True, + ) + assert c.np.allclose(ref_solutions, list_samples) + + # don't check p bounds + list_samples = slist.sample( + resolution, + nthreads=2, + same_parametric_bounds=True, + check_dims=True, + ) + assert c.np.allclose(ref_solutions, list_samples) + + # update p bounds for bsplines + bspline_id = 2 + nurbs_id = 3 + for i in [bspline_id, nurbs_id]: + new_kvs = [kv * 2 for kv in slist[i].kvs] + slist[i].kvs = new_kvs + + # check p bounds, + list_samples = slist.sample( + resolution, + nthreads=2, + same_parametric_bounds=False, + check_dims=True, + ) + assert c.np.allclose(ref_solutions, list_samples) + + def test_extract_boundaries(self): + """extract boundaries. allowed for splines with unmatching dims""" + # prepare 2d splines + splines2d = c.all2p2d() + slist2d = c.splinepy.splinepy_core.SplineList(splines2d) + + # prepare 3d splines + splines2d3d = [*splines2d, *c.all3p3d()] + slist2d3d = c.splinepy.splinepy_core.SplineList(splines2d3d) + + # prepare test func + def _test(pure_list, spline_list, same_p_dims): + """actual test routine""" + ref_boundaries = [] + for s in pure_list: + ref_boundaries.extend( + c.splinepy.splinepy_core.extract_boundaries(s, []) + ) + + list_boundaries = spline_list.extract_boundaries( + nthreads=2, same_para_dims=False + ) + + # for same p_dims, + # try same_para_dims=True by extending test splines + if same_p_dims: + ref_boundaries.extend(ref_boundaries) + list_boundaries.extend( + spline_list.extract_boundaries( + nthreads=2, same_para_dims=True + ) + ) + + # should have same size + assert len(ref_boundaries) == len(list_boundaries) + + # same spline? + for rb, lb in zip(ref_boundaries, list_boundaries): + # currently, core function does not have getter + # for individual attr, so cast + assert c.are_splines_equal(c.to_derived(rb), c.to_derived(lb)) + + _test(splines2d, slist2d, True) + _test(splines2d3d, slist2d3d, False) + + def test_boundary_centers(self): + """boundary centers""" + splines = c.all2p2d() + slist = c.splinepy.splinepy_core.SplineList(splines) + + ref_centers = c.np.vstack( + [c.splinepy.splinepy_core.boundary_centers(s) for s in splines] + ) + + # test same bounds + list_centers = slist.boundary_centers( + nthreads=2, same_parametric_bounds=True, check_dims=False + ) + assert c.np.allclose(ref_centers, list_centers) + + # different bounds + # update p bounds for bsplines + bspline_id = 2 + nurbs_id = 3 + for i in [bspline_id, nurbs_id]: + new_kvs = [kv * 2 for kv in slist[i].kvs] + slist[i].kvs = new_kvs + + list_centers = slist.boundary_centers( + nthreads=2, same_parametric_bounds=False, check_dims=False + ) + assert c.np.allclose(ref_centers, list_centers) + if __name__ == "__main__": c.unittest.main() From c68740757e6928d9b54e5293529c822c16229834 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 15:39:12 +0200 Subject: [PATCH 24/89] add test_raise_dim_mismatch and error message error raised within a thread is messy --- cpp/splinepy/py/py_spline_list.hpp | 41 +++++++++++++++++++++++++----- tests/test_spline_list.py | 17 ++++++++++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 4c22a6428..263729d6c 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include // pybind @@ -32,15 +32,44 @@ inline void RaiseIfElementsHaveUnequalParaDimOrDim(const PySplineList& splist, const int& para_dim = splist[0]->para_dim_; const int& dim = splist[0]->dim_; - auto check_dims = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - CheckParaDimAndDim(*splist[i], para_dim, dim, true); + std::vector mismatches(nthreads); + + auto check_dims_step = [&](int begin, int total_) { + for (int i{begin}; i < total_; i += nthreads) { + if (!CheckParaDimAndDim(*splist[i], para_dim, dim, false)) { + mismatches[begin].push_back(i); + } } }; - splinepy::utils::NThreadExecution(check_dims, + splinepy::utils::NThreadExecution(check_dims_step, static_cast(splist.size()), - nthreads); + nthreads, + splinepy::utils::NThreadQueryType::Step); + // prepare error prints + IntVector all_mismatches{}; + for (const auto& m : mismatches) { + const auto m_size = m.size(); + if (m_size != 0) { + all_mismatches.reserve(all_mismatches.size() + m_size); + all_mismatches.insert(all_mismatches.end(), m.begin(), m.end()); + } + } + + if (all_mismatches.size() == 0) { + return; + } + + std::string mismatch_ids{}; + for (const auto& am : all_mismatches) { + mismatch_ids += std::to_string(am); + mismatch_ids += ", "; + } + splinepy::utils::PrintAndThrowError( + "Splines in following entries has mismatching para_dim or dim compared " + "to the first entry (", + mismatch_ids, + ")"); } /// evaluates splines of same para_dim and dim. diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 45ba13b28..7c518e065 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -137,7 +137,7 @@ def test_extract_boundaries(self): splines2d = c.all2p2d() slist2d = c.splinepy.splinepy_core.SplineList(splines2d) - # prepare 3d splines + # prepare mixed dim splines splines2d3d = [*splines2d, *c.all3p3d()] slist2d3d = c.splinepy.splinepy_core.SplineList(splines2d3d) @@ -204,6 +204,21 @@ def test_boundary_centers(self): ) assert c.np.allclose(ref_centers, list_centers) + def test_raise_dim_mismatch(self): + """see if function raises dim mismatch correctly""" + slist2d = c.splinepy.splinepy_core.SplineList(c.all2p2d()) + + # prepare mixed + slist2d3d = c.splinepy.splinepy_core.SplineList( + [*c.all2p2d(), *c.all3p3d()] + ) + + # nothing happens + slist2d.raise_dim_mismatch(nthreads=2) + + with self.assertRaises(RuntimeError): + slist2d3d.raise_dim_mismatch(nthreads=2) + if __name__ == "__main__": c.unittest.main() From 4e385d688ef27476311e2f89dbb06d4d773c1a67 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 15:43:19 +0200 Subject: [PATCH 25/89] rm --- cpp/splinepy/py/py_spline_list.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 263729d6c..98e530966 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -2,7 +2,6 @@ #include #include -#include // pybind #include From 2a6dca780dff44e8fd0dbff1d6d26f7ecfb7a495 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Thu, 25 May 2023 13:48:40 +0200 Subject: [PATCH 26/89] edit cast and allow nthreadexe --- cpp/splinepy/py/py_spline.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index 481c15805..cc9590d3a 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -904,15 +904,19 @@ class PySpline { /// Internal use only /// Extract CoreSpline_s from list of PySplines inline std::vector -ListOfPySplinesToVectorOfCoreSplines(py::list pysplines) { +ListOfPySplinesToVectorOfCoreSplines(py::list pysplines, + const int nthreads = 1) { // prepare return obj - std::vector core_splines; - core_splines.reserve(pysplines.size()); + const int n_splines = static_cast(pysplines.size()); + std::vector core_splines(n_splines); - // loop and append - for (py::handle pys : pysplines) { - core_splines.emplace_back(py::cast(pys).Core()); - } + auto to_core = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + core_splines[i] = + pysplines[i].template cast>()->Core(); + } + }; + splinepy::utils::NThreadExecution(to_core, n_splines, nthreads); return core_splines; } From e527021400acee859042ac0dc1faaf6889fe6a8f Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Thu, 25 May 2023 13:49:26 +0200 Subject: [PATCH 27/89] adapt to core splines --- cpp/splinepy/py/py_spline_extensions.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/splinepy/py/py_spline_extensions.hpp b/cpp/splinepy/py/py_spline_extensions.hpp index ee04b1648..653320fca 100644 --- a/cpp/splinepy/py/py_spline_extensions.hpp +++ b/cpp/splinepy/py/py_spline_extensions.hpp @@ -16,11 +16,11 @@ namespace py = pybind11; /// checks if given PySpline has specified para_dim and dim /// optionally raise if they don't match -inline bool CheckParaDimAndDim(const PySpline& spline, - const int& para_dim, - const int& dim, - const bool throw_ = true) { - if (para_dim != spline.para_dim_ || dim != spline.dim_) { +inline bool CheckCoreParaDimAndDim(const PySpline::CoreSpline_& spline, + const int& para_dim, + const int& dim, + const bool throw_ = true) { + if (para_dim != spline->SplinepyParaDim() || dim != spline->SplinepyDim()) { if (throw_) { splinepy::utils::PrintAndThrowError( "Spline is expected to have (para_dim", @@ -30,9 +30,9 @@ inline bool CheckParaDimAndDim(const PySpline& spline, ".", "But it has", "(", - spline.para_dim_, + spline->SplinepyParaDim(), ",", - spline.dim_, + spline->SplinepyDim(), ")."); } return false; From b379fcdec766f1d8c3a7c095651ce782e2f647dc Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Thu, 25 May 2023 14:04:58 +0200 Subject: [PATCH 28/89] abandon SplineList and use list() rough draft --- cpp/splinepy/py/py_spline_list.hpp | 255 +++++++++++++++++++---------- 1 file changed, 165 insertions(+), 90 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 98e530966..ba53192e2 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,24 +19,44 @@ namespace splinepy::py { namespace py = pybind11; // alias -using PySplineList = std::vector>; +using CoreSplineVector = + std::vector>; using IntVector = splinepy::utils::DefaultInitializationVector; using DoubleVector = splinepy::utils::DefaultInitializationVector; +/// TODO: move ListOfPySplinesToVectorOfCoreSplines here and rename +inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& splist, + const int nthreads) { + // prepare return obj + const int n_splines = static_cast(splist.size()); + py::list pyspline_list{n_splines}; + + auto to_pyspline = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + pyspline_list[i] = std::make_shared(splist[i]); + } + }; + + splinepy::utils::NThreadExecution(to_pyspline, n_splines, nthreads); + + return pyspline_list; +} + /// @brief raises if elements' para_dim and dims aren't equal /// @param splist /// @param nthreads -inline void RaiseIfElementsHaveUnequalParaDimOrDim(const PySplineList& splist, - const int nthreads) { +inline void +RaiseIfElementsHaveUnequalParaDimOrDim(const CoreSplineVector& splist, + const int nthreads) { // use first spline as guide line - const int& para_dim = splist[0]->para_dim_; - const int& dim = splist[0]->dim_; + const int para_dim = splist[0]->SplinepyParaDim(); + const int dim = splist[0]->SplinepyDim(); std::vector mismatches(nthreads); auto check_dims_step = [&](int begin, int total_) { for (int i{begin}; i < total_; i += nthreads) { - if (!CheckParaDimAndDim(*splist[i], para_dim, dim, false)) { + if (!CheckCoreParaDimAndDim(splist[i], para_dim, dim, false)) { mismatches[begin].push_back(i); } } @@ -71,14 +92,23 @@ inline void RaiseIfElementsHaveUnequalParaDimOrDim(const PySplineList& splist, ")"); } +inline void ListRaiseIfElementsHaveUnequalParaDimOrDim(py::list& splist, + const int nthreads) { + const auto core_vector = + ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + + RaiseIfElementsHaveUnequalParaDimOrDim(core_vector, nthreads); +} + /// evaluates splines of same para_dim and dim. -inline py::array_t ListEvaluate(const PySplineList& splist, - const py::array_t& queries, - const int nthreads, - const bool check_dims) { +inline py::array_t +CoreSplineVectorEvaluate(const CoreSplineVector& splist, + const py::array_t& queries, + const int nthreads, + const bool check_dims) { // use first spline as dimension guide line - const int& para_dim = splist[0]->para_dim_; - const int& dim = splist[0]->dim_; + const int para_dim = splist[0]->SplinepyParaDim(); + const int dim = splist[0]->SplinepyDim(); // query dim check CheckPyArrayShape(queries, {-1, para_dim}, true); @@ -101,7 +131,7 @@ inline py::array_t ListEvaluate(const PySplineList& splist, auto evaluate_step = [&](int begin, int total_) { for (int i{begin}; i < total_; i += nthreads) { const auto [i_spline, i_query] = std::div(i, n_queries); - splist[i_spline]->Core()->SplinepyEvaluate( + splist[i_spline]->SplinepyEvaluate( &queries_ptr[i_query * para_dim], &evaluated_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -116,23 +146,45 @@ inline py::array_t ListEvaluate(const PySplineList& splist, return evaluated; } -inline py::array_t ListDerivative(const PySplineList& splist, +inline py::array_t ListEvaluate(py::list& splist, + const py::array_t& queries, + const int nthreads, + const bool check_dims) { + const auto core_vector = + ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + + return CoreSplineVectorEvaluate(core_vector, queries, nthreads, check_dims); +} + +inline py::array_t +CoreSplineVectorDerivative(const CoreSplineVector& splist, + const py::array_t& queries, + const py::array_t& orders, + const int nthreads) { + return py::array_t(); +} + +inline py::array_t ListDerivative(py::list& splist, const py::array_t& queries, const py::array_t& orders, const int nthreads) { - return py::array_t(); + const auto core_vector = + ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + + return CoreSplineVectorDerivative(core_vector, queries, orders, nthreads); } /// Samples equal resoltions for each para dim -inline py::array_t ListSample(const PySplineList& splist, - const int resolution, - const int nthreads, - const bool same_parametric_bounds, - const bool check_dims) { +inline py::array_t +CoreSplineVectorSample(const CoreSplineVector& splist, + const int resolution, + const int nthreads, + const bool same_parametric_bounds, + const bool check_dims) { // use first spline as dimension guide line const auto& first_spline = *splist[0]; - const int& para_dim = first_spline.para_dim_; - const int& dim = first_spline.dim_; + const int para_dim = first_spline.SplinepyParaDim(); + const int dim = first_spline.SplinepyDim(); // dim check first if (check_dims) { @@ -163,7 +215,7 @@ inline py::array_t ListSample(const PySplineList& splist, // get para bounds DoubleVector para_bounds_vector(2 * para_dim); double* para_bounds = para_bounds_vector.data(); - first_spline.Core()->SplinepyParametricBounds(para_bounds); + first_spline.SplinepyParametricBounds(para_bounds); // prepare queries DoubleVector queries_vector(n_queries * para_dim); @@ -179,7 +231,7 @@ inline py::array_t ListSample(const PySplineList& splist, auto sample_same_bounds_step = [&](int begin, int total_) { for (int i{begin}; i < total_; i += nthreads) { const auto [i_spline, i_query] = std::div(i, n_queries); - splist[i_spline]->Core()->SplinepyEvaluate( + splist[i_spline]->SplinepyEvaluate( &queries[i_query * para_dim], &sampled_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -207,7 +259,7 @@ inline py::array_t ListSample(const PySplineList& splist, for (int i{begin}; i < end; ++i) { // get para_bounds - splist[i]->Core()->SplinepyParametricBounds(para_bounds); + splist[i]->SplinepyParametricBounds(para_bounds); // setup grid points helper grid_points[i].SetUp(para_dim, para_bounds, resolutions); } @@ -227,7 +279,7 @@ inline py::array_t ListSample(const PySplineList& splist, const auto [i_spline, i_query] = std::div(i, n_queries); const auto& gp_helper = grid_points[i_spline]; gp_helper.IdToGridPoint(i_query, thread_query); - splist[i_spline]->Core()->SplinepyEvaluate( + splist[i_spline]->SplinepyEvaluate( thread_query, &sampled_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -243,11 +295,27 @@ inline py::array_t ListSample(const PySplineList& splist, return sampled; } +/// Samples equal resoltions for each para dim +inline py::array_t ListSample(py::list& splist, + const int resolution, + const int nthreads, + const bool same_parametric_bounds, + const bool check_dims) { + const auto core_vector = + ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + + return CoreSplineVectorSample(core_vector, + resolution, + nthreads, + same_parametric_bounds, + check_dims); +} + // extracts boundary splines from splist. -inline std::shared_ptr -ListExtractBoundaries(const PySplineList& splist, - const int nthreads, - const bool same_para_dims) { +inline CoreSplineVector +CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, + const int nthreads, + const bool same_para_dims) { const int n_splines = splist.size(); // to accumulate int n_boundaries{}; @@ -260,7 +328,7 @@ ListExtractBoundaries(const PySplineList& splist, int offset{}; // offset counter if (same_para_dims) { // compute n_boundary - const int n_boundary = splist[0]->para_dim_ * 2; + const int n_boundary = splist[0]->SplinepyParaDim() * 2; // fill offset @@ -273,7 +341,7 @@ ListExtractBoundaries(const PySplineList& splist, // for un-equal boundary sizes, we lookup each one of them for (int i{}; i < n_splines; ++i) { boundary_offsets.push_back(offset); - offset += splist[i]->para_dim_ * 2; + offset += splist[i]->SplinepyParaDim() * 2; } } // current offset value should equal to total boundary count @@ -281,22 +349,17 @@ ListExtractBoundaries(const PySplineList& splist, n_boundaries = offset; // prepare output - auto out_boundaries = std::make_shared(n_boundaries); + CoreSplineVector out_boundaries(n_boundaries); // prepare lambda auto boundary_extract = [&](int begin, int end) { - // deref - auto& ob_deref = *out_boundaries; for (int i{begin}; i < end; ++i) { - // get core - auto& core = *splist[i]->Core(); // start of the offset auto const& this_offset = boundary_offsets[i]; // end of the offset auto const& next_offset = boundary_offsets[i + 1]; for (int j{}; j < next_offset - this_offset; ++j) { - ob_deref[this_offset + j] = - std::make_shared(core.SplinepyExtractBoundary(j)); + out_boundaries[this_offset + j] = splist[i]->SplinepyExtractBoundary(j); } } }; @@ -306,12 +369,23 @@ ListExtractBoundaries(const PySplineList& splist, return out_boundaries; } +inline py::list ListExtractBoundaries(py::list& splist, + const int nthreads, + const bool same_para_dims) { + const auto core_vector = + ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + auto boundaries = + CoreSplineVectorExtractBoundaries(core_vector, nthreads, same_para_dims); + + return CoreSplineVectorToPySplineList(boundaries, nthreads); +} + // computes boundary centers for splines of same para_dim and dim inline py::array_t -ListBoundaryCenters(const PySplineList& splist, - const int nthreads, - const bool same_parametric_bounds, - const bool check_dims) { +CoreSplineVectorBoundaryCenters(const CoreSplineVector& splist, + const int nthreads, + const bool same_parametric_bounds, + const bool check_dims) { // dim check first if (check_dims) { RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); @@ -320,8 +394,8 @@ ListBoundaryCenters(const PySplineList& splist, // prepare output // from here we assume that all the splines have the same para_dim and dim const int n_splines = splist.size(); - const int& para_dim = splist[0]->para_dim_; - const int& dim = splist[0]->dim_; + const int para_dim = splist[0]->SplinepyParaDim(); + const int dim = splist[0]->SplinepyDim(); const int n_queries = 2 * para_dim; const int n_total = n_queries * n_splines; py::array_t boundary_centers({n_total, dim}); @@ -339,7 +413,7 @@ ListBoundaryCenters(const PySplineList& splist, auto calc_para_bounds = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { splinepy::splines::helpers::ScalarTypeBoundaryCenters( - *splist[i]->Core(), + *splist[i], ¶_bounds_ptr[stride * i]); } }; @@ -357,13 +431,12 @@ ListBoundaryCenters(const PySplineList& splist, if (same_parametric_bounds) { queries_vector.resize(2 * para_dim * para_dim); queries = queries_vector.data(); - splinepy::splines::helpers::ScalarTypeBoundaryCenters(*splist[0]->Core(), + splinepy::splines::helpers::ScalarTypeBoundaryCenters(*splist[0], queries); } for (int i{begin}; i < total_; i += nthreads) { const auto [i_spline, i_query] = std::div(i, n_queries); - const auto& core = *splist[i_spline]->Core(); // get ptr start if (!same_parametric_bounds) { @@ -371,7 +444,7 @@ ListBoundaryCenters(const PySplineList& splist, } // eval - core.SplinepyEvaluate( + splist[i_spline]->SplinepyEvaluate( &queries[i_query * para_dim], &boundary_centers_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -385,52 +458,54 @@ ListBoundaryCenters(const PySplineList& splist, return boundary_centers; } -inline std::shared_ptr ListCompose() {} -inline std::shared_ptr ListCompositionDerivative() {} +inline py::array_t +ListBoundaryCenters(py::list& splist, + const int nthreads, + const bool same_parametric_bounds, + const bool check_dims) { + const auto core_vector = + ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + return CoreSplineVectorBoundaryCenters(core_vector, + nthreads, + same_parametric_bounds, + check_dims); +} + +inline std::shared_ptr ListCompose() {} +inline std::shared_ptr ListCompositionDerivative() {} /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have inline void add_spline_list_pyclass(py::module& m) { - // use shared_ptr as holder - auto klasse = py::bind_vector>( - m, - "SplineList"); - - klasse - .def("reserve", - [](PySplineList& splist, py::ssize_t size) { splist.reserve(size); }) - .def("resize", - [](PySplineList& splist, py::ssize_t size) { splist.resize(size); }) - .def("shrink_to_fit", - [](PySplineList& splist) { splist.shrink_to_fit(); }) - .def("swap", - [](PySplineList& splist_a, PySplineList& splist_b) { - splist_a.swap(splist_b); - }) - .def("raise_dim_mismatch", - &RaiseIfElementsHaveUnequalParaDimOrDim, - py::arg("nthreads")) - .def("evaluate", - &ListEvaluate, - py::arg("queries"), - py::arg("nthreads"), - py::arg("check_dims")) - .def("sample", - &ListSample, - py::arg("resolution"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")) - .def("extract_boundaries", - &ListExtractBoundaries, - py::arg("nthreads"), - py::arg("same_para_dims")) - .def("boundary_centers", - &ListBoundaryCenters, - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")); + m.def("raise_dim_mismatch", + &ListRaiseIfElementsHaveUnequalParaDimOrDim, + py::arg("spline_list"), + py::arg("nthreads")); + m.def("evaluate", + &ListEvaluate, + py::arg("spline_list"), + py::arg("queries"), + py::arg("nthreads"), + py::arg("check_dims")); + m.def("sample", + &ListSample, + py::arg("spline_list"), + py::arg("resolution"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")); + m.def("extract_boundaries", + &ListExtractBoundaries, + py::arg("spline_list"), + py::arg("nthreads"), + py::arg("same_para_dims")); + m.def("boundary_centers", + &ListBoundaryCenters, + py::arg("spline_list"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")); } } // namespace splinepy::py From 854b4e39684d779f75a6d5f0b07ac48dd67b8561 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Thu, 25 May 2023 14:05:59 +0200 Subject: [PATCH 29/89] update test - test_extract_boundary is incomplete --- tests/test_spline_list.py | 154 ++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 90 deletions(-) diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 7c518e065..6e2611770 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -5,89 +5,35 @@ class SplineListTest(c.unittest.TestCase): - def test_spline_list(self): - """ - test SplineList and some cpp vector manipulation calls. - std::vector wrapping is provided by pybind, - so, minimal test to make sure it works in our context - """ - # Define some splines - splines = c.all2p2d() - b_id = splines.index(splines[2]) # this should be b_spline - - # short cut - SplineList = c.splinepy.splinepy_core.SplineList - - # test if creating a spline is at the end the same thing - # 0. append - splist0 = SplineList() - splist0.append(splines[0]) - splist0.append(splines[1]) - splist0.append(splines[2]) - splist0.append(splines[3]) - - # 1. extend - splist1 = SplineList() - splist1.extend(splines) - - # 2. iterable init - list - splist2 = SplineList(splines) - - # 3. iterable init - tuple - splist3 = SplineList(tuple(splines)) - - # are they the same? - assert splist0 == splist1 == splist2 == splist3 - - # are they really ref? - # edit cps - splines[b_id].cps[0] += 0.5 - for splist in [splist0, splist1, splist2, splist3]: - # if they are ref, this should be modified - assert splist[b_id].cps._modified - - # test resize - splist4 = SplineList() - - # should fill with nullptr - splist4.resize(len(splines)) - assert len(splist4) == len(splines) - - for s4 in splist4: - # thankfully nullptr is casted to None - assert s4 is None - - for i, s in enumerate(splines): - splist4[i] = s - assert splist0 == splist4 - def test_spline_list_evaluate(self): """list evaluation""" # get list of splines splines = c.all2p2d() - # create SplineList - slist = c.splinepy.splinepy_core.SplineList(splines) - # get reference solution ref_solutions = c.np.vstack([s.evaluate(c.q2D) for s in splines]) # call eval, don't check dim - list_solutions = slist.evaluate(c.q2D, nthreads=2, check_dims=False) + list_solutions = c.splinepy.splinepy_core.evaluate( + splines, c.q2D, nthreads=2, check_dims=False + ) assert c.np.allclose(ref_solutions, list_solutions) # call eval, check dim - list_solutions = slist.evaluate(c.q2D, nthreads=2, check_dims=True) + list_solutions = c.splinepy.splinepy_core.evaluate( + splines, c.q2D, nthreads=2, check_dims=True + ) assert c.np.allclose(ref_solutions, list_solutions) # call eval, single thread - list_solutions = slist.evaluate(c.q2D, nthreads=1, check_dims=True) + list_solutions = c.splinepy.splinepy_core.evaluate( + splines, c.q2D, nthreads=1, check_dims=True + ) assert c.np.allclose(ref_solutions, list_solutions) def test_spline_list_sample(self): """spline sample""" splines = c.all2p2d() - slist = c.splinepy.splinepy_core.SplineList(splines) # set resolution. this is int, # because it is implemented for equal resolution only @@ -98,7 +44,8 @@ def test_spline_list_sample(self): ) # check everything and compute - list_samples = slist.sample( + list_samples = c.splinepy.splinepy_core.sample( + splines, resolution, nthreads=2, same_parametric_bounds=True, @@ -107,7 +54,8 @@ def test_spline_list_sample(self): assert c.np.allclose(ref_solutions, list_samples) # don't check p bounds - list_samples = slist.sample( + list_samples = c.splinepy.splinepy_core.sample( + splines, resolution, nthreads=2, same_parametric_bounds=True, @@ -119,11 +67,12 @@ def test_spline_list_sample(self): bspline_id = 2 nurbs_id = 3 for i in [bspline_id, nurbs_id]: - new_kvs = [kv * 2 for kv in slist[i].kvs] - slist[i].kvs = new_kvs + new_kvs = [kv * 2 for kv in splines[i].kvs] + splines[i].kvs = new_kvs # check p bounds, - list_samples = slist.sample( + list_samples = c.splinepy.splinepy_core.sample( + splines, resolution, nthreads=2, same_parametric_bounds=False, @@ -135,14 +84,12 @@ def test_extract_boundaries(self): """extract boundaries. allowed for splines with unmatching dims""" # prepare 2d splines splines2d = c.all2p2d() - slist2d = c.splinepy.splinepy_core.SplineList(splines2d) # prepare mixed dim splines splines2d3d = [*splines2d, *c.all3p3d()] - slist2d3d = c.splinepy.splinepy_core.SplineList(splines2d3d) # prepare test func - def _test(pure_list, spline_list, same_p_dims): + def _test(pure_list, same_p_dims): """actual test routine""" ref_boundaries = [] for s in pure_list: @@ -150,8 +97,8 @@ def _test(pure_list, spline_list, same_p_dims): c.splinepy.splinepy_core.extract_boundaries(s, []) ) - list_boundaries = spline_list.extract_boundaries( - nthreads=2, same_para_dims=False + list_boundaries = c.splinepy.splinepy_core.extract_boundaries( + pure_list, nthreads=2, same_para_dims=False ) # for same p_dims, @@ -159,8 +106,8 @@ def _test(pure_list, spline_list, same_p_dims): if same_p_dims: ref_boundaries.extend(ref_boundaries) list_boundaries.extend( - spline_list.extract_boundaries( - nthreads=2, same_para_dims=True + c.splinepy.splinepy_core.extract_boundaries( + pure_list, nthreads=2, same_para_dims=True ) ) @@ -173,21 +120,20 @@ def _test(pure_list, spline_list, same_p_dims): # for individual attr, so cast assert c.are_splines_equal(c.to_derived(rb), c.to_derived(lb)) - _test(splines2d, slist2d, True) - _test(splines2d3d, slist2d3d, False) + _test(splines2d, True) + _test(splines2d3d, False) def test_boundary_centers(self): """boundary centers""" splines = c.all2p2d() - slist = c.splinepy.splinepy_core.SplineList(splines) ref_centers = c.np.vstack( [c.splinepy.splinepy_core.boundary_centers(s) for s in splines] ) # test same bounds - list_centers = slist.boundary_centers( - nthreads=2, same_parametric_bounds=True, check_dims=False + list_centers = c.splinepy.splinepy_core.boundary_centers( + splines, nthreads=2, same_parametric_bounds=True, check_dims=False ) assert c.np.allclose(ref_centers, list_centers) @@ -196,28 +142,56 @@ def test_boundary_centers(self): bspline_id = 2 nurbs_id = 3 for i in [bspline_id, nurbs_id]: - new_kvs = [kv * 2 for kv in slist[i].kvs] - slist[i].kvs = new_kvs + new_kvs = [kv * 2 for kv in splines[i].kvs] + splines[i].kvs = new_kvs - list_centers = slist.boundary_centers( - nthreads=2, same_parametric_bounds=False, check_dims=False + list_centers = c.splinepy.splinepy_core.boundary_centers( + splines, nthreads=2, same_parametric_bounds=False, check_dims=False ) assert c.np.allclose(ref_centers, list_centers) def test_raise_dim_mismatch(self): """see if function raises dim mismatch correctly""" - slist2d = c.splinepy.splinepy_core.SplineList(c.all2p2d()) + slist2d = c.all2p2d() # prepare mixed - slist2d3d = c.splinepy.splinepy_core.SplineList( - [*c.all2p2d(), *c.all3p3d()] - ) + slist2d3d = [*c.all2p2d(), *c.all3p3d()] # nothing happens - slist2d.raise_dim_mismatch(nthreads=2) + c.splinepy.splinepy_core.raise_dim_mismatch(slist2d, nthreads=2) with self.assertRaises(RuntimeError): - slist2d3d.raise_dim_mismatch(nthreads=2) + c.splinepy.splinepy_core.raise_dim_mismatch(slist2d3d, nthreads=2) + + def _bezier_noisy_boxes_and_test_shapes(self): + # prepare boxes with some noise + box2d = c.nd_box(2) + box2d.pop("knot_vectors") + rbox2d = c.splinepy.RationalBezier(**box2d) + box2d.pop("weights") + zbox2d = c.splinepy.Bezier(**box2d) + box3d = c.nd_box(3) + box3d.pop("knot_vectors") + rbox3d = c.splinepy.RationalBezier(**box3d) + box3d.pop("weights") + zbox3d = c.splinepy.Bezier(**box2d) + + outer = [zbox2d, rbox2d, zbox3d, rbox3d] + # add some noise + for o in outer: + o.cps = o.cps + c.np.random.normal(0, 0.025, o.cps.shape) + + # test shapes + z2 = c.splinepy.Bezier(**c.z2p2d()) + z3 = c.splinepy.Bezier(**c.z3p3d()) + r2 = c.splinepy.RationalBezier(**c.r2p2d()) + r3 = c.splinepy.RationalBezier(**c.r3p3d()) + + return outer[:2], outer[2:], [z2, r2], [z3, r3] + + def test_list_compose(self): + """check if list compose yield same spline as spline compose""" + pass if __name__ == "__main__": From 4a17d03fa2bbea94bbcc53631c71a048487158bf Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 09:25:58 +0200 Subject: [PATCH 30/89] enforce single thread for core vector -> list causes segfault / freezes. Temprary until we can fix it --- cpp/splinepy/py/py_spline_list.hpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index ba53192e2..e04c63e9e 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -26,10 +26,10 @@ using DoubleVector = splinepy::utils::DefaultInitializationVector; /// TODO: move ListOfPySplinesToVectorOfCoreSplines here and rename inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& splist, - const int nthreads) { + int nthreads) { // prepare return obj const int n_splines = static_cast(splist.size()); - py::list pyspline_list{n_splines}; + py::list pyspline_list(n_splines); auto to_pyspline = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { @@ -37,6 +37,10 @@ inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& splist, } }; + // multi thread execution causes segfault. + // until we find a better solution, do single exe + nthreads = 1; + splinepy::utils::NThreadExecution(to_pyspline, n_splines, nthreads); return pyspline_list; @@ -355,11 +359,13 @@ CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, auto boundary_extract = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { // start of the offset - auto const& this_offset = boundary_offsets[i]; + const auto& this_offset = boundary_offsets[i]; // end of the offset - auto const& next_offset = boundary_offsets[i + 1]; + const auto& next_offset = boundary_offsets[i + 1]; + // get spline + auto& spline_i = *splist[i]; for (int j{}; j < next_offset - this_offset; ++j) { - out_boundaries[this_offset + j] = splist[i]->SplinepyExtractBoundary(j); + out_boundaries[this_offset + j] = spline_i.SplinepyExtractBoundary(j); } } }; From 24d36dde84255a0f7d285b582f552caa7edf5bcd Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 09:47:42 +0200 Subject: [PATCH 31/89] add core submodule lists --- cpp/splinepy/py/py_spline_list.hpp | 61 ++++++++++++++++-------------- tests/test_spline_list.py | 30 ++++++++------- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index e04c63e9e..e3e0f6eb4 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -484,34 +484,39 @@ inline std::shared_ptr ListCompositionDerivative() {} /// nice to have inline void add_spline_list_pyclass(py::module& m) { - m.def("raise_dim_mismatch", - &ListRaiseIfElementsHaveUnequalParaDimOrDim, - py::arg("spline_list"), - py::arg("nthreads")); - m.def("evaluate", - &ListEvaluate, - py::arg("spline_list"), - py::arg("queries"), - py::arg("nthreads"), - py::arg("check_dims")); - m.def("sample", - &ListSample, - py::arg("spline_list"), - py::arg("resolution"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")); - m.def("extract_boundaries", - &ListExtractBoundaries, - py::arg("spline_list"), - py::arg("nthreads"), - py::arg("same_para_dims")); - m.def("boundary_centers", - &ListBoundaryCenters, - py::arg("spline_list"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")); + auto list_module = m.def_submodule( + "lists", + "Module for list based queries with multithreading capabilities."); + + list_module + .def("raise_dim_mismatch", + &ListRaiseIfElementsHaveUnequalParaDimOrDim, + py::arg("spline_list"), + py::arg("nthreads")) + .def("evaluate", + &ListEvaluate, + py::arg("spline_list"), + py::arg("queries"), + py::arg("nthreads"), + py::arg("check_dims")) + .def("sample", + &ListSample, + py::arg("spline_list"), + py::arg("resolution"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")) + .def("extract_boundaries", + &ListExtractBoundaries, + py::arg("spline_list"), + py::arg("nthreads"), + py::arg("same_para_dims")) + .def("boundary_centers", + &ListBoundaryCenters, + py::arg("spline_list"), + py::arg("nthreads"), + py::arg("same_parametric_bounds"), + py::arg("check_dims")); } } // namespace splinepy::py diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 6e2611770..5599820d3 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -14,19 +14,19 @@ def test_spline_list_evaluate(self): ref_solutions = c.np.vstack([s.evaluate(c.q2D) for s in splines]) # call eval, don't check dim - list_solutions = c.splinepy.splinepy_core.evaluate( + list_solutions = c.splinepy.splinepy_core.lists.evaluate( splines, c.q2D, nthreads=2, check_dims=False ) assert c.np.allclose(ref_solutions, list_solutions) # call eval, check dim - list_solutions = c.splinepy.splinepy_core.evaluate( + list_solutions = c.splinepy.splinepy_core.lists.evaluate( splines, c.q2D, nthreads=2, check_dims=True ) assert c.np.allclose(ref_solutions, list_solutions) # call eval, single thread - list_solutions = c.splinepy.splinepy_core.evaluate( + list_solutions = c.splinepy.splinepy_core.lists.evaluate( splines, c.q2D, nthreads=1, check_dims=True ) assert c.np.allclose(ref_solutions, list_solutions) @@ -44,7 +44,7 @@ def test_spline_list_sample(self): ) # check everything and compute - list_samples = c.splinepy.splinepy_core.sample( + list_samples = c.splinepy.splinepy_core.lists.sample( splines, resolution, nthreads=2, @@ -54,7 +54,7 @@ def test_spline_list_sample(self): assert c.np.allclose(ref_solutions, list_samples) # don't check p bounds - list_samples = c.splinepy.splinepy_core.sample( + list_samples = c.splinepy.splinepy_core.lists.sample( splines, resolution, nthreads=2, @@ -71,7 +71,7 @@ def test_spline_list_sample(self): splines[i].kvs = new_kvs # check p bounds, - list_samples = c.splinepy.splinepy_core.sample( + list_samples = c.splinepy.splinepy_core.lists.sample( splines, resolution, nthreads=2, @@ -97,8 +97,10 @@ def _test(pure_list, same_p_dims): c.splinepy.splinepy_core.extract_boundaries(s, []) ) - list_boundaries = c.splinepy.splinepy_core.extract_boundaries( - pure_list, nthreads=2, same_para_dims=False + list_boundaries = ( + c.splinepy.splinepy_core.lists.extract_boundaries( + pure_list, nthreads=2, same_para_dims=False + ) ) # for same p_dims, @@ -106,7 +108,7 @@ def _test(pure_list, same_p_dims): if same_p_dims: ref_boundaries.extend(ref_boundaries) list_boundaries.extend( - c.splinepy.splinepy_core.extract_boundaries( + c.splinepy.splinepy_core.lists.extract_boundaries( pure_list, nthreads=2, same_para_dims=True ) ) @@ -132,7 +134,7 @@ def test_boundary_centers(self): ) # test same bounds - list_centers = c.splinepy.splinepy_core.boundary_centers( + list_centers = c.splinepy.splinepy_core.lists.boundary_centers( splines, nthreads=2, same_parametric_bounds=True, check_dims=False ) assert c.np.allclose(ref_centers, list_centers) @@ -145,7 +147,7 @@ def test_boundary_centers(self): new_kvs = [kv * 2 for kv in splines[i].kvs] splines[i].kvs = new_kvs - list_centers = c.splinepy.splinepy_core.boundary_centers( + list_centers = c.splinepy.splinepy_core.lists.boundary_centers( splines, nthreads=2, same_parametric_bounds=False, check_dims=False ) assert c.np.allclose(ref_centers, list_centers) @@ -158,10 +160,12 @@ def test_raise_dim_mismatch(self): slist2d3d = [*c.all2p2d(), *c.all3p3d()] # nothing happens - c.splinepy.splinepy_core.raise_dim_mismatch(slist2d, nthreads=2) + c.splinepy.splinepy_core.lists.raise_dim_mismatch(slist2d, nthreads=2) with self.assertRaises(RuntimeError): - c.splinepy.splinepy_core.raise_dim_mismatch(slist2d3d, nthreads=2) + c.splinepy.splinepy_core.lists.raise_dim_mismatch( + slist2d3d, nthreads=2 + ) def _bezier_noisy_boxes_and_test_shapes(self): # prepare boxes with some noise From cb874c7938b7a15c8145104b7764661fb161a780 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 09:53:12 +0200 Subject: [PATCH 32/89] add list compose --- cpp/splinepy/py/py_spline_list.hpp | 75 +++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index e3e0f6eb4..4f0a17da2 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -477,8 +477,79 @@ ListBoundaryCenters(py::list& splist, check_dims); } -inline std::shared_ptr ListCompose() {} -inline std::shared_ptr ListCompositionDerivative() {} +/// @brief NThread Compose. Invalid inputs will raise runtime_error on the fly +/// @param outer_splines +/// @param inner_splines +/// @param cartesian_product If true, composes each inner splines to all outer +/// splines. Else, outer and inner splines must have same len. +/// @param nthreads +/// @return +inline std::shared_ptr +ListCompose(const PySplineList& outer_splines, + const PySplineList& inner_splines, + const bool cartesian_product, + const int nthreads) { + + // get size + const int n_outer = outer_splines.size(); + const int n_inner = inner_splines.size(); + // create total counter + int n_total{}; + + // create output + std::shared_ptr composed_splines_ptr; + auto& composed_splines = *composed_splines_ptr; + + // check if size matchs + if (!cartesian_product) { + if (n_outer != n_inner) { + splinepy::utils::PrintAndThrowError( + "Length mismatch of outer_splines (", + n_outer, + ") and inner_splines (", + n_inner, + "). To compose each inner splines to all outer splines, please set " + "cartesian_product=True."); + } + + // acquisition of output space + composed_splines.resize(n_outer); + n_total = n_outer; + } else { + n_total = n_outer * n_inner; + composed_splines.resize(n_total); + } + + // good to go! + // create lambda + // this one visits queries in "transposed" manner compared to sample, for + // example + auto compose = [&](int begin, int end) { + if (!cartesian_product) { + for (int i{begin}; i < end; ++i) { + composed_splines[i] = + splinepy::py::Compose(outer_splines[i], inner_splines[i]); + } + } else { + for (int i{begin}; i < end; ++i) { + const auto [i_outer, i_inner] = std::div(i, n_inner); + composed_splines[i] = splinepy::py::Compose(outer_splines[i_outer], + inner_splines[i_inner]); + } + } + }; + + splinepy::utils::NThreadExecution(compose, n_total, nthreads); + + return composed_splines_ptr; +} + +inline std::shared_ptr +ListCompositionDerivative(const PySplineList& outer_splines, + const PySplineList& inner_splines, + const PySplineList& innter_derivative, + const bool cartesian_product, + const int nthreads) {} /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have From d5c02b54271cddeb7ea454bf6f8497d50da159a8 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 10:20:17 +0200 Subject: [PATCH 33/89] add composition derivative --- cpp/splinepy/py/py_spline_list.hpp | 152 ++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 4f0a17da2..68d464e8e 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -22,6 +22,7 @@ namespace py = pybind11; using CoreSplineVector = std::vector>; using IntVector = splinepy::utils::DefaultInitializationVector; +using IntVectorVector = splinepy::utils::DefaultInitializationVector; using DoubleVector = splinepy::utils::DefaultInitializationVector; /// TODO: move ListOfPySplinesToVectorOfCoreSplines here and rename @@ -544,12 +545,159 @@ ListCompose(const PySplineList& outer_splines, return composed_splines_ptr; } +/// @brief NThread composition derivative. same query options as ListCompose() +/// @param outer_splines +/// @param inner_splines +/// @param inner_derivative +/// @param cartesian_product +/// @param nthreads +/// @return inline std::shared_ptr ListCompositionDerivative(const PySplineList& outer_splines, const PySplineList& inner_splines, - const PySplineList& innter_derivative, + const PySplineList& inner_derivatives, const bool cartesian_product, - const int nthreads) {} + const int nthreads) { + // get size + const int n_outer = outer_splines.size(); + const int n_inner = inner_splines.size(); + const int n_inner_der = inner_derivatives.size(); + + // number of queries equals to para_dim + const int& n_queries = inner_splines[0]->para_dim_; + + // create output + std::shared_ptr composition_derivatives_ptr; + auto& composition_derivatives = *composition_derivatives_ptr; + + // create derivative spline container in case of cartesian product + // this one can be a vector of splinepy base + std::vector> + outer_spline_derivatives, inner_derivatives_single_dims; + + // check if size matchs + // 1. inner and inner_der + if (n_inner != n_inner_der) { + splinepy::utils::PrintAndThrowError( + "Length mismatch - inner_splines (", + n_inner, + ") and inner_derivatives (", + n_inner_der, + ") should have same length. To compose each inner splines to all outer " + "splines, please set cartesian_product=True."); + } + // 2. outer and inner incase of not - cartesian product. + if (!cartesian_product) { + if (n_outer != n_inner) { + splinepy::utils::PrintAndThrowError( + "Length mismatch of outer_splines (", + n_outer, + ") and inner_splines (", + n_inner, + "). To compose each inner splines to all outer splines, please set " + "cartesian_product=True."); + } + + // acquisition of output space + composition_derivatives.resize(n_outer); + + // exe + auto calc_composition_derivatives = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + composition_derivatives[i] = + splinepy::py::CompositionDerivative(outer_splines[i], + inner_splines[i], + inner_derivatives[i]); + } + }; + + splinepy::utils::NThreadExecution(calc_composition_derivatives, + n_outer, + nthreads); + + } else { + const int n_total = n_outer * n_inner; + composition_derivatives.resize(n_total); + + const int n_outer_precompute = n_outer * n_queries; + const int n_inner_precompute = n_inner * n_queries; + + // resize vectors to hold der splines of outer splines and der splines + // their para_dim and dim should match. Otherwise, it will raise. + outer_spline_derivatives.resize(n_outer_precompute); + inner_derivatives_single_dims.resize(n_inner_precompute); + + // make query arrays + IntVectorVector order_queries(n_queries); + for (int i{}; i < n_queries; ++i) { + auto& order_query = order_queries[i]; + order_query.resize(n_queries, 0); + order_query[i] = 1; + } + + // let's fill der splines and extracted splines + auto calc_der_splines_and_extract_dim = [&](int begin, int total_) { + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + + // fill if outer is still in range + if (i_spline < n_outer) { + outer_spline_derivatives[i] = + outer_splines[i_spline]->Core()->SplinepyDerivativeSpline( + order_queries[i_query].data()); + } + + if (i_spline < n_inner) { + inner_derivatives_single_dims[i] = + inner_derivatives[i_spline]->Core()->SplinepyExtractDim(i_query); + } + } + }; + + // precompute + const int precompute_total = + std::max(n_outer_precompute, n_inner_precompute); + splinepy::utils::NThreadExecution(calc_der_splines_and_extract_dim, + precompute_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + + // now, composition der + auto calc_composition_derivatives_step = [&](int begin, int total_) { + for (int i{}; i < total_; i += nthreads) { + const auto [i_outer, i_inner] = std::div(i, n_inner); + + // frequently used core + const auto& inner_core = inner_splines[i_inner]->Core(); + + // this one needs a loop + // create + auto this_comp_der = + outer_spline_derivatives[i_outer] + ->SplinepyCompose(inner_core) + ->SplinepyMultiply(inner_derivatives_single_dims[i_inner]); + + // add + for (int j{1}; j < n_queries; ++j) { + this_comp_der = this_comp_der->SplinepyAdd( + outer_spline_derivatives[i_outer + j] + ->SplinepyCompose(inner_core) + ->SplinepyMultiply(inner_derivatives_single_dims[i_inner])); + } + + // now fill + composition_derivatives[i] = std::make_shared(this_comp_der); + } + }; + + splinepy::utils::NThreadExecution(calc_composition_derivatives_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + } + + return composition_derivatives_ptr; +} /// bind vector of PySpline and add some deprecated cpp functions that maybe /// nice to have From 3d0ffd7b25002b9a5d2d17506d33fc0346530e63 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 13:28:22 +0200 Subject: [PATCH 34/89] fix #148 --- cpp/splinepy/py/py_spline_extensions.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/splinepy/py/py_spline_extensions.hpp b/cpp/splinepy/py/py_spline_extensions.hpp index 653320fca..064fdc878 100644 --- a/cpp/splinepy/py/py_spline_extensions.hpp +++ b/cpp/splinepy/py/py_spline_extensions.hpp @@ -89,11 +89,11 @@ inline std::shared_ptr Add(const std::shared_ptr& a, /// spline composition - currently only for bezier inline std::shared_ptr -Compose(const std::shared_ptr& inner, - const std::shared_ptr& outer) { +Compose(const std::shared_ptr& outer, + const std::shared_ptr& inner) { // performs runtime checks and throws error return std::make_shared( - inner->Core()->SplinepyCompose(outer->Core())); + outer->Core()->SplinepyCompose(inner->Core())); } /// spline derivative spline From 928b06f63a5652d2a6c28992adeb5174eaed132b Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 22 May 2023 13:40:23 +0200 Subject: [PATCH 35/89] add binding --- cpp/splinepy/py/py_spline_list.hpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 68d464e8e..d3451ab39 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -735,7 +735,18 @@ inline void add_spline_list_pyclass(py::module& m) { py::arg("spline_list"), py::arg("nthreads"), py::arg("same_parametric_bounds"), - py::arg("check_dims")); + py::arg("check_dims")) + .def("compose", + &ListCompose, + py::arg("inner_splines"), + py::arg("cartesian_product"), + py::arg("nthreads")) + .def("composition_derivatives", + &ListCompositionDerivative, + py::arg("inner_splines"), + py::arg("inner_derivatives"), + py::arg("cartesian_product"), + py::arg("nthreads")); } } // namespace splinepy::py From 4098f9c8aa96a30f946031688941c75dd0d8800d Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 24 May 2023 10:48:42 +0200 Subject: [PATCH 36/89] compose WIP --- tests/test_spline_list.py | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 5599820d3..ec5ccc930 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -197,6 +197,57 @@ def test_list_compose(self): """check if list compose yield same spline as spline compose""" pass + def _bezier_noisy_boxes_and_test_shapes(self): + # prepare boxes with some noise + box2d = c.nd_box(2) + box2d.pop("knot_vectors") + rbox2d = c.splinepy.RationalBezier(**box2d) + box2d.pop("weights") + zbox2d = c.splinepy.Bezier(**box2d) + box3d = c.nd_box(3) + box3d.pop("knot_vectors") + rbox3d = c.splinepy.RationalBezier(**box3d) + box3d.pop("weights") + zbox3d = c.splinepy.Bezier(**box2d) + + outer = [zbox2d, rbox2d, zbox3d, rbox3d] + # add some noise + for o in outer: + o.cps = o.cps + c.np.random.normal(0, 0.025, o.cps.shape) + + # test shapes + z2 = c.splinepy.Bezier(**c.z2p2d()) + z3 = c.splinepy.Bezier(**c.z3p3d()) + r2 = c.splinepy.RationalBezier(**c.r2p2d()) + r3 = c.splinepy.RationalBezier(**c.r3p3d()) + + return outer[:2], outer[2:], [z2, r2], [z3, r3] + + def test_list_compose(self): + """check if list compose yield same spline as spline compose""" + # prepare boxes with some noise + outer_2d, outer_3d, inner_2d, inner_3d = [ + c.splinepy.splinepy_core.SplineList(beziers) + for beziers in self._bezier_noisy_boxes_and_test_shapes() + ] + + t = [] + for beziers in self._bezier_noisy_boxes_and_test_shapes(): + sl = c.splinepy.splinepy_core.SplineList(beziers) + print(sl[0]) + t.append(sl) + + + ref_composed = [o.compose(i) for o, i in zip(outer_2d, inner_2d)] + + # list compose + list_composed = outer_2d.compose( + inner_2d, cartesian_product=False, nthreads=2 + ) + + for r, l in zip(ref_composed, list_composed): + assert c.are_splines_equal(r, l) + if __name__ == "__main__": c.unittest.main() From 2a79703c199c8883829ecf34656f1ff0e2ebe1eb Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 12:01:36 +0200 Subject: [PATCH 37/89] update to py::list based funcs --- cpp/splinepy/py/py_spline_list.hpp | 134 ++++++++++++++++++----------- tests/test_spline_list.py | 5 +- 2 files changed, 86 insertions(+), 53 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index d3451ab39..b95f94fd4 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -485,21 +485,18 @@ ListBoundaryCenters(py::list& splist, /// splines. Else, outer and inner splines must have same len. /// @param nthreads /// @return -inline std::shared_ptr -ListCompose(const PySplineList& outer_splines, - const PySplineList& inner_splines, - const bool cartesian_product, - const int nthreads) { +inline CoreSplineVector +CoreSplineVectorCompose(const CoreSplineVector& outer_splines, + const CoreSplineVector& inner_splines, + const bool cartesian_product, + const int nthreads) { // get size const int n_outer = outer_splines.size(); const int n_inner = inner_splines.size(); - // create total counter - int n_total{}; // create output - std::shared_ptr composed_splines_ptr; - auto& composed_splines = *composed_splines_ptr; + CoreSplineVector composed_splines; // check if size matchs if (!cartesian_product) { @@ -515,34 +512,51 @@ ListCompose(const PySplineList& outer_splines, // acquisition of output space composed_splines.resize(n_outer); - n_total = n_outer; - } else { - n_total = n_outer * n_inner; - composed_splines.resize(n_total); - } - // good to go! - // create lambda - // this one visits queries in "transposed" manner compared to sample, for - // example - auto compose = [&](int begin, int end) { - if (!cartesian_product) { + auto compose = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { composed_splines[i] = - splinepy::py::Compose(outer_splines[i], inner_splines[i]); + outer_splines[i]->SplinepyCompose(inner_splines[i]); } - } else { - for (int i{begin}; i < end; ++i) { + }; + + splinepy::utils::NThreadExecution(compose, n_outer, nthreads); + + } else { + const int n_total = n_outer * n_inner; + composed_splines.resize(n_total); + + auto compose_step = [&](int begin, int total_) { + for (int i{begin}; i < total_; ++i) { const auto [i_outer, i_inner] = std::div(i, n_inner); - composed_splines[i] = splinepy::py::Compose(outer_splines[i_outer], - inner_splines[i_inner]); + composed_splines[i] = + outer_splines[i_outer]->SplinepyCompose(inner_splines[i_inner]); } - } - }; + }; + splinepy::utils::NThreadExecution(compose_step, + n_outer, + nthreads, + splinepy::utils::NThreadQueryType::Step); + } - splinepy::utils::NThreadExecution(compose, n_total, nthreads); + return composed_splines; +} - return composed_splines_ptr; +inline py::list ListCompose(py::list& outer_splines, + py::list& inner_splines, + const bool cartesian_product, + const int nthreads) { + const auto core_outer = + ListOfPySplinesToVectorOfCoreSplines(outer_splines, nthreads); + const auto core_inner = + ListOfPySplinesToVectorOfCoreSplines(inner_splines, nthreads); + + auto composed_cores = CoreSplineVectorCompose(core_outer, + core_inner, + cartesian_product, + nthreads); + + return CoreSplineVectorToPySplineList(composed_cores, nthreads); } /// @brief NThread composition derivative. same query options as ListCompose() @@ -552,28 +566,24 @@ ListCompose(const PySplineList& outer_splines, /// @param cartesian_product /// @param nthreads /// @return -inline std::shared_ptr -ListCompositionDerivative(const PySplineList& outer_splines, - const PySplineList& inner_splines, - const PySplineList& inner_derivatives, - const bool cartesian_product, - const int nthreads) { +inline CoreSplineVector +CoreSplineVectorCompositionDerivative(const CoreSplineVector& outer_splines, + const CoreSplineVector& inner_splines, + const CoreSplineVector& inner_derivatives, + const bool cartesian_product, + const int nthreads) { // get size const int n_outer = outer_splines.size(); const int n_inner = inner_splines.size(); const int n_inner_der = inner_derivatives.size(); // number of queries equals to para_dim - const int& n_queries = inner_splines[0]->para_dim_; + const int& n_queries = inner_splines[0]->SplinepyParaDim(); // create output - std::shared_ptr composition_derivatives_ptr; - auto& composition_derivatives = *composition_derivatives_ptr; - // create derivative spline container in case of cartesian product - // this one can be a vector of splinepy base - std::vector> - outer_spline_derivatives, inner_derivatives_single_dims; + CoreSplineVector composition_derivatives, outer_spline_derivatives, + inner_derivatives_single_dims; // check if size matchs // 1. inner and inner_der @@ -605,9 +615,9 @@ ListCompositionDerivative(const PySplineList& outer_splines, auto calc_composition_derivatives = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { composition_derivatives[i] = - splinepy::py::CompositionDerivative(outer_splines[i], - inner_splines[i], - inner_derivatives[i]); + outer_splines[i]->SplinepyCompositionDerivative( + inner_splines[i], + inner_derivatives[i]); } }; @@ -643,13 +653,13 @@ ListCompositionDerivative(const PySplineList& outer_splines, // fill if outer is still in range if (i_spline < n_outer) { outer_spline_derivatives[i] = - outer_splines[i_spline]->Core()->SplinepyDerivativeSpline( + outer_splines[i_spline]->SplinepyDerivativeSpline( order_queries[i_query].data()); } if (i_spline < n_inner) { inner_derivatives_single_dims[i] = - inner_derivatives[i_spline]->Core()->SplinepyExtractDim(i_query); + inner_derivatives[i_spline]->SplinepyExtractDim(i_query); } } }; @@ -668,7 +678,7 @@ ListCompositionDerivative(const PySplineList& outer_splines, const auto [i_outer, i_inner] = std::div(i, n_inner); // frequently used core - const auto& inner_core = inner_splines[i_inner]->Core(); + const auto& inner_core = inner_splines[i_inner]; // this one needs a loop // create @@ -686,7 +696,7 @@ ListCompositionDerivative(const PySplineList& outer_splines, } // now fill - composition_derivatives[i] = std::make_shared(this_comp_der); + composition_derivatives[i] = this_comp_der; } }; @@ -696,7 +706,29 @@ ListCompositionDerivative(const PySplineList& outer_splines, splinepy::utils::NThreadQueryType::Step); } - return composition_derivatives_ptr; + return composition_derivatives; +} + +inline py::list ListCompositionDerivative(py::list& outer_splines, + py::list& inner_splines, + py::list& inner_derivatives, + const bool cartesian_product, + const int nthreads) { + const auto core_outer = + ListOfPySplinesToVectorOfCoreSplines(outer_splines, nthreads); + const auto core_inner = + ListOfPySplinesToVectorOfCoreSplines(inner_splines, nthreads); + const auto core_inner_derivatives = + ListOfPySplinesToVectorOfCoreSplines(inner_derivatives, nthreads); + + auto core_com_der = + CoreSplineVectorCompositionDerivative(core_outer, + core_inner, + core_inner_derivatives, + cartesian_product, + nthreads); + + return CoreSplineVectorToPySplineList(core_com_der, nthreads); } /// bind vector of PySpline and add some deprecated cpp functions that maybe @@ -738,11 +770,13 @@ inline void add_spline_list_pyclass(py::module& m) { py::arg("check_dims")) .def("compose", &ListCompose, + py::arg("outer_splines"), py::arg("inner_splines"), py::arg("cartesian_product"), py::arg("nthreads")) .def("composition_derivatives", &ListCompositionDerivative, + py::arg("outer_splines"), py::arg("inner_splines"), py::arg("inner_derivatives"), py::arg("cartesian_product"), diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index ec5ccc930..72073d179 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -226,10 +226,10 @@ def _bezier_noisy_boxes_and_test_shapes(self): def test_list_compose(self): """check if list compose yield same spline as spline compose""" # prepare boxes with some noise - outer_2d, outer_3d, inner_2d, inner_3d = [ + outer_2d, outer_3d, inner_2d, inner_3d = ( c.splinepy.splinepy_core.SplineList(beziers) for beziers in self._bezier_noisy_boxes_and_test_shapes() - ] + ) t = [] for beziers in self._bezier_noisy_boxes_and_test_shapes(): @@ -237,7 +237,6 @@ def test_list_compose(self): print(sl[0]) t.append(sl) - ref_composed = [o.compose(i) for o, i in zip(outer_2d, inner_2d)] # list compose From 819c02d0b1e55b593da5ad982fb9cbd7a25d9260 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 12:19:29 +0200 Subject: [PATCH 38/89] change to n_total --- cpp/splinepy/py/py_spline_list.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index b95f94fd4..11d14d39b 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -534,7 +534,7 @@ CoreSplineVectorCompose(const CoreSplineVector& outer_splines, } }; splinepy::utils::NThreadExecution(compose_step, - n_outer, + n_total, nthreads, splinepy::utils::NThreadQueryType::Step); } From 9ca67c7b620bd27b72aa5e02d4dcac988255ab7d Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 12:19:52 +0200 Subject: [PATCH 39/89] add compose test --- tests/test_spline_list.py | 91 +++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 72073d179..39590aa55 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -178,7 +178,7 @@ def _bezier_noisy_boxes_and_test_shapes(self): box3d.pop("knot_vectors") rbox3d = c.splinepy.RationalBezier(**box3d) box3d.pop("weights") - zbox3d = c.splinepy.Bezier(**box2d) + zbox3d = c.splinepy.Bezier(**box3d) outer = [zbox2d, rbox2d, zbox3d, rbox3d] # add some noise @@ -191,61 +191,66 @@ def _bezier_noisy_boxes_and_test_shapes(self): r2 = c.splinepy.RationalBezier(**c.r2p2d()) r3 = c.splinepy.RationalBezier(**c.r3p3d()) - return outer[:2], outer[2:], [z2, r2], [z3, r3] - - def test_list_compose(self): - """check if list compose yield same spline as spline compose""" - pass - - def _bezier_noisy_boxes_and_test_shapes(self): - # prepare boxes with some noise - box2d = c.nd_box(2) - box2d.pop("knot_vectors") - rbox2d = c.splinepy.RationalBezier(**box2d) - box2d.pop("weights") - zbox2d = c.splinepy.Bezier(**box2d) - box3d = c.nd_box(3) - box3d.pop("knot_vectors") - rbox3d = c.splinepy.RationalBezier(**box3d) - box3d.pop("weights") - zbox3d = c.splinepy.Bezier(**box2d) - - outer = [zbox2d, rbox2d, zbox3d, rbox3d] - # add some noise - for o in outer: - o.cps = o.cps + c.np.random.normal(0, 0.025, o.cps.shape) - - # test shapes - z2 = c.splinepy.Bezier(**c.z2p2d()) - z3 = c.splinepy.Bezier(**c.z3p3d()) - r2 = c.splinepy.RationalBezier(**c.r2p2d()) - r3 = c.splinepy.RationalBezier(**c.r3p3d()) + # fit into unit box + for bez in [z2, z3, r2, r3]: + # offset + bez.cps -= bez.cps.min(axis=0) + # scale + bez.cps = bez.cps * (1 / bez.cps.max(axis=0)) return outer[:2], outer[2:], [z2, r2], [z3, r3] def test_list_compose(self): """check if list compose yield same spline as spline compose""" # prepare boxes with some noise - outer_2d, outer_3d, inner_2d, inner_3d = ( - c.splinepy.splinepy_core.SplineList(beziers) - for beziers in self._bezier_noisy_boxes_and_test_shapes() - ) - - t = [] - for beziers in self._bezier_noisy_boxes_and_test_shapes(): - sl = c.splinepy.splinepy_core.SplineList(beziers) - print(sl[0]) - t.append(sl) + ( + outer_2d, + outer_3d, + inner_2d, + inner_3d, + ) = self._bezier_noisy_boxes_and_test_shapes() ref_composed = [o.compose(i) for o, i in zip(outer_2d, inner_2d)] # list compose - list_composed = outer_2d.compose( - inner_2d, cartesian_product=False, nthreads=2 + list_composed = c.splinepy.splinepy_core.lists.compose( + outer_2d, inner_2d, cartesian_product=False, nthreads=2 + ) + + # add 3d + ref_composed.extend([o.compose(i) for o, i in zip(outer_3d, inner_3d)]) + list_composed.extend( + c.splinepy.splinepy_core.lists.compose( + outer_3d, inner_3d, cartesian_product=False, nthreads=2 + ) + ) + + for r, l in zip(ref_composed, list_composed): + assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) + + # test cartesian + ref_composed = [] + for o in outer_2d: + for i in inner_2d: + ref_composed.append(o.compose(i)) + for o in outer_3d: + for i in inner_3d: + ref_composed.append(o.compose(i)) + + list_composed = c.splinepy.splinepy_core.lists.compose( + outer_2d, inner_2d, cartesian_product=True, nthreads=2 + ) + list_composed.extend( + c.splinepy.splinepy_core.lists.compose( + outer_3d, inner_3d, cartesian_product=True, nthreads=2 + ) ) for r, l in zip(ref_composed, list_composed): - assert c.are_splines_equal(r, l) + assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) + + def test_list_compose_derivatives(self): + pass if __name__ == "__main__": From 227ccfa4cac5307f19860eab14e0c6f1feff47a1 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 13:36:47 +0200 Subject: [PATCH 40/89] fix indices and typo --- cpp/splinepy/py/py_spline_list.hpp | 22 +++++++------- tests/test_spline_list.py | 47 +++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 11d14d39b..9c3365d3e 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -650,15 +650,16 @@ CoreSplineVectorCompositionDerivative(const CoreSplineVector& outer_splines, for (int i{begin}; i < total_; i += nthreads) { const auto [i_spline, i_query] = std::div(i, n_queries); + const int offset = i_spline * n_queries; // fill if outer is still in range if (i_spline < n_outer) { - outer_spline_derivatives[i] = + outer_spline_derivatives[offset + i_query] = outer_splines[i_spline]->SplinepyDerivativeSpline( order_queries[i_query].data()); } if (i_spline < n_inner) { - inner_derivatives_single_dims[i] = + inner_derivatives_single_dims[offset + i_query] = inner_derivatives[i_spline]->SplinepyExtractDim(i_query); } } @@ -671,30 +672,29 @@ CoreSplineVectorCompositionDerivative(const CoreSplineVector& outer_splines, precompute_total, nthreads, splinepy::utils::NThreadQueryType::Step); - // now, composition der auto calc_composition_derivatives_step = [&](int begin, int total_) { - for (int i{}; i < total_; i += nthreads) { + for (int i{begin}; i < total_; i += nthreads) { const auto [i_outer, i_inner] = std::div(i, n_inner); - // frequently used core const auto& inner_core = inner_splines[i_inner]; // this one needs a loop // create auto this_comp_der = - outer_spline_derivatives[i_outer] + outer_spline_derivatives[i_outer * n_queries] ->SplinepyCompose(inner_core) - ->SplinepyMultiply(inner_derivatives_single_dims[i_inner]); + ->SplinepyMultiply( + inner_derivatives_single_dims[i_inner * n_queries]); // add for (int j{1}; j < n_queries; ++j) { this_comp_der = this_comp_der->SplinepyAdd( - outer_spline_derivatives[i_outer + j] + outer_spline_derivatives[i_outer * n_queries + j] ->SplinepyCompose(inner_core) - ->SplinepyMultiply(inner_derivatives_single_dims[i_inner])); + ->SplinepyMultiply( + inner_derivatives_single_dims[i_inner * n_queries + j])); } - // now fill composition_derivatives[i] = this_comp_der; } @@ -774,7 +774,7 @@ inline void add_spline_list_pyclass(py::module& m) { py::arg("inner_splines"), py::arg("cartesian_product"), py::arg("nthreads")) - .def("composition_derivatives", + .def("composition_derivative", &ListCompositionDerivative, py::arg("outer_splines"), py::arg("inner_splines"), diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 39590aa55..26ffbb88e 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -250,7 +250,52 @@ def test_list_compose(self): assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) def test_list_compose_derivatives(self): - pass + # prepare boxes with some noise + ( + outer_2d, + outer_3d, + inner_2d, + inner_3d, + ) = self._bezier_noisy_boxes_and_test_shapes() + + ref_composed = [o.composition_derivatives(i, i_d) for o, i, i_d in zip(outer_2d, inner_2d, inner_2d)] + + # list compose + list_composed = c.splinepy.splinepy_core.lists.composition_derivatives( + outer_2d, inner_2d, inner_2d cartesian_product=False, nthreads=2 + ) + + # add 3d + ref_composed.extend([o.composition_derivatives(i, i_d) for o, i, i_d in zip(outer_3d, inner_3d, inner_3d)]) + list_composed.extend( + c.splinepy.splinepy_core.lists.compose( + outer_3d, inner_3d, inner_3d cartesian_product=False, nthreads=2 + ) + ) + + for r, l in zip(ref_composed, list_composed): + assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) + + # test cartesian + ref_composed = [] + for o in outer_2d: + for i in inner_2d: + ref_composed.append(o.compose(i)) + for o in outer_3d: + for i in inner_3d: + ref_composed.append(o.compose(i)) + + list_composed = c.splinepy.splinepy_core.lists.compose( + outer_2d, inner_2d, cartesian_product=True, nthreads=2 + ) + list_composed.extend( + c.splinepy.splinepy_core.lists.compose( + outer_3d, inner_3d, cartesian_product=True, nthreads=2 + ) + ) + + for r, l in zip(ref_composed, list_composed): + assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) if __name__ == "__main__": From eda50fe3c899b5fb628db3f48dddd30b306a8f5f Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 13:37:16 +0200 Subject: [PATCH 41/89] fix #148 --- cpp/splinepy/splines/rational_bezier.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/splinepy/splines/rational_bezier.hpp b/cpp/splinepy/splines/rational_bezier.hpp index 1da105d9b..083ff78eb 100644 --- a/cpp/splinepy/splines/rational_bezier.hpp +++ b/cpp/splinepy/splines/rational_bezier.hpp @@ -522,7 +522,7 @@ class RationalBezier : public splinepy::splines::SplinepyBase, virtual std::shared_ptr SplinepyExtractDim(const int& phys_dim) const { - return std::make_shared>( + return std::make_shared>( Base_::ExtractDimension(static_cast(phys_dim))); } From e75d93ca910a490bdda2566ff20ab24e7ec353b1 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 13:46:18 +0200 Subject: [PATCH 42/89] edit doc - no inplace support for lists --- cpp/splinepy/py/py_spline_list.hpp | 3 ++- cpp/splinepy/splines/splinepy_base.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 9c3365d3e..329bc9725 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -737,7 +737,8 @@ inline void add_spline_list_pyclass(py::module& m) { auto list_module = m.def_submodule( "lists", - "Module for list based queries with multithreading capabilities."); + "Module for list based queries with multithreading capabilities. Please " + "make sure splines don't have any inplace modifications."); list_module .def("raise_dim_mismatch", diff --git a/cpp/splinepy/splines/splinepy_base.cpp b/cpp/splinepy/splines/splinepy_base.cpp index afbab7d71..2b235c0f2 100644 --- a/cpp/splinepy/splines/splinepy_base.cpp +++ b/cpp/splinepy/splines/splinepy_base.cpp @@ -364,8 +364,8 @@ bool SplinepyBase::SplinepySplineNameMatches(const SplinepyBase& a, splinepy::utils::PrintAndThrowError(description, "Spline name mismatch -" "Spline0:", - "/", a.SplinepySplineName(), + "/", "Spline1:", b.SplinepySplineName()); } @@ -384,8 +384,8 @@ bool SplinepyBase::SplinepyParaDimMatches(const SplinepyBase& a, description, "Spline parametric dimension mismatch - " "Spline0:", - "/", a.SplinepyParaDim(), + "/", "Spline1:", b.SplinepyParaDim()); } @@ -404,8 +404,8 @@ bool SplinepyBase::SplinepyDimMatches(const SplinepyBase& a, description, "Spline parametric dimension mismatch - " "Spline0:", - "/", a.SplinepyDim(), + "/", "Spline1:", b.SplinepyDim()); } From 029b106e03a82cfd142e2ab9742bc69978f9cfe6 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 13:48:31 +0200 Subject: [PATCH 43/89] add composition_der test --- tests/test_spline_list.py | 56 ++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py index 26ffbb88e..f551fc0df 100644 --- a/tests/test_spline_list.py +++ b/tests/test_spline_list.py @@ -258,18 +258,30 @@ def test_list_compose_derivatives(self): inner_3d, ) = self._bezier_noisy_boxes_and_test_shapes() - ref_composed = [o.composition_derivatives(i, i_d) for o, i, i_d in zip(outer_2d, inner_2d, inner_2d)] + # create 2d ref + ref_composed = [ + o.composition_derivative(i, i) for o, i in zip(outer_2d, inner_2d) + ] # list compose - list_composed = c.splinepy.splinepy_core.lists.composition_derivatives( - outer_2d, inner_2d, inner_2d cartesian_product=False, nthreads=2 + list_composed = c.splinepy.splinepy_core.lists.composition_derivative( + outer_2d, inner_2d, inner_2d, cartesian_product=False, nthreads=2 ) # add 3d - ref_composed.extend([o.composition_derivatives(i, i_d) for o, i, i_d in zip(outer_3d, inner_3d, inner_3d)]) + ref_composed.extend( + [ + o.composition_derivative(i, i) + for o, i in zip(outer_3d, inner_3d) + ] + ) list_composed.extend( - c.splinepy.splinepy_core.lists.compose( - outer_3d, inner_3d, inner_3d cartesian_product=False, nthreads=2 + c.splinepy.splinepy_core.lists.composition_derivative( + outer_3d, + inner_3d, + inner_3d, + cartesian_product=False, + nthreads=2, ) ) @@ -277,20 +289,40 @@ def test_list_compose_derivatives(self): assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) # test cartesian + # + # rational_bz.composition_derivative(poly, poly_der) not supported + # so, remove + for inn in [inner_2d, inner_3d]: + # pop poly + inn.pop(0) + + # clone rat + rat = inn[-1].copy() + rat.cps[:, 0] = 0.5 + rat.cps[:, 0] += c.np.random.uniform(-0.4, 0.4, len(rat.cps)) + # force sync - list exe doesn't sync before exe + rat.new_core(**rat._data["properties"]) + inn.append(rat) + ref_composed = [] for o in outer_2d: for i in inner_2d: - ref_composed.append(o.compose(i)) + ref_composed.append(o.composition_derivative(i, i)) for o in outer_3d: for i in inner_3d: - ref_composed.append(o.compose(i)) + ref_composed.append(o.composition_derivative(i, i)) - list_composed = c.splinepy.splinepy_core.lists.compose( - outer_2d, inner_2d, cartesian_product=True, nthreads=2 + list_composed = c.splinepy.splinepy_core.lists.composition_derivative( + outer_2d, inner_2d, inner_2d, cartesian_product=True, nthreads=2 ) + list_composed.extend( - c.splinepy.splinepy_core.lists.compose( - outer_3d, inner_3d, cartesian_product=True, nthreads=2 + c.splinepy.splinepy_core.lists.composition_derivative( + outer_3d, + inner_3d, + inner_3d, + cartesian_product=True, + nthreads=2, ) ) From 33b0ba919f370e9bd2b6d755251d078028c214a1 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 18 Apr 2023 21:56:04 +0200 Subject: [PATCH 44/89] init/* only inits all pybind definitions are implemented in the same file as cpp implementations --- cpp/splinepy/py/init/exporter.cpp | 52 +------------------------- cpp/splinepy/py/init/fitting.cpp | 35 +---------------- cpp/splinepy/py/init/reader.cpp | 8 +--- cpp/splinepy/py/py_fitting.hpp | 35 +++++++++++++++++ cpp/splinepy/py/py_spline_exporter.hpp | 52 ++++++++++++++++++++++++++ cpp/splinepy/py/py_spline_reader.hpp | 8 ++++ 6 files changed, 98 insertions(+), 92 deletions(-) diff --git a/cpp/splinepy/py/init/exporter.cpp b/cpp/splinepy/py/init/exporter.cpp index 287fbb885..9cb5a212f 100644 --- a/cpp/splinepy/py/init/exporter.cpp +++ b/cpp/splinepy/py/init/exporter.cpp @@ -2,56 +2,6 @@ namespace splinepy::py::init { -void init_exporter(py::module_& m) { - // Void functions that define arguments - // returns [connectivity, vertex_ids, edge_information, boundaries] - m.def("retrieve_mfem_information", - &splinepy::py::RetrieveMfemInformation, - py::arg("corner_vertices"), - py::arg("tolerance")); - m.def("interfaces_from_boundary_centers", - &splinepy::py::InterfacesFromBoundaryCenters, - py::arg("face_center_vertices"), - py::arg("tolerance"), - py::arg("para_dim")); - m.def("extract_all_boundary_splines", - &splinepy::py::ExtractAllBoundarySplines, - py::arg("splines"), - py::arg("interfaces"), - py::arg("nthreads") = 1); - m.def("orientations", - &splinepy::py::GetBoundaryOrientations, - py::arg("splines"), - py::arg("base_ids"), - py::arg("base_face_ids"), - py::arg("neighbor_ids"), - py::arg("neighbor_face_ids"), - py::arg("tolerance"), - py::arg("nthreads") = 1); - m.def("boundaries_from_continuity", - &splinepy::py::AddBoundariesFromContinuity, - py::arg("boundary_splines"), - py::arg("boundary_interfaces"), - py::arg("global_interfaces"), - py::arg("tolerance"), - py::arg("nthreads") = 1); - m.def("export_iges", - &splinepy::py::ExportIges, - py::arg("fname"), - py::arg("splines")); - m.def("export_irit", - &splinepy::py::ExportIrit, - py::arg("fname"), - py::arg("splines")); - m.def("export_xml", - &splinepy::py::ExportXml, - py::arg("fname"), - py::arg("splines")); - m.def("export_vtk", - &splinepy::py::ExportVtk, - py::arg("fname"), - py::arg("splines"), - py::arg("resolutions_per_spline")); -} +void init_exporter(py::module_& m) { add_spline_exporter(m); } } // namespace splinepy::py::init diff --git a/cpp/splinepy/py/init/fitting.cpp b/cpp/splinepy/py/init/fitting.cpp index 8ad45b2a5..1e3b53e6f 100644 --- a/cpp/splinepy/py/init/fitting.cpp +++ b/cpp/splinepy/py/init/fitting.cpp @@ -2,39 +2,6 @@ namespace splinepy::py::init { -void init_fitting(py::module_& m) { - // Functions that return fitted bspline as dict. - m.def("interpolate_curve", - &splinepy::py::InterpolateCurve, - py::arg("points"), - py::arg("degree"), - py::arg("centripetal"), - py::arg("knot_vector")); - m.def("approximate_curve", - &splinepy::py::ApproximateCurve, - py::arg("points"), - py::arg("degree"), - py::arg("n_control_points"), - py::arg("centripetal"), - py::arg("knot_vector")); - m.def("interpolate_surface", - &splinepy::py::InterpolateSurface, - py::arg("points"), - py::arg("size_u"), - py::arg("size_v"), - py::arg("degree_u"), - py::arg("degree_v"), - py::arg("centripetal")); - m.def("approximate_surface", - &splinepy::py::ApproximateSurface, - py::arg("points"), - py::arg("num_points_u"), - py::arg("num_points_v"), - py::arg("size_u"), - py::arg("size_v"), - py::arg("degree_u"), - py::arg("degree_v"), - py::arg("centripetal")); -} +void init_fitting(py::module_& m) { add_fitting(m); } } // namespace splinepy::py::init diff --git a/cpp/splinepy/py/init/reader.cpp b/cpp/splinepy/py/init/reader.cpp index 747f8deb9..69f48c341 100644 --- a/cpp/splinepy/py/init/reader.cpp +++ b/cpp/splinepy/py/init/reader.cpp @@ -2,12 +2,6 @@ namespace splinepy::py::init { -void init_reader(py::module_& m) { - // Functions that return list of dict. - // Keys are ["knot_vectors", "control_points", "degrees"] (+ ["weights"]) - m.def("read_iges", &read_iges, py::arg("fname")) - .def("read_xml", &read_xml, py::arg("fname")) - .def("read_irit", &read_irit, py::arg("fname")); -} +void init_reader(py::module_& m) { add_spline_reader(m); } } // namespace splinepy::py::init diff --git a/cpp/splinepy/py/py_fitting.hpp b/cpp/splinepy/py/py_fitting.hpp index 7b773bcd7..a4c26ca38 100644 --- a/cpp/splinepy/py/py_fitting.hpp +++ b/cpp/splinepy/py/py_fitting.hpp @@ -278,4 +278,39 @@ py::dict ApproximateSurface(py::array_t points, return dict_spline; } +inline void add_fitting(py::module& m) { + // Functions that return fitted bspline as dict. + m.def("interpolate_curve", + &splinepy::py::InterpolateCurve, + py::arg("points"), + py::arg("degree"), + py::arg("centripetal"), + py::arg("knot_vector")); + m.def("approximate_curve", + &splinepy::py::ApproximateCurve, + py::arg("points"), + py::arg("degree"), + py::arg("n_control_points"), + py::arg("centripetal"), + py::arg("knot_vector")); + m.def("interpolate_surface", + &splinepy::py::InterpolateSurface, + py::arg("points"), + py::arg("size_u"), + py::arg("size_v"), + py::arg("degree_u"), + py::arg("degree_v"), + py::arg("centripetal")); + m.def("approximate_surface", + &splinepy::py::ApproximateSurface, + py::arg("points"), + py::arg("num_points_u"), + py::arg("num_points_v"), + py::arg("size_u"), + py::arg("size_v"), + py::arg("degree_u"), + py::arg("degree_v"), + py::arg("centripetal")); +} + } // namespace splinepy::py diff --git a/cpp/splinepy/py/py_spline_exporter.hpp b/cpp/splinepy/py/py_spline_exporter.hpp index 2a9c6c6a8..54fbd96ac 100644 --- a/cpp/splinepy/py/py_spline_exporter.hpp +++ b/cpp/splinepy/py/py_spline_exporter.hpp @@ -1446,4 +1446,56 @@ int AddBoundariesFromContinuity(const py::list& boundary_splines, return current_max_id; } +inline void add_spline_exporter(py::module& m) { + // Void functions that define arguments + // returns [connectivity, vertex_ids, edge_information, boundaries] + m.def("retrieve_mfem_information", + &splinepy::py::RetrieveMfemInformation, + py::arg("corner_vertices"), + py::arg("tolerance")); + m.def("interfaces_from_boundary_centers", + &splinepy::py::InterfacesFromBoundaryCenters, + py::arg("face_center_vertices"), + py::arg("tolerance"), + py::arg("para_dim")); + m.def("extract_all_boundary_splines", + &splinepy::py::ExtractAllBoundarySplines, + py::arg("splines"), + py::arg("interfaces"), + py::arg("nthreads") = 1); + m.def("orientations", + &splinepy::py::GetBoundaryOrientations, + py::arg("splines"), + py::arg("base_ids"), + py::arg("base_face_ids"), + py::arg("neighbor_ids"), + py::arg("neighbor_face_ids"), + py::arg("tolerance"), + py::arg("nthreads") = 1); + m.def("boundaries_from_continuity", + &splinepy::py::AddBoundariesFromContinuity, + py::arg("boundary_splines"), + py::arg("boundary_interfaces"), + py::arg("global_interfaces"), + py::arg("tolerance"), + py::arg("nthreads") = 1); + m.def("export_iges", + &splinepy::py::ExportIges, + py::arg("fname"), + py::arg("splines")); + m.def("export_irit", + &splinepy::py::ExportIrit, + py::arg("fname"), + py::arg("splines")); + m.def("export_xml", + &splinepy::py::ExportXml, + py::arg("fname"), + py::arg("splines")); + m.def("export_vtk", + &splinepy::py::ExportVtk, + py::arg("fname"), + py::arg("splines"), + py::arg("resolutions_per_spline")); +} + } // namespace splinepy::py diff --git a/cpp/splinepy/py/py_spline_reader.hpp b/cpp/splinepy/py/py_spline_reader.hpp index 87f65ebdb..705401b90 100644 --- a/cpp/splinepy/py/py_spline_reader.hpp +++ b/cpp/splinepy/py/py_spline_reader.hpp @@ -433,4 +433,12 @@ py::list read_irit(std::string fname) { return sr.read_irit(fname); } +inline void add_spline_reader(py::module& m) { + // Functions that return list of dict. + // Keys are ["knot_vectors", "control_points", "degrees"] (+ ["weights"]) + m.def("read_iges", &read_iges, py::arg("fname")) + .def("read_xml", &read_xml, py::arg("fname")) + .def("read_irit", &read_irit, py::arg("fname")); +} + } // namespace splinepy::py From 0f03159966dafdd4a6f8ebabbb3bcaed3f5210c0 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 26 May 2023 22:34:41 +0200 Subject: [PATCH 45/89] relocate spline list to vec --- cpp/splinepy/py/py_spline.hpp | 19 ------------------- cpp/splinepy/py/py_spline_list.hpp | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index cc9590d3a..10af76d88 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -901,25 +901,6 @@ class PySpline { } }; -/// Internal use only -/// Extract CoreSpline_s from list of PySplines -inline std::vector -ListOfPySplinesToVectorOfCoreSplines(py::list pysplines, - const int nthreads = 1) { - // prepare return obj - const int n_splines = static_cast(pysplines.size()); - std::vector core_splines(n_splines); - - auto to_core = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - core_splines[i] = - pysplines[i].template cast>()->Core(); - } - }; - splinepy::utils::NThreadExecution(to_core, n_splines, nthreads); - - return core_splines; -} inline void add_spline_pyclass(py::module& m) { py::class_> diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 329bc9725..0a1e7cd3c 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -25,6 +25,26 @@ using IntVector = splinepy::utils::DefaultInitializationVector; using IntVectorVector = splinepy::utils::DefaultInitializationVector; using DoubleVector = splinepy::utils::DefaultInitializationVector; +/// Internal use only +/// Extract CoreSpline_s from list of PySplines +inline std::vector +ListOfPySplinesToVectorOfCoreSplines(py::list pysplines, + const int nthreads = 1) { + // prepare return obj + const int n_splines = static_cast(pysplines.size()); + std::vector core_splines(n_splines); + + auto to_core = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + core_splines[i] = + pysplines[i].template cast>()->Core(); + } + }; + splinepy::utils::NThreadExecution(to_core, n_splines, nthreads); + + return core_splines; +} + /// TODO: move ListOfPySplinesToVectorOfCoreSplines here and rename inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& splist, int nthreads) { From 4061b3f00c43cc0b22a416b96749a13319a29dff Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 6 Jun 2023 11:07:23 +0200 Subject: [PATCH 46/89] reorganize exporter and add multipatch --- cpp/splinepy/py/CMakeLists.txt | 1 + cpp/splinepy/py/init/multi_patch.cpp | 7 + cpp/splinepy/py/py_multi_patch.hpp | 1374 ++++++++++++++++++++++++ cpp/splinepy/py/py_spline.hpp | 1 - cpp/splinepy/py/py_spline_exporter.hpp | 1330 +---------------------- cpp/splinepy/py/splinepy_core.cpp | 4 + 6 files changed, 1387 insertions(+), 1330 deletions(-) create mode 100644 cpp/splinepy/py/init/multi_patch.cpp create mode 100644 cpp/splinepy/py/py_multi_patch.hpp diff --git a/cpp/splinepy/py/CMakeLists.txt b/cpp/splinepy/py/CMakeLists.txt index c80aad0aa..92083c565 100644 --- a/cpp/splinepy/py/CMakeLists.txt +++ b/cpp/splinepy/py/CMakeLists.txt @@ -4,6 +4,7 @@ set(PYSPLINEPY_SRCS init/coordinate_reference.cpp init/core_spline.cpp init/fitting.cpp + init/multi_patch.cpp init/reader.cpp init/spline_extensions.cpp init/spline_list.cpp diff --git a/cpp/splinepy/py/init/multi_patch.cpp b/cpp/splinepy/py/init/multi_patch.cpp new file mode 100644 index 000000000..46def9c71 --- /dev/null +++ b/cpp/splinepy/py/init/multi_patch.cpp @@ -0,0 +1,7 @@ +#include + +namespace splinepy::py::init { + +void init_multi_patch(py::module_& m) { add_multi_patch(m); } + +} // namespace splinepy::py::init diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp new file mode 100644 index 000000000..92c51788b --- /dev/null +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -0,0 +1,1374 @@ +#pragma once + +// pybind +#include +#include + +// Bezman +#include + +#include +#include +#include + +namespace splinepy::py { + +namespace py = pybind11; + +template +py::array_t +InterfacesFromBoundaryCenters_(const py::array_t& py_center_vertices, + const double& tolerance) { + // Auxiliary Function to reduce total number of declarations + using PhysicalPointType = bezman::Point; + + // Determine data + double* centers_ptr = static_cast(py_center_vertices.request().ptr); + const std::size_t number_of_center_points = + py_center_vertices.request().shape[0]; + const std::size_t physical_dimension_ = py_center_vertices.request().shape[1]; + constexpr std::size_t n_faces_per_patch = parametric_dimension * 2; + + // Assertions + assert(number_of_center_points > 0); + assert(physical_dimension_ > 0); + assert(number_of_center_points % n_faces_per_patch == 0); + + // Convert points into bezman points + std::vector center_points; + + PhysicalPointType minimumVertex{}, maximumVertex{}; + // Assign first vertex to both min and max + for (std::size_t i_dim{}; i_dim < physical_dimension; i_dim++) { + minimumVertex[i_dim] = centers_ptr[i_dim]; + maximumVertex[i_dim] = centers_ptr[i_dim]; + } + + center_points.reserve(number_of_center_points); + for (std::size_t i_point{}; i_point < number_of_center_points; i_point++) { + PhysicalPointType point{}; + for (std::size_t i_dim{}; i_dim < physical_dimension; i_dim++) { + point[i_dim] = centers_ptr[i_point * physical_dimension_ + i_dim]; + minimumVertex[i_dim] = + std::min(minimumVertex[i_dim], + centers_ptr[i_point * physical_dimension_ + i_dim]); + maximumVertex[i_dim] = + std::max(maximumVertex[i_dim], + centers_ptr[i_point * physical_dimension_ + i_dim]); + } + center_points.push_back(point); + } + + // Hand to bezman for connectivity + const auto connectivity = + bezman::utils::algorithms::FindConnectivityFromCenters< + parametric_dimension, + false>(center_points, maximumVertex - minimumVertex, tolerance); + + // Transform points into an array + const int number_of_patches = connectivity.size(); + py::array_t py_connectivity = + py::array_t(number_of_patches * n_faces_per_patch); + py_connectivity.resize({(int) number_of_patches, (int) n_faces_per_patch}); + int* py_connectivity_ptr = static_cast(py_connectivity.request().ptr); + for (std::size_t i_patch{}; i_patch < connectivity.size(); i_patch++) { + for (std::size_t i_face{}; i_face < n_faces_per_patch; i_face++) { + py_connectivity_ptr[i_patch * n_faces_per_patch + i_face] = + static_cast(connectivity[i_patch][i_face]); + } + } + + return py_connectivity; +} + +/** + * @brief Determines the Connectivity of spline patches + * + * @param py_center_vertices Vertices in the center of the boundaries + * @param tolerance tolerance between two neighboring face centers for them + * to be fused + * @param parametric_dimension Parametric dimension of the spline grid + * @return py::array_t connectivity + */ +py::array_t +InterfacesFromBoundaryCenters(const py::array_t& py_center_vertices, + const double& tolerance, + const int& parametric_dimension) { + // Transform points from pyarray into bezman point vector + double* centers_ptr = static_cast(py_center_vertices.request().ptr); + const std::size_t physical_dimension_ = py_center_vertices.request().shape[1]; + const std::size_t number_of_center_points = + py_center_vertices.request().shape[0]; + + // Check input data + assert(0 == (number_of_center_points % (2 * parametric_dimension))); + + // Convert points into bezman type points + switch (physical_dimension_) { + case 1: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 1uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 1uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 1uL>(py_center_vertices, + tolerance); + break; +#ifdef SPLINEPY_MORE + case 4: + return InterfacesFromBoundaryCenters_<4uL, 1uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 1uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 1uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 1uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 1uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 1uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 1uL>(py_center_vertices, + tolerance); + break; +#endif + default: + break; + } + break; + case 2: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 2uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 2uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 2uL>(py_center_vertices, + tolerance); + break; +#ifdef SPLINEPY_MORE + case 4: + return InterfacesFromBoundaryCenters_<4uL, 2uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 2uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 2uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 2uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 2uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 2uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 2uL>(py_center_vertices, + tolerance); + break; +#endif + + default: + break; + } + break; + case 3: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 3uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 3uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 3uL>(py_center_vertices, + tolerance); + break; +#ifdef SPLINEPY_MORE + case 4: + return InterfacesFromBoundaryCenters_<4uL, 3uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 3uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 3uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 3uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 3uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 3uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 3uL>(py_center_vertices, + tolerance); + break; +#endif + + default: + break; + } + break; +#ifdef SPLINEPY_MORE + case 4: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 4uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 4uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 4uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 4uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 4uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 4uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 4uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 4uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 4uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 4uL>(py_center_vertices, + tolerance); + break; + default: + break; + } + case 5: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 5uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 5uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 5uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 5uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 5uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 5uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 5uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 5uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 5uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 5uL>(py_center_vertices, + tolerance); + break; + default: + break; + } + case 6: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 6uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 6uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 6uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 6uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 6uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 6uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 6uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 6uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 6uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 6uL>(py_center_vertices, + tolerance); + break; + default: + break; + } + case 7: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 7uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 7uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 7uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 7uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 7uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 7uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 7uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 7uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 7uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 7uL>(py_center_vertices, + tolerance); + break; + default: + break; + } + case 8: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 8uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 8uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 8uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 8uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 8uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 8uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 8uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 8uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 8uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 8uL>(py_center_vertices, + tolerance); + break; + default: + break; + } + case 9: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 9uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 9uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 9uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 9uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 9uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 9uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 9uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 9uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 9uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 9uL>(py_center_vertices, + tolerance); + break; + default: + break; + } + case 10: + switch (parametric_dimension) { + case 1: + return InterfacesFromBoundaryCenters_<1uL, 10uL>(py_center_vertices, + tolerance); + break; + case 2: + return InterfacesFromBoundaryCenters_<2uL, 10uL>(py_center_vertices, + tolerance); + break; + case 3: + return InterfacesFromBoundaryCenters_<3uL, 10uL>(py_center_vertices, + tolerance); + break; + case 4: + return InterfacesFromBoundaryCenters_<4uL, 10uL>(py_center_vertices, + tolerance); + break; + case 5: + return InterfacesFromBoundaryCenters_<5uL, 10uL>(py_center_vertices, + tolerance); + break; + case 6: + return InterfacesFromBoundaryCenters_<6uL, 10uL>(py_center_vertices, + tolerance); + break; + case 7: + return InterfacesFromBoundaryCenters_<7uL, 10uL>(py_center_vertices, + tolerance); + break; + case 8: + return InterfacesFromBoundaryCenters_<8uL, 10uL>(py_center_vertices, + tolerance); + break; + case 9: + return InterfacesFromBoundaryCenters_<9uL, 10uL>(py_center_vertices, + tolerance); + break; + case 10: + return InterfacesFromBoundaryCenters_<10uL, 10uL>(py_center_vertices, + tolerance); + break; + default: + break; + } +#endif + default: + break; + } + +#ifdef SPLINEPY_MORE + splinepy::utils::PrintAndThrowError( + "Only implemented for <2-10> : <2-10> dimensions"); +#else + splinepy::utils::PrintAndThrowError( + "Only implemented for <1-3> : <1-3> dimensions"); +#endif + // dummy statement for compiler + return py::array_t(); +} + +/** + * @brief Orientation between two adjacent splines + * + * If two splines share the same boundary this function retrieves their + * orientation, by mapping the mappings of the parametric axis onto each other. + * This is (among others) required for Gismo and Nutils export + * + * @param pyspline_start Spline object from start + * @param boundary_start Boundary ID from start spline + * @param pyspline_end Spline object from end *to which is mapped + * @param boundary_end Boundary ID of adjacent spline + * @param int_mappings_ptr (output) integer mappings + * @param bool_orientations_ptr (output) axis alignement + * @return void + */ +void GetBoundaryOrientation( + const std::shared_ptr& pyspline_start, + const int& boundary_start, + const std::shared_ptr& pyspline_end, + const int& boundary_end, + const double& tolerance, + int* int_mappings_ptr, + bool* bool_orientations_ptr) { + // Init return values and get auxiliary data + const int& para_dim_ = pyspline_start->SplinepyParaDim(); + const int& dim_ = pyspline_start->SplinepyDim(); + + // Checks + if ((para_dim_ != pyspline_end->SplinepyParaDim()) + || (dim_ != pyspline_end->SplinepyDim())) { + splinepy::utils::PrintAndThrowError( + "Spline Orientation can not be checked, as they have mismatching" + "dimensionality start spline has dimensions ", + para_dim_, + "D -> ", + dim_, + "D, the adjacent one has dimensions ", + pyspline_end->SplinepyParaDim(), + "D -> ", + pyspline_end->SplinepyDim(), + "D."); + } + + // First Check the orientation of the first entry by comparing their ids + const int boundary_start_p_dim = static_cast(boundary_start / 2); + const bool boundary_start_orientation = (boundary_start % 2) == 0; + const int boundary_end_p_dim = static_cast(boundary_end / 2); + const bool boundary_end_orientation = (boundary_end % 2) == 0; + int_mappings_ptr[boundary_start_p_dim] = boundary_end_p_dim; + // Note: Here might be a discrepency with gismo's orientation, and it needs to + // be checked in the future. I am awaiting a response from gismo developers, + // it is poosible the orientation of the interface edge might be flipped + // (bugfix: negate the following expression) + bool_orientations_ptr[boundary_start_p_dim] = + (boundary_start_orientation ^ boundary_end_orientation); + + /// Compare jacobians for remaining entries + // Calculate Parametric bounds + std::vector bounds_start(para_dim_ * 2); + pyspline_start->SplinepyParametricBounds(bounds_start.data()); + std::vector bounds_end(para_dim_ * 2); + pyspline_end->SplinepyParametricBounds(bounds_end.data()); + // Determine face center position in parametric space + std::vector boundary_center_start(para_dim_), + boundary_center_end(para_dim_); + + for (int i{}; i < para_dim_; i++) { + if (i == boundary_start_p_dim) { + boundary_center_start[i] = boundary_start_orientation + ? bounds_start[i] + : bounds_start[i + para_dim_]; + } else { + boundary_center_start[i] = + .5 * (bounds_start[i] + bounds_start[i + para_dim_]); + } + if (i == boundary_end_p_dim) { + boundary_center_end[i] = + boundary_end_orientation ? bounds_end[i] : bounds_end[i + para_dim_]; + } else { + boundary_center_end[i] = .5 * (bounds_end[i] + bounds_end[i + para_dim_]); + } + } + + // Calculate Jacobians + std::vector jacobian_start(para_dim_ * dim_), + jacobian_end(para_dim_ * dim_); + pyspline_start->SplinepyJacobian(boundary_center_start.data(), + jacobian_start.data()); + pyspline_end->SplinepyJacobian(boundary_center_end.data(), + jacobian_end.data()); + + // Check the angle between the jacobian entries + for (int i_pd{}; i_pd < para_dim_; i_pd++) { + if (i_pd == boundary_start_p_dim) { + continue; + } + double norm_s{}; + for (int k{}; k < dim_; k++) { + // [i_query * pdim * dim + i_paradim * dim + i_dim] + norm_s += jacobian_start[i_pd + k * para_dim_] + * jacobian_start[i_pd + k * para_dim_]; + } + for (int j{}; j < para_dim_; j++) { + double norm_e{}, dot_p{}; + for (int k{}; k < dim_; k++) { + dot_p += jacobian_start[i_pd + k * para_dim_] + * jacobian_end[j + k * para_dim_]; + norm_e += + jacobian_end[j + k * para_dim_] * jacobian_end[j + k * para_dim_]; + } + + // Check angle + const double cos_angle = abs(dot_p / std::sqrt(norm_s * norm_e)); + if (cos_angle > (1. - tolerance)) { + int_mappings_ptr[i_pd] = j; + bool_orientations_ptr[i_pd] = (dot_p > 0); + break; + } + } + } +} + +/** + * @brief Get the Boundary Orientations object + * + * @param spline_list + * @param base_id + * @param base_face_id + * @param base_id + * @param base_face_id + * @param tolerance + * @param n_threads + * @return py::tuple + */ +py::tuple GetBoundaryOrientations(const py::list& spline_list, + const py::array_t& base_id, + const py::array_t& base_face_id, + const py::array_t& neighbor_id, + const py::array_t& neighbor_face_id, + const double tolerance, + const int n_threads) { + // Basic Checks + // Check if all have same size + if (!((base_id.size() == base_face_id.size()) + && (neighbor_id.size() == neighbor_face_id.size()) + && (base_id.size() == neighbor_face_id.size()))) { + splinepy::utils::PrintAndThrowError( + "The ID arrays need to be of same size, please check for " + "consistencies."); + } + + // Auxiliary data + const int* base_id_ptr = static_cast(base_id.request().ptr); + const int* base_face_id_ptr = static_cast(base_face_id.request().ptr); + const int* neighbor_id_ptr = static_cast(neighbor_id.request().ptr); + const int* neighbor_face_id_ptr = + static_cast(neighbor_face_id.request().ptr); + const auto cpp_spline_list = + ListOfPySplinesToVectorOfCoreSplines(spline_list); + const int n_connections = base_id.size(); + + const int para_dim_ = cpp_spline_list[0]->SplinepyParaDim(); + + py::array_t int_mapping(n_connections * para_dim_); + int* int_mapping_ptr = static_cast(int_mapping.request().ptr); + py::array_t bool_orientations(n_connections * para_dim_); + bool* bool_orientations_ptr = + static_cast(bool_orientations.request().ptr); + + // Provide lambda for multithread execution + auto get_orientation = [&](int start, int end) { + for (int i{start}; i < end; ++i) { + GetBoundaryOrientation(cpp_spline_list[base_id_ptr[i]], + base_face_id_ptr[i], + cpp_spline_list[neighbor_id_ptr[i]], + neighbor_face_id_ptr[i], + tolerance, + &int_mapping_ptr[i * para_dim_], + &bool_orientations_ptr[i * para_dim_]); + } + }; + + // Execute in parallel + splinepy::utils::NThreadExecution(get_orientation, n_connections, n_threads); + + // Resize and return + int_mapping.resize({n_connections, para_dim_}); + bool_orientations.resize({n_connections, para_dim_}); + + return py::make_tuple(int_mapping, bool_orientations); +} + +/** + * @brief Retrieve information related to mfem export + * + * @param py_corner_vertices vertices at the spline-corners + * @param tolerance tolerance to delete duplicates + * @return py::tuple with + * py::array_t : connectivity + * py::array_t : vertex_ids + * py::array_t : edges + * py::array_t : boundaries + * bool : is structured mesh + */ +py::tuple RetrieveMfemInformation(const py::array_t& py_corner_vertices, + const double& tolerance) { + // Unfortunatly bezman requires point-types to perform routines and does not + // work on py arrays All of the arguments serve as outputs except for + // corner_vertices + py::buffer_info corner_vertex_buffer = py_corner_vertices.request(); + double* corner_ptr = static_cast(corner_vertex_buffer.ptr); + const std::size_t physical_dimension_ = corner_vertex_buffer.shape[1]; + const std::size_t number_of_corner_points = corner_vertex_buffer.shape[0]; + // Check if mesh can be used for mfem mesh + bool is_structured{true}; + if (physical_dimension_ == 2) { + // Check if vertex size checks out + assert(number_of_corner_points % 4 == 0); + const std::size_t number_of_patches = number_of_corner_points / 4; + + // Transform corner_vertex into arrays + using PointType = bezman::Point<2ul, double>; + std::vector corner_vertices; + corner_vertices.reserve(number_of_corner_points); + // Init Max and min vertices to determine metric + PointType maxvertex{corner_ptr[0], corner_ptr[1]}, + minvertex{corner_ptr[0], corner_ptr[1]}; + for (std::size_t i_c{}; i_c < number_of_corner_points; i_c++) { + PointType vertex{}; + for (std::size_t i_dim{}; i_dim < physical_dimension_; i_dim++) { + vertex[i_dim] = corner_ptr[i_c * physical_dimension_ + i_dim]; + maxvertex[i_dim] = std::max(vertex[i_dim], maxvertex[i_dim]); + minvertex[i_dim] = std::min(vertex[i_dim], minvertex[i_dim]); + } + corner_vertices.push_back(vertex); + } + + // Retrieve MFEM information using bezman + // Connectivity : std::vector> + // Vertex_ids : std::vector + // edge_information : std::vector> + // boundaries : std::vector> + const auto [connectivity, vertex_ids, edge_information, boundaries] = + [&]() { + try { + return bezman::utils::algorithms::ExtractMFEMInformation( + corner_vertices, + maxvertex - minvertex, + tolerance); + } catch (...) { + is_structured = false; + return std::make_tuple( + // Connectivity + bezman::utils::algorithms::FindConnectivityFromCorners<2>( + corner_vertices, + maxvertex - minvertex, + tolerance, + false), + // All others initialized empty + std::vector{}, + std::vector>{}, + std::vector>{}); + } + }(); + + // -- Transform data to python format -- + // Connectivity + assert(connectivity.size() == number_of_patches); + py::array_t py_connectivity = + py::array_t(connectivity.size() * 4); + py_connectivity.resize({(int) number_of_patches, (int) 4}); + int* py_connectivity_ptr = static_cast(py_connectivity.request().ptr); + for (std::size_t i_patch{}; i_patch < connectivity.size(); i_patch++) { + for (std::size_t i_face{}; i_face < 4ul; i_face++) { + py_connectivity_ptr[i_patch * 4ul + i_face] = + static_cast(connectivity[i_patch][i_face]); + } + } + + // Return only connectivity if the mesh is unstructured and can not be used + // for mfem export + if (!is_structured) { + return py::make_tuple(py_connectivity, + py::array_t{}, + py::array_t{}, + py::array_t{}, + is_structured); + } + + // Vertex IDS + assert(vertex_ids.size() == corner_vertices.size()); + py::array_t py_vertex_ids = py::array_t(number_of_corner_points); + py_vertex_ids.resize({(int) number_of_corner_points}); + int* py_vertex_ids_ptr = static_cast(py_vertex_ids.request().ptr); + for (std::size_t i_ctps{}; i_ctps < number_of_corner_points; i_ctps++) { + py_vertex_ids_ptr[i_ctps] = static_cast(vertex_ids[i_ctps]); + } + + // Edges + assert(edge_information.size() > 0); + py::array_t py_edges = py::array_t(edge_information.size() * 3); + py_edges.resize({(int) edge_information.size(), (int) 3}); + int* py_edges_ptr = static_cast(py_edges.request().ptr); + for (std::size_t i_edge{}; i_edge < edge_information.size(); i_edge++) { + for (std::size_t i_face{}; i_face < 3ul; i_face++) { + py_edges_ptr[i_edge * 3ul + i_face] = + static_cast(edge_information[i_edge][i_face]); + } + } + + // Boundaries + assert(boundaries.size() > 0); + py::array_t py_boundaries = py::array_t(boundaries.size() * 2); + py_boundaries.resize({(int) boundaries.size(), (int) 2}); + int* py_boundaries_ptr = static_cast(py_boundaries.request().ptr); + for (std::size_t i_boundary{}; i_boundary < boundaries.size(); + i_boundary++) { + for (std::size_t i_id{}; i_id < 2ul; i_id++) { + py_boundaries_ptr[i_boundary * 2ul + i_id] = + static_cast(boundaries[i_boundary][i_id]); + } + } + + return py::make_tuple(py_connectivity, + py_vertex_ids, + py_edges, + py_boundaries, + is_structured); + + } else if (physical_dimension_ == 3) { + // Check if vertex size checks out + assert(number_of_corner_points % 8 == 0); + const std::size_t number_of_patches = number_of_corner_points / 8; + + // Transform corner_vertex into arrays + using PointType = bezman::Point<3ul, double>; + std::vector corner_vertices; + corner_vertices.reserve(number_of_corner_points); + // Init Max and min vertices to determine metric + PointType maxvertex{corner_ptr[0], corner_ptr[1], corner_ptr[2]}, + minvertex{corner_ptr[0], corner_ptr[1], corner_ptr[2]}; + for (std::size_t i_c{}; i_c < number_of_corner_points; i_c++) { + PointType vertex{}; + for (std::size_t i_dim{}; i_dim < physical_dimension_; i_dim++) { + vertex[i_dim] = corner_ptr[i_c * physical_dimension_ + i_dim]; + maxvertex[i_dim] = std::max(vertex[i_dim], maxvertex[i_dim]); + minvertex[i_dim] = std::min(vertex[i_dim], minvertex[i_dim]); + } + corner_vertices.push_back(vertex); + } + // Retrieve MFEM information using bezman + // Connectivity : std::vector> + // Vertex_ids : std::vector + // edge_information : std::vector + // boundaries : std::vector + const auto [connectivity, vertex_ids, edge_information, boundaries] = + [&]() { + try { + return bezman::utils::algorithms::ExtractMFEMInformation( + corner_vertices, + maxvertex - minvertex, + tolerance); + } catch (...) { + is_structured = false; + return std::make_tuple( + // Connectivity + bezman::utils::algorithms::FindConnectivityFromCorners<3>( + corner_vertices, + maxvertex - minvertex, + tolerance, + false), + // All others initialized empty + std::vector{}, + std::vector>{}, + std::vector>{}); + } + }(); + + // -- Transform data to python format -- + // Connectivity + assert(connectivity.size() == number_of_patches); + py::array_t py_connectivity = + py::array_t(connectivity.size() * 6); + py_connectivity.resize({(int) number_of_patches, (int) 6}); + int* py_connectivity_ptr = static_cast(py_connectivity.request().ptr); + for (std::size_t i_patch{}; i_patch < connectivity.size(); i_patch++) { + for (std::size_t i_face{}; i_face < 6ul; i_face++) { + py_connectivity_ptr[i_patch * 6ul + i_face] = + static_cast(connectivity[i_patch][i_face]); + } + } + + // Return only connectivity if the mesh is unstructured and can not be used + // for mfem export + if (!is_structured) { + return py::make_tuple(py_connectivity, + py::array_t{}, + py::array_t{}, + py::array_t{}, + is_structured); + } + + // Vertex IDS + assert(vertex_ids.size() == corner_vertices.size()); + py::array_t py_vertex_ids = py::array_t(number_of_corner_points); + py_vertex_ids.resize({(int) number_of_corner_points}); + int* py_vertex_ids_ptr = static_cast(py_vertex_ids.request().ptr); + for (std::size_t i_ctps{}; i_ctps < number_of_corner_points; i_ctps++) { + py_vertex_ids_ptr[i_ctps] = static_cast(vertex_ids[i_ctps]); + } + + // Edges + assert(edge_information.size() > 0); + py::array_t py_edges = py::array_t(edge_information.size() * 3); + py_edges.resize({(int) edge_information.size(), (int) 3}); + int* py_edges_ptr = static_cast(py_edges.request().ptr); + for (std::size_t i_edge{}; i_edge < edge_information.size(); i_edge++) { + for (std::size_t i_face{}; i_face < 3ul; i_face++) { + py_edges_ptr[i_edge * 3ul + i_face] = + static_cast(edge_information[i_edge][i_face]); + } + } + + // Boundaries + assert(boundaries.size() > 0); + py::array_t py_boundaries = py::array_t(boundaries.size() * 4); + py_boundaries.resize({(int) boundaries.size(), (int) 4}); + int* py_boundaries_ptr = static_cast(py_boundaries.request().ptr); + for (std::size_t i_boundary{}; i_boundary < boundaries.size(); + i_boundary++) { + for (std::size_t i_id{}; i_id < 4ul; i_id++) { + py_boundaries_ptr[i_boundary * 4ul + i_id] = + static_cast(boundaries[i_boundary][i_id]); + } + } + + return py::make_tuple(py_connectivity, + py_vertex_ids, + py_edges, + py_boundaries, + is_structured); + } else { + throw std::runtime_error("Dimension mismatch"); + } +} + +/** + * @brief Extract all Boundary Patches and store them in a python list + * + * @param spline_list List of splines + * @param interfaces interfaces, with negative values for boundary elements + * @return py::list + */ +py::list ExtractAllBoundarySplines(const py::list& spline_list, + const py::array_t& interfaces, + const int& n_threads) { + // Check input data + if (static_cast(py::len(spline_list)) != interfaces.shape(0)) { + splinepy::utils::PrintAndThrowError( + "Number of splines in list (", + py::len(spline_list), + ") and number of elements in interfaces (", + interfaces.shape(0), + ") does not match."); + } + + if (n_threads < 1) { + splinepy::utils::PrintAndThrowError( + "Number of threads must be positive integer."); + } + // Auxiliary data + py::list boundary_splines{}; + std::vector lists_to_concatenate(n_threads); + const int* interface_ptr = static_cast(interfaces.request().ptr); + const int n_patches = interfaces.shape(0); + const int n_faces = interfaces.shape(1); + const int para_dim_ = n_faces / 2; + const auto cpp_spline_list = + ListOfPySplinesToVectorOfCoreSplines(spline_list); + const int chunk_size = std::div((n_patches + n_threads - 1), n_threads).quot; + + // This approach is a work-around for parallel execution + auto extract_boundaries = [&](const int start, const int) { + // start : process-ID + // end : unused hence no referencing + + // Auxiliary variables + const int start_index = start * chunk_size; + const int end_index = (start + 1) * chunk_size; + auto& boundaries_local = lists_to_concatenate[start]; + // Start extraction (remaining order) + for (int i{start_index}; i < end_index; i++) { + for (int j{}; j < n_faces; j++) { + if (interface_ptr[i * n_faces + j] < 0) { + boundaries_local.append( + // Extract Boundary splines + PySpline(cpp_spline_list[i]->SplinepyExtractBoundary(j))); + } + } + } + }; + + // Execute in parallel + splinepy::utils::NThreadExecution(extract_boundaries, n_threads, n_threads); + + // Concatenate list of boundaries - should only copy pointers + for (auto& entries : lists_to_concatenate) { + boundary_splines += entries; + } + + return boundary_splines; +} + +/** + * @brief Adds a Boundary using a seed and G-continuity on boundary-splines + * + * This function might be a slight overkill, as it assignes all functions an ID, + * even when previously assigned a different ID -> Future Project + * + * @param boundary_splines boundary patches + * @param boundary_interfaces interfaces between boundary splines + * @param global_interfaces global interfaces (in between "volume"-patches) + * @param tolerance tolerance to be considered g1 (1 - cos(phi) < tolerance) + * @param n_threads number of threads for parallel processing + * @return int number of new boundaries + */ +int AddBoundariesFromContinuity(const py::list& boundary_splines, + const py::array_t& boundary_interfaces, + py::array_t& global_interfaces, + const double& tolerance, + const int& n_threads) { + // Check input data + if (static_cast(py::len(boundary_splines)) + != boundary_interfaces.shape(0)) { + splinepy::utils::PrintAndThrowError( + "Number of splines in list (", + py::len(boundary_splines), + ") and number of elements in connectivity (", + boundary_interfaces.shape(0), + ") does not match."); + } + + // Provide auxiliary values + const auto cpp_spline_list = + ListOfPySplinesToVectorOfCoreSplines(boundary_splines); + const int n_boundary_patches{static_cast(boundary_interfaces.shape(0))}; + const int n_faces_per_boundary_patch{ + static_cast(boundary_interfaces.shape(1))}; + const int para_dim_{n_faces_per_boundary_patch / 2}; + const int dim_ = cpp_spline_list[0]->SplinepyDim(); + const int* boundary_interfaces_ptr = + static_cast(boundary_interfaces.request().ptr); + int* global_interfaces_ptr = + static_cast(global_interfaces.request().ptr); + + // Auxiliary Lambdas to keep code clean + // Check if to tangential vectors are g1 (tol > cos(phi)) + auto areG1 = [&tolerance, &dim_](const std::vector& vec0, + const std::vector& vec1) -> bool { + // Checks in Debug + assert(static_cast(vec0.size()) == dim_); + assert(static_cast(vec1.size()) == dim_); + + // Start actual computation + double norm0{}, norm1{}, dot_p{}; + for (int i{}; i < dim_; i++) { + norm0 += vec0[i] * vec0[i]; + norm1 += vec1[i] * vec1[i]; + dot_p += vec0[i] * vec1[i]; + } + return (tolerance > abs(1 - abs(dot_p) / std::sqrt(norm0 * norm1))); + }; + + // Identify face_id in adjacent patch + auto face_id_in_neighbor = + [&boundary_interfaces_ptr, + &n_faces_per_boundary_patch](const int& base_patch_id, + const int& neighbor_patch_id) -> int { + // Loop over adjacent elements until base_patch_id is found + for (int i{}; i < n_faces_per_boundary_patch; i++) { + if (boundary_interfaces_ptr[neighbor_patch_id * n_faces_per_boundary_patch + + i] + == base_patch_id) { + return i; + } + } + // This part should never be reached + splinepy::utils::PrintAndThrowError("Interface connectivity has errors, " + "unidirectional interface detected."); + return -1; + }; + + // Tangential Vector on boundary based on its derivative + auto tangential_vector = [&cpp_spline_list, ¶_dim_, &dim_]( + const int& patch_id, + const int& face_id) -> std::vector { + // init return value (are default initialized to 0) + std::vector para_coord(para_dim_), bounds(2 * para_dim_), + tangential_vector(dim_); + std::vector orders(para_dim_); + + // Auxiliary values + const int axis_dim = face_id / 2; + const int is_in_front = face_id % 2; + + // Parametric Bounds + const auto& spline = cpp_spline_list[patch_id]; + spline->SplinepyParametricBounds(bounds.data()); + + for (int i{}; i < para_dim_; i++) { + if (i == axis_dim) { + para_coord[i] = bounds[i + is_in_front * para_dim_]; + orders[i] = 1; + } else { + para_coord[i] = .5 * (bounds[i + para_dim_] + bounds[i]); + } + } + spline->SplinepyDerivative(para_coord.data(), + orders.data(), + tangential_vector.data()); + return tangential_vector; + }; + + // Start Computations ------------------------------------------------ // + // while the actual propagation needs to be performed in serial, the + // precomputation of interface tolerances can be performed in parallel + std::vector faces_are_g1(n_faces_per_boundary_patch + * n_boundary_patches); + auto precompute_tolerances = [&](const int start, const int end) { + // Loop over relevant faces + for (int i{start}; i < end; i++) { + // Loop over faces + for (int j{}; j < n_faces_per_boundary_patch; j++) { + const int& adjacent_id = + boundary_interfaces_ptr[i * n_faces_per_boundary_patch + j]; + + if (adjacent_id < i) { + // only compute if the adjacent neighbor has higher id to prevent + // double the work + continue; + } + // Get tangential vector of current patch + const std::vector vec0 = tangential_vector(i, j); + + // Get corresponding tangential vector of neighbor patch + const int adjacent_face_id = face_id_in_neighbor(i, adjacent_id); + const std::vector vec1 = + tangential_vector(adjacent_id, adjacent_face_id); + + // Check tolerance + const bool is_g1 = areG1(vec0, vec1); + faces_are_g1[i * n_faces_per_boundary_patch + j] = is_g1; + faces_are_g1[adjacent_id * n_faces_per_boundary_patch + + adjacent_face_id] = is_g1; + } + } + }; + + // Execute in parallel + splinepy::utils::NThreadExecution(precompute_tolerances, + n_boundary_patches, + n_threads); + + // std::vector can use less memory for bools + std::vector is_assigned(n_boundary_patches); // defaults false + std::vector new_boundary_id(n_boundary_patches); + std::vector queued_splines{}; + + // Start Assignement + // Loop over all patches + int current_max_id{1}; + for (int i{}; i < n_boundary_patches; i++) { + if (is_assigned[i]) { + continue; + } + new_boundary_id[i] = current_max_id; + is_assigned[i] = true; + queued_splines.push_back(i); + + // Start propagation + while (!queued_splines.empty()) { + const int current_id = queued_splines.back(); + queued_splines.pop_back(); + for (int i_face{}; i_face < n_faces_per_boundary_patch; i_face++) { + const int combined_index = + current_id * n_faces_per_boundary_patch + i_face; + // Is the neighborface G1 + if (faces_are_g1[combined_index]) { + const int& adjacent_id = boundary_interfaces_ptr[combined_index]; + // Check if the adjacent patch is already assigned + if (is_assigned[adjacent_id]) { + continue; + } else { + // Assign a BID and continue + new_boundary_id[adjacent_id] = current_max_id; + is_assigned[adjacent_id] = true; + queued_splines.push_back(adjacent_id); + } + } + } + } + + // End propagation and increase id + current_max_id++; + } + + // Assign the new boundary ids to the old interface-vector + const int& n_interfaces = global_interfaces.size(); + int counter{}; + for (int i{}; i < n_interfaces; i++) { + if (global_interfaces_ptr[i] < 0) { + global_interfaces_ptr[i] = -new_boundary_id[counter]; + counter++; + } + } + if (counter != n_boundary_patches) { + splinepy::utils::PrintAndThrowError( + counter, + " new boundary ids were assigned, however ", + n_boundary_patches, + " were expected, which means information was lost. Abort mission"); + } + + return current_max_id; +} + +/// @brief Multi-patch splines. Here, use of the words +/// "patch" and "spline" are interchangeable +class PyMultiPatch { +public: + /// individual patches, a.k.a. splines + py::list patches_; + + /// casted, core splines (patches) from patches_ (list of PySpline). + std::vector core_patches_; + + /// Boundary multi patches. Should consist of one smaller parametric dim + std::shared_ptr boundary_patches_; + + /// patch-to-patch connectivity information + /// shape: (n_patches, n_boundary_elements) + py::array_t interfaces_; + + /// shape: (n_patches,) + py::array_t boundary_ids_; + + /// center of each boundary elements. + py::array_t boundary_centers_; + + /// ctor + PyMultiPatch() = default; +}; + +inline void add_multi_patch(py::module& m) { + // returns [connectivity, vertex_ids, edge_information, boundaries] + + m.def("retrieve_mfem_information", + &splinepy::py::RetrieveMfemInformation, + py::arg("corner_vertices"), + py::arg("tolerance")); + m.def("interfaces_from_boundary_centers", + &splinepy::py::InterfacesFromBoundaryCenters, + py::arg("face_center_vertices"), + py::arg("tolerance"), + py::arg("para_dim")); + m.def("extract_all_boundary_splines", + &splinepy::py::ExtractAllBoundarySplines, + py::arg("splines"), + py::arg("interfaces"), + py::arg("nthreads") = 1); + m.def("orientations", + &splinepy::py::GetBoundaryOrientations, + py::arg("splines"), + py::arg("base_ids"), + py::arg("base_face_ids"), + py::arg("neighbor_ids"), + py::arg("neighbor_face_ids"), + py::arg("tolerance"), + py::arg("nthreads") = 1); + m.def("boundaries_from_continuity", + &splinepy::py::AddBoundariesFromContinuity, + py::arg("boundary_splines"), + py::arg("boundary_interfaces"), + py::arg("global_interfaces"), + py::arg("tolerance"), + py::arg("nthreads") = 1); +} + +} // namespace splinepy::py diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index 10af76d88..b33928be4 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -901,7 +901,6 @@ class PySpline { } }; - inline void add_spline_pyclass(py::module& m) { py::class_> klasse(m, "CoreSpline"); diff --git a/cpp/splinepy/py/py_spline_exporter.hpp b/cpp/splinepy/py/py_spline_exporter.hpp index 54fbd96ac..e4285b0c5 100644 --- a/cpp/splinepy/py/py_spline_exporter.hpp +++ b/cpp/splinepy/py/py_spline_exporter.hpp @@ -9,9 +9,6 @@ #include #include -// Bezman -#include - // SplineLib #include #include @@ -152,1333 +149,8 @@ void ExportVtk(std::string fname, splinelib::sources::input_output::vtk::Sample(sl_io_splines, fname, sl_rps); } -template -py::array_t -InterfacesFromBoundaryCenters_(const py::array_t& py_center_vertices, - const double& tolerance) { - // Auxiliary Function to reduce total number of declarations - using PhysicalPointType = bezman::Point; - - // Determine data - double* centers_ptr = static_cast(py_center_vertices.request().ptr); - const std::size_t number_of_center_points = - py_center_vertices.request().shape[0]; - const std::size_t physical_dimension_ = py_center_vertices.request().shape[1]; - constexpr std::size_t n_faces_per_patch = parametric_dimension * 2; - - // Assertions - assert(number_of_center_points > 0); - assert(physical_dimension_ > 0); - assert(number_of_center_points % n_faces_per_patch == 0); - - // Convert points into bezman points - std::vector center_points; - - PhysicalPointType minimumVertex{}, maximumVertex{}; - // Assign first vertex to both min and max - for (std::size_t i_dim{}; i_dim < physical_dimension; i_dim++) { - minimumVertex[i_dim] = centers_ptr[i_dim]; - maximumVertex[i_dim] = centers_ptr[i_dim]; - } - - center_points.reserve(number_of_center_points); - for (std::size_t i_point{}; i_point < number_of_center_points; i_point++) { - PhysicalPointType point{}; - for (std::size_t i_dim{}; i_dim < physical_dimension; i_dim++) { - point[i_dim] = centers_ptr[i_point * physical_dimension_ + i_dim]; - minimumVertex[i_dim] = - std::min(minimumVertex[i_dim], - centers_ptr[i_point * physical_dimension_ + i_dim]); - maximumVertex[i_dim] = - std::max(maximumVertex[i_dim], - centers_ptr[i_point * physical_dimension_ + i_dim]); - } - center_points.push_back(point); - } - - // Hand to bezman for connectivity - const auto connectivity = - bezman::utils::algorithms::FindConnectivityFromCenters< - parametric_dimension, - false>(center_points, maximumVertex - minimumVertex, tolerance); - - // Transform points into an array - const int number_of_patches = connectivity.size(); - py::array_t py_connectivity = - py::array_t(number_of_patches * n_faces_per_patch); - py_connectivity.resize({(int) number_of_patches, (int) n_faces_per_patch}); - int* py_connectivity_ptr = static_cast(py_connectivity.request().ptr); - for (std::size_t i_patch{}; i_patch < connectivity.size(); i_patch++) { - for (std::size_t i_face{}; i_face < n_faces_per_patch; i_face++) { - py_connectivity_ptr[i_patch * n_faces_per_patch + i_face] = - static_cast(connectivity[i_patch][i_face]); - } - } - - return py_connectivity; -} - -/** - * @brief Determines the Connectivity of spline patches - * - * @param py_center_vertices Vertices in the center of the boundaries - * @param tolerance tolerance between two neighboring face centers for them - * to be fused - * @param parametric_dimension Parametric dimension of the spline grid - * @return py::array_t connectivity - */ -py::array_t -InterfacesFromBoundaryCenters(const py::array_t& py_center_vertices, - const double& tolerance, - const int& parametric_dimension) { - // Transform points from pyarray into bezman point vector - double* centers_ptr = static_cast(py_center_vertices.request().ptr); - const std::size_t physical_dimension_ = py_center_vertices.request().shape[1]; - const std::size_t number_of_center_points = - py_center_vertices.request().shape[0]; - - // Check input data - assert(0 == (number_of_center_points % (2 * parametric_dimension))); - - // Convert points into bezman type points - switch (physical_dimension_) { - case 1: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 1uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 1uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 1uL>(py_center_vertices, - tolerance); - break; -#ifdef SPLINEPY_MORE - case 4: - return InterfacesFromBoundaryCenters_<4uL, 1uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 1uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 1uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 1uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 1uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 1uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 1uL>(py_center_vertices, - tolerance); - break; -#endif - default: - break; - } - break; - case 2: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 2uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 2uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 2uL>(py_center_vertices, - tolerance); - break; -#ifdef SPLINEPY_MORE - case 4: - return InterfacesFromBoundaryCenters_<4uL, 2uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 2uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 2uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 2uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 2uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 2uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 2uL>(py_center_vertices, - tolerance); - break; -#endif - - default: - break; - } - break; - case 3: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 3uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 3uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 3uL>(py_center_vertices, - tolerance); - break; -#ifdef SPLINEPY_MORE - case 4: - return InterfacesFromBoundaryCenters_<4uL, 3uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 3uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 3uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 3uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 3uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 3uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 3uL>(py_center_vertices, - tolerance); - break; -#endif - - default: - break; - } - break; -#ifdef SPLINEPY_MORE - case 4: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 4uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 4uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 4uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 4uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 4uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 4uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 4uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 4uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 4uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 4uL>(py_center_vertices, - tolerance); - break; - default: - break; - } - case 5: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 5uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 5uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 5uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 5uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 5uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 5uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 5uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 5uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 5uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 5uL>(py_center_vertices, - tolerance); - break; - default: - break; - } - case 6: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 6uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 6uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 6uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 6uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 6uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 6uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 6uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 6uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 6uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 6uL>(py_center_vertices, - tolerance); - break; - default: - break; - } - case 7: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 7uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 7uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 7uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 7uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 7uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 7uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 7uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 7uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 7uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 7uL>(py_center_vertices, - tolerance); - break; - default: - break; - } - case 8: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 8uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 8uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 8uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 8uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 8uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 8uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 8uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 8uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 8uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 8uL>(py_center_vertices, - tolerance); - break; - default: - break; - } - case 9: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 9uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 9uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 9uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 9uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 9uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 9uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 9uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 9uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 9uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 9uL>(py_center_vertices, - tolerance); - break; - default: - break; - } - case 10: - switch (parametric_dimension) { - case 1: - return InterfacesFromBoundaryCenters_<1uL, 10uL>(py_center_vertices, - tolerance); - break; - case 2: - return InterfacesFromBoundaryCenters_<2uL, 10uL>(py_center_vertices, - tolerance); - break; - case 3: - return InterfacesFromBoundaryCenters_<3uL, 10uL>(py_center_vertices, - tolerance); - break; - case 4: - return InterfacesFromBoundaryCenters_<4uL, 10uL>(py_center_vertices, - tolerance); - break; - case 5: - return InterfacesFromBoundaryCenters_<5uL, 10uL>(py_center_vertices, - tolerance); - break; - case 6: - return InterfacesFromBoundaryCenters_<6uL, 10uL>(py_center_vertices, - tolerance); - break; - case 7: - return InterfacesFromBoundaryCenters_<7uL, 10uL>(py_center_vertices, - tolerance); - break; - case 8: - return InterfacesFromBoundaryCenters_<8uL, 10uL>(py_center_vertices, - tolerance); - break; - case 9: - return InterfacesFromBoundaryCenters_<9uL, 10uL>(py_center_vertices, - tolerance); - break; - case 10: - return InterfacesFromBoundaryCenters_<10uL, 10uL>(py_center_vertices, - tolerance); - break; - default: - break; - } -#endif - default: - break; - } - -#ifdef SPLINEPY_MORE - splinepy::utils::PrintAndThrowError( - "Only implemented for <2-10> : <2-10> dimensions"); -#else - splinepy::utils::PrintAndThrowError( - "Only implemented for <1-3> : <1-3> dimensions"); -#endif - // dummy statement for compiler - return py::array_t(); -} - -/** - * @brief Orientation between two adjacent splines - * - * If two splines share the same boundary this function retrieves their - * orientation, by mapping the mappings of the parametric axis onto each other. - * This is (among others) required for Gismo and Nutils export - * - * @param pyspline_start Spline object from start - * @param boundary_start Boundary ID from start spline - * @param pyspline_end Spline object from end *to which is mapped - * @param boundary_end Boundary ID of adjacent spline - * @param int_mappings_ptr (output) integer mappings - * @param bool_orientations_ptr (output) axis alignement - * @return void - */ -void GetBoundaryOrientation( - const std::shared_ptr& pyspline_start, - const int& boundary_start, - const std::shared_ptr& pyspline_end, - const int& boundary_end, - const double& tolerance, - int* int_mappings_ptr, - bool* bool_orientations_ptr) { - // Init return values and get auxiliary data - const int& para_dim_ = pyspline_start->SplinepyParaDim(); - const int& dim_ = pyspline_start->SplinepyDim(); - - // Checks - if ((para_dim_ != pyspline_end->SplinepyParaDim()) - || (dim_ != pyspline_end->SplinepyDim())) { - splinepy::utils::PrintAndThrowError( - "Spline Orientation can not be checked, as they have mismatching" - "dimensionality start spline has dimensions ", - para_dim_, - "D -> ", - dim_, - "D, the adjacent one has dimensions ", - pyspline_end->SplinepyParaDim(), - "D -> ", - pyspline_end->SplinepyDim(), - "D."); - } - - // First Check the orientation of the first entry by comparing their ids - const int boundary_start_p_dim = static_cast(boundary_start / 2); - const bool boundary_start_orientation = (boundary_start % 2) == 0; - const int boundary_end_p_dim = static_cast(boundary_end / 2); - const bool boundary_end_orientation = (boundary_end % 2) == 0; - int_mappings_ptr[boundary_start_p_dim] = boundary_end_p_dim; - // Note: Here might be a discrepency with gismo's orientation, and it needs to - // be checked in the future. I am awaiting a response from gismo developers, - // it is poosible the orientation of the interface edge might be flipped - // (bugfix: negate the following expression) - bool_orientations_ptr[boundary_start_p_dim] = - (boundary_start_orientation ^ boundary_end_orientation); - - /// Compare jacobians for remaining entries - // Calculate Parametric bounds - std::vector bounds_start(para_dim_ * 2); - pyspline_start->SplinepyParametricBounds(bounds_start.data()); - std::vector bounds_end(para_dim_ * 2); - pyspline_end->SplinepyParametricBounds(bounds_end.data()); - // Determine face center position in parametric space - std::vector boundary_center_start(para_dim_), - boundary_center_end(para_dim_); - - for (int i{}; i < para_dim_; i++) { - if (i == boundary_start_p_dim) { - boundary_center_start[i] = boundary_start_orientation - ? bounds_start[i] - : bounds_start[i + para_dim_]; - } else { - boundary_center_start[i] = - .5 * (bounds_start[i] + bounds_start[i + para_dim_]); - } - if (i == boundary_end_p_dim) { - boundary_center_end[i] = - boundary_end_orientation ? bounds_end[i] : bounds_end[i + para_dim_]; - } else { - boundary_center_end[i] = .5 * (bounds_end[i] + bounds_end[i + para_dim_]); - } - } - - // Calculate Jacobians - std::vector jacobian_start(para_dim_ * dim_), - jacobian_end(para_dim_ * dim_); - pyspline_start->SplinepyJacobian(boundary_center_start.data(), - jacobian_start.data()); - pyspline_end->SplinepyJacobian(boundary_center_end.data(), - jacobian_end.data()); - - // Check the angle between the jacobian entries - for (int i_pd{}; i_pd < para_dim_; i_pd++) { - if (i_pd == boundary_start_p_dim) { - continue; - } - double norm_s{}; - for (int k{}; k < dim_; k++) { - // [i_query * pdim * dim + i_paradim * dim + i_dim] - norm_s += jacobian_start[i_pd + k * para_dim_] - * jacobian_start[i_pd + k * para_dim_]; - } - for (int j{}; j < para_dim_; j++) { - double norm_e{}, dot_p{}; - for (int k{}; k < dim_; k++) { - dot_p += jacobian_start[i_pd + k * para_dim_] - * jacobian_end[j + k * para_dim_]; - norm_e += - jacobian_end[j + k * para_dim_] * jacobian_end[j + k * para_dim_]; - } - - // Check angle - const double cos_angle = abs(dot_p / std::sqrt(norm_s * norm_e)); - if (cos_angle > (1. - tolerance)) { - int_mappings_ptr[i_pd] = j; - bool_orientations_ptr[i_pd] = (dot_p > 0); - break; - } - } - } -} - -/** - * @brief Get the Boundary Orientations object - * - * @param spline_list - * @param base_id - * @param base_face_id - * @param base_id - * @param base_face_id - * @param tolerance - * @param n_threads - * @return py::tuple - */ -py::tuple GetBoundaryOrientations(const py::list& spline_list, - const py::array_t& base_id, - const py::array_t& base_face_id, - const py::array_t& neighbor_id, - const py::array_t& neighbor_face_id, - const double tolerance, - const int n_threads) { - // Basic Checks - // Check if all have same size - if (!((base_id.size() == base_face_id.size()) - && (neighbor_id.size() == neighbor_face_id.size()) - && (base_id.size() == neighbor_face_id.size()))) { - splinepy::utils::PrintAndThrowError( - "The ID arrays need to be of same size, please check for " - "consistencies."); - } - - // Auxiliary data - const int* base_id_ptr = static_cast(base_id.request().ptr); - const int* base_face_id_ptr = static_cast(base_face_id.request().ptr); - const int* neighbor_id_ptr = static_cast(neighbor_id.request().ptr); - const int* neighbor_face_id_ptr = - static_cast(neighbor_face_id.request().ptr); - const auto cpp_spline_list = - ListOfPySplinesToVectorOfCoreSplines(spline_list); - const int n_connections = base_id.size(); - - const int para_dim_ = cpp_spline_list[0]->SplinepyParaDim(); - - py::array_t int_mapping(n_connections * para_dim_); - int* int_mapping_ptr = static_cast(int_mapping.request().ptr); - py::array_t bool_orientations(n_connections * para_dim_); - bool* bool_orientations_ptr = - static_cast(bool_orientations.request().ptr); - - // Provide lambda for multithread execution - auto get_orientation = [&](int start, int end) { - for (int i{start}; i < end; ++i) { - GetBoundaryOrientation(cpp_spline_list[base_id_ptr[i]], - base_face_id_ptr[i], - cpp_spline_list[neighbor_id_ptr[i]], - neighbor_face_id_ptr[i], - tolerance, - &int_mapping_ptr[i * para_dim_], - &bool_orientations_ptr[i * para_dim_]); - } - }; - - // Execute in parallel - splinepy::utils::NThreadExecution(get_orientation, n_connections, n_threads); - - // Resize and return - int_mapping.resize({n_connections, para_dim_}); - bool_orientations.resize({n_connections, para_dim_}); - - return py::make_tuple(int_mapping, bool_orientations); -} - -/** - * @brief Retrieve information related to mfem export - * - * @param py_corner_vertices vertices at the spline-corners - * @param tolerance tolerance to delete duplicates - * @return py::tuple with - * py::array_t : connectivity - * py::array_t : vertex_ids - * py::array_t : edges - * py::array_t : boundaries - * bool : is structured mesh - */ -py::tuple RetrieveMfemInformation(const py::array_t& py_corner_vertices, - const double& tolerance) { - // Unfortunatly bezman requires point-types to perform routines and does not - // work on py arrays All of the arguments serve as outputs except for - // corner_vertices - py::buffer_info corner_vertex_buffer = py_corner_vertices.request(); - double* corner_ptr = static_cast(corner_vertex_buffer.ptr); - const std::size_t physical_dimension_ = corner_vertex_buffer.shape[1]; - const std::size_t number_of_corner_points = corner_vertex_buffer.shape[0]; - // Check if mesh can be used for mfem mesh - bool is_structured{true}; - if (physical_dimension_ == 2) { - // Check if vertex size checks out - assert(number_of_corner_points % 4 == 0); - const std::size_t number_of_patches = number_of_corner_points / 4; - - // Transform corner_vertex into arrays - using PointType = bezman::Point<2ul, double>; - std::vector corner_vertices; - corner_vertices.reserve(number_of_corner_points); - // Init Max and min vertices to determine metric - PointType maxvertex{corner_ptr[0], corner_ptr[1]}, - minvertex{corner_ptr[0], corner_ptr[1]}; - for (std::size_t i_c{}; i_c < number_of_corner_points; i_c++) { - PointType vertex{}; - for (std::size_t i_dim{}; i_dim < physical_dimension_; i_dim++) { - vertex[i_dim] = corner_ptr[i_c * physical_dimension_ + i_dim]; - maxvertex[i_dim] = std::max(vertex[i_dim], maxvertex[i_dim]); - minvertex[i_dim] = std::min(vertex[i_dim], minvertex[i_dim]); - } - corner_vertices.push_back(vertex); - } - - // Retrieve MFEM information using bezman - // Connectivity : std::vector> - // Vertex_ids : std::vector - // edge_information : std::vector> - // boundaries : std::vector> - const auto [connectivity, vertex_ids, edge_information, boundaries] = - [&]() { - try { - return bezman::utils::algorithms::ExtractMFEMInformation( - corner_vertices, - maxvertex - minvertex, - tolerance); - } catch (...) { - is_structured = false; - return std::make_tuple( - // Connectivity - bezman::utils::algorithms::FindConnectivityFromCorners<2>( - corner_vertices, - maxvertex - minvertex, - tolerance, - false), - // All others initialized empty - std::vector{}, - std::vector>{}, - std::vector>{}); - } - }(); - - // -- Transform data to python format -- - // Connectivity - assert(connectivity.size() == number_of_patches); - py::array_t py_connectivity = - py::array_t(connectivity.size() * 4); - py_connectivity.resize({(int) number_of_patches, (int) 4}); - int* py_connectivity_ptr = static_cast(py_connectivity.request().ptr); - for (std::size_t i_patch{}; i_patch < connectivity.size(); i_patch++) { - for (std::size_t i_face{}; i_face < 4ul; i_face++) { - py_connectivity_ptr[i_patch * 4ul + i_face] = - static_cast(connectivity[i_patch][i_face]); - } - } - - // Return only connectivity if the mesh is unstructured and can not be used - // for mfem export - if (!is_structured) { - return py::make_tuple(py_connectivity, - py::array_t{}, - py::array_t{}, - py::array_t{}, - is_structured); - } - - // Vertex IDS - assert(vertex_ids.size() == corner_vertices.size()); - py::array_t py_vertex_ids = py::array_t(number_of_corner_points); - py_vertex_ids.resize({(int) number_of_corner_points}); - int* py_vertex_ids_ptr = static_cast(py_vertex_ids.request().ptr); - for (std::size_t i_ctps{}; i_ctps < number_of_corner_points; i_ctps++) { - py_vertex_ids_ptr[i_ctps] = static_cast(vertex_ids[i_ctps]); - } - - // Edges - assert(edge_information.size() > 0); - py::array_t py_edges = py::array_t(edge_information.size() * 3); - py_edges.resize({(int) edge_information.size(), (int) 3}); - int* py_edges_ptr = static_cast(py_edges.request().ptr); - for (std::size_t i_edge{}; i_edge < edge_information.size(); i_edge++) { - for (std::size_t i_face{}; i_face < 3ul; i_face++) { - py_edges_ptr[i_edge * 3ul + i_face] = - static_cast(edge_information[i_edge][i_face]); - } - } - - // Boundaries - assert(boundaries.size() > 0); - py::array_t py_boundaries = py::array_t(boundaries.size() * 2); - py_boundaries.resize({(int) boundaries.size(), (int) 2}); - int* py_boundaries_ptr = static_cast(py_boundaries.request().ptr); - for (std::size_t i_boundary{}; i_boundary < boundaries.size(); - i_boundary++) { - for (std::size_t i_id{}; i_id < 2ul; i_id++) { - py_boundaries_ptr[i_boundary * 2ul + i_id] = - static_cast(boundaries[i_boundary][i_id]); - } - } - - return py::make_tuple(py_connectivity, - py_vertex_ids, - py_edges, - py_boundaries, - is_structured); - - } else if (physical_dimension_ == 3) { - // Check if vertex size checks out - assert(number_of_corner_points % 8 == 0); - const std::size_t number_of_patches = number_of_corner_points / 8; - - // Transform corner_vertex into arrays - using PointType = bezman::Point<3ul, double>; - std::vector corner_vertices; - corner_vertices.reserve(number_of_corner_points); - // Init Max and min vertices to determine metric - PointType maxvertex{corner_ptr[0], corner_ptr[1], corner_ptr[2]}, - minvertex{corner_ptr[0], corner_ptr[1], corner_ptr[2]}; - for (std::size_t i_c{}; i_c < number_of_corner_points; i_c++) { - PointType vertex{}; - for (std::size_t i_dim{}; i_dim < physical_dimension_; i_dim++) { - vertex[i_dim] = corner_ptr[i_c * physical_dimension_ + i_dim]; - maxvertex[i_dim] = std::max(vertex[i_dim], maxvertex[i_dim]); - minvertex[i_dim] = std::min(vertex[i_dim], minvertex[i_dim]); - } - corner_vertices.push_back(vertex); - } - // Retrieve MFEM information using bezman - // Connectivity : std::vector> - // Vertex_ids : std::vector - // edge_information : std::vector - // boundaries : std::vector - const auto [connectivity, vertex_ids, edge_information, boundaries] = - [&]() { - try { - return bezman::utils::algorithms::ExtractMFEMInformation( - corner_vertices, - maxvertex - minvertex, - tolerance); - } catch (...) { - is_structured = false; - return std::make_tuple( - // Connectivity - bezman::utils::algorithms::FindConnectivityFromCorners<3>( - corner_vertices, - maxvertex - minvertex, - tolerance, - false), - // All others initialized empty - std::vector{}, - std::vector>{}, - std::vector>{}); - } - }(); - - // -- Transform data to python format -- - // Connectivity - assert(connectivity.size() == number_of_patches); - py::array_t py_connectivity = - py::array_t(connectivity.size() * 6); - py_connectivity.resize({(int) number_of_patches, (int) 6}); - int* py_connectivity_ptr = static_cast(py_connectivity.request().ptr); - for (std::size_t i_patch{}; i_patch < connectivity.size(); i_patch++) { - for (std::size_t i_face{}; i_face < 6ul; i_face++) { - py_connectivity_ptr[i_patch * 6ul + i_face] = - static_cast(connectivity[i_patch][i_face]); - } - } - - // Return only connectivity if the mesh is unstructured and can not be used - // for mfem export - if (!is_structured) { - return py::make_tuple(py_connectivity, - py::array_t{}, - py::array_t{}, - py::array_t{}, - is_structured); - } - - // Vertex IDS - assert(vertex_ids.size() == corner_vertices.size()); - py::array_t py_vertex_ids = py::array_t(number_of_corner_points); - py_vertex_ids.resize({(int) number_of_corner_points}); - int* py_vertex_ids_ptr = static_cast(py_vertex_ids.request().ptr); - for (std::size_t i_ctps{}; i_ctps < number_of_corner_points; i_ctps++) { - py_vertex_ids_ptr[i_ctps] = static_cast(vertex_ids[i_ctps]); - } - - // Edges - assert(edge_information.size() > 0); - py::array_t py_edges = py::array_t(edge_information.size() * 3); - py_edges.resize({(int) edge_information.size(), (int) 3}); - int* py_edges_ptr = static_cast(py_edges.request().ptr); - for (std::size_t i_edge{}; i_edge < edge_information.size(); i_edge++) { - for (std::size_t i_face{}; i_face < 3ul; i_face++) { - py_edges_ptr[i_edge * 3ul + i_face] = - static_cast(edge_information[i_edge][i_face]); - } - } - - // Boundaries - assert(boundaries.size() > 0); - py::array_t py_boundaries = py::array_t(boundaries.size() * 4); - py_boundaries.resize({(int) boundaries.size(), (int) 4}); - int* py_boundaries_ptr = static_cast(py_boundaries.request().ptr); - for (std::size_t i_boundary{}; i_boundary < boundaries.size(); - i_boundary++) { - for (std::size_t i_id{}; i_id < 4ul; i_id++) { - py_boundaries_ptr[i_boundary * 4ul + i_id] = - static_cast(boundaries[i_boundary][i_id]); - } - } - - return py::make_tuple(py_connectivity, - py_vertex_ids, - py_edges, - py_boundaries, - is_structured); - } else { - throw std::runtime_error("Dimension mismatch"); - } -} - -/** - * @brief Extract all Boundary Patches and store them in a python list - * - * @param spline_list List of splines - * @param interfaces interfaces, with negative values for boundary elements - * @return py::list - */ -py::list ExtractAllBoundarySplines(const py::list& spline_list, - const py::array_t& interfaces, - const int& n_threads) { - // Check input data - if (static_cast(py::len(spline_list)) != interfaces.shape(0)) { - splinepy::utils::PrintAndThrowError( - "Number of splines in list (", - py::len(spline_list), - ") and number of elements in interfaces (", - interfaces.shape(0), - ") does not match."); - } - - if (n_threads < 1) { - splinepy::utils::PrintAndThrowError( - "Number of threads must be positive integer."); - } - // Auxiliary data - py::list boundary_splines{}; - std::vector lists_to_concatenate(n_threads); - const int* interface_ptr = static_cast(interfaces.request().ptr); - const int n_patches = interfaces.shape(0); - const int n_faces = interfaces.shape(1); - const int para_dim_ = n_faces / 2; - const auto cpp_spline_list = - ListOfPySplinesToVectorOfCoreSplines(spline_list); - const int chunk_size = std::div((n_patches + n_threads - 1), n_threads).quot; - - // This approach is a work-around for parallel execution - auto extract_boundaries = [&](const int start, const int) { - // start : process-ID - // end : unused hence no referencing - - // Auxiliary variables - const int start_index = start * chunk_size; - const int end_index = (start + 1) * chunk_size; - auto& boundaries_local = lists_to_concatenate[start]; - // Start extraction (remaining order) - for (int i{start_index}; i < end_index; i++) { - for (int j{}; j < n_faces; j++) { - if (interface_ptr[i * n_faces + j] < 0) { - boundaries_local.append( - // Extract Boundary splines - PySpline(cpp_spline_list[i]->SplinepyExtractBoundary(j))); - } - } - } - }; - - // Execute in parallel - splinepy::utils::NThreadExecution(extract_boundaries, n_threads, n_threads); - - // Concatenate list of boundaries - should only copy pointers - for (auto& entries : lists_to_concatenate) { - boundary_splines += entries; - } - - return boundary_splines; -} - -/** - * @brief Adds a Boundary using a seed and G-continuity on boundary-splines - * - * This function might be a slight overkill, as it assignes all functions an ID, - * even when previously assigned a different ID -> Future Project - * - * @param boundary_splines boundary patches - * @param boundary_interfaces interfaces between boundary splines - * @param global_interfaces global interfaces (in between "volume"-patches) - * @param tolerance tolerance to be considered g1 (1 - cos(phi) < tolerance) - * @param n_threads number of threads for parallel processing - * @return int number of new boundaries - */ -int AddBoundariesFromContinuity(const py::list& boundary_splines, - const py::array_t& boundary_interfaces, - py::array_t& global_interfaces, - const double& tolerance, - const int& n_threads) { - // Check input data - if (static_cast(py::len(boundary_splines)) - != boundary_interfaces.shape(0)) { - splinepy::utils::PrintAndThrowError( - "Number of splines in list (", - py::len(boundary_splines), - ") and number of elements in connectivity (", - boundary_interfaces.shape(0), - ") does not match."); - } - - // Provide auxiliary values - const auto cpp_spline_list = - ListOfPySplinesToVectorOfCoreSplines(boundary_splines); - const int n_boundary_patches{static_cast(boundary_interfaces.shape(0))}; - const int n_faces_per_boundary_patch{ - static_cast(boundary_interfaces.shape(1))}; - const int para_dim_{n_faces_per_boundary_patch / 2}; - const int dim_ = cpp_spline_list[0]->SplinepyDim(); - const int* boundary_interfaces_ptr = - static_cast(boundary_interfaces.request().ptr); - int* global_interfaces_ptr = - static_cast(global_interfaces.request().ptr); - - // Auxiliary Lambdas to keep code clean - // Check if to tangential vectors are g1 (tol > cos(phi)) - auto areG1 = [&tolerance, &dim_](const std::vector& vec0, - const std::vector& vec1) -> bool { - // Checks in Debug - assert(static_cast(vec0.size()) == dim_); - assert(static_cast(vec1.size()) == dim_); - - // Start actual computation - double norm0{}, norm1{}, dot_p{}; - for (int i{}; i < dim_; i++) { - norm0 += vec0[i] * vec0[i]; - norm1 += vec1[i] * vec1[i]; - dot_p += vec0[i] * vec1[i]; - } - return (tolerance > abs(1 - abs(dot_p) / std::sqrt(norm0 * norm1))); - }; - - // Identify face_id in adjacent patch - auto face_id_in_neighbor = - [&boundary_interfaces_ptr, - &n_faces_per_boundary_patch](const int& base_patch_id, - const int& neighbor_patch_id) -> int { - // Loop over adjacent elements until base_patch_id is found - for (int i{}; i < n_faces_per_boundary_patch; i++) { - if (boundary_interfaces_ptr[neighbor_patch_id * n_faces_per_boundary_patch - + i] - == base_patch_id) { - return i; - } - } - // This part should never be reached - splinepy::utils::PrintAndThrowError("Interface connectivity has errors, " - "unidirectional interface detected."); - return -1; - }; - - // Tangential Vector on boundary based on its derivative - auto tangential_vector = [&cpp_spline_list, ¶_dim_, &dim_]( - const int& patch_id, - const int& face_id) -> std::vector { - // init return value (are default initialized to 0) - std::vector para_coord(para_dim_), bounds(2 * para_dim_), - tangential_vector(dim_); - std::vector orders(para_dim_); - - // Auxiliary values - const int axis_dim = face_id / 2; - const int is_in_front = face_id % 2; - - // Parametric Bounds - const auto& spline = cpp_spline_list[patch_id]; - spline->SplinepyParametricBounds(bounds.data()); - - for (int i{}; i < para_dim_; i++) { - if (i == axis_dim) { - para_coord[i] = bounds[i + is_in_front * para_dim_]; - orders[i] = 1; - } else { - para_coord[i] = .5 * (bounds[i + para_dim_] + bounds[i]); - } - } - spline->SplinepyDerivative(para_coord.data(), - orders.data(), - tangential_vector.data()); - return tangential_vector; - }; - - // Start Computations ------------------------------------------------ // - // while the actual propagation needs to be performed in serial, the - // precomputation of interface tolerances can be performed in parallel - std::vector faces_are_g1(n_faces_per_boundary_patch - * n_boundary_patches); - auto precompute_tolerances = [&](const int start, const int end) { - // Loop over relevant faces - for (int i{start}; i < end; i++) { - // Loop over faces - for (int j{}; j < n_faces_per_boundary_patch; j++) { - const int& adjacent_id = - boundary_interfaces_ptr[i * n_faces_per_boundary_patch + j]; - - if (adjacent_id < i) { - // only compute if the adjacent neighbor has higher id to prevent - // double the work - continue; - } - // Get tangential vector of current patch - const std::vector vec0 = tangential_vector(i, j); - - // Get corresponding tangential vector of neighbor patch - const int adjacent_face_id = face_id_in_neighbor(i, adjacent_id); - const std::vector vec1 = - tangential_vector(adjacent_id, adjacent_face_id); - - // Check tolerance - const bool is_g1 = areG1(vec0, vec1); - faces_are_g1[i * n_faces_per_boundary_patch + j] = is_g1; - faces_are_g1[adjacent_id * n_faces_per_boundary_patch - + adjacent_face_id] = is_g1; - } - } - }; - - // Execute in parallel - splinepy::utils::NThreadExecution(precompute_tolerances, - n_boundary_patches, - n_threads); - - // std::vector can use less memory for bools - std::vector is_assigned(n_boundary_patches); // defaults false - std::vector new_boundary_id(n_boundary_patches); - std::vector queued_splines{}; - - // Start Assignement - // Loop over all patches - int current_max_id{1}; - for (int i{}; i < n_boundary_patches; i++) { - if (is_assigned[i]) { - continue; - } - new_boundary_id[i] = current_max_id; - is_assigned[i] = true; - queued_splines.push_back(i); - - // Start propagation - while (!queued_splines.empty()) { - const int current_id = queued_splines.back(); - queued_splines.pop_back(); - for (int i_face{}; i_face < n_faces_per_boundary_patch; i_face++) { - const int combined_index = - current_id * n_faces_per_boundary_patch + i_face; - // Is the neighborface G1 - if (faces_are_g1[combined_index]) { - const int& adjacent_id = boundary_interfaces_ptr[combined_index]; - // Check if the adjacent patch is already assigned - if (is_assigned[adjacent_id]) { - continue; - } else { - // Assign a BID and continue - new_boundary_id[adjacent_id] = current_max_id; - is_assigned[adjacent_id] = true; - queued_splines.push_back(adjacent_id); - } - } - } - } - - // End propagation and increase id - current_max_id++; - } - - // Assign the new boundary ids to the old interface-vector - const int& n_interfaces = global_interfaces.size(); - int counter{}; - for (int i{}; i < n_interfaces; i++) { - if (global_interfaces_ptr[i] < 0) { - global_interfaces_ptr[i] = -new_boundary_id[counter]; - counter++; - } - } - if (counter != n_boundary_patches) { - splinepy::utils::PrintAndThrowError( - counter, - " new boundary ids were assigned, however ", - n_boundary_patches, - " were expected, which means information was lost. Abort mission"); - } - - return current_max_id; -} - inline void add_spline_exporter(py::module& m) { - // Void functions that define arguments - // returns [connectivity, vertex_ids, edge_information, boundaries] - m.def("retrieve_mfem_information", - &splinepy::py::RetrieveMfemInformation, - py::arg("corner_vertices"), - py::arg("tolerance")); - m.def("interfaces_from_boundary_centers", - &splinepy::py::InterfacesFromBoundaryCenters, - py::arg("face_center_vertices"), - py::arg("tolerance"), - py::arg("para_dim")); - m.def("extract_all_boundary_splines", - &splinepy::py::ExtractAllBoundarySplines, - py::arg("splines"), - py::arg("interfaces"), - py::arg("nthreads") = 1); - m.def("orientations", - &splinepy::py::GetBoundaryOrientations, - py::arg("splines"), - py::arg("base_ids"), - py::arg("base_face_ids"), - py::arg("neighbor_ids"), - py::arg("neighbor_face_ids"), - py::arg("tolerance"), - py::arg("nthreads") = 1); - m.def("boundaries_from_continuity", - &splinepy::py::AddBoundariesFromContinuity, - py::arg("boundary_splines"), - py::arg("boundary_interfaces"), - py::arg("global_interfaces"), - py::arg("tolerance"), - py::arg("nthreads") = 1); + m.def("export_iges", &splinepy::py::ExportIges, py::arg("fname"), diff --git a/cpp/splinepy/py/splinepy_core.cpp b/cpp/splinepy/py/splinepy_core.cpp index 5a497f3ca..d095146a0 100644 --- a/cpp/splinepy/py/splinepy_core.cpp +++ b/cpp/splinepy/py/splinepy_core.cpp @@ -29,6 +29,9 @@ void init_fitting(py::module_&); // unique vertices void init_uffpy(py::module_&); +// multi_patch +void init_multi_patch(py::module_& m); + } // namespace splinepy::py::init namespace py = pybind11; @@ -44,4 +47,5 @@ PYBIND11_MODULE(splinepy_core, m) { splinepy::py::init::init_exporter(m); splinepy::py::init::init_fitting(m); splinepy::py::init::init_uffpy(m); + splinepy::py::init::init_multi_patch(m); } From dc0484c4854f982fcc806f411b3b6c8f2c9d1933 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 6 Jun 2023 21:52:03 +0200 Subject: [PATCH 47/89] add NullSpline --- cpp/splinepy/splines/null_spline.hpp | 90 ++++++++++++++++++++++++++ cpp/splinepy/splines/splinepy_base.hpp | 1 + 2 files changed, 91 insertions(+) create mode 100644 cpp/splinepy/splines/null_spline.hpp diff --git a/cpp/splinepy/splines/null_spline.hpp b/cpp/splinepy/splines/null_spline.hpp new file mode 100644 index 000000000..1eb6d3c5c --- /dev/null +++ b/cpp/splinepy/splines/null_spline.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include + +namespace splinepy::splines { + +/// @brief Null Spline. Placeholder spline that only implements evaluate to +/// return zeros. +/// +/// Only meant to be used in backend, where you want to use existing pipeline +/// but don't want to create memory consuming objects. +/// For example, if you have numerous list of +class NullSpline : public splinepy::splines::SplinepyBase { +public: + using LookupArray_ = + std::array, kMaxLookupDim_>, + kMaxLookupDim_>; + + /// parametric and physical dimension needs to be known at creation + /// with these two, for whatever reason, if they are exposed to python side, + /// it is still possible to call implemented query functions + const int para_dim_; + const int dim_; + + /// pre-create null splines up to the dimension we support + constexpr static LookupArray_ lookup_ = [] { + LookupArray_ lookup; + int i{1}; + for (auto& para_dim_array : lookup) { + int j{1}; + for (auto& dim_element : para_dim_array) { + dim_element = std::make_shared(i, j); + ++j; + } + ++i; + } + }(); + + /// @brief ctor with parametric and physical dimension + /// @param para_dim + /// @param dim + NullSpline(const int para_dim, const int dim) + : para_dim_(para_dim), + dim_(dim) {} + + /// basic implementations + virtual int SplinepyParaDim() const { return para_dim_; } + virtual int SplinepyDim() const { return dim_; } + virtual std::string SplinepySplineName() const { return "NullSpline"; } + virtual std::string SplinepyWhatAmI() const { + return "NullSpline, parametric dimension: " + std::to_string(para_dim_) + + ", physical dimension: " + std::to_string(dim_); + } + /// @brief Shouldn't ask NullSpline for has_knot_vectors, and is_rational + virtual bool SplinepyHasKnotVectors() const { + splinepy::utils::PrintAndThrowError( + "SplinepyHasKnotVectors() - invalid function call for", + SplinepyWhatAmI()); + return false; + } + virtual bool SplinepyIsRational() const { + splinepy::utils::PrintAndThrowError( + "SplinepyIsRational() - invalid function call for", + SplinepyWhatAmI()); + return false; + } + virtual int SplinepyNumberOfControlPoints() const { return -1; } + virtual int SplinepyNumberOfSupports() const { return -1; } + virtual bool SplinepyIsNull() const { return true; } + + /// Spline evaluation + virtual void SplinepyEvaluate(const double* para_coord, + double* evaluated) const { + + std::fill_n(evaluated, dim_, 0.0); + } + +protected: +// maxdim to pre-create null splines +#ifdef SPLINEPY_MORE + constexpr static int kMaxLookupDim_{3}; +#else + constexpr static int kMaxLookupDim_{10}; +#enif +}; + + +} // splinepy::splines diff --git a/cpp/splinepy/splines/splinepy_base.hpp b/cpp/splinepy/splines/splinepy_base.hpp index c0bb7e877..37b7b9335 100644 --- a/cpp/splinepy/splines/splinepy_base.hpp +++ b/cpp/splinepy/splines/splinepy_base.hpp @@ -86,6 +86,7 @@ class SplinepyBase { virtual bool SplinepyIsRational() const = 0; virtual int SplinepyNumberOfControlPoints() const = 0; virtual int SplinepyNumberOfSupports() const = 0; + virtual bool SplinepyIsNull() const { return false; }; /// Extract core spline properties. Similar to previous update_p virtual void SplinepyCurrentProperties(int* degrees, From acbc38e8e75bbf159906d026b7b04a2b940d5c96 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 6 Jun 2023 22:48:51 +0200 Subject: [PATCH 48/89] move static lookup outside the class --- cpp/splinepy/splines/null_spline.hpp | 60 ++++++++++++++++------------ 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/cpp/splinepy/splines/null_spline.hpp b/cpp/splinepy/splines/null_spline.hpp index 1eb6d3c5c..7a0fcf72b 100644 --- a/cpp/splinepy/splines/null_spline.hpp +++ b/cpp/splinepy/splines/null_spline.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include @@ -14,30 +16,12 @@ namespace splinepy::splines { /// For example, if you have numerous list of class NullSpline : public splinepy::splines::SplinepyBase { public: - using LookupArray_ = - std::array, kMaxLookupDim_>, - kMaxLookupDim_>; - /// parametric and physical dimension needs to be known at creation /// with these two, for whatever reason, if they are exposed to python side, /// it is still possible to call implemented query functions const int para_dim_; const int dim_; - /// pre-create null splines up to the dimension we support - constexpr static LookupArray_ lookup_ = [] { - LookupArray_ lookup; - int i{1}; - for (auto& para_dim_array : lookup) { - int j{1}; - for (auto& dim_element : para_dim_array) { - dim_element = std::make_shared(i, j); - ++j; - } - ++i; - } - }(); - /// @brief ctor with parametric and physical dimension /// @param para_dim /// @param dim @@ -69,22 +53,46 @@ class NullSpline : public splinepy::splines::SplinepyBase { virtual int SplinepyNumberOfControlPoints() const { return -1; } virtual int SplinepyNumberOfSupports() const { return -1; } virtual bool SplinepyIsNull() const { return true; } + virtual void + SplinepyCurrentProperties(int* degrees, + std::vector>* knot_vectors, + double* control_points, + double* weights) const { + splinepy::utils::PrintAndThrowError( + "SplinepyCurrentProperties() - invalid function call for", + SplinepyWhatAmI()); + } - /// Spline evaluation + /// Spline evaluation - fills zeros. virtual void SplinepyEvaluate(const double* para_coord, double* evaluated) const { std::fill_n(evaluated, dim_, 0.0); } +}; -protected: // maxdim to pre-create null splines #ifdef SPLINEPY_MORE - constexpr static int kMaxLookupDim_{3}; +constexpr static int kMaxLookupDim{3}; #else - constexpr static int kMaxLookupDim_{10}; -#enif -}; - +constexpr static int kMaxLookupDim{10}; +#endif +using LookupArray_ = + std::array, kMaxLookupDim>, + kMaxLookupDim>; +/// pre-create null splines up to the dimension we support +static const LookupArray_ kNullSplineLookup = [] { + LookupArray_ lookup; + int i{0}; + for (auto& para_dim_array : lookup) { + int j{0}; + for (auto& dim_element : para_dim_array) { + dim_element = std::make_shared(i + 1, j + 1); + ++j; + } + ++i; + } + return lookup; +}(); -} // splinepy::splines +} // namespace splinepy::splines From 66b5b32dc13c1c5dfcfa36cd376b7567a825132d Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 6 Jun 2023 22:56:31 +0200 Subject: [PATCH 49/89] add null_spline() --- cpp/splinepy/py/py_spline.hpp | 2 +- cpp/splinepy/py/py_spline_extensions.hpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index b33928be4..6146937d2 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -108,7 +108,7 @@ class PySpline { dim_(another_py_spline.dim_) { // nichts } - PySpline(std::shared_ptr& another_py_spline_ptr) + PySpline(const std::shared_ptr& another_py_spline_ptr) : PySpline(*another_py_spline_ptr) {} /// Creates a corresponding spline based on kwargs diff --git a/cpp/splinepy/py/py_spline_extensions.hpp b/cpp/splinepy/py/py_spline_extensions.hpp index 064fdc878..5450c8b34 100644 --- a/cpp/splinepy/py/py_spline_extensions.hpp +++ b/cpp/splinepy/py/py_spline_extensions.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace splinepy::py { @@ -266,6 +267,12 @@ inline void AnnulCore(std::shared_ptr& spline) { spline->dim_ = -1; } +static std::shared_ptr CreateNullSpline(const int para_dim, + const int dim) { + return std::make_shared( + splinepy::splines::kNullSplineLookup[para_dim - 1][dim - 1]); +} + inline void add_spline_extensions(py::module& m) { m.def("insert_knots", &splinepy::py::InsertKnots, @@ -316,6 +323,10 @@ inline void add_spline_extensions(py::module& m) { m.def("core_ref_count", &splinepy::py::CoreRefCount, py::arg("spline")); m.def("have_core", &splinepy::py::HaveCore, py::arg("spline")); m.def("annul_core", &splinepy::py::AnnulCore, py::arg("spline")); + m.def("null_spline", + &splinepy::py::CreateNullSpline, + py::arg("para_dim"), + py::arg("dim")); } } // namespace splinepy::py From 470fd2d30219d990f420a9c9b5d21defd87dabdc Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 6 Jun 2023 22:56:53 +0200 Subject: [PATCH 50/89] multi_patch WIP --- cpp/splinepy/py/py_multi_patch.hpp | 41 ++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 92c51788b..e44790079 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1313,14 +1313,22 @@ int AddBoundariesFromContinuity(const py::list& boundary_splines, /// "patch" and "spline" are interchangeable class PyMultiPatch { public: + using CorePatches_ = std::vector; + /// individual patches, a.k.a. splines py::list patches_; /// casted, core splines (patches) from patches_ (list of PySpline). - std::vector core_patches_; + CorePatches_ core_patches_; + + /// sub patches - similar concept to subelement / face elements + std::shared_ptr sub_patches_ = nullptr; + + /// ids for boundary_patches from sub_patches_ + py::array_t boundary_patch_ids_; /// Boundary multi patches. Should consist of one smaller parametric dim - std::shared_ptr boundary_patches_; + std::shared_ptr boundary_patches_ = nullptr; /// patch-to-patch connectivity information /// shape: (n_patches, n_boundary_elements) @@ -1334,6 +1342,35 @@ class PyMultiPatch { /// ctor PyMultiPatch() = default; + + /// list (iterable) init -> pybind will cast to list for us if needed + PyMultiPatch(py::list& patches){ + // once, we will turn patches into + }; + + CorePatches_& CorePatches() { + if (core_patches_.size() == 0) { + splinepy::utils::PrintAndThrowError("No splines/patches set"); + } + } + + void SetPatches(py::list& patches) {} + py::list GetPatches() { return patches_; } + + void SetInterfaces(py::array_t& interfaces) {} + py::array_t GetInterfaces() {} + + std::shared_ptr BoundaryPatches(const int nthreads) {} + + py::array_t Evaluate(py::array_t queries, + const int nthreads) {} + + py::array_t Sample(const int resolution, const int nthreads) {} + + bool AddFields(py::args fields, + const bool check_dims, + const bool check_degrees, + const bool check_control_mesh_resolutions) {} }; inline void add_multi_patch(py::module& m) { From f97c274dce6ba24f7cd5db3f743fe10d957fe9b4 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 7 Jun 2023 12:02:32 +0200 Subject: [PATCH 51/89] fill current properties, iff not nullptr allows individual copy --- cpp/splinepy/splines/bezier.hpp | 22 +++++---- cpp/splinepy/splines/bspline.hpp | 51 +++++++++++--------- cpp/splinepy/splines/nurbs.hpp | 61 ++++++++++++++---------- cpp/splinepy/splines/rational_bezier.hpp | 35 +++++++++----- 4 files changed, 100 insertions(+), 69 deletions(-) diff --git a/cpp/splinepy/splines/bezier.hpp b/cpp/splinepy/splines/bezier.hpp index a378806e7..bfc157a2b 100644 --- a/cpp/splinepy/splines/bezier.hpp +++ b/cpp/splinepy/splines/bezier.hpp @@ -131,19 +131,23 @@ class Bezier : public splinepy::splines::SplinepyBase, double* weights /* untouched */) const { // degrees - for (std::size_t i{}; i < kParaDim; ++i) { - degrees[i] = static_cast(Base_::GetDegrees()[i]); + if (degrees) { + for (std::size_t i{}; i < kParaDim; ++i) { + degrees[i] = static_cast(Base_::GetDegrees()[i]); + } } // control_points - const std::size_t ncps = Base_::control_points.size(); - for (std::size_t i{}; i < ncps; ++i) { - if constexpr (dim > 1) { - for (std::size_t j{}; j < kDim; ++j) { - control_points[i * kDim + j] = Base_::control_points[i][j]; + if (control_points) { + const std::size_t ncps = Base_::control_points.size(); + for (std::size_t i{}; i < ncps; ++i) { + if constexpr (dim > 1) { + for (std::size_t j{}; j < kDim; ++j) { + control_points[i * kDim + j] = Base_::control_points[i][j]; + } + } else { + control_points[i] = Base_::control_points[i]; } - } else { - control_points[i] = Base_::control_points[i]; } } } diff --git a/cpp/splinepy/splines/bspline.hpp b/cpp/splinepy/splines/bspline.hpp index ad6d6b6b9..de651a19b 100644 --- a/cpp/splinepy/splines/bspline.hpp +++ b/cpp/splinepy/splines/bspline.hpp @@ -188,34 +188,41 @@ class BSpline : public splinepy::splines::SplinepyBase, const auto& vector_space = GetVectorSpace(); // degrees - for (std::size_t i{}; i < kParaDim; ++i) { - degrees[i] = static_cast(parameter_space.GetDegrees()[i]); + if (degrees) { + for (std::size_t i{}; i < kParaDim; ++i) { + degrees[i] = static_cast(parameter_space.GetDegrees()[i]); + } } // knot_vectors - const auto& core_kvs = parameter_space.GetKnotVectors(); - knot_vectors->clear(); - knot_vectors->reserve(kParaDim); - for (std::size_t i{}; i < kParaDim; ++i) { - const auto& core_kv = *core_kvs[i]; - const std::size_t kvsize = static_cast(core_kv.GetSize()); - std::vector kv; - kv.reserve(kvsize); - // Index wants int - for (int j{}; j < static_cast(kvsize); ++j) { - kv.emplace_back(static_cast(core_kv[splinelib::Index{j}])); + if (knot_vectors) { + const auto& core_kvs = parameter_space.GetKnotVectors(); + knot_vectors->clear(); + knot_vectors->reserve(kParaDim); + for (std::size_t i{}; i < kParaDim; ++i) { + const auto& core_kv = *core_kvs[i]; + const std::size_t kvsize = static_cast(core_kv.GetSize()); + std::vector kv; + kv.reserve(kvsize); + // Index wants int + for (int j{}; j < static_cast(kvsize); ++j) { + kv.emplace_back(static_cast(core_kv[splinelib::Index{j}])); + } + knot_vectors->push_back(std::move(kv)); } - knot_vectors->push_back(std::move(kv)); } - // control_points and weights - std::size_t ncps = vector_space.GetNumberOfCoordinates(); - // fill it up, phil! - for (std::size_t i{}; i < ncps; ++i) { - auto const& coord_named_phil = vector_space[splinelib::Index{ - static_cast(i)}]; - for (std::size_t j{}; j < kDim; ++j) { - control_points[i * kDim + j] = static_cast(coord_named_phil[j]); + // control_points + if (control_points) { + std::size_t ncps = vector_space.GetNumberOfCoordinates(); + // fill it up, phil! + for (std::size_t i{}; i < ncps; ++i) { + auto const& coord_named_phil = vector_space[splinelib::Index{ + static_cast(i)}]; + for (std::size_t j{}; j < kDim; ++j) { + control_points[i * kDim + j] = + static_cast(coord_named_phil[j]); + } } } } diff --git a/cpp/splinepy/splines/nurbs.hpp b/cpp/splinepy/splines/nurbs.hpp index 08d3a4e34..91a790073 100644 --- a/cpp/splinepy/splines/nurbs.hpp +++ b/cpp/splinepy/splines/nurbs.hpp @@ -191,39 +191,50 @@ class Nurbs : public splinepy::splines::SplinepyBase, const auto& vector_space = GetWeightedVectorSpace(); // degrees - for (std::size_t i{}; i < kParaDim; ++i) { - degrees[i] = static_cast(parameter_space.GetDegrees()[i]); + if (degrees) { + for (std::size_t i{}; i < kParaDim; ++i) { + degrees[i] = static_cast(parameter_space.GetDegrees()[i]); + } } // knot_vectors - const auto& core_kvs = parameter_space.GetKnotVectors(); - knot_vectors->clear(); - knot_vectors->reserve(kParaDim); - for (std::size_t i{}; i < kParaDim; ++i) { - const auto& core_kv = *core_kvs[i]; - const std::size_t kvsize = static_cast(core_kv.GetSize()); - std::vector kv; - kv.reserve(kvsize); - // Index wants int - for (int j{}; j < static_cast(kvsize); ++j) { - kv.emplace_back(static_cast(core_kv[splinelib::Index{j}])); + if (knot_vectors) { + const auto& core_kvs = parameter_space.GetKnotVectors(); + knot_vectors->clear(); + knot_vectors->reserve(kParaDim); + for (std::size_t i{}; i < kParaDim; ++i) { + const auto& core_kv = *core_kvs[i]; + const std::size_t kvsize = static_cast(core_kv.GetSize()); + std::vector kv; + kv.reserve(kvsize); + // Index wants int + for (int j{}; j < static_cast(kvsize); ++j) { + kv.emplace_back(static_cast(core_kv[splinelib::Index{j}])); + } + knot_vectors->push_back(std::move(kv)); } - knot_vectors->push_back(std::move(kv)); } // control_points and weights - std::size_t ncps = vector_space.GetNumberOfCoordinates(); - // fill it up, phil! - for (std::size_t i{}; i < ncps; ++i) { - auto const& coord_named_phil = vector_space[splinelib::Index{ - static_cast(i)}]; - // unweight - phil needs to first project before filling. - auto const& projected_phil = - WeightedVectorSpace_::Project(coord_named_phil); - for (std::size_t j{}; j < kDim; ++j) { - control_points[i * kDim + j] = static_cast(projected_phil[j]); + if (control_points || weights) { + std::size_t ncps = vector_space.GetNumberOfCoordinates(); + // fill it up, phil! + for (std::size_t i{}; i < ncps; ++i) { + auto const& coord_named_phil = vector_space[splinelib::Index{ + static_cast(i)}]; + // unweight - phil needs to first project before filling. + if (control_points) { + auto const& projected_phil = + WeightedVectorSpace_::Project(coord_named_phil); + for (std::size_t j{}; j < kDim; ++j) { + control_points[i * kDim + j] = + static_cast(projected_phil[j]); + } + } + if (weights) { + weights[i] = static_cast(coord_named_phil[dim]); + } } - weights[i] = static_cast(coord_named_phil[dim]); } } diff --git a/cpp/splinepy/splines/rational_bezier.hpp b/cpp/splinepy/splines/rational_bezier.hpp index 083ff78eb..46312f2d0 100644 --- a/cpp/splinepy/splines/rational_bezier.hpp +++ b/cpp/splinepy/splines/rational_bezier.hpp @@ -141,23 +141,32 @@ class RationalBezier : public splinepy::splines::SplinepyBase, double* weights) const { // degrees - for (std::size_t i{}; i < kParaDim; ++i) { - degrees[i] = static_cast(Base_::GetDegrees()[i]); + if (degrees) { + for (std::size_t i{}; i < kParaDim; ++i) { + degrees[i] = static_cast(Base_::GetDegrees()[i]); + } } // control_points and weights - const std::size_t ncps = Base_::GetWeightedControlPoints().size(); - for (std::size_t i{}; i < ncps; ++i) { - const double w = Base_::GetWeights()[i]; - weights[i] = w; - double inv_weight = static_cast(1.) / w; - if constexpr (dim > 1) { - for (std::size_t j{}; j < kDim; ++j) { - control_points[i * kDim + j] = - Base_::GetWeightedControlPoints()[i][j] * inv_weight; + if (control_points || weights) { + const std::size_t ncps = Base_::GetWeightedControlPoints().size(); + for (std::size_t i{}; i < ncps; ++i) { + const double w = Base_::GetWeights()[i]; + if (weights) { + weights[i] = w; + } + if (control_points) { + double inv_weight = static_cast(1.) / w; + if constexpr (dim > 1) { + for (std::size_t j{}; j < kDim; ++j) { + control_points[i * kDim + j] = + Base_::GetWeightedControlPoints()[i][j] * inv_weight; + } + } else { + control_points[i] = + Base_::GetWeightedControlPoints()[i] * inv_weight; + } } - } else { - control_points[i] = Base_::GetWeightedControlPoints()[i] * inv_weight; } } } From 2f804049d9eb1dfdc145f4edb338ae6309ee352b Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 7 Jun 2023 15:19:49 +0200 Subject: [PATCH 52/89] add converters and mismatch check --- cpp/splinepy/py/py_multi_patch.hpp | 218 ++++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 6 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index e44790079..61ec314bd 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + // pybind #include #include @@ -7,6 +10,7 @@ // Bezman #include +// #include #include #include @@ -15,6 +19,211 @@ namespace splinepy::py { namespace py = pybind11; +// alias for frequently used vectors +using CoreSplineVector = + std::vector>; +using IntVector = splinepy::utils::DefaultInitializationVector; +using IntVectorVector = splinepy::utils::DefaultInitializationVector; +using DoubleVector = splinepy::utils::DefaultInitializationVector; + +/// @brief Extracts CoreSpline from list of PySplines. +/// @param pysplines +/// @param nthreads +/// @return +inline std::vector +ToCoreSplineVector(py::list pysplines, const int nthreads = 1) { + // prepare return obj + const int n_splines = static_cast(pysplines.size()); + std::vector core_splines(n_splines); + + auto to_core = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + core_splines[i] = + pysplines[i].template cast>()->Core(); + } + }; + splinepy::utils::NThreadExecution(to_core, n_splines, nthreads); + + return core_splines; +} + +/// @brief +/// @param splist +/// @param nthreads +/// @return +inline py::list ToPySplineList(CoreSplineVector& splist, int nthreads) { + // prepare return obj + const int n_splines = static_cast(splist.size()); + py::list pyspline_list(n_splines); + + auto to_pyspline = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + pyspline_list[i] = std::make_shared(splist[i]); + } + }; + + // multi thread execution causes segfault. + // until we find a better solution, do single exe + nthreads = 1; + + splinepy::utils::NThreadExecution(to_pyspline, n_splines, nthreads); + + return pyspline_list; +} + +/// @brief raises if there's any mismatch. +/// @param splist +/// @param para_dim +/// @param dim +/// @param degrees +/// @param control_mesh_resolutions +/// @param nthreads +inline void RaiseMisMatch(const CoreSplineVector& splist, + const std::string name, + const int para_dim, + const int dim, + const IntVector degrees, + const IntVector control_mesh_resolutions, + const int nthreads) { + // for verbose output + std::unordered_map mismatches{}; + + // check flags + bool check_name{}, check_para_dim{}, check_dim{}, check_degrees{}, + check_control_mesh_resolutions{}; + // input vector sizes + const int d_size = degrees.size(); + const int cmr_size = control_mesh_resolutions.size(); + // true para_dim to use as reference -> will be updated / compared + int ref_para_dim{para_dim}; + + // parse input and allocate vector for mismatch book keeping + if (name.size() > 0) { + check_name = true; + mismatches["name"].resize(nthreads); + } + if (dim > 0) { + check_dim = true; + mismatches["dim"].resize(nthreads); + } + if (d_size > 0) { + check_degrees = true; + ref_para_dim = d_size; + mismatches["degrees"].resize(nthreads); + } + if (cmr_size > 0) { + check_control_mesh_resolutions = true; + mismatches["control_mesh_resolutions"].resize(nthreads); + } + if (para_dim > 0 || check_degrees || check_control_mesh_resolutions) { + check_para_dim = true; + + // sanity check + for (const auto& candidate : {para_dim, d_size, cmr_size}) { + if (candidate != ref_para_dim && candidate > 0) { + splinepy::utils::PrintAndThrowError( + "Mismatch in given para_dim (", + para_dim, + "), size of degrees, (", + d_size, + "), size of control_mesh_resolutions (", + cmr_size, + ")."); + } + } + mismatches["para_dim"].resize(nthreads); + } + + // lambda for nthread comparison + auto check_mismatch_step = [&](int begin, int total_) { + // in step-style query, begin is i_thread + const int thread_index = begin; + // alloc vectors incase we need to compare + IntVector spline_degree(ref_para_dim), spline_cmr(ref_para_dim); + + for (int i{begin}; i < total_; i += nthreads) { + // get spline to check + const auto& spline = splist[i]; + + // skip null splines + if (spline->SplinepyIsNull()) { + continue; + } + + // default value for para_dim match + bool para_dim_matches{true}; + + // check + if (check_name && (name != spline->SplinepySplineName())) { + mismatches["name"][thread_index].push_back(i); + } + if (check_para_dim && (ref_para_dim != spline->SplinepyParaDim())) { + mismatches["para_dim"][thread_index].push_back(i); + para_dim_matches = false; + } + if (check_dim && (dim != spline->SplinepyDim())) { + mismatches["dim"][thread_index].push_back(i); + } + // check properties that arerelevent iff para_dim matches + if (para_dim_matches) { + if (check_degrees) { + spline->SplinepyCurrentProperties(spline_degree.data(), + nullptr, + nullptr, + nullptr); + if (spline_degree != degrees) { + mismatches["degrees"][thread_index].push_back(i); + } + } + if (check_control_mesh_resolutions) { + spline->SplinepyControlMeshResolutions(spline_cmr.data()); + if (spline_cmr != control_mesh_resolutions) { + mismatches["control_mesh_resolutions"][thread_index].push_back(i); + } + } + } + } + }; + + splinepy::utils::NThreadExecution(check_mismatch_step, + static_cast(splist.size()), + nthreads, + splinepy::utils::NThreadQueryType::Step); + + // prepare output or maybe exit + std::unordered_map concat_mismatches{}; + bool raise{false}; + + for (const auto& [key, mismatch_per_threads] : mismatches) { + auto& concat_vec = concat_mismatches[key]; + for (const auto& mismatch : mismatch_per_threads) { + const auto m_size = mismatch.size(); + if (m_size != 0) { + concat_vec.insert(concat_vec.end(), mismatch.begin(), mismatch.end()); + } + } + if (concat_vec.size() != 0) { + raise = true; + } + }; + + // everything matches + if (!raise) { + return; + } + + // form mismatch info + std::string mismatch_info{}; + for (const auto& [key, concat_mismatch] : concat_mismatches) { + mismatch_info += "\n[" + key + "] : "; + for (const auto& ids : concat_mismatch) { + mismatch_info += std::to_string(ids) + ", "; + } + } + + splinepy::utils::PrintAndThrowError("Found mismatches.", mismatch_info); +} + template py::array_t InterfacesFromBoundaryCenters_(const py::array_t& py_center_vertices, @@ -740,8 +949,7 @@ py::tuple GetBoundaryOrientations(const py::list& spline_list, const int* neighbor_id_ptr = static_cast(neighbor_id.request().ptr); const int* neighbor_face_id_ptr = static_cast(neighbor_face_id.request().ptr); - const auto cpp_spline_list = - ListOfPySplinesToVectorOfCoreSplines(spline_list); + const auto cpp_spline_list = ToCoreSplineVector(spline_list); const int n_connections = base_id.size(); const int para_dim_ = cpp_spline_list[0]->SplinepyParaDim(); @@ -1060,8 +1268,7 @@ py::list ExtractAllBoundarySplines(const py::list& spline_list, const int n_patches = interfaces.shape(0); const int n_faces = interfaces.shape(1); const int para_dim_ = n_faces / 2; - const auto cpp_spline_list = - ListOfPySplinesToVectorOfCoreSplines(spline_list); + const auto cpp_spline_list = ToCoreSplineVector(spline_list); const int chunk_size = std::div((n_patches + n_threads - 1), n_threads).quot; // This approach is a work-around for parallel execution @@ -1126,8 +1333,7 @@ int AddBoundariesFromContinuity(const py::list& boundary_splines, } // Provide auxiliary values - const auto cpp_spline_list = - ListOfPySplinesToVectorOfCoreSplines(boundary_splines); + const auto cpp_spline_list = ToCoreSplineVector(boundary_splines); const int n_boundary_patches{static_cast(boundary_interfaces.shape(0))}; const int n_faces_per_boundary_patch{ static_cast(boundary_interfaces.shape(1))}; From 6f0f2603b4ff75113bb37b2cf0378b660882959d Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 7 Jun 2023 16:19:31 +0200 Subject: [PATCH 53/89] add None converter, evaluate, sample --- cpp/splinepy/py/py_multi_patch.hpp | 211 ++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 5 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 61ec314bd..bdd624e18 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -47,6 +47,44 @@ ToCoreSplineVector(py::list pysplines, const int nthreads = 1) { return core_splines; } +/// @brief Overload to allow None entries. Will be filled with null spline +/// @param pysplines +/// @param para_dim_if_none +/// @param dim_if_none +/// @param nthreads +/// @return +inline std::vector +ToCoreSplineVector(py::list pysplines, + const int para_dim_if_none, + const int dim_if_none, + const int nthreads) { + // prepare return obj + const int n_splines = static_cast(pysplines.size()); + std::vector core_splines(n_splines); + + auto to_core = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + // get accessor + auto spline = pysplines[i]; + + // if None, fill null spline + if (spline.is_none()) { + core_splines[i] = + splinepy::splines::kNullSplineLookup[para_dim_if_none - 1] + [dim_if_none - 1]; + continue; + } + + // not none, + core_splines[i] = + spline.template cast>()->Core(); + } + }; + splinepy::utils::NThreadExecution(to_core, n_splines, nthreads); + + return core_splines; +} + /// @brief /// @param splist /// @param nthreads @@ -1550,28 +1588,191 @@ class PyMultiPatch { PyMultiPatch() = default; /// list (iterable) init -> pybind will cast to list for us if needed - PyMultiPatch(py::list& patches){ - // once, we will turn patches into + PyMultiPatch(py::list& patches, const int nthreads = 1){ + // once, we will turn patches into core patches }; CorePatches_& CorePatches() { if (core_patches_.size() == 0) { splinepy::utils::PrintAndThrowError("No splines/patches set"); } + return core_patches_; } - void SetPatches(py::list& patches) {} + void Clear() { + patches_ = py::list(); + core_patches_ = CorePatches_{}; + sub_patches_ = nullptr; + boundary_patch_ids_ = py::array_t(); + boundary_patches_ = nullptr; + interfaces_ = py::array_t(); + boundary_ids_ = py::array_t(); + boundary_centers_ = py::array_t(); + } + + void SetPatches(py::list& patches, const int nthreads = 1) {} py::list GetPatches() { return patches_; } + int ParaDim() { return CorePatches()[0]->SplinepyParaDim(); } + + int Dim() { return CorePatches()[0]->SplinepyDim(); } + void SetInterfaces(py::array_t& interfaces) {} py::array_t GetInterfaces() {} + std::shared_ptr SubPatches(const int nthreads) {} + std::shared_ptr BoundaryPatches(const int nthreads) {} py::array_t Evaluate(py::array_t queries, - const int nthreads) {} + const int nthreads) { + // use first spline as dimension guide line + const int para_dim = ParaDim(); + const int dim = Dim(); + + // query dim check + CheckPyArrayShape(queries, {-1, para_dim}, true); + + // prepare input and output + double* queries_ptr = static_cast(queries.request().ptr); + const int n_splines = core_patches_.size(); + const int n_queries = queries.shape(0); + const int n_total = n_splines * n_queries; + py::array_t evaluated({n_total, dim}); + double* evaluated_ptr = static_cast(evaluated.request().ptr); + + // each thread evaluates similar amount of queries from each spline + auto evaluate_step = [&](int begin, int total_) { + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + core_patches_[i_spline]->SplinepyEvaluate( + &queries_ptr[i_query * para_dim], + &evaluated_ptr[(i_spline * n_queries + i_query) * dim]); + } + }; + + // exe + splinepy::utils::NThreadExecution(evaluate_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + + return evaluated; + } + + py::array_t Sample(const int resolution, + const int nthreads, + const bool same_parametric_bounds) { + const int para_dim = ParaDim(); + const int dim = Dim(); + + // n_queries, and n_splines; + int n_queries{1}; + const int n_splines = core_patches_.size(); + + // prepare resolutions + IntVector resolutions_vector(para_dim); + int* resolutions = resolutions_vector.data(); + for (int i{}; i < para_dim; ++i) { + resolutions[i] = resolution; + n_queries *= resolution; + } + + // prepare input / output + const int n_total = n_splines * n_queries; + py::array_t sampled({n_total, dim}); + double* sampled_ptr = static_cast(sampled.request().ptr); + + // if you know all the queries have same parametric bounds + // you don't need to re-compute queries + if (same_parametric_bounds) { + + // get para bounds + DoubleVector para_bounds_vector(2 * para_dim); + double* para_bounds = para_bounds_vector.data(); + core_patches_[0]->SplinepyParametricBounds(para_bounds); + + // prepare queries + DoubleVector queries_vector(n_queries * para_dim); + double* queries = queries_vector.data(); + + // use grid point generator to fill queries + splinepy::utils::CStyleArrayPointerGridPoints gp_generator(para_dim, + para_bounds, + resolutions); + gp_generator.Fill(queries); + + // create lambda for nthread exe + auto sample_same_bounds_step = [&](int begin, int total_) { + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + core_patches_[i_spline]->SplinepyEvaluate( + &queries[i_query * para_dim], + &sampled_ptr[(i_spline * n_queries + i_query) * dim]); + } + }; + + splinepy::utils::NThreadExecution( + sample_same_bounds_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + + } else { + // here, we will execute 2 times: + // first, to create grid point helpers for each spline + // second, to sample + + // create a container to hold grid point helper. + splinepy::utils::DefaultInitializationVector< + splinepy::utils::CStyleArrayPointerGridPoints> + grid_points(n_splines); + + // create grid_points + auto create_grid_points = [&](int begin, int end) { + DoubleVector para_bounds_vector(2 * para_dim); + double* para_bounds = para_bounds_vector.data(); + + for (int i{begin}; i < end; ++i) { + // get para_bounds + core_patches_[i]->SplinepyParametricBounds(para_bounds); + // setup grid points helper + grid_points[i].SetUp(para_dim, para_bounds, resolutions); + } + }; + + // pre compute entries -> this one is a chunk query + splinepy::utils::NThreadExecution(create_grid_points, + n_splines, + nthreads); + + // similar to the one with same_parametric_bounds, except it computes + // query on the fly + auto sample_step = [&](int begin, int total_) { + // each thread needs just one query array + DoubleVector thread_query_vector(para_dim); + double* thread_query = thread_query_vector.data(); + + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + const auto& gp_helper = grid_points[i_spline]; + gp_helper.IdToGridPoint(i_query, thread_query); + core_patches_[i_spline]->SplinepyEvaluate( + thread_query, + &sampled_ptr[(i_spline * n_queries + i_query) * dim]); + } + }; + + // exe - this one is step + splinepy::utils::NThreadExecution( + sample_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + } - py::array_t Sample(const int resolution, const int nthreads) {} + return sampled; + } bool AddFields(py::args fields, const bool check_dims, From 83e046e495fb3663fe21f2dec9a1d661882cfafe Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 7 Jun 2023 20:11:56 +0200 Subject: [PATCH 54/89] add to_derived() --- cpp/splinepy/py/py_spline.hpp | 10 ++++++++++ splinepy/__init__.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index 6146937d2..f5ad3c33a 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -27,6 +27,10 @@ namespace py = pybind11; using namespace splinelib::sources; +/// @brief python function splinepy.to_derived() +static const auto py_to_derived = + py::module_::import("splinepy").attr("to_derived"); + template static bool CheckPyArrayShape(const py::array_t arr, const std::vector& shape, @@ -899,6 +903,11 @@ class PySpline { CoordinateReferences() { return Core()->SplinepyCoordinateReferences(); } + + /// @brief returns current spline as package's derived spline types based on + /// splinepy.settings.NAME_TO_TYPE + /// @return + py::object ToDerived() { return py_to_derived(py::cast(this)); } }; inline void add_spline_pyclass(py::module& m) { @@ -981,6 +990,7 @@ inline void add_spline_pyclass(py::module& m) { py::arg("tolerance")) .def("coordinate_references", &splinepy::py::PySpline::CoordinateReferences) + .def("to_derived", &splinepy::py::PySpline::ToDerived) .def(py::pickle( [](splinepy::py::PySpline& spl) { return py::make_tuple(spl.CurrentCoreProperties(), spl.data_); diff --git a/splinepy/__init__.py b/splinepy/__init__.py index 474e0b895..dfc1e4b73 100644 --- a/splinepy/__init__.py +++ b/splinepy/__init__.py @@ -27,6 +27,22 @@ # configure logging utils.log.configure() + +def to_derived(spline): + """ + Returns derived spline type based on NAME_TO_TYPE conversion. + + Parameters + ---------- + spline: CoreSpline + + Returns + ------- + derived_spline: DerivedSpline + """ + return settings.NAME_TO_TYPE[spline.name](spline=spline) + + __all__ = [ "__version__", "bezier", @@ -49,4 +65,5 @@ "Spline", "load_splines", "load_solution", + "to_derived", ] From 1e81e04c72e09b61b477126d36b1eb4dc3cae928 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 7 Jun 2023 20:14:13 +0200 Subject: [PATCH 55/89] extend mismatch check and add patch setters --- cpp/splinepy/py/py_multi_patch.hpp | 263 ++++++++++++++++++++++++++++- 1 file changed, 254 insertions(+), 9 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index bdd624e18..ad7990a71 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -109,19 +109,20 @@ inline py::list ToPySplineList(CoreSplineVector& splist, int nthreads) { return pyspline_list; } -/// @brief raises if there's any mismatch. +/// @brief raises if there's any mismatch between specified properties and all +/// the entries in the vector. /// @param splist /// @param para_dim /// @param dim /// @param degrees /// @param control_mesh_resolutions /// @param nthreads -inline void RaiseMisMatch(const CoreSplineVector& splist, +inline void RaiseMismatch(const CoreSplineVector& splist, const std::string name, const int para_dim, const int dim, - const IntVector degrees, - const IntVector control_mesh_resolutions, + const IntVector& degrees, + const IntVector& control_mesh_resolutions, const int nthreads) { // for verbose output std::unordered_map mismatches{}; @@ -262,6 +263,158 @@ inline void RaiseMisMatch(const CoreSplineVector& splist, splinepy::utils::PrintAndThrowError("Found mismatches.", mismatch_info); } +/// @brief raises if there's any mismatch between two given vectors of splines. +/// @param splist +/// @param para_dim +/// @param dim +/// @param degrees +/// @param control_mesh_resolutions +/// @param nthreads +inline void RaiseMismatch(const CoreSplineVector& splist0, + const CoreSplineVector& splist1, + const bool name, + const bool para_dim, + const bool dim, + const bool degrees, + const bool control_mesh_resolutions, + const int nthreads) { + // len check + if (splist0.size() != splist1.size()) { + splinepy::utils::PrintAndThrowError( + "Size mismatch between first and second list of splines.", + splist0.size(), + "vs", + splist1.size()); + } + // for verbose output + std::unordered_map mismatches{}; + bool check_para_dim{false}; + + // parse input and allocate vector for mismatch book keeping + if (name) { + mismatches["name"].resize(nthreads); + } + if (dim) { + mismatches["dim"].resize(nthreads); + } + if (degrees) { + mismatches["degrees"].resize(nthreads); + } + if (control_mesh_resolutions) { + mismatches["control_mesh_resolutions"].resize(nthreads); + } + if (para_dim || degrees || control_mesh_resolutions) { + check_para_dim = true; + mismatches["para_dim"].resize(nthreads); + } + + // lambda for nthread comparison + auto check_mismatch_step = [&](int begin, int total_) { + // in step-style query, begin is i_thread + const int thread_index = begin; + + // alloc some tmp vector if needed + IntVector int_vec0, int_vec1; + + for (int i{begin}; i < total_; i += nthreads) { + // get spline to check + const auto& spline0 = splist0[i]; + const auto& spline1 = splist1[i]; + + // skip null splines + if (spline1->SplinepyIsNull() || spline0->SplinepyIsNull()) { + continue; + } + + // default value for para_dim match + bool para_dim_matches{true}; + + // check + if (name + && (spline0->SplinepySplineName() != spline1->SplinepySplineName())) { + mismatches["name"][thread_index].push_back(i); + } + if (para_dim + && (spline0->SplinepyParaDim() != spline1->SplinepyParaDim())) { + mismatches["para_dim"][thread_index].push_back(i); + para_dim_matches = false; + } + if (dim && (spline0->SplinepyDim() != spline1->SplinepyDim())) { + mismatches["dim"][thread_index].push_back(i); + } + + // check properties that are relevent iff para_dim matches + if (para_dim_matches && (degrees || control_mesh_resolutions)) { + // alloc some space + int_vec0.resize(spline0->SplinepyParaDim()); + int_vec1.resize(spline1->SplinepyParaDim()); + + if (degrees) { + spline0->SplinepyCurrentProperties(int_vec0.data(), + nullptr, + nullptr, + nullptr); + spline1->SplinepyCurrentProperties(int_vec1.data(), + nullptr, + nullptr, + nullptr); + + if (int_vec0 != int_vec1) { + mismatches["degrees"][thread_index].push_back(i); + } + } + + if (control_mesh_resolutions) { + spline0->SplinepyControlMeshResolutions(int_vec0.data()); + spline1->SplinepyControlMeshResolutions(int_vec1.data()); + + if (int_vec0 != int_vec1) { + mismatches["control_mesh_resolutions"][thread_index].push_back(i); + } + } + } + } + }; + + splinepy::utils::NThreadExecution(check_mismatch_step, + static_cast(splist0.size()), + nthreads, + splinepy::utils::NThreadQueryType::Step); + + // prepare output or maybe exit + std::unordered_map concat_mismatches{}; + bool raise{false}; + + for (const auto& [key, mismatch_per_threads] : mismatches) { + auto& concat_vec = concat_mismatches[key]; + for (const auto& mismatch : mismatch_per_threads) { + const auto m_size = mismatch.size(); + if (m_size != 0) { + concat_vec.insert(concat_vec.end(), mismatch.begin(), mismatch.end()); + } + } + if (concat_vec.size() != 0) { + raise = true; + } + }; + + // everything matches + if (!raise) { + return; + } + + // form mismatch info + std::string mismatch_info{}; + for (const auto& [key, concat_mismatch] : concat_mismatches) { + mismatch_info += "\n[" + key + "] : "; + for (const auto& ids : concat_mismatch) { + mismatch_info += std::to_string(ids) + ", "; + } + } + + splinepy::utils::PrintAndThrowError("Found mismatches.", mismatch_info); +} + template py::array_t InterfacesFromBoundaryCenters_(const py::array_t& py_center_vertices, @@ -1584,13 +1737,39 @@ class PyMultiPatch { /// center of each boundary elements. py::array_t boundary_centers_; + /// default number of threads, only used in SetPatches, as it will just be a + /// setter in python side + int n_default_threads_{1}; + + /// internal flag to indicate that this a field multipatch that may contain + /// null spline + bool has_null_splines_{false}; + /// ctor PyMultiPatch() = default; /// list (iterable) init -> pybind will cast to list for us if needed - PyMultiPatch(py::list& patches, const int nthreads = 1){ - // once, we will turn patches into core patches - }; + PyMultiPatch(py::list& patches, const int n_default_threads = 1) + : n_default_threads_(n_default_threads) { + SetPatchesNThreads(patches, n_default_threads); + } + + /// @brief Internal use only. used to save field splines. + /// @param patchces + /// @param n_default_threads + /// @param para_dim_if_none + /// @param dim_if_none + PyMultiPatch(py::list& patches, + const int n_default_threads, + const int para_dim_if_none, + const int dim_if_none) + : n_default_threads_(n_default_threads), + has_null_splines_(true) { + SetPatchesWithNullSplines(patches, + n_default_threads, + para_dim_if_none, + dim_if_none); + } CorePatches_& CorePatches() { if (core_patches_.size() == 0) { @@ -1610,14 +1789,80 @@ class PyMultiPatch { boundary_centers_ = py::array_t(); } - void SetPatches(py::list& patches, const int nthreads = 1) {} + // could overload, but it won't be clear for pybind + + /// @brief default patches setter, exposed to python + /// @param patches + void SetPatchesDefault(py::list& patches) { + SetPatchesNThreads(patches, n_default_threads_); + } + /// @brief patches setter with nthreads + /// @param patches + /// @param nthreads + void SetPatchesNThreads(py::list& patches, const int nthreads = 1) { + // clear first, incase this is a new setting + Clear(); + + // register patches + patches_ = patches; + + // convert + core_patches_ = ToCoreSplineVector(patches, nthreads); + + // check only dim mismatch + RaiseMismatch(core_patches_, + "", + core_patches_[0]->SplinepyParaDim(), + core_patches_[0]->SplinepyDim(), + {}, + {}, + nthreads); + } + /// @brief patches setter for field patches that may contain null splines. + /// @param patches + /// @param nthreads + /// @param para_dim_if_none + /// @param dim_if_none + /// @param check_conformity + void SetPatchesWithNullSplines(py::list& patches, + const int nthreads, + const int para_dim_if_none, + const int dim_if_none) { + // clear + Clear(); + + // save + patches_ = patches; + + // convert using null spline friendly converter + core_patches_ = + ToCoreSplineVector(patches, para_dim_if_none, dim_if_none, nthreads); + + // check only dim mismatch + RaiseMismatch(core_patches_, + "", + core_patches_[0]->SplinepyParaDim(), + core_patches_[0]->SplinepyDim(), + {}, + {}, + nthreads); + } py::list GetPatches() { return patches_; } int ParaDim() { return CorePatches()[0]->SplinepyParaDim(); } int Dim() { return CorePatches()[0]->SplinepyDim(); } - void SetInterfaces(py::array_t& interfaces) {} + void SetInterfaces(py::array_t& interfaces) { + // size check + splinepy::py::CheckPyArrayShape( + interfaces, + {static_cast(CorePatches().size()), + static_cast(CorePatches()[0]->SplinepyParaDim() * 2)}, + true); + + interfaces_ = interfaces; + } py::array_t GetInterfaces() {} std::shared_ptr SubPatches(const int nthreads) {} From 18299fe74dea21a4d7be8bdf5a2eb5cfda346e30 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 7 Jun 2023 22:34:39 +0200 Subject: [PATCH 56/89] edit naming, add sub_multipach, sub_patch_centers --- cpp/splinepy/py/py_multi_patch.hpp | 146 ++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 12 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index ad7990a71..6465cd484 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -96,7 +96,7 @@ inline py::list ToPySplineList(CoreSplineVector& splist, int nthreads) { auto to_pyspline = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { - pyspline_list[i] = std::make_shared(splist[i]); + pyspline_list[i] = std::make_shared(splist[i])->ToDerived(); } }; @@ -1719,13 +1719,16 @@ class PyMultiPatch { CorePatches_ core_patches_; /// sub patches - similar concept to subelement / face elements - std::shared_ptr sub_patches_ = nullptr; + std::shared_ptr sub_multi_patches_ = nullptr; + + /// center of each sub patch. + py::array_t sub_patch_centers_; /// ids for boundary_patches from sub_patches_ py::array_t boundary_patch_ids_; /// Boundary multi patches. Should consist of one smaller parametric dim - std::shared_ptr boundary_patches_ = nullptr; + std::shared_ptr boundary_multi_patches_ = nullptr; /// patch-to-patch connectivity information /// shape: (n_patches, n_boundary_elements) @@ -1734,9 +1737,6 @@ class PyMultiPatch { /// shape: (n_patches,) py::array_t boundary_ids_; - /// center of each boundary elements. - py::array_t boundary_centers_; - /// default number of threads, only used in SetPatches, as it will just be a /// setter in python side int n_default_threads_{1}; @@ -1781,12 +1781,12 @@ class PyMultiPatch { void Clear() { patches_ = py::list(); core_patches_ = CorePatches_{}; - sub_patches_ = nullptr; + sub_multi_patches_ = nullptr; boundary_patch_ids_ = py::array_t(); - boundary_patches_ = nullptr; + boundary_multi_patches_ = nullptr; interfaces_ = py::array_t(); boundary_ids_ = py::array_t(); - boundary_centers_ = py::array_t(); + sub_patch_centers_ = py::array_t(); } // could overload, but it won't be clear for pybind @@ -1849,10 +1849,131 @@ class PyMultiPatch { } py::list GetPatches() { return patches_; } + /// @brief returns para_dim of the first patch + /// @return int ParaDim() { return CorePatches()[0]->SplinepyParaDim(); } + /// @brief returns dim of the first patch + /// @return int Dim() { return CorePatches()[0]->SplinepyDim(); } + /// @brief returns sub patches as multi patches + /// @param nthreads + /// @return + std::shared_ptr SubMultiPatches(const int nthreads) { + // quick exit if they exist. + if (sub_multi_patches_) { + return sub_multi_patches_; + } + + const int n_splines = CorePatches().size(); + // to accumulate + const int n_boundary = ParaDim() * 2; + const int n_boundaries = n_splines * n_boundary; + + // prepare output + CoreSplineVector out_boundaries(n_boundaries); + + // prepare lambda + auto boundary_extract = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + // start of the offset + const int offset = i * n_boundary; + // deref this spline + auto& spline = *core_patches_[i]; + for (int j{}; j < n_boundary; ++j) { + out_boundaries[offset + j] = spline.SplinepyExtractBoundary(j); + } + } + }; + + splinepy::utils::NThreadExecution(boundary_extract, n_splines, nthreads); + + // create multi patch to return + sub_multi_patches_ = std::make_shared(); + + // set both core and py splines + sub_multi_patches_->patches_ = ToPySplineList(out_boundaries, nthreads); + sub_multi_patches_->core_patches_ = std::move(out_boundaries); + + return sub_multi_patches_; + } + + py::array_t SubPatchCenters(const int nthreads, + const bool same_parametric_bounds) { + if (sub_patch_centers_.size() != 0) { + return sub_patch_centers_; + } + + // prepare output + // from here we assume that all the splines have the same para_dim and dim + const int n_splines = CorePatches().size(); + const int para_dim = ParaDim(); + const int dim = Dim(); + const int n_queries = 2 * para_dim; + const int n_total = n_queries * n_splines; + sub_patch_centers_ = py::array_t({n_total, dim}); + double* sub_patch_centers_ptr = + static_cast(sub_patch_centers_.request().ptr); + + // pre-compute boundary centers + DoubleVector para_bounds; + double* para_bounds_ptr; + if (!same_parametric_bounds) { + para_bounds.resize(n_total * para_dim); + para_bounds_ptr = para_bounds.data(); + const int stride = n_queries * para_dim; + + auto calc_para_bounds = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + splinepy::splines::helpers::ScalarTypeBoundaryCenters( + *core_patches_[i], + ¶_bounds_ptr[stride * i]); + } + }; + + // exe + splinepy::utils::NThreadExecution(calc_para_bounds, n_splines, nthreads); + } + + auto calc_sub_patch_centers_step = [&](int begin, int total_) { + // each thread needs one query + DoubleVector queries_vector; /* unused if same_parametric_bounds=true*/ + double* queries; + + // pre compute boundary centers if para bounds are the same + if (same_parametric_bounds) { + queries_vector.resize(2 * para_dim * para_dim); + queries = queries_vector.data(); + splinepy::splines::helpers::ScalarTypeBoundaryCenters(*core_patches_[0], + queries); + } + + for (int i{begin}; i < total_; i += nthreads) { + const auto [i_spline, i_query] = std::div(i, n_queries); + + // get ptr start + if (!same_parametric_bounds) { + queries = ¶_bounds_ptr[i_spline * n_queries * para_dim]; + } + + // eval + core_patches_[i_spline]->SplinepyEvaluate( + &queries[i_query * para_dim], + &sub_patch_centers_ptr[(i_spline * n_queries + i_query) * dim]); + } + }; + + splinepy::utils::NThreadExecution(calc_sub_patch_centers_step, + n_total, + nthreads, + splinepy::utils::NThreadQueryType::Step); + + return sub_patch_centers_; + } + + /// @brief sets interface info. runs array shape check + /// @param interfaces void SetInterfaces(py::array_t& interfaces) { // size check splinepy::py::CheckPyArrayShape( @@ -1863,11 +1984,12 @@ class PyMultiPatch { interfaces_ = interfaces; } - py::array_t GetInterfaces() {} - std::shared_ptr SubPatches(const int nthreads) {} + /// @brief either returns existing or creates one + /// @return + py::array_t GetInterfaces() {} - std::shared_ptr BoundaryPatches(const int nthreads) {} + std::shared_ptr BoundaryMultiPatches(const int nthreads) {} py::array_t Evaluate(py::array_t queries, const int nthreads) { From 503af18ba9f1bf2277aba38c1decf49ad6e257b0 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 10:16:56 +0200 Subject: [PATCH 57/89] add SPLINEPY_COMPILE_PYTHON def --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 078c384a8..f28c18484 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,10 @@ if(SPLINEPY_MORE) set(SPLINEPY_DEFS ${SPLINEPY_DEFS} SPLINEPY_MORE) endif(SPLINEPY_MORE) +if(SPLINEPY_COMPILE_PYTHON) + set(SPLINEPY_DEFS ${SPLINEPY_DEFS} SPLINEPY_COMPILE_PYTHON) +endif(SPLINEPY_COMPILE_PYTHON) + if(SPLINEPY_BUILD_EXPLICIT) set(SPLINEPY_DEFS ${SPLINEPY_DEFS} SPLINEPY_BUILD_EXPLICIT) From 64aaac84c2a6978a93047a4f42b164f6263b918d Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 10:18:38 +0200 Subject: [PATCH 58/89] load converter only if python core is compiled --- cpp/splinepy/py/py_spline.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index f5ad3c33a..7de44dffa 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -27,9 +27,15 @@ namespace py = pybind11; using namespace splinelib::sources; +#ifdef SPLINEPY_COMPILE_PYTHON /// @brief python function splinepy.to_derived() -static const auto py_to_derived = +inline static const auto py_to_derived = py::module_::import("splinepy").attr("to_derived"); +#else +// dummy function that will just return py::obj +inline py::object py_to_derived(py::object&& pyobj) { return pyobj; } + +#endif template static bool CheckPyArrayShape(const py::array_t arr, From ea1af6201ea5f385c23e14f7436a16bff8732610 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 10:20:46 +0200 Subject: [PATCH 59/89] add inline, pbounds and extract boundary --- cpp/splinepy/splines/null_spline.hpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cpp/splinepy/splines/null_spline.hpp b/cpp/splinepy/splines/null_spline.hpp index 7a0fcf72b..de10ac995 100644 --- a/cpp/splinepy/splines/null_spline.hpp +++ b/cpp/splinepy/splines/null_spline.hpp @@ -8,6 +8,9 @@ namespace splinepy::splines { +inline std::shared_ptr NullSplineFromLookup(const int para_dim, + const int dim); + /// @brief Null Spline. Placeholder spline that only implements evaluate to /// return zeros. /// @@ -69,6 +72,18 @@ class NullSpline : public splinepy::splines::SplinepyBase { std::fill_n(evaluated, dim_, 0.0); } + + /// Parametric AABB. fills zeros. this enables Sample() interface + virtual void SplinepyParametricBounds(double* para_bounds) const { + std::fill_n(para_bounds, para_dim_, 0.0); + } + + /// required to support boundary only evaluations of multi patch field + /// Boundary spline extraction + virtual std::shared_ptr + SplinepyExtractBoundary(const int& boundary_id) { + return NullSplineFromLookup(para_dim_ - 1, dim_); + }; }; // maxdim to pre-create null splines @@ -81,7 +96,7 @@ using LookupArray_ = std::array, kMaxLookupDim>, kMaxLookupDim>; /// pre-create null splines up to the dimension we support -static const LookupArray_ kNullSplineLookup = [] { +inline static const LookupArray_ kNullSplineLookup = [] { LookupArray_ lookup; int i{0}; for (auto& para_dim_array : lookup) { @@ -95,4 +110,9 @@ static const LookupArray_ kNullSplineLookup = [] { return lookup; }(); +std::shared_ptr NullSplineFromLookup(const int para_dim, + const int dim) { + return kNullSplineLookup[para_dim - 1][dim - 1]; +} + } // namespace splinepy::splines From addec55a2be6197caf6e74d0f3bcc1ce4941af6e Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 11:04:06 +0200 Subject: [PATCH 60/89] multipatch rough draft --- cpp/splinepy/py/py_multi_patch.hpp | 412 +++++++++++++++++++---- cpp/splinepy/py/py_spline_extensions.hpp | 27 -- cpp/splinepy/py/py_spline_list.hpp | 99 +++--- 3 files changed, 400 insertions(+), 138 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 6465cd484..55d52f999 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include @@ -10,6 +13,9 @@ // Bezman #include +// uff +#include + // #include #include @@ -92,19 +98,48 @@ ToCoreSplineVector(py::list pysplines, inline py::list ToPySplineList(CoreSplineVector& splist, int nthreads) { // prepare return obj const int n_splines = static_cast(splist.size()); - py::list pyspline_list(n_splines); - auto to_pyspline = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - pyspline_list[i] = std::make_shared(splist[i])->ToDerived(); + // temporary list to gather splines + std::vector lists_to_concat(nthreads); + + // to return + py::list pyspline_list{}; + + // get start to end for each thread + const int chunk_size = std::div((n_splines + nthreads - 1), nthreads).quot; + IntVector interval; + interval.reserve(std::max(std::max(nthreads, n_splines), 1) + 1); + interval.emplace_back(0); + for (int i{1}; i < nthreads; ++i) { + interval.emplace_back(chunk_size * i); + } + interval.emplace_back(n_splines); + + // locally create and use __iadd__ to concat + auto to_pyspline_step = [&](int begin, int total_) { + const auto& start = interval[begin]; + const auto& end = interval[begin + 1]; + + // assign size + auto& local_list = lists_to_concat[begin]; + local_list = py::list(start - end); + + // loop chunk, concat + for (int i{start}; i < end; ++i) { + local_list[i] = std::make_shared(splist[i])->ToDerived(); } }; - // multi thread execution causes segfault. - // until we find a better solution, do single exe - nthreads = 1; + // exe + splinepy::utils::NThreadExecution(to_pyspline_step, + n_splines, + nthreads, + splinepy::utils::NThreadQueryType::Step); - splinepy::utils::NThreadExecution(to_pyspline, n_splines, nthreads); + // concat + for (auto& local_list : lists_to_concat) { + pyspline_list += local_list; + } return pyspline_list; } @@ -321,19 +356,10 @@ inline void RaiseMismatch(const CoreSplineVector& splist0, const auto& spline0 = splist0[i]; const auto& spline1 = splist1[i]; - // skip null splines - if (spline1->SplinepyIsNull() || spline0->SplinepyIsNull()) { - continue; - } - // default value for para_dim match bool para_dim_matches{true}; - // check - if (name - && (spline0->SplinepySplineName() != spline1->SplinepySplineName())) { - mismatches["name"][thread_index].push_back(i); - } + // check dims if (para_dim && (spline0->SplinepyParaDim() != spline1->SplinepyParaDim())) { mismatches["para_dim"][thread_index].push_back(i); @@ -342,6 +368,16 @@ inline void RaiseMismatch(const CoreSplineVector& splist0, if (dim && (spline0->SplinepyDim() != spline1->SplinepyDim())) { mismatches["dim"][thread_index].push_back(i); } + // null spline should have at least same dims. but nothing else matters. + // skip null splines + if (spline1->SplinepyIsNull() || spline0->SplinepyIsNull()) { + continue; + } + + if (name + && (spline0->SplinepySplineName() != spline1->SplinepySplineName())) { + mismatches["name"][thread_index].push_back(i); + } // check properties that are relevent iff para_dim matches if (para_dim_matches && (degrees || control_mesh_resolutions)) { @@ -1719,7 +1755,7 @@ class PyMultiPatch { CorePatches_ core_patches_; /// sub patches - similar concept to subelement / face elements - std::shared_ptr sub_multi_patches_ = nullptr; + std::shared_ptr sub_multi_patch_ = nullptr; /// center of each sub patch. py::array_t sub_patch_centers_; @@ -1728,7 +1764,7 @@ class PyMultiPatch { py::array_t boundary_patch_ids_; /// Boundary multi patches. Should consist of one smaller parametric dim - std::shared_ptr boundary_multi_patches_ = nullptr; + std::shared_ptr boundary_multi_patch_ = nullptr; /// patch-to-patch connectivity information /// shape: (n_patches, n_boundary_elements) @@ -1737,20 +1773,34 @@ class PyMultiPatch { /// shape: (n_patches,) py::array_t boundary_ids_; - /// default number of threads, only used in SetPatches, as it will just be a - /// setter in python side + // fields - raw list format + py::list field_list_; + + // fields - they are saved as multi-patches + std::vector> field_multi_patches_; + + /// default number of threads for all the operations besides queries int n_default_threads_{1}; + /// hint flag that can be used to accelerate property decisions + bool same_parametric_bounds_{false}; + /// internal flag to indicate that this a field multipatch that may contain /// null spline bool has_null_splines_{false}; + /// default tolerance + double tolerance_{1e-11}; + /// ctor PyMultiPatch() = default; /// list (iterable) init -> pybind will cast to list for us if needed - PyMultiPatch(py::list& patches, const int n_default_threads = 1) - : n_default_threads_(n_default_threads) { + PyMultiPatch(py::list& patches, + const int n_default_threads = 1, + const bool same_parametric_bounds = false) + : n_default_threads_(n_default_threads), + same_parametric_bounds_(same_parametric_bounds) { SetPatchesNThreads(patches, n_default_threads); } @@ -1781,12 +1831,13 @@ class PyMultiPatch { void Clear() { patches_ = py::list(); core_patches_ = CorePatches_{}; - sub_multi_patches_ = nullptr; + sub_multi_patch_ = nullptr; boundary_patch_ids_ = py::array_t(); - boundary_multi_patches_ = nullptr; + boundary_multi_patch_ = nullptr; interfaces_ = py::array_t(); boundary_ids_ = py::array_t(); sub_patch_centers_ = py::array_t(); + field_multi_patches_ = {}; } // could overload, but it won't be clear for pybind @@ -1857,13 +1908,23 @@ class PyMultiPatch { /// @return int Dim() { return CorePatches()[0]->SplinepyDim(); } - /// @brief returns sub patches as multi patches - /// @param nthreads + /// @brief my name is MultiPatch /// @return - std::shared_ptr SubMultiPatches(const int nthreads) { + std::string Name() { return "MultiPatch"; } + + /// @brief MultiPatch with n patches + /// @return + std::string WhatAmI() { + return "MultiPatch with " + std::to_string(core_patches_.size()) + + " patches."; + } + + /// @brief returns sub patches as multi patches, uses default nthreads + /// @return + std::shared_ptr SubMultiPatches() { // quick exit if they exist. - if (sub_multi_patches_) { - return sub_multi_patches_; + if (sub_multi_patch_) { + return sub_multi_patch_; } const int n_splines = CorePatches().size(); @@ -1887,21 +1948,24 @@ class PyMultiPatch { } }; - splinepy::utils::NThreadExecution(boundary_extract, n_splines, nthreads); + splinepy::utils::NThreadExecution(boundary_extract, + n_splines, + n_default_threads_); // create multi patch to return - sub_multi_patches_ = std::make_shared(); + sub_multi_patch_ = std::make_shared(); // set both core and py splines - sub_multi_patches_->patches_ = ToPySplineList(out_boundaries, nthreads); - sub_multi_patches_->core_patches_ = std::move(out_boundaries); + sub_multi_patch_->patches_ = + ToPySplineList(out_boundaries, n_default_threads_); + sub_multi_patch_->core_patches_ = std::move(out_boundaries); - return sub_multi_patches_; + return sub_multi_patch_; } - py::array_t SubPatchCenters(const int nthreads, - const bool same_parametric_bounds) { - if (sub_patch_centers_.size() != 0) { + py::array_t SubPatchCenters() { + // return saved + if (sub_patch_centers_.size() > 0) { return sub_patch_centers_; } @@ -1919,7 +1983,7 @@ class PyMultiPatch { // pre-compute boundary centers DoubleVector para_bounds; double* para_bounds_ptr; - if (!same_parametric_bounds) { + if (!same_parametric_bounds_) { para_bounds.resize(n_total * para_dim); para_bounds_ptr = para_bounds.data(); const int stride = n_queries * para_dim; @@ -1933,7 +1997,9 @@ class PyMultiPatch { }; // exe - splinepy::utils::NThreadExecution(calc_para_bounds, n_splines, nthreads); + splinepy::utils::NThreadExecution(calc_para_bounds, + n_splines, + n_default_threads_); } auto calc_sub_patch_centers_step = [&](int begin, int total_) { @@ -1942,18 +2008,18 @@ class PyMultiPatch { double* queries; // pre compute boundary centers if para bounds are the same - if (same_parametric_bounds) { + if (same_parametric_bounds_) { queries_vector.resize(2 * para_dim * para_dim); queries = queries_vector.data(); splinepy::splines::helpers::ScalarTypeBoundaryCenters(*core_patches_[0], queries); } - for (int i{begin}; i < total_; i += nthreads) { + for (int i{begin}; i < total_; i += n_default_threads_) { const auto [i_spline, i_query] = std::div(i, n_queries); // get ptr start - if (!same_parametric_bounds) { + if (!same_parametric_bounds_) { queries = ¶_bounds_ptr[i_spline * n_queries * para_dim]; } @@ -1966,30 +2032,192 @@ class PyMultiPatch { splinepy::utils::NThreadExecution(calc_sub_patch_centers_step, n_total, - nthreads, + n_default_threads_, splinepy::utils::NThreadQueryType::Step); return sub_patch_centers_; } - /// @brief sets interface info. runs array shape check + /// @brief setter and getter for interface info. runs array shape check for + /// setter /// @param interfaces - void SetInterfaces(py::array_t& interfaces) { - // size check - splinepy::py::CheckPyArrayShape( - interfaces, - {static_cast(CorePatches().size()), - static_cast(CorePatches()[0]->SplinepyParaDim() * 2)}, - true); - - interfaces_ = interfaces; + py::array_t Interfaces(const py::array_t& interfaces) { + // check if we should set or get + // set + if (interfaces.size() > 0) { + // size check + splinepy::py::CheckPyArrayShape( + interfaces, + {static_cast(CorePatches().size()), + static_cast(CorePatches()[0]->SplinepyParaDim() * 2)}, + true); + + interfaces_ = interfaces; + return interfaces_; + } + + // here's get + if (interfaces_.size() > 0) { + return interfaces_; + } + + // now, first compute, then get + // first, centers. will either compute or return saved + + // get centers + auto sub_p_centers = SubPatchCenters(); + const int n_centers = sub_p_centers.shape(0); + const int dim = Dim(); + double* sub_p_centers_ptr = + static_cast(sub_p_centers.request().ptr); + // call uff + // create temporary arrays + DoubleVector metric(Dim(), 1.); + DoubleVector new_points(n_centers * dim); + IntVector new_point_masks(n_centers); + IntVector inverse(n_centers); + int n_newpoints{}; // out + + // temporary copied for raw input -> uff doesn't have `const` for simplest + // fortran compatibility + int n_centers_in{n_centers}, dim_in{dim}; + double tolerance_in{tolerance_}; + + uff::uff(sub_p_centers_ptr, + n_centers_in, + dim_in, + metric.data(), + tolerance_in, + true, + new_points.data(), + new_point_masks.data(), + n_newpoints, + inverse.data()); + + // create return - reassign interfaces_ + const int n_boundary = ParaDim() * 2; + interfaces_ = + py::array_t({static_cast(CorePatches().size()), n_boundary}); + int* interfaces_ptr = static_cast(interfaces.request().ptr); + + // sanity check? + if (static_cast(interfaces_.size()) != n_centers) { + splinepy::utils::PrintAndThrowError( + "Size mismatch between interfaces and sub_patch_centers."); + } + + // turn inverse to interfaces + auto inverse_to_interfaces = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + const auto& inv = inverse[i]; + if (inv < i) { + interfaces_ptr[i] = std::div(inv, n_boundary).quot; + interfaces_ptr[inv] = std::div(i, n_boundary).quot; + } else { + interfaces_ptr[i] = -1; + } + } + }; + + splinepy::utils::NThreadExecution(inverse_to_interfaces, + n_centers, + n_default_threads_); + + // one more sanity check - elements should not be referenced more than + // once. could do this with mutex, but probably won't gain much. this does + // not perform orientation check + // this one, we actually want 0 init + std::vector inv_counts(n_centers); + for (const auto& inv : inverse) { + if (++inv_counts[inv] > 2) { + splinepy::utils::PrintAndThrowError( + "Interface is invalid, found a subpatch center that overlaps more " + "than twice."); + } + } + + // Original - TODO remove if above is fine + // interfaces_ = InterfacesFromBoundaryCenters( + // SubPatchCenters(n_default_threads_, same_parametric_bounds_), + // tolerance_, + // ParaDim()); + + return interfaces_; } - /// @brief either returns existing or creates one - /// @return - py::array_t GetInterfaces() {} + py::array_t BoundaryPatchIds() { + // return if it exists + if (boundary_patch_ids_.size() != 0) { + return boundary_patch_ids_; + } + + // prepare only ingredient - interfaces + // negative entries in interfaces mean boundary patch + auto interfaces = Interfaces({}); + const int n_interfaces = interfaces.size(); + const int* interfaces_ptr = static_cast(interfaces.request().ptr); + + // create new array + boundary_patch_ids_ = py::array_t(n_interfaces); + int* boundary_patch_ids_ptr = + static_cast(boundary_patch_ids_.request().ptr); + + // add ids of negative interface entries + int j{}; + for (int i{}; i < n_interfaces; ++i) { + if (interfaces_ptr[i] < 0) { + boundary_patch_ids_ptr[j] = i; + ++j; + } + } + + // resize ids + boundary_patch_ids_.resize({j}, false); + + return boundary_patch_ids_; + } + + std::shared_ptr BoundaryMultiPatches() { + // return early if exists + if (boundary_multi_patch_) { + return boundary_multi_patch_; + } + + // get boundary_patch_ids; + auto boundary_pid = BoundaryPatchIds(); + int* boundary_pid_ptr = static_cast(boundary_pid.request().ptr); + const int n_boundary_pid = boundary_pid.size(); - std::shared_ptr BoundaryMultiPatches(const int nthreads) {} + // values needed for offset + const int n_subpatches = ParaDim() * 2; + + // create boundary patches with enough space + CoreSplineVector boundary_core_patches(n_boundary_pid); + + // extract patches + auto extract_boundaries_step = [&](const int begin, const int total_) { + for (int i{begin}; i < total_; i += n_default_threads_) { + const auto [i_spline, i_subpatch] = + std::div(boundary_pid_ptr[i], n_subpatches); + boundary_core_patches[i] = + core_patches_[i_spline]->SplinepyExtractBoundary(i_subpatch); + } + }; + + // Execute in parallel + splinepy::utils::NThreadExecution(extract_boundaries_step, + n_boundary_pid, + n_default_threads_, + splinepy::utils::NThreadQueryType::Step); + + // create return patch + boundary_multi_patch_ = std::make_shared(); + boundary_multi_patch_->patches_ = + ToPySplineList(boundary_core_patches, n_default_threads_); + boundary_multi_patch_->core_patches_ = std::move(boundary_core_patches); + + return boundary_multi_patch_; + } py::array_t Evaluate(py::array_t queries, const int nthreads) { @@ -2141,15 +2369,69 @@ class PyMultiPatch { return sampled; } - bool AddFields(py::args fields, + void AddFields(py::list& fields, + const bool check_name, const bool check_dims, const bool check_degrees, - const bool check_control_mesh_resolutions) {} + const bool check_control_mesh_resolutions, + const int nthreads) { + + // allocate space + const auto n_current_fields = static_cast(field_multi_patches_.size()); + const auto n_new_fields = static_cast(fields.size()); + const int n_total_fields = n_current_fields + n_new_fields; + field_multi_patches_.resize(n_total_fields); + + // some hint values for null splines + const int para_dim = ParaDim(); + const int dim = Dim(); + + // prepare error message + std::string field_mismatch_info{}; + std::mutex field_mismatch_mutex; + + // loop each field + auto field_to_multi_patch = [&](int begin, int end) { + // with in this nthread exe, we only use nthreads=1. This won't create + // any threads. + for (int i{n_current_fields + begin}, j{begin}; j < end; ++i, ++j) { + // create multipatch + py::list casted_list = fields[j].template cast(); + field_multi_patches_[i] = + std::make_shared(casted_list, para_dim, dim, 1); + try { + // check mismatch - doesn't check null splines + RaiseMismatch(core_patches_, + field_multi_patches_[i]->core_patches_, + check_name, + check_dims, + check_dims, + check_degrees, + check_control_mesh_resolutions, + 1); + + } catch (const std::runtime_error& e) { + // set true, and add error message. + std::lock_guard guard(field_mismatch_mutex); + field_mismatch_info += "[mismatch error from the field with index (" + + std::to_string(j) + ")]\n" + e.what(); + } + } + }; + + // raise, if there were error. + if (field_mismatch_info.size() != 0) { + splinepy::utils::PrintAndThrowError(field_mismatch_info); + } + + // all good, extend to list + field_list_ += fields; + } }; inline void add_multi_patch(py::module& m) { - // returns [connectivity, vertex_ids, edge_information, boundaries] + // returns [connectivity, vertex_ids, edge_information, boundaries] m.def("retrieve_mfem_information", &splinepy::py::RetrieveMfemInformation, py::arg("corner_vertices"), @@ -2180,6 +2462,12 @@ inline void add_multi_patch(py::module& m) { py::arg("global_interfaces"), py::arg("tolerance"), py::arg("nthreads") = 1); + + py::class_> + klasse(m, "PyMultiPatch"); + + klasse.def(py::init<>()); } } // namespace splinepy::py diff --git a/cpp/splinepy/py/py_spline_extensions.hpp b/cpp/splinepy/py/py_spline_extensions.hpp index 5450c8b34..f53b06852 100644 --- a/cpp/splinepy/py/py_spline_extensions.hpp +++ b/cpp/splinepy/py/py_spline_extensions.hpp @@ -15,33 +15,6 @@ namespace splinepy::py { namespace py = pybind11; -/// checks if given PySpline has specified para_dim and dim -/// optionally raise if they don't match -inline bool CheckCoreParaDimAndDim(const PySpline::CoreSpline_& spline, - const int& para_dim, - const int& dim, - const bool throw_ = true) { - if (para_dim != spline->SplinepyParaDim() || dim != spline->SplinepyDim()) { - if (throw_) { - splinepy::utils::PrintAndThrowError( - "Spline is expected to have (para_dim", - para_dim, - ", dim", - dim, - ".", - "But it has", - "(", - spline->SplinepyParaDim(), - ",", - spline->SplinepyDim(), - ")."); - } - return false; - } - - return true; -} - /// (multiple) knot insertion, single dimension inline py::list InsertKnots(std::shared_ptr& spline, int para_dim, diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp index 0a1e7cd3c..7c4b23be5 100644 --- a/cpp/splinepy/py/py_spline_list.hpp +++ b/cpp/splinepy/py/py_spline_list.hpp @@ -46,15 +46,15 @@ ListOfPySplinesToVectorOfCoreSplines(py::list pysplines, } /// TODO: move ListOfPySplinesToVectorOfCoreSplines here and rename -inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& splist, +inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& core_patches_, int nthreads) { // prepare return obj - const int n_splines = static_cast(splist.size()); + const int n_splines = static_cast(core_patches_.size()); py::list pyspline_list(n_splines); auto to_pyspline = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { - pyspline_list[i] = std::make_shared(splist[i]); + pyspline_list[i] = std::make_shared(core_patches_[i]); } }; @@ -68,27 +68,28 @@ inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& splist, } /// @brief raises if elements' para_dim and dims aren't equal -/// @param splist +/// @param core_patches_ /// @param nthreads inline void -RaiseIfElementsHaveUnequalParaDimOrDim(const CoreSplineVector& splist, +RaiseIfElementsHaveUnequalParaDimOrDim(const CoreSplineVector& core_patches_, const int nthreads) { // use first spline as guide line - const int para_dim = splist[0]->SplinepyParaDim(); - const int dim = splist[0]->SplinepyDim(); + const int para_dim = core_patches_[0]->SplinepyParaDim(); + const int dim = core_patches_[0]->SplinepyDim(); std::vector mismatches(nthreads); auto check_dims_step = [&](int begin, int total_) { for (int i{begin}; i < total_; i += nthreads) { - if (!CheckCoreParaDimAndDim(splist[i], para_dim, dim, false)) { + if (core_patches_[i]->SplinepyParaDim() != para_dim + || core_patches_[i]->SplinepyDim() != dim) { mismatches[begin].push_back(i); } } }; splinepy::utils::NThreadExecution(check_dims_step, - static_cast(splist.size()), + static_cast(core_patches_.size()), nthreads, splinepy::utils::NThreadQueryType::Step); // prepare error prints @@ -117,23 +118,23 @@ RaiseIfElementsHaveUnequalParaDimOrDim(const CoreSplineVector& splist, ")"); } -inline void ListRaiseIfElementsHaveUnequalParaDimOrDim(py::list& splist, +inline void ListRaiseIfElementsHaveUnequalParaDimOrDim(py::list& core_patches_, const int nthreads) { const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); RaiseIfElementsHaveUnequalParaDimOrDim(core_vector, nthreads); } /// evaluates splines of same para_dim and dim. inline py::array_t -CoreSplineVectorEvaluate(const CoreSplineVector& splist, +CoreSplineVectorEvaluate(const CoreSplineVector& core_patches_, const py::array_t& queries, const int nthreads, const bool check_dims) { // use first spline as dimension guide line - const int para_dim = splist[0]->SplinepyParaDim(); - const int dim = splist[0]->SplinepyDim(); + const int para_dim = core_patches_[0]->SplinepyParaDim(); + const int dim = core_patches_[0]->SplinepyDim(); // query dim check CheckPyArrayShape(queries, {-1, para_dim}, true); @@ -141,12 +142,12 @@ CoreSplineVectorEvaluate(const CoreSplineVector& splist, // check dims if wanted. Else it will assume that every spline has same // dimension as the first entry's if (check_dims) { - RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); + RaiseIfElementsHaveUnequalParaDimOrDim(core_patches_, nthreads); } // prepare input and output double* queries_ptr = static_cast(queries.request().ptr); - const int n_splines = splist.size(); + const int n_splines = core_patches_.size(); const int n_queries = queries.shape(0); const int n_total = n_splines * n_queries; py::array_t evaluated({n_total, dim}); @@ -156,7 +157,7 @@ CoreSplineVectorEvaluate(const CoreSplineVector& splist, auto evaluate_step = [&](int begin, int total_) { for (int i{begin}; i < total_; i += nthreads) { const auto [i_spline, i_query] = std::div(i, n_queries); - splist[i_spline]->SplinepyEvaluate( + core_patches_[i_spline]->SplinepyEvaluate( &queries_ptr[i_query * para_dim], &evaluated_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -171,54 +172,54 @@ CoreSplineVectorEvaluate(const CoreSplineVector& splist, return evaluated; } -inline py::array_t ListEvaluate(py::list& splist, +inline py::array_t ListEvaluate(py::list& core_patches_, const py::array_t& queries, const int nthreads, const bool check_dims) { const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); return CoreSplineVectorEvaluate(core_vector, queries, nthreads, check_dims); } inline py::array_t -CoreSplineVectorDerivative(const CoreSplineVector& splist, +CoreSplineVectorDerivative(const CoreSplineVector& core_patches_, const py::array_t& queries, const py::array_t& orders, const int nthreads) { return py::array_t(); } -inline py::array_t ListDerivative(py::list& splist, +inline py::array_t ListDerivative(py::list& core_patches_, const py::array_t& queries, const py::array_t& orders, const int nthreads) { const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); return CoreSplineVectorDerivative(core_vector, queries, orders, nthreads); } /// Samples equal resoltions for each para dim inline py::array_t -CoreSplineVectorSample(const CoreSplineVector& splist, +CoreSplineVectorSample(const CoreSplineVector& core_patches_, const int resolution, const int nthreads, const bool same_parametric_bounds, const bool check_dims) { // use first spline as dimension guide line - const auto& first_spline = *splist[0]; + const auto& first_spline = *core_patches_[0]; const int para_dim = first_spline.SplinepyParaDim(); const int dim = first_spline.SplinepyDim(); // dim check first if (check_dims) { - RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); + RaiseIfElementsHaveUnequalParaDimOrDim(core_patches_, nthreads); } // n_queries, and n_splines; int n_queries{1}; - const int n_splines = splist.size(); + const int n_splines = core_patches_.size(); // prepare resolutions IntVector resolutions_vector(para_dim); @@ -256,7 +257,7 @@ CoreSplineVectorSample(const CoreSplineVector& splist, auto sample_same_bounds_step = [&](int begin, int total_) { for (int i{begin}; i < total_; i += nthreads) { const auto [i_spline, i_query] = std::div(i, n_queries); - splist[i_spline]->SplinepyEvaluate( + core_patches_[i_spline]->SplinepyEvaluate( &queries[i_query * para_dim], &sampled_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -284,7 +285,7 @@ CoreSplineVectorSample(const CoreSplineVector& splist, for (int i{begin}; i < end; ++i) { // get para_bounds - splist[i]->SplinepyParametricBounds(para_bounds); + core_patches_[i]->SplinepyParametricBounds(para_bounds); // setup grid points helper grid_points[i].SetUp(para_dim, para_bounds, resolutions); } @@ -304,7 +305,7 @@ CoreSplineVectorSample(const CoreSplineVector& splist, const auto [i_spline, i_query] = std::div(i, n_queries); const auto& gp_helper = grid_points[i_spline]; gp_helper.IdToGridPoint(i_query, thread_query); - splist[i_spline]->SplinepyEvaluate( + core_patches_[i_spline]->SplinepyEvaluate( thread_query, &sampled_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -321,13 +322,13 @@ CoreSplineVectorSample(const CoreSplineVector& splist, } /// Samples equal resoltions for each para dim -inline py::array_t ListSample(py::list& splist, +inline py::array_t ListSample(py::list& core_patches_, const int resolution, const int nthreads, const bool same_parametric_bounds, const bool check_dims) { const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); return CoreSplineVectorSample(core_vector, resolution, @@ -336,12 +337,12 @@ inline py::array_t ListSample(py::list& splist, check_dims); } -// extracts boundary splines from splist. +// extracts boundary splines from core_patches_. inline CoreSplineVector -CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, +CoreSplineVectorExtractBoundaries(const CoreSplineVector& core_patches_, const int nthreads, const bool same_para_dims) { - const int n_splines = splist.size(); + const int n_splines = core_patches_.size(); // to accumulate int n_boundaries{}; // gather offsets for boundary add one last so that we can always find out @@ -353,7 +354,7 @@ CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, int offset{}; // offset counter if (same_para_dims) { // compute n_boundary - const int n_boundary = splist[0]->SplinepyParaDim() * 2; + const int n_boundary = core_patches_[0]->SplinepyParaDim() * 2; // fill offset @@ -366,7 +367,7 @@ CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, // for un-equal boundary sizes, we lookup each one of them for (int i{}; i < n_splines; ++i) { boundary_offsets.push_back(offset); - offset += splist[i]->SplinepyParaDim() * 2; + offset += core_patches_[i]->SplinepyParaDim() * 2; } } // current offset value should equal to total boundary count @@ -384,7 +385,7 @@ CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, // end of the offset const auto& next_offset = boundary_offsets[i + 1]; // get spline - auto& spline_i = *splist[i]; + auto& spline_i = *core_patches_[i]; for (int j{}; j < next_offset - this_offset; ++j) { out_boundaries[this_offset + j] = spline_i.SplinepyExtractBoundary(j); } @@ -396,11 +397,11 @@ CoreSplineVectorExtractBoundaries(const CoreSplineVector& splist, return out_boundaries; } -inline py::list ListExtractBoundaries(py::list& splist, +inline py::list ListExtractBoundaries(py::list& core_patches_, const int nthreads, const bool same_para_dims) { const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); auto boundaries = CoreSplineVectorExtractBoundaries(core_vector, nthreads, same_para_dims); @@ -409,20 +410,20 @@ inline py::list ListExtractBoundaries(py::list& splist, // computes boundary centers for splines of same para_dim and dim inline py::array_t -CoreSplineVectorBoundaryCenters(const CoreSplineVector& splist, +CoreSplineVectorBoundaryCenters(const CoreSplineVector& core_patches_, const int nthreads, const bool same_parametric_bounds, const bool check_dims) { // dim check first if (check_dims) { - RaiseIfElementsHaveUnequalParaDimOrDim(splist, nthreads); + RaiseIfElementsHaveUnequalParaDimOrDim(core_patches_, nthreads); } // prepare output // from here we assume that all the splines have the same para_dim and dim - const int n_splines = splist.size(); - const int para_dim = splist[0]->SplinepyParaDim(); - const int dim = splist[0]->SplinepyDim(); + const int n_splines = core_patches_.size(); + const int para_dim = core_patches_[0]->SplinepyParaDim(); + const int dim = core_patches_[0]->SplinepyDim(); const int n_queries = 2 * para_dim; const int n_total = n_queries * n_splines; py::array_t boundary_centers({n_total, dim}); @@ -440,7 +441,7 @@ CoreSplineVectorBoundaryCenters(const CoreSplineVector& splist, auto calc_para_bounds = [&](int begin, int end) { for (int i{begin}; i < end; ++i) { splinepy::splines::helpers::ScalarTypeBoundaryCenters( - *splist[i], + *core_patches_[i], ¶_bounds_ptr[stride * i]); } }; @@ -458,7 +459,7 @@ CoreSplineVectorBoundaryCenters(const CoreSplineVector& splist, if (same_parametric_bounds) { queries_vector.resize(2 * para_dim * para_dim); queries = queries_vector.data(); - splinepy::splines::helpers::ScalarTypeBoundaryCenters(*splist[0], + splinepy::splines::helpers::ScalarTypeBoundaryCenters(*core_patches_[0], queries); } @@ -471,7 +472,7 @@ CoreSplineVectorBoundaryCenters(const CoreSplineVector& splist, } // eval - splist[i_spline]->SplinepyEvaluate( + core_patches_[i_spline]->SplinepyEvaluate( &queries[i_query * para_dim], &boundary_centers_ptr[(i_spline * n_queries + i_query) * dim]); } @@ -486,12 +487,12 @@ CoreSplineVectorBoundaryCenters(const CoreSplineVector& splist, } inline py::array_t -ListBoundaryCenters(py::list& splist, +ListBoundaryCenters(py::list& core_patches_, const int nthreads, const bool same_parametric_bounds, const bool check_dims) { const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(splist, nthreads); + ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); return CoreSplineVectorBoundaryCenters(core_vector, nthreads, same_parametric_bounds, From 9456299bd2d455f15de7a13f06ea2223cda87098 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 11:50:46 +0200 Subject: [PATCH 61/89] rm splinelist test --- tests/test_spline_list.py | 334 -------------------------------------- 1 file changed, 334 deletions(-) delete mode 100644 tests/test_spline_list.py diff --git a/tests/test_spline_list.py b/tests/test_spline_list.py deleted file mode 100644 index f551fc0df..000000000 --- a/tests/test_spline_list.py +++ /dev/null @@ -1,334 +0,0 @@ -try: - from . import common as c -except BaseException: - import common as c - - -class SplineListTest(c.unittest.TestCase): - def test_spline_list_evaluate(self): - """list evaluation""" - # get list of splines - splines = c.all2p2d() - - # get reference solution - ref_solutions = c.np.vstack([s.evaluate(c.q2D) for s in splines]) - - # call eval, don't check dim - list_solutions = c.splinepy.splinepy_core.lists.evaluate( - splines, c.q2D, nthreads=2, check_dims=False - ) - assert c.np.allclose(ref_solutions, list_solutions) - - # call eval, check dim - list_solutions = c.splinepy.splinepy_core.lists.evaluate( - splines, c.q2D, nthreads=2, check_dims=True - ) - assert c.np.allclose(ref_solutions, list_solutions) - - # call eval, single thread - list_solutions = c.splinepy.splinepy_core.lists.evaluate( - splines, c.q2D, nthreads=1, check_dims=True - ) - assert c.np.allclose(ref_solutions, list_solutions) - - def test_spline_list_sample(self): - """spline sample""" - splines = c.all2p2d() - - # set resolution. this is int, - # because it is implemented for equal resolution only - resolution = 3 - - ref_solutions = c.np.vstack( - [s.sample([resolution] * splines[0].para_dim) for s in splines] - ) - - # check everything and compute - list_samples = c.splinepy.splinepy_core.lists.sample( - splines, - resolution, - nthreads=2, - same_parametric_bounds=True, - check_dims=True, - ) - assert c.np.allclose(ref_solutions, list_samples) - - # don't check p bounds - list_samples = c.splinepy.splinepy_core.lists.sample( - splines, - resolution, - nthreads=2, - same_parametric_bounds=True, - check_dims=True, - ) - assert c.np.allclose(ref_solutions, list_samples) - - # update p bounds for bsplines - bspline_id = 2 - nurbs_id = 3 - for i in [bspline_id, nurbs_id]: - new_kvs = [kv * 2 for kv in splines[i].kvs] - splines[i].kvs = new_kvs - - # check p bounds, - list_samples = c.splinepy.splinepy_core.lists.sample( - splines, - resolution, - nthreads=2, - same_parametric_bounds=False, - check_dims=True, - ) - assert c.np.allclose(ref_solutions, list_samples) - - def test_extract_boundaries(self): - """extract boundaries. allowed for splines with unmatching dims""" - # prepare 2d splines - splines2d = c.all2p2d() - - # prepare mixed dim splines - splines2d3d = [*splines2d, *c.all3p3d()] - - # prepare test func - def _test(pure_list, same_p_dims): - """actual test routine""" - ref_boundaries = [] - for s in pure_list: - ref_boundaries.extend( - c.splinepy.splinepy_core.extract_boundaries(s, []) - ) - - list_boundaries = ( - c.splinepy.splinepy_core.lists.extract_boundaries( - pure_list, nthreads=2, same_para_dims=False - ) - ) - - # for same p_dims, - # try same_para_dims=True by extending test splines - if same_p_dims: - ref_boundaries.extend(ref_boundaries) - list_boundaries.extend( - c.splinepy.splinepy_core.lists.extract_boundaries( - pure_list, nthreads=2, same_para_dims=True - ) - ) - - # should have same size - assert len(ref_boundaries) == len(list_boundaries) - - # same spline? - for rb, lb in zip(ref_boundaries, list_boundaries): - # currently, core function does not have getter - # for individual attr, so cast - assert c.are_splines_equal(c.to_derived(rb), c.to_derived(lb)) - - _test(splines2d, True) - _test(splines2d3d, False) - - def test_boundary_centers(self): - """boundary centers""" - splines = c.all2p2d() - - ref_centers = c.np.vstack( - [c.splinepy.splinepy_core.boundary_centers(s) for s in splines] - ) - - # test same bounds - list_centers = c.splinepy.splinepy_core.lists.boundary_centers( - splines, nthreads=2, same_parametric_bounds=True, check_dims=False - ) - assert c.np.allclose(ref_centers, list_centers) - - # different bounds - # update p bounds for bsplines - bspline_id = 2 - nurbs_id = 3 - for i in [bspline_id, nurbs_id]: - new_kvs = [kv * 2 for kv in splines[i].kvs] - splines[i].kvs = new_kvs - - list_centers = c.splinepy.splinepy_core.lists.boundary_centers( - splines, nthreads=2, same_parametric_bounds=False, check_dims=False - ) - assert c.np.allclose(ref_centers, list_centers) - - def test_raise_dim_mismatch(self): - """see if function raises dim mismatch correctly""" - slist2d = c.all2p2d() - - # prepare mixed - slist2d3d = [*c.all2p2d(), *c.all3p3d()] - - # nothing happens - c.splinepy.splinepy_core.lists.raise_dim_mismatch(slist2d, nthreads=2) - - with self.assertRaises(RuntimeError): - c.splinepy.splinepy_core.lists.raise_dim_mismatch( - slist2d3d, nthreads=2 - ) - - def _bezier_noisy_boxes_and_test_shapes(self): - # prepare boxes with some noise - box2d = c.nd_box(2) - box2d.pop("knot_vectors") - rbox2d = c.splinepy.RationalBezier(**box2d) - box2d.pop("weights") - zbox2d = c.splinepy.Bezier(**box2d) - box3d = c.nd_box(3) - box3d.pop("knot_vectors") - rbox3d = c.splinepy.RationalBezier(**box3d) - box3d.pop("weights") - zbox3d = c.splinepy.Bezier(**box3d) - - outer = [zbox2d, rbox2d, zbox3d, rbox3d] - # add some noise - for o in outer: - o.cps = o.cps + c.np.random.normal(0, 0.025, o.cps.shape) - - # test shapes - z2 = c.splinepy.Bezier(**c.z2p2d()) - z3 = c.splinepy.Bezier(**c.z3p3d()) - r2 = c.splinepy.RationalBezier(**c.r2p2d()) - r3 = c.splinepy.RationalBezier(**c.r3p3d()) - - # fit into unit box - for bez in [z2, z3, r2, r3]: - # offset - bez.cps -= bez.cps.min(axis=0) - # scale - bez.cps = bez.cps * (1 / bez.cps.max(axis=0)) - - return outer[:2], outer[2:], [z2, r2], [z3, r3] - - def test_list_compose(self): - """check if list compose yield same spline as spline compose""" - # prepare boxes with some noise - ( - outer_2d, - outer_3d, - inner_2d, - inner_3d, - ) = self._bezier_noisy_boxes_and_test_shapes() - - ref_composed = [o.compose(i) for o, i in zip(outer_2d, inner_2d)] - - # list compose - list_composed = c.splinepy.splinepy_core.lists.compose( - outer_2d, inner_2d, cartesian_product=False, nthreads=2 - ) - - # add 3d - ref_composed.extend([o.compose(i) for o, i in zip(outer_3d, inner_3d)]) - list_composed.extend( - c.splinepy.splinepy_core.lists.compose( - outer_3d, inner_3d, cartesian_product=False, nthreads=2 - ) - ) - - for r, l in zip(ref_composed, list_composed): - assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) - - # test cartesian - ref_composed = [] - for o in outer_2d: - for i in inner_2d: - ref_composed.append(o.compose(i)) - for o in outer_3d: - for i in inner_3d: - ref_composed.append(o.compose(i)) - - list_composed = c.splinepy.splinepy_core.lists.compose( - outer_2d, inner_2d, cartesian_product=True, nthreads=2 - ) - list_composed.extend( - c.splinepy.splinepy_core.lists.compose( - outer_3d, inner_3d, cartesian_product=True, nthreads=2 - ) - ) - - for r, l in zip(ref_composed, list_composed): - assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) - - def test_list_compose_derivatives(self): - # prepare boxes with some noise - ( - outer_2d, - outer_3d, - inner_2d, - inner_3d, - ) = self._bezier_noisy_boxes_and_test_shapes() - - # create 2d ref - ref_composed = [ - o.composition_derivative(i, i) for o, i in zip(outer_2d, inner_2d) - ] - - # list compose - list_composed = c.splinepy.splinepy_core.lists.composition_derivative( - outer_2d, inner_2d, inner_2d, cartesian_product=False, nthreads=2 - ) - - # add 3d - ref_composed.extend( - [ - o.composition_derivative(i, i) - for o, i in zip(outer_3d, inner_3d) - ] - ) - list_composed.extend( - c.splinepy.splinepy_core.lists.composition_derivative( - outer_3d, - inner_3d, - inner_3d, - cartesian_product=False, - nthreads=2, - ) - ) - - for r, l in zip(ref_composed, list_composed): - assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) - - # test cartesian - # - # rational_bz.composition_derivative(poly, poly_der) not supported - # so, remove - for inn in [inner_2d, inner_3d]: - # pop poly - inn.pop(0) - - # clone rat - rat = inn[-1].copy() - rat.cps[:, 0] = 0.5 - rat.cps[:, 0] += c.np.random.uniform(-0.4, 0.4, len(rat.cps)) - # force sync - list exe doesn't sync before exe - rat.new_core(**rat._data["properties"]) - inn.append(rat) - - ref_composed = [] - for o in outer_2d: - for i in inner_2d: - ref_composed.append(o.composition_derivative(i, i)) - for o in outer_3d: - for i in inner_3d: - ref_composed.append(o.composition_derivative(i, i)) - - list_composed = c.splinepy.splinepy_core.lists.composition_derivative( - outer_2d, inner_2d, inner_2d, cartesian_product=True, nthreads=2 - ) - - list_composed.extend( - c.splinepy.splinepy_core.lists.composition_derivative( - outer_3d, - inner_3d, - inner_3d, - cartesian_product=True, - nthreads=2, - ) - ) - - for r, l in zip(ref_composed, list_composed): - assert c.are_splines_equal(c.to_derived(r), c.to_derived(l)) - - -if __name__ == "__main__": - c.unittest.main() From f238676c9adf48b34c0fcd152f526da620c34449 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 12:04:27 +0200 Subject: [PATCH 62/89] bind PyMultiPatch --- cpp/splinepy/py/py_multi_patch.hpp | 77 +++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 55d52f999..b0c780b90 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1840,6 +1840,25 @@ class PyMultiPatch { field_multi_patches_ = {}; } + /// @brief returns para_dim of the first patch + /// @return + int ParaDim() { return CorePatches()[0]->SplinepyParaDim(); } + + /// @brief returns dim of the first patch + /// @return + int Dim() { return CorePatches()[0]->SplinepyDim(); } + + /// @brief my name is MultiPatch + /// @return + std::string Name() { return "MultiPatch"; } + + /// @brief MultiPatch with n patches + /// @return + std::string WhatAmI() { + return "MultiPatch with " + std::to_string(core_patches_.size()) + + " patches."; + } + // could overload, but it won't be clear for pybind /// @brief default patches setter, exposed to python @@ -1900,28 +1919,9 @@ class PyMultiPatch { } py::list GetPatches() { return patches_; } - /// @brief returns para_dim of the first patch - /// @return - int ParaDim() { return CorePatches()[0]->SplinepyParaDim(); } - - /// @brief returns dim of the first patch - /// @return - int Dim() { return CorePatches()[0]->SplinepyDim(); } - - /// @brief my name is MultiPatch - /// @return - std::string Name() { return "MultiPatch"; } - - /// @brief MultiPatch with n patches - /// @return - std::string WhatAmI() { - return "MultiPatch with " + std::to_string(core_patches_.size()) - + " patches."; - } - /// @brief returns sub patches as multi patches, uses default nthreads /// @return - std::shared_ptr SubMultiPatches() { + std::shared_ptr SubMultiPatch() { // quick exit if they exist. if (sub_multi_patch_) { return sub_multi_patch_; @@ -2177,7 +2177,7 @@ class PyMultiPatch { return boundary_patch_ids_; } - std::shared_ptr BoundaryMultiPatches() { + std::shared_ptr BoundaryMultiPatch() { // return early if exists if (boundary_multi_patch_) { return boundary_multi_patch_; @@ -2467,7 +2467,40 @@ inline void add_multi_patch(py::module& m) { std::shared_ptr> klasse(m, "PyMultiPatch"); - klasse.def(py::init<>()); + klasse.def(py::init<>()) + .def(py::init()) + .def("clear", &PyMultiPatch::Clear) + .def_property_readonly("para_dim", &PyMultiPatch::ParaDim) + .def_property_readonly("dim", &PyMultiPatch::Dim) + .def_property_readonly("name", &PyMultiPatch::Name) + .def_property_readonly("whatami", &PyMultiPatch::WhatAmI) + .def_property("patches", + &PyMultiPatch::GetPatches, + &PyMultiPatch::SetPatchesDefault) + .def("sub_multi_patch", &PyMultiPatch::SubMultiPatch) + .def("sub_patch_centers", &PyMultiPatch::SubPatchCenters) + .def("interfaces", &PyMultiPatch::Interfaces) + .def("boundary_patch_ids", &PyMultiPatch::BoundaryPatchIds) + .def("boundary_multi_patch", &PyMultiPatch::BoundaryMultiPatch) + .def("evaluate", + &PyMultiPatch::Evaluate, + py::arg("queries"), + py::arg("nthreads")) + .def("sample", + &PyMultiPatch::Sample, + py::arg("resolution"), + py::arg("nthreads"), + py::arg("same_parametric_bounds")) + .def("add_fields", + &PyMultiPatch::AddFields, + py::arg("fields"), + py::arg("check_name"), + py::arg("check_dims"), + py::arg("check_degrees"), + py::arg("checK_control_mesh_resolutions"), + py::arg("nthreads")) + //.def("", &PyMultiPatch::) + ; } } // namespace splinepy::py From 794c2bcac6e3c29b5ea6ce67aacbaf299fbc646b Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 12:09:30 +0200 Subject: [PATCH 63/89] inherit __init__ --- splinepy/multipatch.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 1b6b30744..a558b3b00 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -8,6 +8,7 @@ from splinepy._base import SplinepyBase from splinepy.spline import Spline from splinepy.splinepy_core import ( + PyMultiPatch, boundaries_from_continuity, boundary_centers, extract_all_boundary_splines, @@ -16,7 +17,7 @@ from splinepy.utils.data import enforce_contiguous -class Multipatch(SplinepyBase): +class Multipatch(SplinepyBase, PyMultiPatch): """ System of patches to store information such as boundaries and interfaces @@ -47,6 +48,11 @@ def __init__( None """ # Init values + if splines is not None: + super().__init__(splines, settings.NTHREADS, False) + else: + super().__init__() + self._init_members() self._logd("Instantiated Multipatch object") From 11df9c91e689120020c39b86b4a3fffa1e7b3dc4 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 12:16:07 +0200 Subject: [PATCH 64/89] get/set for default values, and field getter --- cpp/splinepy/py/py_multi_patch.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index b0c780b90..d14f73cee 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -2427,6 +2427,10 @@ class PyMultiPatch { // all good, extend to list field_list_ += fields; } + + py::list GetFields() { + return field_list_; + } }; inline void add_multi_patch(py::module& m) { @@ -2469,6 +2473,9 @@ inline void add_multi_patch(py::module& m) { klasse.def(py::init<>()) .def(py::init()) + .def_readwrite("n_default_threads", &PyMultiPatch::n_default_threads_) + .def_readwrite("same_parametric_bounds", &PyMultiPatch::same_parametric_bounds_) + .def_readwrite("tolerance", &PyMultiPatch::tolerance_) .def("clear", &PyMultiPatch::Clear) .def_property_readonly("para_dim", &PyMultiPatch::ParaDim) .def_property_readonly("dim", &PyMultiPatch::Dim) @@ -2499,6 +2506,7 @@ inline void add_multi_patch(py::module& m) { py::arg("check_degrees"), py::arg("checK_control_mesh_resolutions"), py::arg("nthreads")) + .def("fields", &PyMultiPatch::GetFields) //.def("", &PyMultiPatch::) ; } From ec116a47fcd1edba68cdf41d3ae71d5ec2ec5140 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 12:56:10 +0200 Subject: [PATCH 65/89] use core's interfaces, splines --- cpp/splinepy/py/py_multi_patch.hpp | 164 ++++++++++++++--------------- splinepy/multipatch.py | 97 +++-------------- 2 files changed, 98 insertions(+), 163 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index d14f73cee..91d9eddab 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -2060,87 +2060,88 @@ class PyMultiPatch { if (interfaces_.size() > 0) { return interfaces_; } - - // now, first compute, then get - // first, centers. will either compute or return saved - - // get centers - auto sub_p_centers = SubPatchCenters(); - const int n_centers = sub_p_centers.shape(0); - const int dim = Dim(); - double* sub_p_centers_ptr = - static_cast(sub_p_centers.request().ptr); - // call uff - // create temporary arrays - DoubleVector metric(Dim(), 1.); - DoubleVector new_points(n_centers * dim); - IntVector new_point_masks(n_centers); - IntVector inverse(n_centers); - int n_newpoints{}; // out - - // temporary copied for raw input -> uff doesn't have `const` for simplest - // fortran compatibility - int n_centers_in{n_centers}, dim_in{dim}; - double tolerance_in{tolerance_}; - - uff::uff(sub_p_centers_ptr, - n_centers_in, - dim_in, - metric.data(), - tolerance_in, - true, - new_points.data(), - new_point_masks.data(), - n_newpoints, - inverse.data()); - - // create return - reassign interfaces_ - const int n_boundary = ParaDim() * 2; - interfaces_ = - py::array_t({static_cast(CorePatches().size()), n_boundary}); - int* interfaces_ptr = static_cast(interfaces.request().ptr); - - // sanity check? - if (static_cast(interfaces_.size()) != n_centers) { - splinepy::utils::PrintAndThrowError( - "Size mismatch between interfaces and sub_patch_centers."); - } - - // turn inverse to interfaces - auto inverse_to_interfaces = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - const auto& inv = inverse[i]; - if (inv < i) { - interfaces_ptr[i] = std::div(inv, n_boundary).quot; - interfaces_ptr[inv] = std::div(i, n_boundary).quot; - } else { - interfaces_ptr[i] = -1; + /* + // now, first compute, then get + // first, centers. will either compute or return saved + + // get centers + auto sub_p_centers = SubPatchCenters(); + const int n_centers = sub_p_centers.shape(0); + const int dim = Dim(); + double* sub_p_centers_ptr = + static_cast(sub_p_centers.request().ptr); + // call uff + // create temporary arrays + DoubleVector metric(Dim(), 1.); + DoubleVector new_points(n_centers * dim); + IntVector new_point_masks(n_centers); + IntVector inverse(n_centers); + int n_newpoints{}; // out + + // temporary copied for raw input -> uff doesn't have `const` for + simplest + // fortran compatibility + int n_centers_in{n_centers}, dim_in{dim}; + double tolerance_in{tolerance_}; + + uff::uff(sub_p_centers_ptr, + n_centers_in, + dim_in, + metric.data(), + tolerance_in, + true, + new_points.data(), + new_point_masks.data(), + n_newpoints, + inverse.data()); + + // create return - reassign interfaces_ + const int n_boundary = ParaDim() * 2; + interfaces_ = + py::array_t({static_cast(CorePatches().size()), + n_boundary}); int* interfaces_ptr = + static_cast(interfaces.request().ptr); + + // sanity check? + if (static_cast(interfaces_.size()) != n_centers) { + splinepy::utils::PrintAndThrowError( + "Size mismatch between interfaces and sub_patch_centers."); } - } - }; - - splinepy::utils::NThreadExecution(inverse_to_interfaces, - n_centers, - n_default_threads_); - - // one more sanity check - elements should not be referenced more than - // once. could do this with mutex, but probably won't gain much. this does - // not perform orientation check - // this one, we actually want 0 init - std::vector inv_counts(n_centers); - for (const auto& inv : inverse) { - if (++inv_counts[inv] > 2) { - splinepy::utils::PrintAndThrowError( - "Interface is invalid, found a subpatch center that overlaps more " - "than twice."); - } - } + // turn inverse to interfaces + auto inverse_to_interfaces = [&](int begin, int end) { + for (int i{begin}; i < end; ++i) { + const auto& inv = inverse[i]; + if (inv < i) { + interfaces_ptr[i] = std::div(inv, n_boundary).quot; + interfaces_ptr[inv] = std::div(i, n_boundary).quot; + } else { + interfaces_ptr[i] = -1; + } + } + }; + + splinepy::utils::NThreadExecution(inverse_to_interfaces, + n_centers, + n_default_threads_); + + // one more sanity check - elements should not be referenced more than + // once. could do this with mutex, but probably won't gain much. this + does + // not perform orientation check + // this one, we actually want 0 init + std::vector inv_counts(n_centers); + for (const auto& inv : inverse) { + if (++inv_counts[inv] > 2) { + splinepy::utils::PrintAndThrowError( + "Interface is invalid, found a subpatch center that overlaps + more " "than twice."); + } + } + */ // Original - TODO remove if above is fine - // interfaces_ = InterfacesFromBoundaryCenters( - // SubPatchCenters(n_default_threads_, same_parametric_bounds_), - // tolerance_, - // ParaDim()); + interfaces_ = + InterfacesFromBoundaryCenters(SubPatchCenters(), tolerance_, ParaDim()); return interfaces_; } @@ -2428,9 +2429,7 @@ class PyMultiPatch { field_list_ += fields; } - py::list GetFields() { - return field_list_; - } + py::list GetFields() { return field_list_; } }; inline void add_multi_patch(py::module& m) { @@ -2474,7 +2473,8 @@ inline void add_multi_patch(py::module& m) { klasse.def(py::init<>()) .def(py::init()) .def_readwrite("n_default_threads", &PyMultiPatch::n_default_threads_) - .def_readwrite("same_parametric_bounds", &PyMultiPatch::same_parametric_bounds_) + .def_readwrite("same_parametric_bounds", + &PyMultiPatch::same_parametric_bounds_) .def_readwrite("tolerance", &PyMultiPatch::tolerance_) .def("clear", &PyMultiPatch::Clear) .def_property_readonly("para_dim", &PyMultiPatch::ParaDim) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index a558b3b00..613877d4b 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -6,15 +6,12 @@ from splinepy import settings from splinepy._base import SplinepyBase -from splinepy.spline import Spline from splinepy.splinepy_core import ( PyMultiPatch, boundaries_from_continuity, boundary_centers, extract_all_boundary_splines, - interfaces_from_boundary_centers, ) -from splinepy.utils.data import enforce_contiguous class Multipatch(SplinepyBase, PyMultiPatch): @@ -95,40 +92,11 @@ def splines(self): """ - return self._spline_list + return self.patches @splines.setter def splines(self, list_of_splines): - # We need to start from scratch if new splines are added - self._init_members() - - if not isinstance(list_of_splines, list): - if issubclass(type(list_of_splines), Spline): - list_of_splines = list(list_of_splines) - else: - raise ValueError("Wrong input format") - - # Check if all entries in the list are splines - spline_para_dim = list_of_splines[0].para_dim - spline_dim = list_of_splines[0].dim - for spline in list_of_splines: - if not issubclass(type(spline), Spline): - raise ValueError( - "Only Splinepy-Spline types are allowed as list " "entries" - ) - if not spline.dim == spline_dim: - raise ValueError( - "Dimension mismatch between splines in list of splines" - ) - if not spline.para_dim == spline_para_dim: - raise ValueError( - "Parametric dimension mismatch between splines in list" - " of splines" - ) - # Updating the spline set will erase all precalculated data - self._boundaries = None - - self._spline_list = list_of_splines + self.patches = list_of_splines @property def interfaces(self): @@ -143,51 +111,14 @@ def interfaces(self): interfaces : np.ndarray inter-patch connectivitiy and boundaries """ - if self.splines is None: - raise ValueError("Connectivity set for unknown list of splines.") - - if self._interfaces is None: - self._logd("No interfaces available, calculating on the fly") - self.determine_interfaces() - return self._interfaces + # empty list as input will compute interfaces based on input + return super().interfaces([]) @interfaces.setter def interfaces(self, con): - # Performes some checks prior to assigning the interface connectivity - # to the multipatch object - if self.splines is None: - raise ValueError("Connectivity set for unknown list of splines.") - - # Check types - if not isinstance(con, np.ndarray) or (not len(con.shape)): - raise ValueError( - "Connectivity must be stored in a numpy 2D array." - ) - con = enforce_contiguous(con, np.int32) - - # One boundary for min and max for each parametric dimension - n_boundaries_per_spline = self.splines[0].para_dim * 2 - if not ( - (con.shape[1] == n_boundaries_per_spline) - and (con.shape[0] == len(self.splines)) - ): - raise ValueError( - "Connectivity size mismatch. Expected size is " - "n_patch x n_boundaries" - ) - - # If the multipatch is a boundary representation, than it must not - # contain any negative entries as lower dimensional objects must always - # be interconnected - if self._as_boundary: - if np.any(con < 0): - raise ValueError( - "Interfaces are not interconnected, but interconnection is" - " required for boundary representations" - ) - + """super() checks validity of input""" # Assignment - self._interfaces = con + super().interfaces(con) @property def boundaries(self): @@ -369,15 +300,19 @@ def determine_interfaces(self, tolerance=None): if tolerance is None: tolerance = settings.TOLERANCE - # Using the setter instead of the the member, all necessery - # checks will be performed - self.interfaces = interfaces_from_boundary_centers( - self.spline_boundary_centers, tolerance, self.para_dim - ) + # save previous default tol + previous_tol = self.tolerance + + # set given tolerance and compute interfaces + self.tolerance = tolerance + interfaces = self.interfaces self._logd("Successfully provided new interfaces using uff algorithm") - return self.interfaces + # set default tol back + self.tolerance = previous_tol + + return interfaces def boundary_from_function( self, From dd84b8b74502989f6d898a37b8407e3aab62aaa8 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 15:43:39 +0200 Subject: [PATCH 66/89] only single thread core-to-pyspline --- cpp/splinepy/py/py_multi_patch.hpp | 51 +++++------------------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 91d9eddab..3b3360dc5 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -95,50 +95,16 @@ ToCoreSplineVector(py::list pysplines, /// @param splist /// @param nthreads /// @return -inline py::list ToPySplineList(CoreSplineVector& splist, int nthreads) { +inline py::list ToPySplineList(CoreSplineVector& splist) { // prepare return obj const int n_splines = static_cast(splist.size()); - // temporary list to gather splines - std::vector lists_to_concat(nthreads); - // to return - py::list pyspline_list{}; - - // get start to end for each thread - const int chunk_size = std::div((n_splines + nthreads - 1), nthreads).quot; - IntVector interval; - interval.reserve(std::max(std::max(nthreads, n_splines), 1) + 1); - interval.emplace_back(0); - for (int i{1}; i < nthreads; ++i) { - interval.emplace_back(chunk_size * i); - } - interval.emplace_back(n_splines); - - // locally create and use __iadd__ to concat - auto to_pyspline_step = [&](int begin, int total_) { - const auto& start = interval[begin]; - const auto& end = interval[begin + 1]; - - // assign size - auto& local_list = lists_to_concat[begin]; - local_list = py::list(start - end); - - // loop chunk, concat - for (int i{start}; i < end; ++i) { - local_list[i] = std::make_shared(splist[i])->ToDerived(); - } - }; - - // exe - splinepy::utils::NThreadExecution(to_pyspline_step, - n_splines, - nthreads, - splinepy::utils::NThreadQueryType::Step); + py::list pyspline_list(n_splines); - // concat - for (auto& local_list : lists_to_concat) { - pyspline_list += local_list; + // cast - single threads, as we should respect GIL + for (int i{}; i < n_splines; ++i) { + pyspline_list[i] = std::make_shared(splist[i])->ToDerived(); } return pyspline_list; @@ -1561,6 +1527,7 @@ int AddBoundariesFromContinuity(const py::list& boundary_splines, // Provide auxiliary values const auto cpp_spline_list = ToCoreSplineVector(boundary_splines); + const int n_boundary_patches{static_cast(boundary_interfaces.shape(0))}; const int n_faces_per_boundary_patch{ static_cast(boundary_interfaces.shape(1))}; @@ -1956,8 +1923,7 @@ class PyMultiPatch { sub_multi_patch_ = std::make_shared(); // set both core and py splines - sub_multi_patch_->patches_ = - ToPySplineList(out_boundaries, n_default_threads_); + sub_multi_patch_->patches_ = ToPySplineList(out_boundaries); sub_multi_patch_->core_patches_ = std::move(out_boundaries); return sub_multi_patch_; @@ -2213,8 +2179,7 @@ class PyMultiPatch { // create return patch boundary_multi_patch_ = std::make_shared(); - boundary_multi_patch_->patches_ = - ToPySplineList(boundary_core_patches, n_default_threads_); + boundary_multi_patch_->patches_ = ToPySplineList(boundary_core_patches); boundary_multi_patch_->core_patches_ = std::move(boundary_core_patches); return boundary_multi_patch_; From f2e6a103958cd23a6e27febe956618405876de76 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 15:43:52 +0200 Subject: [PATCH 67/89] local import of to_derived --- cpp/splinepy/py/py_spline.hpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index 7de44dffa..54a580c7a 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -27,16 +27,6 @@ namespace py = pybind11; using namespace splinelib::sources; -#ifdef SPLINEPY_COMPILE_PYTHON -/// @brief python function splinepy.to_derived() -inline static const auto py_to_derived = - py::module_::import("splinepy").attr("to_derived"); -#else -// dummy function that will just return py::obj -inline py::object py_to_derived(py::object&& pyobj) { return pyobj; } - -#endif - template static bool CheckPyArrayShape(const py::array_t arr, const std::vector& shape, @@ -913,7 +903,10 @@ class PySpline { /// @brief returns current spline as package's derived spline types based on /// splinepy.settings.NAME_TO_TYPE /// @return - py::object ToDerived() { return py_to_derived(py::cast(this)); } + py::object ToDerived() { + const auto to_derived = py::module_::import("splinepy").attr("to_derived"); + return to_derived(py::cast(this)); + } }; inline void add_spline_pyclass(py::module& m) { From b991ca9db5cc0d2d656606cc491f7d4e6c7c4f40 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 15:44:05 +0200 Subject: [PATCH 68/89] update boundary_patches --- splinepy/multipatch.py | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 613877d4b..447bdb666 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -147,16 +147,15 @@ def boundaries(self): return boundary_list - def boundary_patches(self, use_saved=True, nthreads=None): + def boundary_patches(self, nthreads=None): """Extract all boundary patches of a given Multipatch system as splines Parameters ---------- - use_saved : bool - Reuse an already computed patch system nthreads : int Number of threads to be used for extraction, defaults to settings.NTHREADS + Returns ------- boundary_patches : Multipatch @@ -164,19 +163,16 @@ def boundary_patches(self, use_saved=True, nthreads=None): """ if nthreads is None: nthreads = settings.NTHREADS - if (not use_saved) or (self._boundary_splines is None): - self._logd("Determining boundary spline patches") - patches = extract_all_boundary_splines( - self.splines, self.interfaces, nthreads - ) - self._boundary_splines = Multipatch( - splines=[ - settings.NAME_TO_TYPE[p.name](spline=p) for p in patches - ], - as_boundary=True, - ) - return self._boundary_splines + # apply nthreads + previous_nthreads = self.n_default_threads + self.n_default_threads = nthreads + + b_patches = super().boundary_multi_patch() + + self.n_default_threads = previous_nthreads + + return b_patches def set_boundary( self, @@ -393,7 +389,6 @@ def boundary_from_function( def boundaries_from_continuity( self, - use_saved=True, nthreads=None, tolerance=None, ): @@ -404,8 +399,6 @@ def boundaries_from_continuity( Parameters ---------- - use_saved : bool - Allow function to reuse precomputed values nthreads : int Number of threads to be used to determine boundaries and aux values tolerance : double @@ -419,15 +412,13 @@ def boundaries_from_continuity( tolerance = settings.TOLERANCE if nthreads is None: nthreads = settings.NTHREADS - b_patches = self.boundary_patches( - use_saved=use_saved, nthreads=nthreads - ) + b_patches = self.boundary_patches(nthreads=nthreads) # Pass information to c++ backend self._logd("Start propagation of information...") n_new_boundaries = boundaries_from_continuity( - b_patches.splines, - b_patches.interfaces, + b_patches.patches, + b_patches.interfaces([]), self.interfaces, tolerance, nthreads, From 7a072312fd061a1c6190a097979dad05aba3c547 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 15:47:42 +0200 Subject: [PATCH 69/89] update spline centers --- splinepy/multipatch.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 447bdb666..e04c3efe0 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -6,12 +6,7 @@ from splinepy import settings from splinepy._base import SplinepyBase -from splinepy.splinepy_core import ( - PyMultiPatch, - boundaries_from_continuity, - boundary_centers, - extract_all_boundary_splines, -) +from splinepy.splinepy_core import PyMultiPatch, boundaries_from_continuity class Multipatch(SplinepyBase, PyMultiPatch): @@ -270,13 +265,7 @@ def spline_boundary_centers(self): spline_centers : np.ndarray coordinates of the patch-boundary centers """ - # If spline list is empty will throw excetion - if self._spline_boundary_centers is None: - self._spline_boundary_centers = np.vstack( - [boundary_centers(s) for s in self.splines] - ) - - return self._spline_boundary_centers + return self.sub_patch_centers() def determine_interfaces(self, tolerance=None): """ From fc4c6d8e8240e154132ec708bf0173026d504a4c Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 15:52:19 +0200 Subject: [PATCH 70/89] remove splinelist --- cpp/splinepy/py/CMakeLists.txt | 1 - cpp/splinepy/py/init/spline_list.cpp | 7 - cpp/splinepy/py/py_multi_patch.hpp | 2 +- cpp/splinepy/py/py_spline_list.hpp | 808 --------------------------- cpp/splinepy/py/splinepy_core.cpp | 4 - 5 files changed, 1 insertion(+), 821 deletions(-) delete mode 100644 cpp/splinepy/py/init/spline_list.cpp delete mode 100644 cpp/splinepy/py/py_spline_list.hpp diff --git a/cpp/splinepy/py/CMakeLists.txt b/cpp/splinepy/py/CMakeLists.txt index 92083c565..d5be39cd1 100644 --- a/cpp/splinepy/py/CMakeLists.txt +++ b/cpp/splinepy/py/CMakeLists.txt @@ -7,7 +7,6 @@ set(PYSPLINEPY_SRCS init/multi_patch.cpp init/reader.cpp init/spline_extensions.cpp - init/spline_list.cpp init/uffpy.cpp splinepy_core.cpp) diff --git a/cpp/splinepy/py/init/spline_list.cpp b/cpp/splinepy/py/init/spline_list.cpp deleted file mode 100644 index 3430be96c..000000000 --- a/cpp/splinepy/py/init/spline_list.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -namespace splinepy::py::init { -namespace py = pybind11; -void init_spline_list(py::module_& m) { add_spline_list_pyclass(m); } - -} // namespace splinepy::py::init diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 3b3360dc5..f3d867ff6 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -18,7 +18,7 @@ // #include -#include +#include #include namespace splinepy::py { diff --git a/cpp/splinepy/py/py_spline_list.hpp b/cpp/splinepy/py/py_spline_list.hpp deleted file mode 100644 index 7c4b23be5..000000000 --- a/cpp/splinepy/py/py_spline_list.hpp +++ /dev/null @@ -1,808 +0,0 @@ -#pragma once - -#include -#include - -// pybind -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace splinepy::py { - -namespace py = pybind11; - -// alias -using CoreSplineVector = - std::vector>; -using IntVector = splinepy::utils::DefaultInitializationVector; -using IntVectorVector = splinepy::utils::DefaultInitializationVector; -using DoubleVector = splinepy::utils::DefaultInitializationVector; - -/// Internal use only -/// Extract CoreSpline_s from list of PySplines -inline std::vector -ListOfPySplinesToVectorOfCoreSplines(py::list pysplines, - const int nthreads = 1) { - // prepare return obj - const int n_splines = static_cast(pysplines.size()); - std::vector core_splines(n_splines); - - auto to_core = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - core_splines[i] = - pysplines[i].template cast>()->Core(); - } - }; - splinepy::utils::NThreadExecution(to_core, n_splines, nthreads); - - return core_splines; -} - -/// TODO: move ListOfPySplinesToVectorOfCoreSplines here and rename -inline py::list CoreSplineVectorToPySplineList(CoreSplineVector& core_patches_, - int nthreads) { - // prepare return obj - const int n_splines = static_cast(core_patches_.size()); - py::list pyspline_list(n_splines); - - auto to_pyspline = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - pyspline_list[i] = std::make_shared(core_patches_[i]); - } - }; - - // multi thread execution causes segfault. - // until we find a better solution, do single exe - nthreads = 1; - - splinepy::utils::NThreadExecution(to_pyspline, n_splines, nthreads); - - return pyspline_list; -} - -/// @brief raises if elements' para_dim and dims aren't equal -/// @param core_patches_ -/// @param nthreads -inline void -RaiseIfElementsHaveUnequalParaDimOrDim(const CoreSplineVector& core_patches_, - const int nthreads) { - // use first spline as guide line - const int para_dim = core_patches_[0]->SplinepyParaDim(); - const int dim = core_patches_[0]->SplinepyDim(); - - std::vector mismatches(nthreads); - - auto check_dims_step = [&](int begin, int total_) { - for (int i{begin}; i < total_; i += nthreads) { - if (core_patches_[i]->SplinepyParaDim() != para_dim - || core_patches_[i]->SplinepyDim() != dim) { - mismatches[begin].push_back(i); - } - } - }; - - splinepy::utils::NThreadExecution(check_dims_step, - static_cast(core_patches_.size()), - nthreads, - splinepy::utils::NThreadQueryType::Step); - // prepare error prints - IntVector all_mismatches{}; - for (const auto& m : mismatches) { - const auto m_size = m.size(); - if (m_size != 0) { - all_mismatches.reserve(all_mismatches.size() + m_size); - all_mismatches.insert(all_mismatches.end(), m.begin(), m.end()); - } - } - - if (all_mismatches.size() == 0) { - return; - } - - std::string mismatch_ids{}; - for (const auto& am : all_mismatches) { - mismatch_ids += std::to_string(am); - mismatch_ids += ", "; - } - splinepy::utils::PrintAndThrowError( - "Splines in following entries has mismatching para_dim or dim compared " - "to the first entry (", - mismatch_ids, - ")"); -} - -inline void ListRaiseIfElementsHaveUnequalParaDimOrDim(py::list& core_patches_, - const int nthreads) { - const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); - - RaiseIfElementsHaveUnequalParaDimOrDim(core_vector, nthreads); -} - -/// evaluates splines of same para_dim and dim. -inline py::array_t -CoreSplineVectorEvaluate(const CoreSplineVector& core_patches_, - const py::array_t& queries, - const int nthreads, - const bool check_dims) { - // use first spline as dimension guide line - const int para_dim = core_patches_[0]->SplinepyParaDim(); - const int dim = core_patches_[0]->SplinepyDim(); - - // query dim check - CheckPyArrayShape(queries, {-1, para_dim}, true); - - // check dims if wanted. Else it will assume that every spline has same - // dimension as the first entry's - if (check_dims) { - RaiseIfElementsHaveUnequalParaDimOrDim(core_patches_, nthreads); - } - - // prepare input and output - double* queries_ptr = static_cast(queries.request().ptr); - const int n_splines = core_patches_.size(); - const int n_queries = queries.shape(0); - const int n_total = n_splines * n_queries; - py::array_t evaluated({n_total, dim}); - double* evaluated_ptr = static_cast(evaluated.request().ptr); - - // each thread evaluates similar amount of queries from each spline - auto evaluate_step = [&](int begin, int total_) { - for (int i{begin}; i < total_; i += nthreads) { - const auto [i_spline, i_query] = std::div(i, n_queries); - core_patches_[i_spline]->SplinepyEvaluate( - &queries_ptr[i_query * para_dim], - &evaluated_ptr[(i_spline * n_queries + i_query) * dim]); - } - }; - - // exe - splinepy::utils::NThreadExecution(evaluate_step, - n_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - - return evaluated; -} - -inline py::array_t ListEvaluate(py::list& core_patches_, - const py::array_t& queries, - const int nthreads, - const bool check_dims) { - const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); - - return CoreSplineVectorEvaluate(core_vector, queries, nthreads, check_dims); -} - -inline py::array_t -CoreSplineVectorDerivative(const CoreSplineVector& core_patches_, - const py::array_t& queries, - const py::array_t& orders, - const int nthreads) { - return py::array_t(); -} - -inline py::array_t ListDerivative(py::list& core_patches_, - const py::array_t& queries, - const py::array_t& orders, - const int nthreads) { - const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); - - return CoreSplineVectorDerivative(core_vector, queries, orders, nthreads); -} - -/// Samples equal resoltions for each para dim -inline py::array_t -CoreSplineVectorSample(const CoreSplineVector& core_patches_, - const int resolution, - const int nthreads, - const bool same_parametric_bounds, - const bool check_dims) { - // use first spline as dimension guide line - const auto& first_spline = *core_patches_[0]; - const int para_dim = first_spline.SplinepyParaDim(); - const int dim = first_spline.SplinepyDim(); - - // dim check first - if (check_dims) { - RaiseIfElementsHaveUnequalParaDimOrDim(core_patches_, nthreads); - } - - // n_queries, and n_splines; - int n_queries{1}; - const int n_splines = core_patches_.size(); - - // prepare resolutions - IntVector resolutions_vector(para_dim); - int* resolutions = resolutions_vector.data(); - for (int i{}; i < para_dim; ++i) { - resolutions[i] = resolution; - n_queries *= resolution; - } - - // prepare input / output - const int n_total = n_splines * n_queries; - py::array_t sampled({n_total, dim}); - double* sampled_ptr = static_cast(sampled.request().ptr); - - // if you know all the queries have same parametric bounds - // you don't need to re-compute queries - if (same_parametric_bounds) { - - // get para bounds - DoubleVector para_bounds_vector(2 * para_dim); - double* para_bounds = para_bounds_vector.data(); - first_spline.SplinepyParametricBounds(para_bounds); - - // prepare queries - DoubleVector queries_vector(n_queries * para_dim); - double* queries = queries_vector.data(); - - // use grid point generator to fill queries - splinepy::utils::CStyleArrayPointerGridPoints gp_generator(para_dim, - para_bounds, - resolutions); - gp_generator.Fill(queries); - - // create lambda for nthread exe - auto sample_same_bounds_step = [&](int begin, int total_) { - for (int i{begin}; i < total_; i += nthreads) { - const auto [i_spline, i_query] = std::div(i, n_queries); - core_patches_[i_spline]->SplinepyEvaluate( - &queries[i_query * para_dim], - &sampled_ptr[(i_spline * n_queries + i_query) * dim]); - } - }; - - splinepy::utils::NThreadExecution(sample_same_bounds_step, - n_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - - } else { - // here, we will execute 2 times: - // first, to create grid point helpers for each spline - // second, to sample - - // create a container to hold grid point helper. - splinepy::utils::DefaultInitializationVector< - splinepy::utils::CStyleArrayPointerGridPoints> - grid_points(n_splines); - - // create grid_points - auto create_grid_points = [&](int begin, int end) { - DoubleVector para_bounds_vector(2 * para_dim); - double* para_bounds = para_bounds_vector.data(); - - for (int i{begin}; i < end; ++i) { - // get para_bounds - core_patches_[i]->SplinepyParametricBounds(para_bounds); - // setup grid points helper - grid_points[i].SetUp(para_dim, para_bounds, resolutions); - } - }; - - // pre compute entries -> this one is a chunk query - splinepy::utils::NThreadExecution(create_grid_points, n_splines, nthreads); - - // similar to the one with same_parametric_bounds, except it computes query - // on the fly - auto sample_step = [&](int begin, int total_) { - // each thread needs just one query array - DoubleVector thread_query_vector(para_dim); - double* thread_query = thread_query_vector.data(); - - for (int i{begin}; i < total_; i += nthreads) { - const auto [i_spline, i_query] = std::div(i, n_queries); - const auto& gp_helper = grid_points[i_spline]; - gp_helper.IdToGridPoint(i_query, thread_query); - core_patches_[i_spline]->SplinepyEvaluate( - thread_query, - &sampled_ptr[(i_spline * n_queries + i_query) * dim]); - } - }; - - // exe - this one is step - splinepy::utils::NThreadExecution(sample_step, - n_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - } - - return sampled; -} - -/// Samples equal resoltions for each para dim -inline py::array_t ListSample(py::list& core_patches_, - const int resolution, - const int nthreads, - const bool same_parametric_bounds, - const bool check_dims) { - const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); - - return CoreSplineVectorSample(core_vector, - resolution, - nthreads, - same_parametric_bounds, - check_dims); -} - -// extracts boundary splines from core_patches_. -inline CoreSplineVector -CoreSplineVectorExtractBoundaries(const CoreSplineVector& core_patches_, - const int nthreads, - const bool same_para_dims) { - const int n_splines = core_patches_.size(); - // to accumulate - int n_boundaries{}; - // gather offsets for boundary add one last so that we can always find out - // n_boundary for each spline - IntVector boundary_offsets{}; - boundary_offsets.reserve(n_splines + 1); - - // in case of same para dim, it'd be equidistance - int offset{}; // offset counter - if (same_para_dims) { - // compute n_boundary - const int n_boundary = core_patches_[0]->SplinepyParaDim() * 2; - - // fill offset - - for (int i{}; i < n_splines; ++i) { - boundary_offsets.push_back(offset); - offset += n_boundary; - } - - } else { - // for un-equal boundary sizes, we lookup each one of them - for (int i{}; i < n_splines; ++i) { - boundary_offsets.push_back(offset); - offset += core_patches_[i]->SplinepyParaDim() * 2; - } - } - // current offset value should equal to total boundary count - boundary_offsets.push_back(offset); - n_boundaries = offset; - - // prepare output - CoreSplineVector out_boundaries(n_boundaries); - - // prepare lambda - auto boundary_extract = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - // start of the offset - const auto& this_offset = boundary_offsets[i]; - // end of the offset - const auto& next_offset = boundary_offsets[i + 1]; - // get spline - auto& spline_i = *core_patches_[i]; - for (int j{}; j < next_offset - this_offset; ++j) { - out_boundaries[this_offset + j] = spline_i.SplinepyExtractBoundary(j); - } - } - }; - - splinepy::utils::NThreadExecution(boundary_extract, n_splines, nthreads); - - return out_boundaries; -} - -inline py::list ListExtractBoundaries(py::list& core_patches_, - const int nthreads, - const bool same_para_dims) { - const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); - auto boundaries = - CoreSplineVectorExtractBoundaries(core_vector, nthreads, same_para_dims); - - return CoreSplineVectorToPySplineList(boundaries, nthreads); -} - -// computes boundary centers for splines of same para_dim and dim -inline py::array_t -CoreSplineVectorBoundaryCenters(const CoreSplineVector& core_patches_, - const int nthreads, - const bool same_parametric_bounds, - const bool check_dims) { - // dim check first - if (check_dims) { - RaiseIfElementsHaveUnequalParaDimOrDim(core_patches_, nthreads); - } - - // prepare output - // from here we assume that all the splines have the same para_dim and dim - const int n_splines = core_patches_.size(); - const int para_dim = core_patches_[0]->SplinepyParaDim(); - const int dim = core_patches_[0]->SplinepyDim(); - const int n_queries = 2 * para_dim; - const int n_total = n_queries * n_splines; - py::array_t boundary_centers({n_total, dim}); - double* boundary_centers_ptr = - static_cast(boundary_centers.request().ptr); - - // pre-compute boundary centers - DoubleVector para_bounds; - double* para_bounds_ptr; - if (!same_parametric_bounds) { - para_bounds.resize(n_total * para_dim); - para_bounds_ptr = para_bounds.data(); - const int stride = n_queries * para_dim; - - auto calc_para_bounds = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - splinepy::splines::helpers::ScalarTypeBoundaryCenters( - *core_patches_[i], - ¶_bounds_ptr[stride * i]); - } - }; - - // exe - splinepy::utils::NThreadExecution(calc_para_bounds, n_splines, nthreads); - } - - auto calc_boundary_centers_step = [&](int begin, int total_) { - // each thread needs one query - DoubleVector queries_vector; /* unused if same_parametric_bounds=true*/ - double* queries; - - // pre compute boundary centers if para bounds are the same - if (same_parametric_bounds) { - queries_vector.resize(2 * para_dim * para_dim); - queries = queries_vector.data(); - splinepy::splines::helpers::ScalarTypeBoundaryCenters(*core_patches_[0], - queries); - } - - for (int i{begin}; i < total_; i += nthreads) { - const auto [i_spline, i_query] = std::div(i, n_queries); - - // get ptr start - if (!same_parametric_bounds) { - queries = ¶_bounds_ptr[i_spline * n_queries * para_dim]; - } - - // eval - core_patches_[i_spline]->SplinepyEvaluate( - &queries[i_query * para_dim], - &boundary_centers_ptr[(i_spline * n_queries + i_query) * dim]); - } - }; - - splinepy::utils::NThreadExecution(calc_boundary_centers_step, - n_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - - return boundary_centers; -} - -inline py::array_t -ListBoundaryCenters(py::list& core_patches_, - const int nthreads, - const bool same_parametric_bounds, - const bool check_dims) { - const auto core_vector = - ListOfPySplinesToVectorOfCoreSplines(core_patches_, nthreads); - return CoreSplineVectorBoundaryCenters(core_vector, - nthreads, - same_parametric_bounds, - check_dims); -} - -/// @brief NThread Compose. Invalid inputs will raise runtime_error on the fly -/// @param outer_splines -/// @param inner_splines -/// @param cartesian_product If true, composes each inner splines to all outer -/// splines. Else, outer and inner splines must have same len. -/// @param nthreads -/// @return -inline CoreSplineVector -CoreSplineVectorCompose(const CoreSplineVector& outer_splines, - const CoreSplineVector& inner_splines, - const bool cartesian_product, - const int nthreads) { - - // get size - const int n_outer = outer_splines.size(); - const int n_inner = inner_splines.size(); - - // create output - CoreSplineVector composed_splines; - - // check if size matchs - if (!cartesian_product) { - if (n_outer != n_inner) { - splinepy::utils::PrintAndThrowError( - "Length mismatch of outer_splines (", - n_outer, - ") and inner_splines (", - n_inner, - "). To compose each inner splines to all outer splines, please set " - "cartesian_product=True."); - } - - // acquisition of output space - composed_splines.resize(n_outer); - - auto compose = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - composed_splines[i] = - outer_splines[i]->SplinepyCompose(inner_splines[i]); - } - }; - - splinepy::utils::NThreadExecution(compose, n_outer, nthreads); - - } else { - const int n_total = n_outer * n_inner; - composed_splines.resize(n_total); - - auto compose_step = [&](int begin, int total_) { - for (int i{begin}; i < total_; ++i) { - const auto [i_outer, i_inner] = std::div(i, n_inner); - composed_splines[i] = - outer_splines[i_outer]->SplinepyCompose(inner_splines[i_inner]); - } - }; - splinepy::utils::NThreadExecution(compose_step, - n_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - } - - return composed_splines; -} - -inline py::list ListCompose(py::list& outer_splines, - py::list& inner_splines, - const bool cartesian_product, - const int nthreads) { - const auto core_outer = - ListOfPySplinesToVectorOfCoreSplines(outer_splines, nthreads); - const auto core_inner = - ListOfPySplinesToVectorOfCoreSplines(inner_splines, nthreads); - - auto composed_cores = CoreSplineVectorCompose(core_outer, - core_inner, - cartesian_product, - nthreads); - - return CoreSplineVectorToPySplineList(composed_cores, nthreads); -} - -/// @brief NThread composition derivative. same query options as ListCompose() -/// @param outer_splines -/// @param inner_splines -/// @param inner_derivative -/// @param cartesian_product -/// @param nthreads -/// @return -inline CoreSplineVector -CoreSplineVectorCompositionDerivative(const CoreSplineVector& outer_splines, - const CoreSplineVector& inner_splines, - const CoreSplineVector& inner_derivatives, - const bool cartesian_product, - const int nthreads) { - // get size - const int n_outer = outer_splines.size(); - const int n_inner = inner_splines.size(); - const int n_inner_der = inner_derivatives.size(); - - // number of queries equals to para_dim - const int& n_queries = inner_splines[0]->SplinepyParaDim(); - - // create output - // create derivative spline container in case of cartesian product - CoreSplineVector composition_derivatives, outer_spline_derivatives, - inner_derivatives_single_dims; - - // check if size matchs - // 1. inner and inner_der - if (n_inner != n_inner_der) { - splinepy::utils::PrintAndThrowError( - "Length mismatch - inner_splines (", - n_inner, - ") and inner_derivatives (", - n_inner_der, - ") should have same length. To compose each inner splines to all outer " - "splines, please set cartesian_product=True."); - } - // 2. outer and inner incase of not - cartesian product. - if (!cartesian_product) { - if (n_outer != n_inner) { - splinepy::utils::PrintAndThrowError( - "Length mismatch of outer_splines (", - n_outer, - ") and inner_splines (", - n_inner, - "). To compose each inner splines to all outer splines, please set " - "cartesian_product=True."); - } - - // acquisition of output space - composition_derivatives.resize(n_outer); - - // exe - auto calc_composition_derivatives = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - composition_derivatives[i] = - outer_splines[i]->SplinepyCompositionDerivative( - inner_splines[i], - inner_derivatives[i]); - } - }; - - splinepy::utils::NThreadExecution(calc_composition_derivatives, - n_outer, - nthreads); - - } else { - const int n_total = n_outer * n_inner; - composition_derivatives.resize(n_total); - - const int n_outer_precompute = n_outer * n_queries; - const int n_inner_precompute = n_inner * n_queries; - - // resize vectors to hold der splines of outer splines and der splines - // their para_dim and dim should match. Otherwise, it will raise. - outer_spline_derivatives.resize(n_outer_precompute); - inner_derivatives_single_dims.resize(n_inner_precompute); - - // make query arrays - IntVectorVector order_queries(n_queries); - for (int i{}; i < n_queries; ++i) { - auto& order_query = order_queries[i]; - order_query.resize(n_queries, 0); - order_query[i] = 1; - } - - // let's fill der splines and extracted splines - auto calc_der_splines_and_extract_dim = [&](int begin, int total_) { - for (int i{begin}; i < total_; i += nthreads) { - const auto [i_spline, i_query] = std::div(i, n_queries); - - const int offset = i_spline * n_queries; - // fill if outer is still in range - if (i_spline < n_outer) { - outer_spline_derivatives[offset + i_query] = - outer_splines[i_spline]->SplinepyDerivativeSpline( - order_queries[i_query].data()); - } - - if (i_spline < n_inner) { - inner_derivatives_single_dims[offset + i_query] = - inner_derivatives[i_spline]->SplinepyExtractDim(i_query); - } - } - }; - - // precompute - const int precompute_total = - std::max(n_outer_precompute, n_inner_precompute); - splinepy::utils::NThreadExecution(calc_der_splines_and_extract_dim, - precompute_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - // now, composition der - auto calc_composition_derivatives_step = [&](int begin, int total_) { - for (int i{begin}; i < total_; i += nthreads) { - const auto [i_outer, i_inner] = std::div(i, n_inner); - // frequently used core - const auto& inner_core = inner_splines[i_inner]; - - // this one needs a loop - // create - auto this_comp_der = - outer_spline_derivatives[i_outer * n_queries] - ->SplinepyCompose(inner_core) - ->SplinepyMultiply( - inner_derivatives_single_dims[i_inner * n_queries]); - - // add - for (int j{1}; j < n_queries; ++j) { - this_comp_der = this_comp_der->SplinepyAdd( - outer_spline_derivatives[i_outer * n_queries + j] - ->SplinepyCompose(inner_core) - ->SplinepyMultiply( - inner_derivatives_single_dims[i_inner * n_queries + j])); - } - // now fill - composition_derivatives[i] = this_comp_der; - } - }; - - splinepy::utils::NThreadExecution(calc_composition_derivatives_step, - n_total, - nthreads, - splinepy::utils::NThreadQueryType::Step); - } - - return composition_derivatives; -} - -inline py::list ListCompositionDerivative(py::list& outer_splines, - py::list& inner_splines, - py::list& inner_derivatives, - const bool cartesian_product, - const int nthreads) { - const auto core_outer = - ListOfPySplinesToVectorOfCoreSplines(outer_splines, nthreads); - const auto core_inner = - ListOfPySplinesToVectorOfCoreSplines(inner_splines, nthreads); - const auto core_inner_derivatives = - ListOfPySplinesToVectorOfCoreSplines(inner_derivatives, nthreads); - - auto core_com_der = - CoreSplineVectorCompositionDerivative(core_outer, - core_inner, - core_inner_derivatives, - cartesian_product, - nthreads); - - return CoreSplineVectorToPySplineList(core_com_der, nthreads); -} - -/// bind vector of PySpline and add some deprecated cpp functions that maybe -/// nice to have -inline void add_spline_list_pyclass(py::module& m) { - - auto list_module = m.def_submodule( - "lists", - "Module for list based queries with multithreading capabilities. Please " - "make sure splines don't have any inplace modifications."); - - list_module - .def("raise_dim_mismatch", - &ListRaiseIfElementsHaveUnequalParaDimOrDim, - py::arg("spline_list"), - py::arg("nthreads")) - .def("evaluate", - &ListEvaluate, - py::arg("spline_list"), - py::arg("queries"), - py::arg("nthreads"), - py::arg("check_dims")) - .def("sample", - &ListSample, - py::arg("spline_list"), - py::arg("resolution"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")) - .def("extract_boundaries", - &ListExtractBoundaries, - py::arg("spline_list"), - py::arg("nthreads"), - py::arg("same_para_dims")) - .def("boundary_centers", - &ListBoundaryCenters, - py::arg("spline_list"), - py::arg("nthreads"), - py::arg("same_parametric_bounds"), - py::arg("check_dims")) - .def("compose", - &ListCompose, - py::arg("outer_splines"), - py::arg("inner_splines"), - py::arg("cartesian_product"), - py::arg("nthreads")) - .def("composition_derivative", - &ListCompositionDerivative, - py::arg("outer_splines"), - py::arg("inner_splines"), - py::arg("inner_derivatives"), - py::arg("cartesian_product"), - py::arg("nthreads")); -} - -} // namespace splinepy::py diff --git a/cpp/splinepy/py/splinepy_core.cpp b/cpp/splinepy/py/splinepy_core.cpp index d095146a0..a37c17296 100644 --- a/cpp/splinepy/py/splinepy_core.cpp +++ b/cpp/splinepy/py/splinepy_core.cpp @@ -14,9 +14,6 @@ void init_core_spline(py::module_&); // Extensions void init_spline_extensions(py::module_& m); -// SplineList -void init_spline_list(py::module_& m); - // Reader void init_reader(py::module_&); @@ -42,7 +39,6 @@ PYBIND11_MODULE(splinepy_core, m) { splinepy::py::init::init_coordinate_references(m); splinepy::py::init::init_core_spline(m); splinepy::py::init::init_spline_extensions(m); - splinepy::py::init::init_spline_list(m); splinepy::py::init::init_reader(m); splinepy::py::init::init_exporter(m); splinepy::py::init::init_fitting(m); From 23a985dababe31702bcc80a44a72aca9daa97d04 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 13 Jun 2023 06:33:14 +0200 Subject: [PATCH 71/89] Apply suggestions: clean up and fix --- cpp/splinepy/py/py_multi_patch.hpp | 85 ++-------------------------- cpp/splinepy/splines/null_spline.hpp | 2 +- tests/common.py | 5 -- 3 files changed, 5 insertions(+), 87 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index f3d867ff6..fa475e382 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1826,13 +1826,13 @@ class PyMultiPatch { + " patches."; } - // could overload, but it won't be clear for pybind - + /// could overload, but it won't be clear for pybind /// @brief default patches setter, exposed to python /// @param patches void SetPatchesDefault(py::list& patches) { SetPatchesNThreads(patches, n_default_threads_); } + /// @brief patches setter with nthreads /// @param patches /// @param nthreads @@ -1855,6 +1855,7 @@ class PyMultiPatch { {}, nthreads); } + /// @brief patches setter for field patches that may contain null splines. /// @param patches /// @param nthreads @@ -2026,86 +2027,8 @@ class PyMultiPatch { if (interfaces_.size() > 0) { return interfaces_; } - /* - // now, first compute, then get - // first, centers. will either compute or return saved - - // get centers - auto sub_p_centers = SubPatchCenters(); - const int n_centers = sub_p_centers.shape(0); - const int dim = Dim(); - double* sub_p_centers_ptr = - static_cast(sub_p_centers.request().ptr); - // call uff - // create temporary arrays - DoubleVector metric(Dim(), 1.); - DoubleVector new_points(n_centers * dim); - IntVector new_point_masks(n_centers); - IntVector inverse(n_centers); - int n_newpoints{}; // out - - // temporary copied for raw input -> uff doesn't have `const` for - simplest - // fortran compatibility - int n_centers_in{n_centers}, dim_in{dim}; - double tolerance_in{tolerance_}; - - uff::uff(sub_p_centers_ptr, - n_centers_in, - dim_in, - metric.data(), - tolerance_in, - true, - new_points.data(), - new_point_masks.data(), - n_newpoints, - inverse.data()); - - // create return - reassign interfaces_ - const int n_boundary = ParaDim() * 2; - interfaces_ = - py::array_t({static_cast(CorePatches().size()), - n_boundary}); int* interfaces_ptr = - static_cast(interfaces.request().ptr); - - // sanity check? - if (static_cast(interfaces_.size()) != n_centers) { - splinepy::utils::PrintAndThrowError( - "Size mismatch between interfaces and sub_patch_centers."); - } - // turn inverse to interfaces - auto inverse_to_interfaces = [&](int begin, int end) { - for (int i{begin}; i < end; ++i) { - const auto& inv = inverse[i]; - if (inv < i) { - interfaces_ptr[i] = std::div(inv, n_boundary).quot; - interfaces_ptr[inv] = std::div(i, n_boundary).quot; - } else { - interfaces_ptr[i] = -1; - } - } - }; - - splinepy::utils::NThreadExecution(inverse_to_interfaces, - n_centers, - n_default_threads_); - - // one more sanity check - elements should not be referenced more than - // once. could do this with mutex, but probably won't gain much. this - does - // not perform orientation check - // this one, we actually want 0 init - std::vector inv_counts(n_centers); - for (const auto& inv : inverse) { - if (++inv_counts[inv] > 2) { - splinepy::utils::PrintAndThrowError( - "Interface is invalid, found a subpatch center that overlaps - more " "than twice."); - } - } - */ - // Original - TODO remove if above is fine + // here's compute interfaces_ = InterfacesFromBoundaryCenters(SubPatchCenters(), tolerance_, ParaDim()); diff --git a/cpp/splinepy/splines/null_spline.hpp b/cpp/splinepy/splines/null_spline.hpp index de10ac995..2c315f279 100644 --- a/cpp/splinepy/splines/null_spline.hpp +++ b/cpp/splinepy/splines/null_spline.hpp @@ -75,7 +75,7 @@ class NullSpline : public splinepy::splines::SplinepyBase { /// Parametric AABB. fills zeros. this enables Sample() interface virtual void SplinepyParametricBounds(double* para_bounds) const { - std::fill_n(para_bounds, para_dim_, 0.0); + std::fill_n(para_bounds, para_dim_ * 2, 0.0); } /// required to support boundary only evaluations of multi patch field diff --git a/tests/common.py b/tests/common.py index 4f203e0bf..13db5f0a2 100644 --- a/tests/common.py +++ b/tests/common.py @@ -229,11 +229,6 @@ def to_tmpf(tmpd): return os.path.join(tmpd, "nqv248p90") -def to_derived(spline): - """initialize to derived types in python""" - return splinepy.settings.NAME_TO_TYPE[spline.name](spline=spline) - - def are_splines_equal(a, b): """returns True if Splines are equivalent""" if not a.whatami == b.whatami: From 8e46c06a4d173940940050dabb5dc68f5e9687c1 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 14 Jun 2023 18:41:42 +0200 Subject: [PATCH 72/89] add move ctor --- cpp/splinepy/py/py_multi_patch.hpp | 10 ++++++++++ cpp/splinepy/py/py_spline.hpp | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index fa475e382..6df9b916e 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1762,6 +1762,9 @@ class PyMultiPatch { /// ctor PyMultiPatch() = default; + /// move ctor + PyMultiPatch(PyMultiPatch&& other) noexcept = default; + /// list (iterable) init -> pybind will cast to list for us if needed PyMultiPatch(py::list& patches, const int n_default_threads = 1, @@ -1788,6 +1791,12 @@ class PyMultiPatch { dim_if_none); } + /// @brief given other shared pointer, tries to steal the contents with move + /// ctor. Expected use is only for to_derived call. + /// @param other + PyMultiPatch(std::shared_ptr& other) + : PyMultiPatch(std::move(*other)) {} + CorePatches_& CorePatches() { if (core_patches_.size() == 0) { splinepy::utils::PrintAndThrowError("No splines/patches set"); @@ -1804,6 +1813,7 @@ class PyMultiPatch { interfaces_ = py::array_t(); boundary_ids_ = py::array_t(); sub_patch_centers_ = py::array_t(); + field_list_ = py::list(); field_multi_patches_ = {}; } diff --git a/cpp/splinepy/py/py_spline.hpp b/cpp/splinepy/py/py_spline.hpp index 54a580c7a..713d5eedd 100644 --- a/cpp/splinepy/py/py_spline.hpp +++ b/cpp/splinepy/py/py_spline.hpp @@ -989,7 +989,6 @@ inline void add_spline_pyclass(py::module& m) { py::arg("tolerance")) .def("coordinate_references", &splinepy::py::PySpline::CoordinateReferences) - .def("to_derived", &splinepy::py::PySpline::ToDerived) .def(py::pickle( [](splinepy::py::PySpline& spl) { return py::make_tuple(spl.CurrentCoreProperties(), spl.data_); From dc73b10c9067c3c59083c934295a211e12b22baf Mon Sep 17 00:00:00 2001 From: jzwar Date: Tue, 28 Mar 2023 10:28:36 +0200 Subject: [PATCH 73/89] Add Fields to Multipatch --- splinepy/multipatch.py | 90 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index e04c3efe0..0b675667b 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -74,6 +74,7 @@ def _init_members(self): self._interfaces = None self._boundary_splines = None self._spline_boundary_centers = None + self._field_list = [] @property def splines(self): @@ -440,3 +441,92 @@ def combine_boundaries(self, mask=None): return None self.interfaces[boundary_ids] = np.min(mask) + + def add_fields( + self, + *fields, + check_compliance=False, + check_conformity=False, + ): + """ + Add fields using lists of splines + + Parameters + ---------- + fields : list + Any number of list of splines to represent n-dimensional field with + equal parametric dimensionality + check_compliance : bool (False) + Check if field list is admissible, by comparing the parametric + dimensionality of the field entries with the spline list, and compare + patch sizes + check_conformity : bool (False) + Check for conformity between patches and fields by comparing degrees + and control-mesh-resolutions + + Returns + ------- + None + """ + if check_compliance: + for field in fields: + # Check if is a list + if not isinstance(field, list): + if issubclass(type(field), Spline): + field = list(field) + # Check if size matches + if not len(field) == len(self.splines): + raise ValueError( + "Size mismatch between fields and geometry" + ) + # Check parametric dimensions + for i, spline in enumerate(field): + if self.splines[i].para_dim != spline.para_dim: + raise ValueError( + "Mismatch between spline and field dimensionality" + f"for spline {i}, expected" + f"{self.splines[i].para_dim}P{self.splines[i].dim}" + f"D, but got {spline.para_dim}P{spline.dim}D." + ) + if check_conformity: + # Further check degrees and ctps-mesh-res + if self.splines[i].degrees != spline.degrees: + raise ValueError( + "Mismatch between spline and field degrees" + f"for spline {i}, expected" + f"{self.splines[i].degrees}, but got " + f"{spline.degrees}." + ) + if ( + self.splines[i].control_mesh_resolutions + != spline.control_mesh_resolutions + ): + raise ValueError( + "Mismatch between spline and field ctps-" + f"resolution for spline {i}, expected" + f"{self.splines[i].control_mesh_resolutions}" + f", but got {spline.control_mesh_resolutions}." + ) + # Add fields + self._field_list.extend(fields) + + @property + def fields(self): + """Save fields as individul splines on patches + + Parameters + ---------- + None + + Returns + ------- + fields : list + List of all field representation in the form of list of splines + """ + return self._field_list + + @fields.setter + def fields(self, a): + raise ValueError( + "Fields may not be set directly, use add_fields instead" + ) From b2585203939312b936d3b383fa08ee8cf2657989 Mon Sep 17 00:00:00 2001 From: jzwar Date: Tue, 28 Mar 2023 13:01:29 +0200 Subject: [PATCH 74/89] Move spline-export to seperate function --- splinepy/io/gismo.py | 185 +++++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 85 deletions(-) diff --git a/splinepy/io/gismo.py b/splinepy/io/gismo.py index 486b4034d..9f380bf6d 100644 --- a/splinepy/io/gismo.py +++ b/splinepy/io/gismo.py @@ -7,12 +7,103 @@ from splinepy.utils.log import debug, warning +def _spline_to_ET(root, multipatch, index_offset): + from splinepy import NURBS, BSpline + from splinepy.spline import Spline + + for id, spline in enumerate(multipatch.splines): + if not issubclass(type(spline), Spline): + raise ValueError( + "One of the splines handed to export is not a valid spline" + " representation" + ) + + # Transform bezier types, as they are not supported in gismo + if spline.name.startswith("Bezier"): + type_name = "BSpline" + spline = BSpline( + **spline.todict(), + knot_vectors=[ + [0] * (a + 1) + [1] * (a + 1) for a in spline.degrees + ], + ) + elif spline.name.startswith("RationalBezier"): + type_name = "Nurbs" + spline = NURBS( + **spline.todict(), + knot_vectors=[ + [0] * (a + 1) + [1] * (a + 1) for a in spline.degrees + ], + ) + elif spline.name.startswith("BSpline"): + type_name = "BSpline" + elif spline.name.startswith("NURBS"): + type_name = "Nurbs" + + # Start element definition + spline_element = ET.SubElement( + root, + "Geometry", + type="Tensor" + type_name + str(spline.para_dim), + id=str(id + index_offset), + ) + + # Define Basis functions + if "weights" in spline.required_properties: + spline_basis_base = ET.SubElement( + spline_element, + "Basis", + type="Tensor" + type_name + "Basis" + str(spline.para_dim), + ) + spline_basis = ET.SubElement( + spline_basis_base, + "Basis", + type="TensorBSplineBasis" + str(spline.para_dim), + ) + else: + spline_basis = ET.SubElement( + spline_element, + "Basis", + type="Tensor" + type_name + "Basis" + str(spline.para_dim), + ) + + for i_para in range(spline.para_dim): + basis_fun = ET.SubElement( + spline_basis, "Basis", type="BSplineBasis", index=str(i_para) + ) + knot_vector = ET.SubElement( + basis_fun, "KnotVector", degree=str(spline.degrees[i_para]) + ) + knot_vector.text = " ".join( + [str(k) for k in spline.knot_vectors[i_para]] + ) + if "weights" in spline.required_properties: + # Add weights + weights = ET.SubElement( + spline_basis_base, + "weights", + ) + weights.text = "\n".join( + [str(w) for w in spline.weights.flatten()] + ) + coords = ET.SubElement( + spline_element, + "coefs", + geoDim=str(spline.dim), + ) + coords.text = "\n".join( + [" ".join([str(xx) for xx in x]) for x in spline.control_points] + ) + + def export( fname, multipatch=None, indent=True, labeled_boundaries=True, options=None, + export_fields=False, + collapse_fields=False, ): """Export as gismo readable xml geometry file Use gismo-specific xml-keywords to export (list of) splines. All Bezier @@ -34,12 +125,18 @@ def export( following keys, 'tag'->string, 'text'->string (optional), 'attributes'->dictionary (optional), 'children'->list in the same format (optional) + export_fields : bool + Export fields to seperate files ending with field.xml, e.g., + filename.xml.field1.xml + collapse_fields : cool + Only valid if export_fields is True, writes all fields in the same file + by collapsing the control points, requires conformity (not checked) Returns ------- None """ - from splinepy import NURBS, BSpline, Multipatch + from splinepy import Multipatch from splinepy.settings import NTHREADS, TOLERANCE from splinepy.spline import Spline from splinepy.splinepy_core import orientations @@ -208,93 +305,11 @@ def export( for (sid, bid) in zip(bc_data_i[0], bc_data_i[1]) ] ) + ### # Individual spline data ### - for id, spline in enumerate(multipatch.splines): - if not issubclass(type(spline), Spline): - raise ValueError( - "One of the splines handed to export is not a valid spline" - " representation" - ) - - # Transform bezier types, as they are not supported in gismo - if spline.name.startswith("Bezier"): - type_name = "BSpline" - spline = BSpline( - **spline.todict(), - knot_vectors=[ - [0] * (a + 1) + [1] * (a + 1) for a in spline.degrees - ], - ) - elif spline.name.startswith("RationalBezier"): - type_name = "Nurbs" - spline = NURBS( - **spline.todict(), - knot_vectors=[ - [0] * (a + 1) + [1] * (a + 1) for a in spline.degrees - ], - ) - elif spline.name.startswith("BSpline"): - type_name = "BSpline" - elif spline.name.startswith("NURBS"): - type_name = "Nurbs" - - # Start element definition - spline_element = ET.SubElement( - xml_data, - "Geometry", - type="Tensor" + type_name + str(spline.para_dim), - id=str(id + index_offset), - ) - - # Define Basis functions - if "weights" in spline.required_properties: - spline_basis_base = ET.SubElement( - spline_element, - "Basis", - type="Tensor" + type_name + "Basis" + str(spline.para_dim), - ) - spline_basis = ET.SubElement( - spline_basis_base, - "Basis", - type="TensorBSplineBasis" + str(spline.para_dim), - ) - else: - spline_basis = ET.SubElement( - spline_element, - "Basis", - type="Tensor" + type_name + "Basis" + str(spline.para_dim), - ) - - for i_para in range(spline.para_dim): - basis_fun = ET.SubElement( - spline_basis, "Basis", type="BSplineBasis", index=str(i_para) - ) - knot_vector = ET.SubElement( - basis_fun, "KnotVector", degree=str(spline.degrees[i_para]) - ) - knot_vector.text = " ".join( - [str(k) for k in spline.knot_vectors[i_para]] - ) - if "weights" in spline.required_properties: - # Add weights - weights = ET.SubElement( - spline_basis_base, - "weights", - ) - weights.text = "\n".join( - [str(w) for w in spline.weights.flatten()] - ) - - coords = ET.SubElement( - spline_element, - "coefs", - geoDim=str(spline.dim), - ) - coords.text = "\n".join( - [" ".join([str(xx) for xx in x]) for x in spline.control_points] - ) + _spline_to_ET(xml_data, multipatch, index_offset) # Add addtional options to the xml file if options is not None: From e12eaccc5271cb74df04fb794902c12e6ec98218 Mon Sep 17 00:00:00 2001 From: jzwar Date: Tue, 28 Mar 2023 13:42:57 +0200 Subject: [PATCH 75/89] Enable Field export in gismo --- splinepy/io/gismo.py | 59 +++++++++++++++++++++++++++++++++++++++--- splinepy/multipatch.py | 5 ++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/splinepy/io/gismo.py b/splinepy/io/gismo.py index 9f380bf6d..4ca948c7a 100644 --- a/splinepy/io/gismo.py +++ b/splinepy/io/gismo.py @@ -1,3 +1,4 @@ +import copy import xml.etree.ElementTree as ET from sys import version as python_version @@ -7,11 +8,53 @@ from splinepy.utils.log import debug, warning -def _spline_to_ET(root, multipatch, index_offset): +def _spline_to_ET(root, multipatch, index_offset, fields_only=False): + """ + Write spline data to xml element in gismo format + + Parameters + ---------- + root : ElementTree.Subelement + branch in element tree to which the spline info is to be added + multipatch : Multipatch + Multipatch containing the information requested + index_offset : int + index_offset for ids in xml export + (All geometries are assigned individual ids that must be unique in the + xml file) + fields_only : bool (False) + If set, exports only the fields associated to the multipatch data + + Returns + ------- + None + """ from splinepy import NURBS, BSpline from splinepy.spline import Spline + if fields_only and (len(multipatch.fields) == 0): + return + for id, spline in enumerate(multipatch.splines): + if fields_only: + coefs = np.hstack( + [ + multipatch.fields[j][id].control_points + for j in range(len(multipatch.fields)) + ] + ) + if "weights" in spline.required_properties: + weights = np.hstack( + [ + multipatch.fields[j][id].weights + for j in range(len(multipatch.fields)) + ] + ) + else: + coefs = spline.control_points + if "weights" in spline.required_properties: + weights = spline.weights + if not issubclass(type(spline), Spline): raise ValueError( "One of the splines handed to export is not a valid spline" @@ -84,15 +127,15 @@ def _spline_to_ET(root, multipatch, index_offset): "weights", ) weights.text = "\n".join( - [str(w) for w in spline.weights.flatten()] + [" ".join([str(ww) for ww in w]) for w in weights] ) coords = ET.SubElement( spline_element, "coefs", - geoDim=str(spline.dim), + geoDim=str(coefs.shape[1]), ) coords.text = "\n".join( - [" ".join([str(xx) for xx in x]) for x in spline.control_points] + [" ".join([str(xx) for xx in x]) for x in coefs] ) @@ -309,6 +352,14 @@ def export( ### # Individual spline data ### + # Export fields first, as all necessary information is already available + if export_fields: + field_xml = copy.deepcopy(xml_data) + _spline_to_ET(field_xml, multipatch, index_offset, fields_only=True) + file_content = ET.tostring(field_xml) + with open(fname + "fields.xml", "wb") as f: + f.write(file_content) + _spline_to_ET(xml_data, multipatch, index_offset) # Add addtional options to the xml file diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 0b675667b..abbb8de8d 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -490,6 +490,11 @@ def add_fields( ) if check_conformity: # Further check degrees and ctps-mesh-res + if type(self.splines[i]) is not type(spline): + raise ValueError( + "Mismatch between spline and field type" + f"for spline {i}." + ) if self.splines[i].degrees != spline.degrees: raise ValueError( "Mismatch between spline and field degrees" From 9972a5f8e2708046dfcaabba3736eb64eb276221 Mon Sep 17 00:00:00 2001 From: jzwar Date: Tue, 28 Mar 2023 13:53:54 +0200 Subject: [PATCH 76/89] Bugfix and indentation --- splinepy/io/gismo.py | 4 +++- splinepy/multipatch.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/splinepy/io/gismo.py b/splinepy/io/gismo.py index 4ca948c7a..e1568bf3d 100644 --- a/splinepy/io/gismo.py +++ b/splinepy/io/gismo.py @@ -356,8 +356,10 @@ def export( if export_fields: field_xml = copy.deepcopy(xml_data) _spline_to_ET(field_xml, multipatch, index_offset, fields_only=True) + if int(python_version.split(".")[1]) >= 9 and indent: + ET.indent(field_xml) file_content = ET.tostring(field_xml) - with open(fname + "fields.xml", "wb") as f: + with open(fname + ".fields.xml", "wb") as f: f.write(file_content) _spline_to_ET(xml_data, multipatch, index_offset) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index abbb8de8d..36a4892fb 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -495,7 +495,7 @@ def add_fields( "Mismatch between spline and field type" f"for spline {i}." ) - if self.splines[i].degrees != spline.degrees: + if np.any(self.splines[i].degrees != spline.degrees): raise ValueError( "Mismatch between spline and field degrees" f"for spline {i}, expected" @@ -503,8 +503,8 @@ def add_fields( f"{spline.degrees}." ) if ( - self.splines[i].control_mesh_resolutions - != spline.control_mesh_resolutions + np.any(self.splines[i].control_mesh_resolutions + != spline.control_mesh_resolutions) ): raise ValueError( "Mismatch between spline and field ctps-" From 0233b0e06879b82453cf6ff911db9ec4bdc610ad Mon Sep 17 00:00:00 2001 From: jzwar Date: Tue, 28 Mar 2023 15:08:16 +0200 Subject: [PATCH 77/89] Renaming solved the issue --- splinepy/io/gismo.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/splinepy/io/gismo.py b/splinepy/io/gismo.py index e1568bf3d..17c8a46a9 100644 --- a/splinepy/io/gismo.py +++ b/splinepy/io/gismo.py @@ -122,11 +122,11 @@ def _spline_to_ET(root, multipatch, index_offset, fields_only=False): ) if "weights" in spline.required_properties: # Add weights - weights = ET.SubElement( + weights_element = ET.SubElement( spline_basis_base, "weights", ) - weights.text = "\n".join( + weights_element.text = "\n".join( [" ".join([str(ww) for ww in w]) for w in weights] ) coords = ET.SubElement( @@ -146,7 +146,6 @@ def export( labeled_boundaries=True, options=None, export_fields=False, - collapse_fields=False, ): """Export as gismo readable xml geometry file Use gismo-specific xml-keywords to export (list of) splines. All Bezier From 5e91743ee6eea4ae659562b39cfa1d2e33bb1526 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Mar 2023 13:09:47 +0000 Subject: [PATCH 78/89] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- splinepy/multipatch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 36a4892fb..2b4950029 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -502,9 +502,9 @@ def add_fields( f"{self.splines[i].degrees}, but got " f"{spline.degrees}." ) - if ( - np.any(self.splines[i].control_mesh_resolutions - != spline.control_mesh_resolutions) + if np.any( + self.splines[i].control_mesh_resolutions + != spline.control_mesh_resolutions ): raise ValueError( "Mismatch between spline and field ctps-" From 0c43ea93fd989a71c16e7ee80127279e0933152d Mon Sep 17 00:00:00 2001 From: jzwar Date: Thu, 30 Mar 2023 16:42:19 +0200 Subject: [PATCH 79/89] Support only --- splinepy/io/gismo.py | 43 ++++++++++++++++++++++++++++++++++-------- splinepy/multipatch.py | 4 +++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/splinepy/io/gismo.py b/splinepy/io/gismo.py index 17c8a46a9..046a1a7b2 100644 --- a/splinepy/io/gismo.py +++ b/splinepy/io/gismo.py @@ -35,20 +35,47 @@ def _spline_to_ET(root, multipatch, index_offset, fields_only=False): if fields_only and (len(multipatch.fields) == 0): return + if fields_only: + supports = np.vstack( + [ + (i, j, 0) + for j, field in enumerate(multipatch.fields) + for i, v in enumerate(field) + if v is not None + ] + ) + + # Very unintuitive solution to counting the support ids #indextrick + indices = np.argsort(supports[:,0]) + counter = np.arange(supports.shape[0]) + bincount = np.bincount(supports[:,0]) + # Get number of occurences for previous element to correct index shift + index_shift = np.cumsum(np.hstack([[0],bincount[:-1]])) + counter -= np.repeat(index_shift, bincount) + supports[indices,2] = counter + + # Write Matrix + design_v_support = ET.SubElement( + root, + "Matrix", + rows=str(supports.shape[0]), + cols=str(supports.shape[1]), + id=str(10), + ) + design_v_support.text = "\n".join( + [" ".join([str(xx) for xx in x]) for x in supports] + ) + for id, spline in enumerate(multipatch.splines): if fields_only: + # Check supports + support = supports[supports[:, 0] == id, 1] coefs = np.hstack( - [ - multipatch.fields[j][id].control_points - for j in range(len(multipatch.fields)) - ] + [multipatch.fields[j][id].control_points for j in support] ) if "weights" in spline.required_properties: weights = np.hstack( - [ - multipatch.fields[j][id].weights - for j in range(len(multipatch.fields)) - ] + [multipatch.fields[j][id].weights for j in support] ) else: coefs = spline.control_points diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 2b4950029..9e61100d3 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -481,6 +481,8 @@ def add_fields( ) # Check parametric dimensions for i, spline in enumerate(field): + if spline is None: + continue if self.splines[i].para_dim != spline.para_dim: raise ValueError( "Mismatch between spline and field dimensionality" @@ -491,7 +493,7 @@ def add_fields( if check_conformity: # Further check degrees and ctps-mesh-res if type(self.splines[i]) is not type(spline): - raise ValueError( + self._logd( "Mismatch between spline and field type" f"for spline {i}." ) From b1022c2d1521ee108bb770dc1f6a093fd4129f1a Mon Sep 17 00:00:00 2001 From: jzwar Date: Fri, 31 Mar 2023 18:14:16 +0200 Subject: [PATCH 80/89] Stable sort to ensure ordered numbers --- splinepy/io/gismo.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/splinepy/io/gismo.py b/splinepy/io/gismo.py index 046a1a7b2..761206269 100644 --- a/splinepy/io/gismo.py +++ b/splinepy/io/gismo.py @@ -36,23 +36,24 @@ def _spline_to_ET(root, multipatch, index_offset, fields_only=False): return if fields_only: - supports = np.vstack( + supports = np.array( [ (i, j, 0) for j, field in enumerate(multipatch.fields) for i, v in enumerate(field) if v is not None - ] + ], + dtype=np.int64, ) # Very unintuitive solution to counting the support ids #indextrick - indices = np.argsort(supports[:,0]) + indices = np.argsort(supports[:, 0], kind="stable") counter = np.arange(supports.shape[0]) - bincount = np.bincount(supports[:,0]) + bincount = np.bincount(supports[:, 0]) # Get number of occurences for previous element to correct index shift - index_shift = np.cumsum(np.hstack([[0],bincount[:-1]])) + index_shift = np.cumsum(np.hstack([[0], bincount[:-1]])) counter -= np.repeat(index_shift, bincount) - supports[indices,2] = counter + supports[indices, 2] = counter # Write Matrix design_v_support = ET.SubElement( From e546e959e552abc801ae1cb7feb9e9096f6c9727 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 17:25:58 +0200 Subject: [PATCH 81/89] use core function --- splinepy/multipatch.py | 69 +++++++++--------------------------------- 1 file changed, 14 insertions(+), 55 deletions(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 9e61100d3..4f63a38d9 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -447,6 +447,7 @@ def add_fields( *fields, check_compliance=False, check_conformity=False, + nthreads=None, ): """ Add fields using lists of splines @@ -463,59 +464,23 @@ def add_fields( check_conformity : bool (False) Check for conformity between patches and fields by comparing degrees and control-mesh-resolutions + nthreads : int Returns ------- None """ - if check_compliance: - for field in fields: - # Check if is a list - if not isinstance(field, list): - if issubclass(type(field), Spline): - field = list(field) - # Check if size matches - if not len(field) == len(self.splines): - raise ValueError( - "Size mismatch between fields and geometry" - ) - # Check parametric dimensions - for i, spline in enumerate(field): - if spline is None: - continue - if self.splines[i].para_dim != spline.para_dim: - raise ValueError( - "Mismatch between spline and field dimensionality" - f"for spline {i}, expected" - f"{self.splines[i].para_dim}P{self.splines[i].dim}" - f"D, but got {spline.para_dim}P{spline.dim}D." - ) - if check_conformity: - # Further check degrees and ctps-mesh-res - if type(self.splines[i]) is not type(spline): - self._logd( - "Mismatch between spline and field type" - f"for spline {i}." - ) - if np.any(self.splines[i].degrees != spline.degrees): - raise ValueError( - "Mismatch between spline and field degrees" - f"for spline {i}, expected" - f"{self.splines[i].degrees}, but got " - f"{spline.degrees}." - ) - if np.any( - self.splines[i].control_mesh_resolutions - != spline.control_mesh_resolutions - ): - raise ValueError( - "Mismatch between spline and field ctps-" - f"resolution for spline {i}, expected" - f"{self.splines[i].control_mesh_resolutions}" - f", but got {spline.control_mesh_resolutions}." - ) - # Add fields - self._field_list.extend(fields) + if nthreads is None: + nthreads = settings.NTHREADS + + super().add_fields( + fields, + check_compliance, + check_compliance, + check_conformity, + check_conformity, + nthreads, + ) @property def fields(self): @@ -530,10 +495,4 @@ def fields(self): fields : list List of all field representation in the form of list of splines """ - return self._field_list - - @fields.setter - def fields(self, a): - raise ValueError( - "Fields may not be set directly, use add_fields instead" - ) + return self.fields() From bfa5b97934215b3c37a4c9848509f078557162ba Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 12 Jun 2023 17:28:56 +0200 Subject: [PATCH 82/89] remove unused parts --- splinepy/multipatch.py | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index 4f63a38d9..dd2e0f509 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -15,11 +15,12 @@ class Multipatch(SplinepyBase, PyMultiPatch): interfaces """ + __slots__ = () + def __init__( self, splines=None, interfaces=None, - as_boundary=False, ): """ Multipatch @@ -30,10 +31,6 @@ def __init__( List of splines to store as multipatch interfaces : array-like Defines the connectivity inbetween patches - as_boundary : bool - Multipatch is a boundary object of a higher dimensional geometry. If - set to true, additional checks are performed on the interfaces, - requiring strict interconnectivity between all patches Returns ------- @@ -45,37 +42,11 @@ def __init__( else: super().__init__() - self._init_members() - self._logd("Instantiated Multipatch object") - # Set properties - self._as_boundary = as_boundary - - if splines is not None: - self.splines = splines - if interfaces is not None: self.interfaces = interfaces - def _init_members(self): - """Defaults all relevant members to None - - Parameters - ---------- - None - - Returns - ------- - None - """ - self._boundaries = None - self._spline_list = None - self._interfaces = None - self._boundary_splines = None - self._spline_boundary_centers = None - self._field_list = [] - @property def splines(self): """ From 5304ad2d7a2eb0154c3004eeb5cf37b408c07517 Mon Sep 17 00:00:00 2001 From: Jaewook Lee <47114801+j042@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:20:11 +0200 Subject: [PATCH 83/89] Update splinepy/multipatch.py Co-authored-by: jzwar <63095162+jzwar@users.noreply.github.com> --- splinepy/multipatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index dd2e0f509..b18c1bf35 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -466,4 +466,4 @@ def fields(self): fields : list List of all field representation in the form of list of splines """ - return self.fields() + return super().fields() From 0313c33c4d1bc214174e6833744c4d2837691f99 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 19 Jun 2023 12:04:51 +0200 Subject: [PATCH 84/89] Revert "fix warning" This reverts commit 0e98ae994dc7210e4cf49e022239ae677ae6e6d5. --- cpp/splinepy/splines/helpers/properties.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/splines/helpers/properties.hpp b/cpp/splinepy/splines/helpers/properties.hpp index dcb44318b..ddcc22718 100644 --- a/cpp/splinepy/splines/helpers/properties.hpp +++ b/cpp/splinepy/splines/helpers/properties.hpp @@ -83,11 +83,11 @@ inline void GetGrevilleAbscissae(const SplineType& spline, for (int j{}; j < cmr; ++j) { if constexpr (SplineType::kHasKnotVectors) { // - using IndexType = splinelib::Index; const auto& knot_vectors = spline.GetKnotVectors()[i_para_dim]; double factor{}; for (int k{0}; k < degrees[i_para_dim]; ++k) { - factor += knot_vectors->operator[](IndexType(k + j + 1)); + factor += + knot_vectors->operator[](typename splinelib::Index(k + j + 1)); } greville_abscissae[j] = static_cast(inv_factor * factor); } else { From 5d76f354fa4a748cec0f7d5da33ba07a48925c26 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 19 Jun 2023 12:11:44 +0200 Subject: [PATCH 85/89] *fields to fields --- splinepy/multipatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index b18c1bf35..e266f93a8 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -415,7 +415,7 @@ def combine_boundaries(self, mask=None): def add_fields( self, - *fields, + fields, check_compliance=False, check_conformity=False, nthreads=None, From ce05df25daf8cd54be7079af3c107e7c59fe7e21 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 19 Jun 2023 13:10:06 +0200 Subject: [PATCH 86/89] clean up --- CMakeLists.txt | 4 ---- cpp/splinepy/py/py_multi_patch.hpp | 19 +++++++------------ 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f28c18484..078c384a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,10 +71,6 @@ if(SPLINEPY_MORE) set(SPLINEPY_DEFS ${SPLINEPY_DEFS} SPLINEPY_MORE) endif(SPLINEPY_MORE) -if(SPLINEPY_COMPILE_PYTHON) - set(SPLINEPY_DEFS ${SPLINEPY_DEFS} SPLINEPY_COMPILE_PYTHON) -endif(SPLINEPY_COMPILE_PYTHON) - if(SPLINEPY_BUILD_EXPLICIT) set(SPLINEPY_DEFS ${SPLINEPY_DEFS} SPLINEPY_BUILD_EXPLICIT) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index 6df9b916e..ef15d3094 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -80,8 +80,6 @@ ToCoreSplineVector(py::list pysplines, [dim_if_none - 1]; continue; } - - // not none, core_splines[i] = spline.template cast>()->Core(); } @@ -2030,18 +2028,15 @@ class PyMultiPatch { true); interfaces_ = interfaces; - return interfaces_; - } - - // here's get - if (interfaces_.size() > 0) { - return interfaces_; + } else if (interfaces_.size() == 0) { + // get, but need to compute since saved member is empty + interfaces_ = InterfacesFromBoundaryCenters(SubPatchCenters(), + tolerance_, + ParaDim()); } - // here's compute - interfaces_ = - InterfacesFromBoundaryCenters(SubPatchCenters(), tolerance_, ParaDim()); - + // regardless of set/get, it will always return the saved member of + // current state return interfaces_; } From cf556cecf91ce909e0b4d15be68f4277fe6b8ca9 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 19 Jun 2023 14:25:56 +0200 Subject: [PATCH 87/89] always return derived multipatch --- cpp/splinepy/py/py_multi_patch.hpp | 25 +++++++++++++++++++++++-- splinepy/multipatch.py | 21 ++++++++++++--------- splinepy/settings.py | 3 ++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index ef15d3094..cc53ba0b1 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -1707,6 +1707,16 @@ int AddBoundariesFromContinuity(const py::list& boundary_splines, return current_max_id; } +class PyMultiPatch; + +/// @brief ToDerived for PyMultiPatches +/// @param core_obj +/// @return +py::object ToDerived(std::shared_ptr core_obj) { + const auto to_derived = py::module_::import("splinepy").attr("to_derived"); + return to_derived(py::cast(core_obj)); +} + /// @brief Multi-patch splines. Here, use of the words /// "patch" and "spline" are interchangeable class PyMultiPatch { @@ -1938,6 +1948,12 @@ class PyMultiPatch { return sub_multi_patch_; } + /// @brief returns derived class of SubMultiPatch + /// @return + py::object PySubMultiPatch() { return ToDerived(SubMultiPatch()); } + + /// @brief Computes centers of subpatches + /// @return py::array_t SubPatchCenters() { // return saved if (sub_patch_centers_.size() > 0) { @@ -2113,6 +2129,10 @@ class PyMultiPatch { return boundary_multi_patch_; } + /// @brief returns derived class of boundary multi patch + /// @return + py::object PyBoundaryMultiPatch() { return ToDerived(BoundaryMultiPatch()); } + py::array_t Evaluate(py::array_t queries, const int nthreads) { // use first spline as dimension guide line @@ -2365,6 +2385,7 @@ inline void add_multi_patch(py::module& m) { klasse.def(py::init<>()) .def(py::init()) + .def(py::init&>()) // for to_derived() .def_readwrite("n_default_threads", &PyMultiPatch::n_default_threads_) .def_readwrite("same_parametric_bounds", &PyMultiPatch::same_parametric_bounds_) @@ -2377,11 +2398,11 @@ inline void add_multi_patch(py::module& m) { .def_property("patches", &PyMultiPatch::GetPatches, &PyMultiPatch::SetPatchesDefault) - .def("sub_multi_patch", &PyMultiPatch::SubMultiPatch) + .def("sub_multi_patch", &PyMultiPatch::PySubMultiPatch) .def("sub_patch_centers", &PyMultiPatch::SubPatchCenters) .def("interfaces", &PyMultiPatch::Interfaces) .def("boundary_patch_ids", &PyMultiPatch::BoundaryPatchIds) - .def("boundary_multi_patch", &PyMultiPatch::BoundaryMultiPatch) + .def("boundary_multi_patch", &PyMultiPatch::PyBoundaryMultiPatch) .def("evaluate", &PyMultiPatch::Evaluate, py::arg("queries"), diff --git a/splinepy/multipatch.py b/splinepy/multipatch.py index e266f93a8..fe2b4f249 100644 --- a/splinepy/multipatch.py +++ b/splinepy/multipatch.py @@ -17,11 +17,7 @@ class Multipatch(SplinepyBase, PyMultiPatch): __slots__ = () - def __init__( - self, - splines=None, - interfaces=None, - ): + def __init__(self, splines=None, interfaces=None, *, spline=None): """ Multipatch @@ -31,18 +27,25 @@ def __init__( List of splines to store as multipatch interfaces : array-like Defines the connectivity inbetween patches + spline : PyMultiPatch + keyword only argument, implemented to support to_derived() interface. + calls move constructor. Returns ------- None """ # Init values - if splines is not None: + if spline is not None: + if not isinstance(spline, PyMultiPatch): + raise TypeError("spline must be PyMultiPatch.") + super().__init__(spline) + elif splines is not None: super().__init__(splines, settings.NTHREADS, False) else: super().__init__() - self._logd("Instantiated Multipatch object") + self._logd("Initialized Multipatch object") if interfaces is not None: self.interfaces = interfaces @@ -378,8 +381,8 @@ def boundaries_from_continuity( # Pass information to c++ backend self._logd("Start propagation of information...") n_new_boundaries = boundaries_from_continuity( - b_patches.patches, - b_patches.interfaces([]), + b_patches.splines, + b_patches.interfaces, self.interfaces, tolerance, nthreads, diff --git a/splinepy/settings.py b/splinepy/settings.py index de1c8b6e2..eea8b18c9 100644 --- a/splinepy/settings.py +++ b/splinepy/settings.py @@ -7,13 +7,14 @@ def __splinepy_name_to_type__(): """Workaround to provide flexible string to type conversion without causing circular import """ - from splinepy import NURBS, Bezier, BSpline, RationalBezier + from splinepy import NURBS, Bezier, BSpline, Multipatch, RationalBezier return dict( Bezier=Bezier, RationalBezier=RationalBezier, BSpline=BSpline, NURBS=NURBS, + MultiPatch=Multipatch, ) From e90a9a91587651a76b1520a83989605b22104680 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 19 Jun 2023 14:40:29 +0200 Subject: [PATCH 88/89] propagate same_parametric_bounds for fields --- cpp/splinepy/py/py_multi_patch.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cpp/splinepy/py/py_multi_patch.hpp b/cpp/splinepy/py/py_multi_patch.hpp index cc53ba0b1..0f677e9ae 100644 --- a/cpp/splinepy/py/py_multi_patch.hpp +++ b/cpp/splinepy/py/py_multi_patch.hpp @@ -202,7 +202,7 @@ inline void RaiseMismatch(const CoreSplineVector& splist, if (check_dim && (dim != spline->SplinepyDim())) { mismatches["dim"][thread_index].push_back(i); } - // check properties that arerelevent iff para_dim matches + // check properties that are relevent iff para_dim matches if (para_dim_matches) { if (check_degrees) { spline->SplinepyCurrentProperties(spline_degree.data(), @@ -2311,8 +2311,13 @@ class PyMultiPatch { for (int i{n_current_fields + begin}, j{begin}; j < end; ++i, ++j) { // create multipatch py::list casted_list = fields[j].template cast(); - field_multi_patches_[i] = + auto field_multi_patch = std::make_shared(casted_list, para_dim, dim, 1); + // propagate same_parametric_bounds_ flag + field_multi_patch->same_parametric_bounds_ = same_parametric_bounds_; + // set item + field_multi_patches_[i] = field_multi_patch; + try { // check mismatch - doesn't check null splines RaiseMismatch(core_patches_, From b873f2b5a0dd61d4c8c7ea07e44a1429afc01d0c Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Mon, 19 Jun 2023 15:36:40 +0200 Subject: [PATCH 89/89] v[2]++ --- splinepy/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splinepy/_version.py b/splinepy/_version.py index 40b07ef16..5681085f3 100644 --- a/splinepy/_version.py +++ b/splinepy/_version.py @@ -1 +1 @@ -__version__ = "0.0.23" +__version__ = "0.0.24"