IgANet
IgANets - Isogeometric Analysis Networks
Loading...
Searching...
No Matches
bspline.hpp
Go to the documentation of this file.
1
15#pragma once
16
17#include <algorithm>
18#include <exception>
19#include <filesystem>
20#include <functional>
21#include <regex>
22
23#include <core.hpp>
24#include <options.hpp>
25#include <patch.hpp>
26
27#include <utils/blocktensor.hpp>
28#include <utils/container.hpp>
29#include <utils/fqn.hpp>
31#include <utils/integer_pow.hpp>
32#include <utils/linalg.hpp>
33#include <utils/serialize.hpp>
34#include <utils/tensorarray.hpp>
35#include <utils/vslice.hpp>
36
41#define GENERATE_EXPR_SEQ (curl)(div)(grad)(hess)(jac)(lapl)
42
47#define GENERATE_IEXPR_SEQ (icurl)(idiv)(igrad)(ihess)(ijac)(ilapl)
48
49namespace iganet {
50using namespace literals;
51using utils::operator+;
52
53// clang-format off
55enum class init : short_t {
56 none = 0,
57 zeros = 1,
58 ones = 2,
59 linear =
60 3,
61 random = 4,
62 greville = 5,
63 linspace = 6
65};
66// clang-format on
67
74enum class deriv : short_t {
75 func = 0,
77 dx = 1,
78 dy = 10,
79 dz = 100,
80 dt = 1000,
81};
82
91inline constexpr auto operator+(deriv lhs, deriv rhs) {
92 return deriv(static_cast<short_t>(lhs) + static_cast<short_t>(rhs));
93}
94
103inline constexpr auto operator^(deriv lhs, short_t rhs) {
104 return deriv(static_cast<short_t>(lhs) * static_cast<short_t>(rhs));
105}
106
179template <typename real_t, short_t GeoDim, short_t... Degrees>
181 : public utils::Serializable,
182 public BSplinePatch<real_t, GeoDim, sizeof...(Degrees)> {
184 template <typename BSplineCore> friend class BSplineCommon;
185
186protected:
189 static constexpr const short_t parDim_ = sizeof...(Degrees);
190
193 static constexpr const short_t geoDim_ = GeoDim;
194
197 static constexpr const std::array<short_t, parDim_> degrees_ = {Degrees...};
198
201 std::array<int64_t, parDim_> nknots_;
202
205 std::array<int64_t, parDim_> ncoeffs_;
206
210 std::array<int64_t, parDim_> ncoeffs_reverse_;
211
215
220
223
224public:
226 using value_type = real_t;
227
233 template <template <typename, short_t, short_t...> class BSpline,
234 std::make_signed<short_t>::type degree_elevate = 0>
236
241
245 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
247
250 template <typename other_t>
253
255 inline torch::Device device() const noexcept override {
256 return options_.device();
257 }
258
260 inline int32_t device_index() const noexcept override {
261 return options_.device_index();
262 }
263
265 inline torch::Dtype dtype() const noexcept override {
266 return options_.dtype();
267 }
268
270 inline torch::Layout layout() const noexcept override {
271 return options_.layout();
272 }
273
275 inline bool requires_grad() const noexcept override {
276 return options_.requires_grad();
277 }
278
280 inline bool pinned_memory() const noexcept override {
281 return options_.pinned_memory();
282 }
283
285 inline bool is_sparse() const noexcept override {
286 return options_.is_sparse();
287 }
288
290 inline static constexpr bool is_uniform() noexcept { return true; }
291
293 inline static constexpr bool is_nonuniform() noexcept { return false; }
294
302 inline UniformBSplineCore &
303 set_requires_grad(bool requires_grad) noexcept override {
304 if (options_.requires_grad() == requires_grad)
305 return *this;
306
307 for (short_t i = 0; i < parDim_; ++i)
309
310 for (short_t i = 0; i < geoDim_; ++i)
312
314 options_.~Options<real_t>();
316
317 return *this;
318 }
319
321 inline const Options<real_t> &options() const noexcept { return options_; }
322
332
340 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
341 enum init init = init::greville,
344 // Reverse ncoeffs
345 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
346
347 // Initialize knot vectors
348 init_knots();
349
350 // Initialize coefficients
352 }
353
367 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
369 bool clone = false,
372 // Reverse ncoeffs
373 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
374
375 // Initialize knot vectors
376 init_knots();
377
378 // Check compatibility
379 for (short_t i = 0; i < geoDim_; ++i)
380 if (coeffs[i].numel() != ncumcoeffs())
381 throw std::runtime_error("Invalid number of coefficients");
382
383 // Copy/clone coefficients
384 if (clone)
385 for (short_t i = 0; i < geoDim_; ++i)
386 coeffs_[i] = coeffs[i]
387 .clone()
388 .to(options.requires_grad(false))
389 .requires_grad_(options.requires_grad());
390 else
391 for (short_t i = 0; i < geoDim_; ++i)
392 coeffs_[i] = coeffs[i];
393 }
394
405 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
410 // Reverse ncoeffs
411 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
412
413 // Initialize knot vectors
414 init_knots();
415 }
416
422 template <typename other_t>
428 // Reverse ncoeffs
429 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
430
431 // Clone coefficients
432 for (short_t i = 0; i < geoDim_; ++i)
433 coeffs_[i] = other.coeffs(i)
434 .clone()
435 .to(options.requires_grad(false))
436 .requires_grad_(options.requires_grad());
437
438 // Clone knot vectors
439 for (short_t i = 0; i < parDim_; ++i)
440 knots_[i] = other.knots(i)
441 .clone()
442 .to(options.requires_grad(false))
443 .requires_grad_(options.requires_grad());
444 }
445
449 inline static constexpr short_t parDim() noexcept { return parDim_; }
450
454 inline static constexpr short_t geoDim() noexcept { return geoDim_; }
455
459 inline static constexpr const std::array<short_t, parDim_> &
461 return degrees_;
462 }
463
470 inline static constexpr const short_t &degree(short_t i) noexcept {
471 assert(i >= 0 && i < parDim_);
472 return degrees_[i];
473 }
474
480 return knots_;
481 }
482
489 inline const torch::Tensor &knots(short_t i) const noexcept {
490 assert(i >= 0 && i < parDim_);
491 return knots_[i];
492 }
493
499
506 inline torch::Tensor &knots(short_t i) noexcept {
507 assert(i >= 0 && i < parDim_);
508 return knots_[i];
509 }
510
515 inline const std::array<int64_t, parDim_> &nknots() const noexcept {
516 return nknots_;
517 }
518
525 inline int64_t nknots(short_t i) const noexcept {
526 assert(i >= 0 && i < parDim_);
527 return nknots_[i];
528 }
529
535 return coeffs_;
536 }
537
544 inline const torch::Tensor &coeffs(short_t i) const noexcept {
545 assert(i >= 0 && i < geoDim_);
546 return coeffs_[i];
547 }
548
554
561 inline torch::Tensor &coeffs(short_t i) noexcept {
562 assert(i >= 0 && i < geoDim_);
563 return coeffs_[i];
564 }
565
575
582 inline const auto coeffs_view(short_t i) const noexcept {
583 assert(i >= 0 && i < geoDim_);
584 if constexpr (parDim_ > 1)
585 if (coeffs_[i].dim() > 1)
587 else
589 else
590 return coeffs_[i];
591 }
592
597 int64_t s = 1;
598
599 for (short_t i = 0; i < parDim_; ++i)
600 s *= ncoeffs(i);
601
602 return s;
603 }
604
609 inline const std::array<int64_t, parDim_> &ncoeffs() const noexcept {
610 return ncoeffs_;
611 }
612
619 inline int64_t ncoeffs(short_t i) const noexcept {
620 assert(i >= 0 && i < parDim_);
621 return ncoeffs_[i];
622 }
623
624private:
628 template <std::size_t... Is>
629 inline torch::Tensor as_tensor_(std::index_sequence<Is...>) const noexcept {
630 return torch::cat({coeffs_[Is]...});
631 }
632
633public:
637 inline torch::Tensor as_tensor() const noexcept override {
638 return as_tensor_(std::make_index_sequence<geoDim_>{});
639 }
640
641private:
645 template <std::size_t... Is>
646 inline UniformBSplineCore &
647 from_tensor_(std::index_sequence<Is...>,
648 const torch::Tensor &tensor) noexcept {
649 ((coeffs_[Is] = tensor.index(
650 {torch::indexing::Slice(Is * ncumcoeffs(), (Is + 1) * ncumcoeffs()),
651 "..."})),
652 ...);
653 return *this;
654 }
655
656public:
662 inline UniformBSplineCore &
663 from_tensor(const torch::Tensor &tensor) noexcept override {
664 return from_tensor_(std::make_index_sequence<geoDim_>{}, tensor);
665 }
666
669 //
672 return geoDim_ * ncumcoeffs();
673 }
674
689 inline auto greville(bool interior = false) const {
690 if constexpr (parDim_ == 0) {
691 return torch::zeros(1, options_);
692 } else {
694
695 // Fill coefficients with the tensor-product of Greville
696 // abscissae values per univariate dimension
697 for (short_t i = 0; i < parDim_; ++i) {
698 coeffs[i] = torch::ones(1, options_);
699
700 for (short_t j = 0; j < parDim_; ++j) {
701 if (i == j) {
702
703 int64_t offset = interior ? 1 : 0;
704 int64_t count = ncoeffs_[j] - (interior ? 2 : 0);
705
706 // idx_base: (count, 1)
707 auto idx_base = torch::arange(count, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(1);
708
709 // offsets: (1, degree)
710 auto offsets = torch::arange(1, degrees_[j] + 1, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(0);
711
712 // indices: (count, degree)
713 auto indices = idx_base + offset + offsets;
714
715 // Gather relevant knot values: shape (count, degree)
716 auto gathered = knots_[j].index_select(0, indices.flatten()).view({count, degrees_[j]});
717
718 // Compute mean along degree dimension (dim=1)
719 auto greville_ = gathered.mean(1);
720
721 coeffs[i] = torch::kron(greville_, coeffs[i]);
722 } else
723 coeffs[i] =
724 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs[i]);
725 }
726
727 // Enable gradient calculation for non-leaf tensor
728 if (options_.requires_grad())
729 coeffs[i].retain_grad();
730 }
731
732 return coeffs;
733 }
734 }
735
758 eval_from_precomputed(const torch::Tensor &basfunc,
759 const torch::Tensor &coeff_indices, int64_t numeval,
760 torch::IntArrayRef sizes) const override {
761
763
764 for (short_t i = 0; i < geoDim_; ++i)
765 result.set(
767 basfunc,
769 .view(sizes));
770 return result;
771 }
772
775 const torch::Tensor &coeff_indices, int64_t numeval,
776 torch::IntArrayRef sizes) const override {
777
779
780 if constexpr (parDim_ == 0) {
781 for (short_t i = 0; i < geoDim_; ++i)
782 result.set(i, coeffs_[i]);
783 }
784
785 else {
786 // Lambda expression to evaluate the spline function
787 std::function<torch::Tensor(short_t, short_t)> eval_;
788
789 eval_ = [&, this](short_t i, short_t dim) {
790 if (dim == 0) {
791 return torch::matmul(coeffs(i)
793 .view({numeval, -1, degrees_[0] + 1}),
794 basfunc[0].view({numeval, -1, 1}));
795 } else {
796 return torch::matmul(
797 (eval_(i, dim - 1)).view({numeval, -1, degrees_[dim] + 1}),
798 basfunc[dim].view({numeval, -1, 1}));
799 }
800 };
801
802 for (short_t i = 0; i < geoDim_; ++i)
803 result.set(i, (eval_(i, parDim_ - 1)).view(sizes));
804 }
805 return result;
806 }
808
859 template <deriv deriv = deriv::func, bool memory_optimized = false>
860 inline auto eval(const torch::Tensor &xi) const {
861 if constexpr (parDim_ == 1)
863 else
864 throw std::runtime_error("Invalid parametric dimension");
865 }
866
867 template <deriv deriv = deriv::func, bool memory_optimized = false>
872
888 template <deriv deriv = deriv::func, bool memory_optimized = false>
894
913 template <deriv deriv = deriv::func, bool memory_optimized = false>
916 const torch::Tensor &coeff_indices) const {
917
919
920 if constexpr (parDim_ == 0) {
921 for (short_t i = 0; i < geoDim_; ++i)
922 if constexpr (deriv == deriv::func)
923 result.set(i, coeffs_[i]);
924 else
925 result.set(i, torch::zeros_like(coeffs_[i]));
926 return result;
927 } // parDim == 0
928
929 else {
930
931 // Check compatibility of arguments
932 for (short_t i = 0; i < parDim_; ++i)
933 assert(xi[i].sizes() == knot_indices[i].sizes());
934 for (short_t i = 1; i < parDim_; ++i)
935 assert(xi[0].sizes() == xi[i].sizes());
936
937 if constexpr (memory_optimized) {
938 // memory-optimized
939
940 if (coeffs(0).dim() > 1)
941 throw std::runtime_error(
942 "Memory-optimized evaluation requires single-valued coefficient");
943
944 else {
945 auto basfunc =
947
948 // Lambda expression to evaluate the spline function
949 std::function<torch::Tensor(short_t, short_t)> eval_;
950
951 eval_ = [&, this](short_t i, short_t dim) {
952 if (dim == 0) {
953 return torch::matmul(
954 coeffs(i)
956 .view({xi[0].numel(), -1, degrees_[0] + 1}),
957 basfunc[0].view({xi[0].numel(), -1, 1}));
958 } else {
959 return torch::matmul(
960 (eval_(i, dim - 1))
961 .view({xi[0].numel(), -1, degrees_[dim] + 1}),
962 basfunc[dim].view({xi[0].numel(), -1, 1}));
963 }
964 };
965
966 for (short_t i = 0; i < geoDim_; ++i)
967 result.set(i, (eval_(i, parDim_ - 1)).view(xi[0].sizes()));
968
969 return result;
970 } // coeffs(0).dim() > 1
971 }
972
973 else {
974 // not memory-optimized
975
977
978 if (coeffs(0).dim() > 1) {
979 // coeffs has extra dimension
980 auto sizes = xi[0].sizes() + (-1_i64);
981 for (short_t i = 0; i < geoDim_; ++i)
982 result.set(i, utils::dotproduct(basfunc.unsqueeze(-1),
983 coeffs(i)
985 .view({-1, xi[0].numel(),
986 coeffs(i).size(-1)}))
987 .view(sizes));
988 } else {
989 // coeffs does not have extra dimension
990 for (short_t i = 0; i < geoDim_; ++i)
992 coeffs(i)
994 .view({-1, xi[0].numel()}))
995 .view(xi[0].sizes()));
996 }
997 return result;
998 }
999 }
1000 }
1001
1020 inline auto find_knot_indices(const torch::Tensor &xi) const noexcept {
1021 if constexpr (parDim_ == 0)
1022 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1023 else
1025 }
1026
1029 if constexpr (parDim_ == 0)
1031 else {
1033
1034 for (short_t i = 0; i < parDim_; ++i)
1035 result[i] =
1036 torch::min(
1037 torch::full_like(xi[i], ncoeffs_[i] - 1, options_),
1038 torch::floor(xi[i] * (ncoeffs_[i] - degrees_[i]) + degrees_[i]))
1039 .to(torch::kInt64);
1040
1041 return result;
1042 }
1043 }
1045
1054 template <bool memory_optimized = false>
1055 inline auto find_coeff_indices(const torch::Tensor &indices) const {
1056 if constexpr (parDim_ == 0)
1057 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1058 else
1061 }
1062
1063 template <bool memory_optimized = false>
1064 inline auto
1066 using utils::operator-;
1067
1068 if constexpr (parDim_ == 0)
1069 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1070 else if constexpr (parDim_ == 1)
1071 return utils::VSlice<memory_optimized>(indices[0].flatten(), -degrees_[0],
1072 1);
1073 else {
1074 return utils::VSlice<memory_optimized>(
1076 utils::make_array<int64_t>(-degrees_),
1077 utils::make_array<int64_t, parDim_>(1),
1079 }
1080 }
1082
1092 template <deriv deriv = deriv::func, bool memory_optimized = false>
1093 inline auto eval_basfunc(const torch::Tensor &xi) const {
1094 if constexpr (parDim_ == 0) {
1095 if constexpr (deriv == deriv::func)
1096 return torch::ones_like(coeffs_[0]);
1097 else
1098 return torch::zeros_like(coeffs_[0]);
1099 } else
1101 }
1102
1103 template <deriv deriv = deriv::func, bool memory_optimized = false>
1104 inline auto eval_basfunc(const utils::TensorArray<parDim_> &xi) const {
1105 if constexpr (parDim_ == 0) {
1106 if constexpr (deriv == deriv::func)
1107 return torch::ones_like(coeffs_[0]);
1108 else
1109 return torch::zeros_like(coeffs_[0]);
1110 } else
1112 }
1114
1127 template <deriv deriv = deriv::func, bool memory_optimized = false>
1128 inline auto eval_basfunc(const torch::Tensor &xi,
1129 const torch::Tensor &knot_indices) const {
1130 if constexpr (parDim_ == 0) {
1131 if constexpr (deriv == deriv::func)
1132 return torch::ones_like(coeffs_[0]);
1133 else
1134 return torch::zeros_like(coeffs_[0]);
1135 } else
1138 }
1139
1140 template <deriv deriv = deriv::func, bool memory_optimized = false>
1141 inline auto
1144
1145 if constexpr (parDim_ == 0) {
1146 if constexpr (deriv == deriv::func)
1147 return torch::ones_like(coeffs_[0]);
1148 else
1149 return torch::zeros_like(coeffs_[0]);
1150 }
1151
1152 else {
1153 // Check compatibility of arguments
1154 for (short_t i = 0; i < parDim_; ++i)
1155 assert(xi[i].sizes() == knot_indices[i].sizes());
1156 for (short_t i = 1; i < parDim_; ++i)
1157 assert(xi[0].sizes() == xi[i].sizes());
1158
1159 if constexpr (memory_optimized) {
1160
1161 // Lambda expression to evaluate the vector of basis functions
1162 auto basfunc_ = [&,
1163 this]<std::size_t... Is>(std::index_sequence<Is...>) {
1166 (short_t)deriv /
1169 degrees_[Is], Is,
1171 xi[Is].flatten(), knot_indices[Is].flatten())
1172 .transpose(0, 1))...};
1173 };
1174
1175 return basfunc_(std::make_index_sequence<parDim_>{});
1176
1177 }
1178
1179 else /* not memory optimize */ {
1180
1181 if constexpr (parDim_ == 1) {
1184 xi[0].flatten(), knot_indices[0].flatten());
1185
1186 } else {
1187
1188 // Lambda expression to evaluate the cumulated basis function
1189 auto basfunc_ = [&, this]<std::size_t... Is>(
1190 std::index_sequence<Is...>) {
1191 return (1 * ... *
1193 (short_t)deriv /
1195 10>())) *
1198 degrees_[Is], Is,
1200 10>(xi[Is].flatten(),
1201 knot_indices[Is].flatten())...);
1202 };
1203
1204 // Note that the kronecker product must be called in reverse order
1206 }
1207 }
1208 }
1209 }
1211
1213 inline UniformBSplineCore &
1214 transform(const std::function<
1215 std::array<real_t, geoDim_>(const std::array<real_t, parDim_> &)>
1217 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1218
1219 // 0D
1220 if constexpr (parDim_ == 0) {
1221 auto c = transformation(std::array<real_t, parDim_>{});
1222 for (short_t d = 0; d < geoDim_; ++d)
1223 coeffs_[d].detach()[0] = c[d];
1224 }
1225
1226 // 1D
1227 else if constexpr (parDim_ == 1) {
1228#pragma omp parallel for
1229 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1230 auto c = transformation(
1231 std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1232 for (short_t d = 0; d < geoDim_; ++d)
1233 coeffs_[d].detach()[i] = c[d];
1234 }
1235 }
1236
1237 // 2D
1238 else if constexpr (parDim_ == 2) {
1239#pragma omp parallel for collapse(2)
1240 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1241 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1242 auto c = transformation(std::array<real_t, parDim_>{
1243 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1244 for (short_t d = 0; d < geoDim_; ++d)
1245 coeffs_[d].detach()[j * ncoeffs_[0] + i] = c[d];
1246 }
1247 }
1248 }
1249
1250 // 3D
1251 else if constexpr (parDim_ == 3) {
1252#pragma omp parallel for collapse(3)
1253 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1254 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1255 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1256 auto c = transformation(std::array<real_t, parDim_>{
1257 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1258 k / real_t(ncoeffs_[2] - 1)});
1259 for (short_t d = 0; d < geoDim_; ++d)
1260 coeffs_[d].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1261 j * ncoeffs_[0] + i] = c[d];
1262 }
1263 }
1264 }
1265 }
1266
1267 // 4D
1268 else if constexpr (parDim_ == 4) {
1269#pragma omp parallel for collapse(4)
1270 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1271 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1272 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1273 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1274 auto c = transformation(std::array<real_t, parDim_>{
1275 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1276 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1277 for (short_t d = 0; d < geoDim_; ++d)
1278 coeffs_[d]
1279 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1280 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1281 i] = c[d];
1282 }
1283 }
1284 }
1285 }
1286 } else
1287 throw std::runtime_error("Unsupported parametric dimension");
1288
1289 return *this;
1290 }
1291
1293 template <std::size_t N>
1294 inline UniformBSplineCore &
1295 transform(const std::function<
1296 std::array<real_t, N>(const std::array<real_t, parDim_> &)>
1298 std::array<short_t, N> dims) {
1299 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1300
1301 // 0D
1302 if constexpr (parDim_ == 0) {
1303 auto c = transformation(std::array<real_t, parDim_>{});
1304 for (std::size_t d = 0; d < N; ++d)
1305 coeffs_[dims[d]].detach()[0] = c[d];
1306 }
1307
1308 // 1D
1309 else if constexpr (parDim_ == 1) {
1310#pragma omp parallel for
1311 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1312 auto c = transformation(
1313 std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1314 for (std::size_t d = 0; d < N; ++d)
1315 coeffs_[dims[d]].detach()[i] = c[d];
1316 }
1317 }
1318
1319 // 2D
1320 else if constexpr (parDim_ == 2) {
1321#pragma omp parallel for collapse(2)
1322 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1323 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1324 auto c = transformation(std::array<real_t, parDim_>{
1325 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1326 for (std::size_t d = 0; d < N; ++d)
1327 coeffs_[dims[d]].detach()[j * ncoeffs_[0] + i] = c[d];
1328 }
1329 }
1330 }
1331
1332 // 3D
1333 else if constexpr (parDim_ == 3) {
1334#pragma omp parallel for collapse(3)
1335 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1336 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1337 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1338 auto c = transformation(std::array<real_t, parDim_>{
1339 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1340 k / real_t(ncoeffs_[2] - 1)});
1341 for (std::size_t d = 0; d < N; ++d)
1342 coeffs_[dims[d]].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1343 j * ncoeffs_[0] + i] = c[d];
1344 }
1345 }
1346 }
1347 }
1348
1349 // 4D
1350 else if constexpr (parDim_ == 4) {
1351#pragma omp parallel for collapse(4)
1352 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1353 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1354 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1355 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1356 auto c = transformation(std::array<real_t, parDim_>{
1357 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1358 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1359 for (std::size_t d = 0; d < N; ++d)
1360 coeffs_[dims[d]]
1361 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1362 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1363 i] = c[d];
1364 }
1365 }
1366 }
1367 }
1368 } else
1369 throw std::runtime_error("Unsupported parametric dimension");
1370
1371 return *this;
1372 }
1373
1375 inline nlohmann::json to_json() const override {
1376 nlohmann::json json;
1377 json["degrees"] = degrees_;
1378 json["geoDim"] = geoDim_;
1379 json["parDim"] = parDim_;
1380 json["ncoeffs"] = ncoeffs_;
1381 json["nknots"] = nknots_;
1382 json["knots"] = knots_to_json();
1383 json["coeffs"] = coeffs_to_json();
1384
1385 return json;
1386 }
1387
1389 inline nlohmann::json knots_to_json() const {
1390 return ::iganet::utils::to_json<real_t, 1>(knots_);
1391 }
1392
1394 inline nlohmann::json coeffs_to_json() const {
1395 auto coeffs_json = nlohmann::json::array();
1396 for (short_t g = 0; g < geoDim_; ++g) {
1397 auto [coeffs_cpu, coeffs_accessor] =
1398 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
1399
1400 auto json = nlohmann::json::array();
1401
1402 if constexpr (parDim_ == 0) {
1403 json.push_back(coeffs_accessor[0]);
1404 }
1405
1406 else {
1407 for (int64_t i = 0; i < ncumcoeffs(); ++i)
1408 json.push_back(coeffs_accessor[i]);
1409 }
1410
1411 coeffs_json.push_back(json);
1412 }
1413 return coeffs_json;
1414 }
1415
1417 inline UniformBSplineCore &from_json(const nlohmann::json &json) {
1418
1419 if (json["geoDim"].get<short_t>() != geoDim_)
1420 throw std::runtime_error(
1421 "JSON object provides incompatible geometric dimensions");
1422
1423 if (json["parDim"].get<short_t>() != parDim_)
1424 throw std::runtime_error(
1425 "JSON object provides incompatible parametric dimensions");
1426
1427 if (json["degrees"].get<std::array<short_t, parDim_>>() != degrees_)
1428 throw std::runtime_error("JSON object provides incompatible degrees");
1429
1430 nknots_ = json["nknots"].get<std::array<int64_t, parDim_>>();
1431 ncoeffs_ = json["ncoeffs"].get<std::array<int64_t, parDim_>>();
1432
1433 // Reverse ncoeffs
1435 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1436
1437 auto kv = json["knots"].get<std::array<std::vector<real_t>, parDim_>>();
1438
1439 for (short_t i = 0; i < parDim_; ++i)
1441
1442 auto c = json["coeffs"].get<std::array<std::vector<real_t>, geoDim_>>();
1443
1444 for (short_t i = 0; i < geoDim_; ++i)
1446
1447 return *this;
1448 }
1449
1451 inline pugi::xml_document to_xml(int id = 0, std::string label = "",
1452 int index = -1) const {
1453 pugi::xml_document doc;
1454 pugi::xml_node root = doc.append_child("xml");
1455 to_xml(root, id, label, index);
1456
1457 return doc;
1458 }
1459
1461 inline pugi::xml_node &to_xml(pugi::xml_node &root, int id = 0,
1462 std::string label = "", int index = -1) const {
1463 // add Geometry node
1464 pugi::xml_node geo = root.append_child("Geometry");
1465
1466 // 0D parametric dimension
1467 if constexpr (parDim_ == 0) {
1468 geo.append_attribute("type") = "Point";
1469
1470 if (id >= 0)
1471 geo.append_attribute("id") = id;
1472
1473 if (index >= 0)
1474 geo.append_attribute("index") = index;
1475
1476 if (!label.empty())
1477 geo.append_attribute("label") = label.c_str();
1478 }
1479
1480 // 1D parametric dimension
1481 else if constexpr (parDim_ == 1) {
1482 geo.append_attribute("type") = "BSpline";
1483
1484 if (id >= 0)
1485 geo.append_attribute("id") = id;
1486
1487 if (index >= 0)
1488 geo.append_attribute("index") = index;
1489
1490 if (!label.empty())
1491 geo.append_attribute("label") = label.c_str();
1492
1493 // add Basis node
1494 pugi::xml_node basis = geo.append_child("Basis");
1495 basis.append_attribute("type") = "BSplineBasis";
1496
1497 // add KnotVector node
1498 pugi::xml_node knots = basis.append_child("KnotVector");
1499 knots.append_attribute("degree") = degrees_[0];
1500
1501 std::stringstream ss;
1502 auto [knots_cpu, knots_accessor] =
1503 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
1504 for (int64_t i = 0; i < nknots_[0]; ++i)
1505 ss << std::to_string(knots_accessor[i])
1506 << (i < nknots_[0] - 1 ? " " : "");
1507 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1508 }
1509
1510 // >1D parametric dimension
1511 else {
1512 geo.append_attribute("type") =
1513 std::string("TensorBSpline").append(std::to_string(parDim_)).c_str();
1514
1515 if (id >= 0)
1516 geo.append_attribute("id") = id;
1517
1518 if (index >= 0)
1519 geo.append_attribute("index") = index;
1520
1521 if (!label.empty())
1522 geo.append_attribute("label") = label.c_str();
1523
1524 // add Basis node
1525 pugi::xml_node bases = geo.append_child("Basis");
1526 bases.append_attribute("type") = std::string("TensorBSplineBasis")
1527 .append(std::to_string(parDim_))
1528 .c_str();
1529
1530 for (short_t index = 0; index < parDim_; ++index) {
1531 pugi::xml_node basis = bases.append_child("Basis");
1532 basis.append_attribute("type") = "BSplineBasis";
1533 basis.append_attribute("index") = index;
1534
1535 // add KnotVector node
1536 pugi::xml_node knots = basis.append_child("KnotVector");
1537 knots.append_attribute("degree") = degrees_[index];
1538
1539 std::stringstream ss;
1540 auto [knots_cpu, knots_accessor] =
1541 utils::to_tensorAccessor<real_t, 1>(knots_[index], torch::kCPU);
1542 for (int64_t i = 0; i < nknots_[index]; ++i)
1543 ss << std::to_string(knots_accessor[i])
1544 << (i < nknots_[index] - 1 ? " " : "");
1545 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1546 }
1547
1548 } // parametric dimension
1549
1550 // add Coefs node
1551 pugi::xml_node coefs = geo.append_child("coefs");
1552 coefs.append_attribute("geoDim") = geoDim_;
1553
1555 utils::to_tensorAccessor<real_t, 1>(coeffs_, torch::kCPU);
1556 std::stringstream ss;
1557
1558 if constexpr (parDim_ == 0) {
1559 for (short_t g = 0; g < geoDim_; ++g)
1560 ss << std::to_string(coeffs_accessors[g][0]) << " ";
1561
1562 } else {
1563 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
1564 for (short_t g = 0; g < geoDim_; ++g)
1565 ss << std::to_string(coeffs_accessors[g][i]) << " ";
1566 }
1567
1568 coefs.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1569
1570 return root;
1571 }
1572
1574 inline UniformBSplineCore &from_xml(const pugi::xml_document &doc, int id = 0,
1575 std::string label = "", int index = -1) {
1576 return from_xml(doc.child("xml"), id, label, index);
1577 }
1578
1580 inline UniformBSplineCore &from_xml(const pugi::xml_node &root, int id = 0,
1581 std::string label = "", int index = -1) {
1582
1583 std::array<bool, std::max(parDim_, short_t{1})> nknots_found{false},
1584 ncoeffs_found{false};
1585
1586 // Loop through all geometry nodes
1587 for (pugi::xml_node geo : root.children("Geometry")) {
1588
1589 // 0D parametric dimension
1590 if constexpr (parDim_ == 0) {
1591
1592 // Check for "Point" with given id, index, label
1593 if (geo.attribute("type").value() == std::string("Point") &&
1594 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1595 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1596 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1597
1598 nknots_found[0] = true;
1599 ncoeffs_found[0] = true;
1600 } // "Point"
1601 else
1602 continue; // try next "Geometry"
1603 }
1604
1605 // 1D parametric dimension
1606 else if constexpr (parDim_ == 1) {
1607
1608 // Check for "BSpline" with given id, index, label
1609 if (geo.attribute("type").value() == std::string("BSpline") &&
1610 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1611 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1612 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1613
1614 // Check for "BSplineBasis"
1615 if (pugi::xml_node basis = geo.child("Basis");
1616 basis.attribute("type").value() == std::string("BSplineBasis")) {
1617
1618 // Check for "KnotVector"
1619 if (pugi::xml_node knots = basis.child("KnotVector");
1620 knots.attribute("degree").as_int() == degrees_[0]) {
1621
1622 std::vector<real_t> kv;
1623 std::string values = std::regex_replace(
1624 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1625 for (auto value = strtok(&values[0], " "); value != NULL;
1626 value = strtok(NULL, " "))
1627 kv.push_back(static_cast<real_t>(std::stod(value)));
1628
1630 nknots_[0] = kv.size();
1631 ncoeffs_[0] = nknots_[0] - degrees_[0] - 1;
1632
1633 nknots_found[0] = true;
1634 ncoeffs_found[0] = true;
1635
1636 } // "KnotVector"
1637
1638 } // "BSplineBasis"
1639
1640 } // "Bspline"
1641 else
1642 continue; // try next "Geometry"
1643 }
1644
1645 // >1D parametric dimension
1646 else {
1647
1648 // Check for "TensorBSpline<parDim>" with given id, index, label
1649 if (geo.attribute("type").value() ==
1650 std::string("TensorBSpline").append(std::to_string(parDim_)) &&
1651 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1652 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1653 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1654
1655 // Check for "TensorBSplineBasis<parDim>"
1656 if (pugi::xml_node bases = geo.child("Basis");
1657 bases.attribute("type").value() ==
1658 std::string("TensorBSplineBasis")
1659 .append(std::to_string(parDim_))) {
1660
1661 // Loop through all basis nodes
1662 for (pugi::xml_node basis : bases.children("Basis")) {
1663
1664 // Check for "BSplineBasis"
1665 if (basis.attribute("type").value() ==
1666 std::string("BSplineBasis")) {
1667
1668 short_t index = basis.attribute("index").as_int();
1669
1670 // Check for "KnotVector"
1671 if (pugi::xml_node knots = basis.child("KnotVector");
1672 knots.attribute("degree").as_int() == degrees_[index]) {
1673
1674 std::vector<real_t> kv;
1675 std::string values = std::regex_replace(
1676 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1677
1678 for (auto value = strtok(&values[0], " "); value != NULL;
1679 value = strtok(NULL, " "))
1680 kv.push_back(static_cast<real_t>(std::stod(value)));
1681
1683 nknots_[index] = kv.size();
1684 ncoeffs_[index] = nknots_[index] - degrees_[index] - 1;
1685
1686 nknots_found[index] = true;
1687 ncoeffs_found[index] = true;
1688
1689 } // "KnotVector"
1690
1691 } // "BSplineBasis"
1692
1693 } // "Basis"
1694
1695 } // "TensorBSplineBasis<parDim>"
1696
1697 } // "TensorBSpline<parDim>"
1698 else
1699 continue; // try next "Geometry"
1700
1701 } // parametric dimension
1702
1703 if (std::any_of(std::begin(nknots_found), std::end(nknots_found),
1704 [](bool i) { return !i; }))
1705 throw std::runtime_error(
1706 "XML object is not compatible with B-spline object");
1707
1708 // Reverse ncoeffs
1710 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1711
1712 // Fill coefficients with zeros
1713 int64_t size = ncumcoeffs();
1714 for (short_t i = 0; i < geoDim_; ++i)
1715 coeffs_[i] = torch::zeros(size, options_.device(torch::kCPU));
1716
1717 // Check for "coefs"
1718 if (pugi::xml_node coefs = geo.child("coefs")) {
1719
1720 std::string values = std::regex_replace(
1721 coefs.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1722 auto coeffs_accessors = utils::to_tensorAccessor<real_t, 1>(coeffs_);
1723
1724 if constexpr (parDim_ == 0) {
1725 auto value = strtok(&values[0], " ");
1726
1727 for (short_t g = 0; g < geoDim_; ++g) {
1728 if (value == NULL)
1729 throw std::runtime_error(
1730 "XML object does not provide enough coefficients");
1731
1732 coeffs_accessors[g][0] = static_cast<real_t>(std::stod(value));
1733 value = strtok(NULL, " ");
1734 }
1735
1736 if (value != NULL)
1737 throw std::runtime_error(
1738 "XML object provides too many coefficients");
1739
1740 } else {
1741 auto value = strtok(&values[0], " ");
1742
1743 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
1744 for (short_t g = 0; g < geoDim_; ++g) {
1745 if (value == NULL)
1746 throw std::runtime_error(
1747 "XML object does not provide enough coefficients");
1748
1749 coeffs_accessors[g][i] = static_cast<real_t>(std::stod(value));
1750 value = strtok(NULL, " ");
1751 }
1752
1753 if (value != NULL)
1754 throw std::runtime_error(
1755 "XML object provides too many coefficients");
1756 }
1757
1758 // Copy coefficients to device (if needed)
1759 for (short_t i = 0; i < geoDim_; ++i)
1760 coeffs_[i] = coeffs_[i].to(options_.device());
1761
1762 if constexpr (parDim_ == 0) {
1763 if (nknots_found[0] && ncoeffs_found[0])
1764 return *this;
1765 } else if (std::all_of(std::begin(nknots_found), std::end(nknots_found),
1766 [](bool i) { return i; }) &&
1767 std::all_of(std::begin(ncoeffs_found),
1768 std::end(ncoeffs_found),
1769 [](bool i) { return i; }))
1770 return *this;
1771
1772 else
1773 throw std::runtime_error(
1774 "XML object is not compatible with B-spline object");
1775
1776 } // Coefs
1777 else
1778 throw std::runtime_error("XML object does not provide coefficients");
1779
1780 } // "Geometry"
1781
1782 throw std::runtime_error("XML object does not provide geometry with given "
1783 "id, index, and/or label");
1784 return *this;
1785 }
1786
1788 inline void load(const std::string &filename,
1789 const std::string &key = "bspline") {
1790 torch::serialize::InputArchive archive;
1791 archive.load_from(filename);
1792 read(archive, key);
1793 }
1794
1796 inline torch::serialize::InputArchive &
1797 read(torch::serialize::InputArchive &archive,
1798 const std::string &key = "bspline") {
1799 torch::Tensor tensor;
1800
1801 archive.read(key + ".parDim", tensor);
1802 if (tensor.item<int64_t>() != parDim_)
1803 throw std::runtime_error("parDim mismatch");
1804
1805 archive.read(key + ".geoDim", tensor);
1806 if (tensor.item<int64_t>() != geoDim_)
1807 throw std::runtime_error("geoDim mismatch");
1808
1809 for (short_t i = 0; i < parDim_; ++i) {
1810 archive.read(key + ".degree[" + std::to_string(i) + "]", tensor);
1811 if (tensor.item<int64_t>() != degrees_[i])
1812 throw std::runtime_error("degrees mismatch");
1813 }
1814
1815 for (short_t i = 0; i < parDim_; ++i) {
1816 archive.read(key + ".nknots[" + std::to_string(i) + "]", tensor);
1817 nknots_[i] = tensor.item<int64_t>();
1818 }
1819
1820 for (short_t i = 0; i < parDim_; ++i)
1821 archive.read(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
1822
1823 for (short_t i = 0; i < parDim_; ++i) {
1824 archive.read(key + ".ncoeffs[" + std::to_string(i) + "]", tensor);
1825 ncoeffs_[i] = tensor.item<int64_t>();
1826 }
1827
1828 for (short_t i = 0; i < geoDim_; ++i)
1829 archive.read(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
1830
1831 return archive;
1832 }
1833
1835 inline void save(const std::string &filename,
1836 const std::string &key = "bspline") const {
1837 torch::serialize::OutputArchive archive;
1838 write(archive, key).save_to(filename);
1839 }
1840
1842 inline torch::serialize::OutputArchive &
1843 write(torch::serialize::OutputArchive &archive,
1844 const std::string &key = "bspline") const {
1845 archive.write(key + ".parDim", torch::full({1}, parDim_));
1846 archive.write(key + ".geoDim", torch::full({1}, geoDim_));
1847
1848 for (short_t i = 0; i < parDim_; ++i)
1849 archive.write(key + ".degree[" + std::to_string(i) + "]",
1850 torch::full({1}, degrees_[i]));
1851
1852 for (short_t i = 0; i < parDim_; ++i)
1853 archive.write(key + ".nknots[" + std::to_string(i) + "]",
1854 torch::full({1}, nknots_[i]));
1855
1856 for (short_t i = 0; i < parDim_; ++i)
1857 archive.write(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
1858
1859 for (short_t i = 0; i < parDim_; ++i)
1860 archive.write(key + ".ncoeffs[" + std::to_string(i) + "]",
1861 torch::full({1}, ncoeffs_[i]));
1862
1863 for (short_t i = 0; i < geoDim_; ++i)
1864 archive.write(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
1865
1866 return archive;
1867 }
1868
1871 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
1873 real_t rtol = real_t{1e-5}, real_t atol = real_t{1e-8}) const {
1874 if constexpr (!std::is_same<real_t, other_t>::value)
1875 return false;
1876 bool result(true);
1877
1878 result *= (parDim_ == other.parDim());
1879 result *= (geoDim_ == other.geoDim());
1880
1881 for (short_t i = 0; i < parDim_; ++i)
1882 result *= (degree(i) == other.degree(i));
1883
1884 for (short_t i = 0; i < parDim_; ++i)
1885 result *= (nknots(i) == other.nknots(i));
1886
1887 for (short_t i = 0; i < parDim_; ++i)
1888 result *= (ncoeffs(i) == other.ncoeffs(i));
1889
1890 if (!result)
1891 return result;
1892
1893 for (short_t i = 0; i < parDim_; ++i)
1894 result *= torch::allclose(knots(i), other.knots(i), rtol, atol);
1895
1896 for (short_t i = 0; i < geoDim_; ++i)
1897 result *= torch::allclose(coeffs(i), other.coeffs(i), rtol, atol);
1898
1899 return result;
1900 }
1901
1903 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
1906 if constexpr (!std::is_same<real_t, other_t>::value)
1907 return false;
1908 bool result(true);
1909
1910 result *= (parDim_ == other.parDim());
1911 result *= (geoDim_ == other.geoDim());
1912
1913 if (!result)
1914 return result;
1915
1916 for (short_t i = 0; i < parDim_; ++i)
1917 result *= (degree(i) == other.degree(i));
1918
1919 for (short_t i = 0; i < parDim_; ++i)
1920 result *= (nknots(i) == other.nknots(i));
1921
1922 for (short_t i = 0; i < parDim_; ++i)
1923 result *= (ncoeffs(i) == other.ncoeffs(i));
1924
1925 for (short_t i = 0; i < parDim_; ++i)
1926 result *= torch::equal(knots(i), other.knots(i));
1927
1928 for (short_t i = 0; i < geoDim_; ++i)
1929 result *= torch::equal(coeffs(i), other.coeffs(i));
1930
1931 return result;
1932 }
1933
1935 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
1938 return !(
1939 *this ==
1940 other); // Do not change this to (*this != other) is it does not work
1941 }
1942
1949 inline UniformBSplineCore &uniform_refine(int numRefine = 1, int dim = -1) {
1950 assert(numRefine > 0);
1951 assert(dim == -1 || (dim >= 0 && dim < parDim_));
1952
1953 // Update number of knots and coefficients
1954 std::array<int64_t, parDim_> nknots(nknots_);
1955 std::array<int64_t, parDim_> ncoeffs(ncoeffs_);
1956
1957 for (short_t refine = 0; refine < numRefine; ++refine) {
1958 if (dim == -1)
1959 for (short_t i = 0; i < parDim_; ++i) {
1960 ncoeffs[i] += nknots[i] - 2 * degrees_[i] - 1; // must be done first
1961 nknots[i] += nknots[i] - 2 * degrees_[i] - 1;
1962 }
1963 else {
1964 ncoeffs[dim] +=
1965 nknots[dim] - 2 * degrees_[dim] - 1; // must be done first
1966 nknots[dim] += nknots[dim] - 2 * degrees_[dim] - 1;
1967 }
1968 }
1969
1970 // Update knot vectors
1972
1973 for (short_t i = 0; i < parDim_; ++i) {
1974 std::vector<real_t> kv;
1975 kv.reserve(nknots[i]);
1976
1977 for (int64_t j = 0; j < degrees_[i]; ++j)
1978 kv.push_back(static_cast<real_t>(0));
1979
1980 for (int64_t j = 0; j < ncoeffs[i] - degrees_[i] + 1; ++j)
1981 kv.push_back(static_cast<real_t>(j) /
1982 static_cast<real_t>(ncoeffs[i] - degrees_[i]));
1983
1984 for (int64_t j = 0; j < degrees_[i]; ++j)
1985 kv.push_back(static_cast<real_t>(1));
1986
1988 }
1989
1990 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
1991 // \f$m_d\f$ is the number of coefficients after the update. To
1992 // update the coefficients using the Oslo algorithm (Algorithm
1993 // 4.11 from \cite Lyche:2011) we need to neglect the last
1994 // \f$p_d+1\f$ knots in what follows
1995 for (short_t i = 0; i < parDim_; ++i)
1996 knots_indices[i] = knots[i].index(
1997 {torch::indexing::Slice(0, knots[i].numel() - degrees_[i] - 1)});
1998
1999 // Get indices of the first \f$m_d\f$ new knots relative to old
2000 // knot vectors
2002
2003 // Update coefficient vector
2005
2006 // Swap old and new data
2007 knots.swap(knots_);
2008 nknots.swap(nknots_);
2009 ncoeffs.swap(ncoeffs_);
2010
2012 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2013
2014 return *this;
2015 }
2016
2017private:
2021 inline int64_t constexpr eval_prefactor() const {
2022 if constexpr (degree > terminal)
2024 else
2025 return 1;
2026 }
2027
2028public:
2030 inline void init_knots() {
2031
2032 for (short_t i = 0; i < parDim_; ++i) {
2033
2034 // Check that open knot vector can be created
2035 if ((ncoeffs_[i] < degrees_[i] + 1) || (ncoeffs_[i] < 2))
2036 throw std::runtime_error(
2037 "Not enough coefficients to create open knot vector");
2038
2039 nknots_[i] = ncoeffs_[i] + degrees_[i] + 1;
2041
2042 auto start = torch::zeros({degrees_[i]}, options_);
2043 auto end = torch::ones({degrees_[i]}, options_);
2044 auto inner = torch::empty({0}, options_);
2045
2046 if (num_inner > 0) {
2047 inner = torch::arange(0, num_inner + 1, options_);
2048 inner = inner / static_cast<real_t>(num_inner);
2049 }
2050
2051 knots_[i] = torch::cat({start, inner, end});
2052 }
2053
2054 }
2055
2057 inline void init_coeffs(enum init init) {
2058 switch (init) {
2059
2060 case (init::none): {
2061 break;
2062 }
2063
2064 case (init::zeros): {
2065
2066 // Fill coefficients with zeros
2067 int64_t size = ncumcoeffs();
2068 for (short_t i = 0; i < geoDim_; ++i)
2069 coeffs_[i] = torch::zeros(size, options_);
2070
2071 break;
2072 }
2073
2074 case (init::ones): {
2075
2076 // Fill coefficients with ones
2077 int64_t size = ncumcoeffs();
2078 for (short_t i = 0; i < geoDim_; ++i)
2079 coeffs_[i] = torch::ones(size, options_);
2080
2081 break;
2082 }
2083
2084 case (init::random): {
2085
2086 // Fill coefficients with random values
2087 int64_t size = ncumcoeffs();
2088 for (short_t i = 0; i < geoDim_; ++i)
2089 coeffs_[i] = torch::rand(size, options_);
2090
2091 break;
2092 }
2093
2094 case (init::linear): {
2095
2096 // Fill coefficients with the tensor-product of linearly
2097 // increasing values between 0 and 1 per univariate dimension
2098 for (short_t i = 0; i < geoDim_; ++i) {
2099 coeffs_[i] = torch::ones(1, options_);
2100
2101 for (short_t j = 0; j < parDim_; ++j) {
2102 if (i == j)
2103 coeffs_[i] = torch::kron(torch::linspace(static_cast<real_t>(0),
2104 static_cast<real_t>(1),
2105 ncoeffs_[j], options_),
2106 coeffs_[i]);
2107 else
2108 coeffs_[i] =
2109 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2110 }
2111
2112 // Enable gradient calculation for non-leaf tensor
2113 if (options_.requires_grad())
2114 coeffs_[i].retain_grad();
2115 }
2116 break;
2117 }
2118
2119 case (init::greville): {
2120
2121 // Fill coefficients with the tensor-product of Greville
2122 // abscissae values per univariate dimension
2123 for (short_t i = 0; i < geoDim_; ++i) {
2124 coeffs_[i] = torch::ones(1, options_);
2125
2126 for (short_t j = 0; j < parDim_; ++j) {
2127 if (i == j) {
2128
2130
2131 // idx_base: (count, 1)
2132 auto idx_base = torch::arange(count, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(1);
2133
2134 // offsets: (1, degree)
2135 auto offsets = torch::arange(1, degrees_[j] + 1, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(0);
2136
2137 // indices: (count, degree)
2138 auto indices = idx_base + offsets;
2139
2140 // Gather relevant knot values: shape (count, degree)
2141 auto gathered = knots_[j].index_select(0, indices.flatten()).view({count, degrees_[j]});
2142
2143 // Compute mean along degree dimension (dim=1)
2144 auto greville_ = gathered.mean(1);
2145
2146 coeffs_[i] = torch::kron(greville_, coeffs_[i]);
2147 } else
2148 coeffs_[i] =
2149 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2150 }
2151
2152 // Enable gradient calculation for non-leaf tensor
2153 if (options_.requires_grad())
2154 coeffs_[i].retain_grad();
2155 }
2156 break;
2157 }
2158
2159 case (init::linspace): {
2160
2161 // Fill coefficients with increasing values
2162 int64_t size = ncumcoeffs();
2163 for (short_t i = 0; i < geoDim_; ++i)
2164 coeffs_[i] = torch::linspace(
2165 std::pow(10, i) * 0, std::pow(10, i) * (size - 1), size, options_);
2166
2167 break;
2168 }
2169
2170 default:
2171 throw std::runtime_error("Unsupported init option");
2172 }
2173 }
2174
2175protected:
2179
2180 // Check compatibility of arguments
2181 for (short_t i = 0; i < parDim_; ++i)
2182 assert(knots[i].numel() == knot_indices[i].numel() + degrees_[i] + 1);
2183
2184 if constexpr (parDim_ == 1) {
2185
2187 knots[0].flatten(), knot_indices[0].flatten());
2188
2190
2191 for (short_t i = 0; i < geoDim_; ++i)
2192 coeffs(i) =
2195 .view({-1, knot_indices[0].numel()}))
2196 .view(knot_indices[0].sizes());
2197
2198 } else {
2199
2200 // Lambda expressions to evaluate the basis functions
2201 auto basfunc_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
2202 if constexpr (sizeof...(Is) == 1)
2204 knots[Is].flatten(), knot_indices[Is].flatten()),
2205 ...);
2206 else
2208 knots[Is].flatten(), knot_indices[Is].flatten())...);
2209 };
2210
2212
2213 // Lambda expression to calculate the partial product of array
2214 // entry from start_index to stop_index (including the latter)
2217 int64_t result{1};
2218 for (short_t i = start_index; i <= stop_index; ++i)
2219 result *= array[i].numel();
2220 return result;
2221 };
2222
2223 utils::TensorArray<parDim_> knot_indices_;
2224
2225 for (short_t i = 0; i < parDim_; ++i)
2226 knot_indices_[i] =
2229 .repeat(prod_(knot_indices, i + 1, parDim_ - 1));
2230
2231 auto coeff_indices = find_coeff_indices(knot_indices_);
2232
2233 for (short_t i = 0; i < geoDim_; ++i)
2235 coeffs(i)
2237 .view({-1, knot_indices_[0].numel()}))
2238 .view(knot_indices_[0].sizes());
2239 }
2240 }
2241
2242 // clang-format off
2346 // clang-format on
2347 template <short_t degree, short_t dim, short_t deriv>
2348 inline auto eval_basfunc_univariate(const torch::Tensor &xi,
2349 const torch::Tensor &knot_indices) const {
2350 assert(xi.sizes() == knot_indices.sizes());
2351
2352 if constexpr (deriv > degree) {
2353 return torch::zeros({degree + 1, xi.numel()}, options_);
2354 } else {
2355 // Algorithm 2.22 from \cite Lyche:2011
2356 torch::Tensor b = torch::ones({xi.numel()}, options_);
2357
2358 // Calculate R_k, k = 1, ..., p_d-r_d
2359 for (short_t k = 1; k <= degree - deriv; ++k) {
2360
2361 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2362 auto t1 =
2363 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2364 auto t21 =
2365 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2366 t1;
2367
2368 // We handle the special case 0/0:=0 by first creating a
2369 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2370 // we do not have to take the absolute value as t2 >= t1.
2373
2374 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2375 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2376 // equals the original expression if the mask is 0, i.e.,
2377 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2378 auto w = torch::div(xi.repeat(k) - t1 - mask, t21 - mask);
2379
2380 // Calculate the vector of B-splines evaluated at xi
2381 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2382 torch::zeros_like(xi, options_)},
2383 0) +
2384 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2385 }
2386
2387 // Calculate DR_k, k = p_d-r_d+1, ..., p_d
2388 for (short_t k = degree - deriv + 1; k <= degree; ++k) {
2389
2390 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2391 auto t21 =
2392 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2393 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2394
2395 // We handle the special case 0/0:=0 by first creating a
2396 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2397 // we do not have to take the absolute value as t2 >= t1.
2400
2401 // Instead of computing 1/(t2-t1) which is prone to yielding
2402 // 0/0 we compute (1-mask)/(t2-t1-mask) which equals the
2403 // original expression if the mask is 0, i.e., t2-t1 >= eps
2404 // and 1 otherwise since t1 <= xi < t2.
2405 auto w = torch::div(torch::ones_like(t21, options_) - mask, t21 - mask);
2406
2407 // Calculate the vector of B-splines evaluated at xi
2408 b = torch::cat({torch::mul(-w, b), torch::zeros_like(xi, options_)},
2409 0) +
2410 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2411 }
2412
2413 return b.view({degree + 1, xi.numel()});
2414 }
2415 }
2416
2423 template <short_t degree, short_t dim>
2424 inline auto
2425 update_coeffs_univariate(const torch::Tensor &knots,
2426 const torch::Tensor &knot_indices) const {
2427 // Algorithm 2.22 from \cite Lyche:2011 modified to implement
2428 // the Oslo algorithm (Algorithm 4.11 from \cite Lyche:2011)
2429 torch::Tensor b = torch::ones({knot_indices.numel()}, options_);
2430
2431 // Calculate R_k, k = 1, ..., p_d
2432 for (short_t k = 1; k <= degree; ++k) {
2433
2434 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2435 auto t1 =
2436 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2437 auto t21 =
2438 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2439 t1;
2440
2441 // We handle the special case 0/0:=0 by first creating a
2442 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2443 // we do not have to take the absolute value as t2 >= t1.
2446
2447 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2448 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2449 // equals the original expression if the mask is 0, i.e.,
2450 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2451 auto w = torch::div(
2452 knots.index({torch::indexing::Slice(k, knot_indices.numel() + k)})
2453 .repeat(k) -
2454 t1 - mask,
2455 t21 - mask);
2456
2457 // Calculate the vector of B-splines evaluated at xi
2458 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2459 torch::zeros_like(knot_indices, options_)},
2460 0) +
2461 torch::cat(
2462 {torch::zeros_like(knot_indices, options_), torch::mul(w, b)}, 0);
2463 }
2464
2465 return b.view({degree + 1, knot_indices.numel()});
2466 }
2467
2468public:
2472 auto to_gismo() const {
2473
2474#ifdef IGANET_WITH_GISMO
2475
2476 gismo::gsMatrix<real_t> coefs(ncumcoeffs(), geoDim_);
2477
2478 for (short_t g = 0; g < geoDim_; ++g) {
2479 auto [coeffs_cpu, coeffs_accessor] =
2480 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2481 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2482 coefs.col(g) =
2483 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2484 }
2485
2486 std::array<gismo::gsKnotVector<real_t>, parDim_> kv;
2487
2488 for (short_t i = 0; i < parDim_; ++i) {
2489 auto [knots_cpu, knots_accessor] =
2490 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
2491 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2492 kv[i] = gismo::gsKnotVector<real_t>(degrees_[i], knots_cpu_ptr,
2493 knots_cpu_ptr + knots_cpu.size(0));
2494 }
2495
2496 if constexpr (parDim_ == 1) {
2497
2498 return gismo::gsBSpline<real_t>(gismo::give(kv[0]), gismo::give(coefs));
2499
2500 } else if constexpr (parDim_ == 2) {
2501
2502 return gismo::gsTensorBSpline<parDim_, real_t>(
2503 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(coefs));
2504
2505 } else if constexpr (parDim_ == 3) {
2506
2507 return gismo::gsTensorBSpline<parDim_, real_t>(
2508 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
2509 gismo::give(coefs));
2510
2511 } else if constexpr (parDim_ == 4) {
2512
2513 return gismo::gsTensorBSpline<parDim_, real_t>(
2514 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
2515 gismo::give(kv[3]), gismo::give(coefs));
2516
2517 } else
2518 throw std::runtime_error("Invalid parametric dimension");
2519
2520#else
2521 throw std::runtime_error(
2522 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2523#endif
2524 }
2525
2526#ifdef IGANET_WITH_GISMO
2527
2528 // @brief Updates a given gsBSpline object from the B-spline object
2529 gismo::gsBSpline<real_t> &to_gismo(gismo::gsBSpline<real_t> &bspline,
2530 bool updateKnotVector = true,
2531 bool updateCoeffs = true) const {
2532
2533 if (updateKnotVector) {
2534
2535 if constexpr (parDim_ == 1) {
2536
2537 if (bspline.degree(0) != degrees_[0])
2538 throw std::runtime_error("Degrees mismatch");
2539
2540 auto [knots_cpu, knots_accessor] =
2541 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
2542 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2543
2544 gismo::gsKnotVector<real_t> kv(degrees_[0], knots_cpu_ptr,
2545 knots_cpu_ptr + knots_cpu.size(0));
2546
2547 bspline.knots(0).swap(kv);
2548
2549 } else
2550 throw std::runtime_error("Invalid parametric dimension");
2551 }
2552
2553 if (updateCoeffs) {
2554
2555 for (short_t g = 0; g < geoDim_; ++g) {
2556 auto [coeffs_cpu, coeffs_accessor] =
2557 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2558 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2559 bspline.coefs().col(g) =
2560 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2561 }
2562 }
2563
2564 return bspline;
2565 }
2566
2567 // @brief Updates a given gsTensorBSpline object from the B-spline object
2568 gismo::gsTensorBSpline<parDim_, real_t> &
2569 to_gismo(gismo::gsTensorBSpline<parDim_, real_t> &bspline,
2570 bool updateKnotVector = true, bool updateCoeffs = true) const {
2571
2572 if (updateKnotVector) {
2573
2574 // Check compatibility of arguments
2575 for (short_t i = 0; i < parDim_; ++i)
2576 assert(bspline.degree(i) == degrees_[i]);
2577
2578 for (short_t i = 0; i < parDim_; ++i) {
2579 auto [knots_cpu, knots_accessor] =
2580 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
2581 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2582
2583 gismo::gsKnotVector<real_t> kv(degrees_[i], knots_cpu_ptr,
2584 knots_cpu_ptr + knots_cpu.size(0));
2585 bspline.knots(i).swap(kv);
2586 }
2587 }
2588
2589 if (updateCoeffs) {
2590
2591 for (short_t g = 0; g < geoDim_; ++g) {
2592 auto [coeffs_cpu, coeffs_accessor] =
2593 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2594 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2595 bspline.coefs().col(g) =
2596 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2597 }
2598 }
2599
2600 return bspline;
2601 }
2602
2603#else // IGANET_WITH_GISMO
2604
2605 template <typename BSpline>
2606 BSpline &to_gismo(BSpline &bspline, bool, bool) const {
2607 throw std::runtime_error(
2608 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2609 return bspline;
2610 }
2611
2612#endif // IGANET_WITH_GISMO
2613
2614#ifdef IGANET_WITH_GISMO
2615
2616 // @brief Updates the B-spline object from a given gsBSpline object
2617 auto &from_gismo(const gismo::gsBSpline<real_t> &bspline,
2618 bool updateCoeffs = true, bool updateKnotVector = false) {
2619
2620 if (updateKnotVector) {
2621
2622 throw std::runtime_error(
2623 "Knot vectors can only be updated for Non-uniform B-splines");
2624 }
2625
2626 if (updateCoeffs) {
2627
2628 if (bspline.coefs().cols() != geoDim_)
2629 throw std::runtime_error("Geometric dimensions mismatch");
2630
2631 if (bspline.coefs().rows() != ncumcoeffs())
2632 throw std::runtime_error("Coefficient vector dimensions mismatch");
2633
2634 for (short_t g = 0; g < geoDim_; ++g) {
2635
2636 auto [coeffs_cpu, coeffs_accessor] =
2637 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2638
2639 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
2640
2641 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
2643
2644 coeffs_[g] = coeffs_[g].to(options_.device());
2645 }
2646 }
2647
2648 return *this;
2649 }
2650
2651 // @brief Updates the B-spline object from a given gsTensorBSpline object
2652 auto &from_gismo(const gismo::gsTensorBSpline<parDim_, real_t> &bspline,
2653 bool updateCoeffs = true, bool updateKnotVector = false) {
2654
2655 if (updateKnotVector) {
2656
2657 throw std::runtime_error(
2658 "Knot vectors can only be updated for Non-uniform B-splines");
2659 }
2660
2661 if (updateCoeffs) {
2662
2663 if (bspline.coefs().cols() != geoDim_)
2664 throw std::runtime_error("Geometric dimensions mismatch");
2665
2666 if (bspline.coefs().rows() != ncumcoeffs())
2667 throw std::runtime_error("Coefficient vector dimensions mismatch");
2668
2669 for (short_t g = 0; g < geoDim_; ++g) {
2670
2671 auto [coeffs_cpu, coeffs_accessor] =
2672 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2673
2674 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
2675
2676 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
2678
2679 coeffs_[g] = coeffs_[g].to(options_.device());
2680 }
2681 }
2682
2683 return *this;
2684 }
2685
2686#else // IGANET_WITH_GISMO
2687
2688 template <typename BSpline> auto &from_gismo(BSpline &bspline, bool, bool) {
2689 throw std::runtime_error(
2690 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2691 return *this;
2692 }
2693
2694#endif // IGANET_WITH_GISMO
2695};
2696
2698template <typename real_t, short_t GeoDim, short_t... Degrees>
2699inline torch::serialize::OutputArchive &
2700operator<<(torch::serialize::OutputArchive &archive,
2702 return obj.write(archive);
2703}
2704
2706template <typename real_t, short_t GeoDim, short_t... Degrees>
2707inline torch::serialize::InputArchive &
2708operator>>(torch::serialize::InputArchive &archive,
2710 return obj.read(archive);
2711}
2712
2718template <typename real_t, short_t GeoDim, short_t... Degrees>
2720 : public UniformBSplineCore<real_t, GeoDim, Degrees...> {
2721private:
2724
2725public:
2727 using value_type = real_t;
2728
2734 template <template <typename, short_t, short_t...> class BSpline,
2735 std::make_signed<short_t>::type degree_elevate = 0>
2737
2741 using self_type = typename Base::template derived_type<NonUniformBSplineCore,
2743
2747 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2750
2753 template <typename other_t>
2756
2758 static constexpr bool is_uniform() { return false; }
2759
2761 static constexpr bool is_nonuniform() { return true; }
2762
2764 using UniformBSplineCore<real_t, GeoDim, Degrees...>::UniformBSplineCore;
2765
2773 NonUniformBSplineCore(const std::array<std::vector<typename Base::value_type>,
2774 Base::parDim_> &kv,
2775 enum init init = init::greville,
2777 : Base(options) {
2778 init_knots(kv);
2780 }
2781
2795 NonUniformBSplineCore(const std::array<std::vector<typename Base::value_type>,
2796 Base::parDim_> &kv,
2798 bool clone = false,
2800 : Base(options) {
2801 init_knots(kv);
2802
2803 // Copy/clone coefficients
2804 if (clone)
2805 for (short_t i = 0; i < Base::geoDim_; ++i)
2807 .clone()
2808 .to(options.requires_grad(false))
2809 .requires_grad_(Base::options.requires_grad());
2810 else
2811 for (short_t i = 0; i < Base::geoDim_; ++i)
2812 Base::coeffs_[i] = coeffs[i];
2813 }
2814
2815private:
2817 inline void init_knots(
2818 const std::array<std::vector<typename Base::value_type>, Base::parDim_>
2819 &kv) {
2820 for (short_t i = 0; i < Base::parDim_; ++i) {
2821
2822 // Check that knot vector has enough (n+p+1) entries
2823 if (2 * Base::degrees_[i] > kv[i].size() - 2)
2824 throw std::runtime_error("Knot vector is too short for an open knot "
2825 "vector (n+p+1 > 2*(p+1))");
2826
2828 Base::nknots_[i] = Base::knots_[i].size(0);
2831 }
2832 // Reverse ncoeffs
2833 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
2834 }
2835
2836public:
2840 template <deriv deriv = deriv::func, bool memory_optimized = false>
2841 inline auto eval(const torch::Tensor &xi) const {
2843 }
2844
2845 template <deriv deriv = deriv::func, bool memory_optimized = false>
2846 inline auto eval(const utils::TensorArray<Base::parDim_> &xi) const {
2847 if constexpr (Base::parDim_ == 0) {
2849 for (short_t i = 0; i < Base::geoDim_; ++i)
2850 if constexpr (deriv == deriv::func)
2851 result.set(i, Base::coeffs_[i]);
2852 else
2853 result.set(i, torch::zeros_like(Base::coeffs_[i]));
2854 return result;
2855 } else
2856 return Base::template eval<deriv, memory_optimized>(
2858 }
2859
2860 template <deriv deriv = deriv::func, bool memory_optimized = false>
2861 inline auto
2864 if constexpr (Base::parDim_ == 0) {
2866 for (short_t i = 0; i < Base::geoDim_; ++i)
2867 if constexpr (deriv == deriv::func)
2868 result.set(i, Base::coeffs_[i]);
2869 else
2870 result.set(i, torch::zeros_like(Base::coeffs_[i]));
2871 return result;
2872 } else
2873 return Base::template eval<deriv, memory_optimized>(xi, knot_indices);
2874 }
2875
2876 template <deriv deriv = deriv::func, bool memory_optimized = false>
2879 const torch::Tensor &coeff_indices) const {
2880 if constexpr (Base::parDim_ == 0) {
2882 for (short_t i = 0; i < Base::geoDim_; ++i)
2883 if constexpr (deriv == deriv::func)
2884 result.set(i, Base::coeffs_[i]);
2885 else
2886 result.set(i, torch::zeros_like(Base::coeffs_[i]));
2887 return result;
2888 } else
2889 return Base::template eval<deriv, memory_optimized>(xi, knot_indices,
2891 }
2893
2907 inline auto find_knot_indices(const torch::Tensor &xi) const {
2908 if constexpr (Base::parDim_ == 0)
2909 return torch::zeros_like(Base::coeffs_[0]).to(torch::kInt64);
2910 else
2912 }
2913
2914 inline auto
2916
2918 for (short_t i = 0; i < Base::parDim_; ++i) {
2919 auto nnz = Base::knots_[i].repeat({xi[i].numel(), 1}) >
2920 xi[i].flatten().view({-1, 1});
2921 indices[i] =
2922 torch::remainder(std::get<1>(((nnz.cumsum(1) == 1) & nnz).max(1)) - 1,
2924 .view(xi[i].sizes());
2925 }
2926 return indices;
2927 }
2929
2937 int dim = -1) {
2938 assert(numRefine > 0);
2939 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
2940
2941 // Update knot vectors, number of knots and coefficients
2942 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
2944
2945 for (short_t i = 0; i < Base::parDim_; ++i) {
2946 auto [kv_cpu, kv_accessor] =
2947 utils::to_tensorAccessor<typename Base::value_type, 1>(
2948 Base::knots_[i], torch::kCPU);
2949
2950 std::vector<typename Base::value_type> kv;
2951 kv.reserve(Base::nknots_[i]);
2952 kv.push_back(kv_accessor[0]);
2953
2954 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
2955
2956 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]))
2957 for (short_t refine = 1; refine < (2 << (numRefine - 1)); ++refine)
2958 kv.push_back(kv_accessor[j - 1] +
2959 static_cast<typename Base::value_type>(refine) /
2960 static_cast<typename Base::value_type>(
2961 2 << (numRefine - 1)) *
2962 (kv_accessor[j] - kv_accessor[j - 1]));
2963
2964 kv.push_back(kv_accessor[j]);
2965 }
2966
2968 nknots[i] = kv.size();
2969 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
2970 }
2971
2972 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
2973 // \f$m_d\f$ is the number of coefficients after the update. To
2974 // update the coefficients using the Oslo algorithm (Algorithm
2975 // 4.11 from \cite Lyche:2011) we need to neglect the last
2976 // \f$p_d+1\f$ knots in what follows
2977 for (short_t i = 0; i < Base::parDim_; ++i)
2978 knots_indices[i] = knots[i].index({torch::indexing::Slice(
2979 0, knots[i].numel() - Base::degrees_[i] - 1)});
2980
2981 // Get indices of the first \f$m_d\f$ new knots relative to old
2982 // knot vectors
2984
2985 // Update coefficient vector
2987
2988 // Swap old and new data
2989 knots.swap(Base::knots_);
2990 nknots.swap(Base::nknots_);
2991 ncoeffs.swap(Base::ncoeffs_);
2992
2994 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
2995
2996 return *this;
2997 }
2998
3001 inline NonUniformBSplineCore &
3003 std::array<int64_t, Base::parDim_> nknots(Base::nknots_);
3004 std::array<int64_t, Base::parDim_> ncoeffs(Base::ncoeffs_);
3006
3007 // Update number of knots and coefficients and generate new knot
3008 // vectors
3009 for (short_t i = 0; i < Base::parDim_; ++i) {
3010 nknots[i] += knots[i].numel();
3011 ncoeffs[i] += knots[i].numel();
3012 knots_[i] =
3013 std::get<0>(torch::sort(torch::cat({Base::knots_[i], knots[i]})));
3014 }
3015
3016 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3017 // \f$m_d\f$ is the number of coefficients after the update. To
3018 // update the coefficients using the Oslo algorithm (Algorithm
3019 // 4.11 from \cite Lyche:2011) we need to neglect the last
3020 // \f$p_d+1\f$ knots in what follows
3021 for (short_t i = 0; i < Base::parDim_; ++i)
3022 knots_indices[i] = knots_[i].index({torch::indexing::Slice(
3023 0, knots_[i].numel() - Base::degrees_[i] - 1)});
3024
3025 // Get indices of the first \f$m_d\f$ new knots relative to old
3026 // knot vectors
3028
3029 // Update coefficient vector
3031
3032 // Swap old and new data
3033 knots_.swap(Base::knots_);
3034 nknots.swap(Base::nknots_);
3035 ncoeffs.swap(Base::ncoeffs_);
3036
3038 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3039
3040 return *this;
3041 }
3042
3046 int dim = -1) {
3047 assert(numReduce > 0);
3048 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
3049
3050 // Update knot vectors, number of knots and coefficients
3051 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
3053
3054 for (short_t i = 0; i < Base::parDim_; ++i) {
3055 auto [kv_cpu, kv_accessor] =
3056 utils::to_tensorAccessor<typename Base::value_type, 1>(
3057 Base::knots_[i], torch::kCPU);
3058
3059 std::vector<typename Base::value_type> kv;
3060 kv.reserve(Base::nknots_[i]);
3061 kv.push_back(kv_accessor[0]);
3062
3063 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
3064
3065 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]) &&
3066 (kv_accessor[j] < kv_accessor[kv_accessor.size(0) - 1]))
3067 for (short_t reduce = 0; reduce < numReduce; ++reduce)
3068 kv.push_back(kv_accessor[j]);
3069
3070 kv.push_back(kv_accessor[j]);
3071 }
3072
3074 nknots[i] = kv.size();
3075 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
3076 }
3077
3078 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3079 // \f$m_d\f$ is the number of coefficients after the update. To
3080 // update the coefficients using the Oslo algorithm (Algorithm
3081 // 4.11 from \cite Lyche:2011) we need to neglect the last
3082 // \f$p_d+1\f$ knots in what follows
3083 for (short_t i = 0; i < Base::parDim_; ++i)
3084 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3085 0, knots[i].numel() - Base::degrees_[i] - 1)});
3086
3087 // Get indices of the first \f$m_d\f$ new knots relative to old
3088 // knot vectors
3090
3091 // Update coefficient vector
3093
3094 // Swap old and new data
3095 knots.swap(Base::knots_);
3096 nknots.swap(Base::nknots_);
3097 ncoeffs.swap(Base::ncoeffs_);
3098
3100 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3101
3102 return *this;
3103 }
3104
3105#ifdef IGANET_WITH_GISMO
3106
3107 // @brief Updates the B-spline object from a given gsBSpline object
3108 auto &from_gismo(const gismo::gsBSpline<typename Base::value_type> &bspline,
3109 bool updateCoeffs = true, bool updateKnotVector = false) {
3110
3111 if (updateKnotVector) {
3112
3113 if constexpr (Base::parDim_ == 1) {
3114
3115 if (bspline.degree(0) != Base::degrees_[0])
3116 throw std::runtime_error("Degrees mismatch");
3117
3118 if (bspline.knots(0).size() != Base::nknots_[0])
3119 throw std::runtime_error("Knot vector dimensions mismatch");
3120
3121 auto [knots0_cpu, knots0_accessor] =
3122 utils::to_tensorAccessor<typename Base::value_type, 1>(
3123 Base::knots_[0], torch::kCPU);
3124
3125 const typename Base::value_type *knots0_ptr =
3126 bspline.knots(0).asMatrix().data();
3127
3128 for (int64_t i = 0; i < Base::nknots_[0]; ++i)
3130
3132
3133 } else
3134 throw std::runtime_error("Invalid parametric dimension");
3135 }
3136
3137 if (updateCoeffs) {
3138
3139 if (bspline.coefs().rows() != Base::geoDim_)
3140 throw std::runtime_error("Geometric dimensions mismatch");
3141
3142 if (bspline.coefs().cols() != Base::ncumcoeffs())
3143 throw std::runtime_error("Coefficient vector dimensions mismatch");
3144
3145 for (short_t g = 0; g < Base::geoDim_; ++g) {
3146
3147 auto [coeffs_cpu, coeffs_accessor] =
3148 utils::to_tensorAccessor<typename Base::value_type, 1>(
3149 Base::coeffs_[g], torch::kCPU);
3150
3151 const typename Base::value_type *coeffs_ptr =
3152 bspline.coefs().row(g).data();
3153
3154 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3156
3158 }
3159 }
3160
3161 return *this;
3162 }
3163
3164 // @brief Updates the B-spline object from a given gsTensorBSpline object
3165 auto &from_gismo(
3166 const gismo::gsTensorBSpline<Base::parDim_, typename Base::value_type>
3167 &bspline,
3168 bool updateCoeffs = true, bool updateKnotVector = false) {
3169
3170 if (updateKnotVector) {
3171
3172 for (short_t i = 0; i < Base::parDim_; ++i) {
3173 if (bspline.degree(i) != Base::degrees_[i])
3174 throw std::runtime_error("Degrees mismatch");
3175
3176 if (bspline.knots(i).size() != Base::nknots_[i])
3177 throw std::runtime_error("Knot vector dimensions mismatch");
3178
3179 auto [knots_cpu, knots_accessor] =
3180 utils::to_tensorAccessor<typename Base::value_type, 1>(
3181 Base::knots_[i], torch::kCPU);
3182
3183 const typename Base::value_type *knots_ptr =
3184 bspline.knots(i).asMatrix().data();
3185
3186 for (int64_t i = 0; i < Base::nknots_[i]; ++i)
3188
3190 }
3191 }
3192
3193 if (updateCoeffs) {
3194
3195 if (bspline.coefs().rows() != Base::geoDim_)
3196 throw std::runtime_error("Geometric dimensions mismatch");
3197
3198 if (bspline.coefs().cols() != Base::ncumcoeffs())
3199 throw std::runtime_error("Coefficient vector dimensions mismatch");
3200
3201 for (short_t g = 0; g < Base::geoDim_; ++g) {
3202
3203 auto [coeffs_cpu, coeffs_accessor] =
3204 utils::to_tensorAccessor<typename Base::value_type, 1>(
3205 Base::coeffs_[g], torch::kCPU);
3206
3207 const typename Base::value_type *coeffs_ptr =
3208 bspline.coefs().row(g).data();
3209
3210 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3212
3214 }
3215 }
3216
3217 return *this;
3218 }
3219
3220#else // IGANET_WITH_GISMO
3221
3222 template <typename BSpline> auto &from_gismo(BSpline &bspline, bool, bool) {
3223 throw std::runtime_error(
3224 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3225 return *this;
3226 }
3227
3228#endif // IGANET_WITH_GISMO
3229};
3230
3231namespace detail {
3233class SplineType {};
3234} // namespace detail
3235
3237template <typename... T>
3239 std::conjunction<std::is_base_of<detail::SplineType, T>...>;
3240
3242template <typename... T>
3243inline constexpr bool is_SplineType_v = is_SplineType<T...>::value;
3244
3258template <typename BSplineCore>
3260 public BSplineCore,
3261 protected utils::FullQualifiedName {
3262public:
3264 using BSplineCore::BSplineCore;
3265
3271 template <template <typename, short_t, short_t...> class T,
3272 std::make_signed<short_t>::type degree_elevate = 0>
3274 typename BSplineCore::template derived_type<T, degree_elevate>>;
3275
3281
3285 template <typename real_t, short_t GeoDim, short_t... Degrees>
3287 BSplineCommon<typename BSplineCore::template derived_self_type<
3288 real_t, GeoDim, Degrees...>>;
3289
3292 template <typename other_t>
3294 typename BSplineCore::template real_derived_self_type<other_t>>;
3295
3297 using Ptr = std::shared_ptr<BSplineCommon>;
3298
3300 using uPtr = std::unique_ptr<BSplineCommon>;
3301
3303 BSplineCommon(const BSplineCommon &) = default;
3304
3307 if (clone)
3308 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3309 BSplineCore::coeffs_[i] = other.coeffs(i).clone();
3310 }
3311
3315 bool clone = false)
3316 : BSplineCommon(other) {
3317 if (clone)
3318 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3319 BSplineCore::coeffs_[i] = coeffs[i].clone();
3320 else
3321 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3322 BSplineCore::coeffs_[i] = coeffs[i];
3323 }
3324
3327
3332 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3333 BSplineCore::coeffs_[i] = std::move(coeffs[i]);
3334 }
3335
3338 inline static Ptr
3343
3344 inline static Ptr
3345 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3346 enum init init = init::greville,
3349 return uPtr(new BSplineCommon(ncoeffs, init, options));
3350 }
3351
3352 inline static Ptr
3353 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3355 bool clone = false,
3358 return uPtr(new BSplineCommon(ncoeffs, coeffs, clone, options));
3359 }
3360
3361 inline static Ptr
3362 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3366 return uPtr(new BSplineCommon(ncoeffs, coeffs, options));
3367 }
3368
3369 inline static Ptr
3370 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
3371 BSplineCore::parDim_> &kv,
3372 enum init init = init::greville,
3375 return uPtr(new BSplineCommon(kv, init, options));
3376 }
3377
3378 inline static Ptr
3379 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
3380 BSplineCore::parDim_> &kv,
3382 bool clone = false,
3385 return uPtr(new BSplineCommon(kv, coeffs, clone, options));
3386 }
3388
3391 inline static Ptr
3396
3397 inline static Ptr
3398 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3399 enum init init = init::greville,
3402 return Ptr(new BSplineCommon(ncoeffs, init, options));
3403 }
3404
3405 inline static Ptr
3406 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3408 bool clone = false,
3411 return Ptr(new BSplineCommon(ncoeffs, coeffs, clone, options));
3412 }
3413
3414 inline static Ptr
3415 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3419 return Ptr(new BSplineCommon(ncoeffs, coeffs, options));
3420 }
3421
3422 inline static Ptr
3423 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
3424 BSplineCore::parDim_> &kv,
3425 enum init init = init::greville,
3428 return Ptr(new BSplineCommon(kv, init, options));
3429 }
3430
3431 inline static Ptr
3432 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
3433 BSplineCore::parDim_> &kv,
3435 bool clone = false,
3438 return Ptr(new BSplineCommon(kv, coeffs, clone, options));
3439 }
3441
3448 inline BSplineCommon &uniform_refine(int numRefine = 1, int dim = -1) {
3449 BSplineCore::uniform_refine(numRefine, dim);
3450 return *this;
3451 }
3452
3454 inline auto clone() const {
3456
3457 result.nknots_ = BSplineCore::nknots_;
3458 result.ncoeffs_ = BSplineCore::ncoeffs_;
3459 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3460
3461 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3462 result.knots_[i] = BSplineCore::knots_[i].clone();
3463
3464 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3465 result.coeffs_[i] = BSplineCore::coeffs_[i].clone();
3466
3467 return result;
3468 }
3469
3471 template <typename real_t> inline auto to(Options<real_t> options) const {
3473 result(options);
3474
3475 result.nknots_ = BSplineCore::nknots_;
3476 result.ncoeffs_ = BSplineCore::ncoeffs_;
3477 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3478
3479 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3480 result.knots_[i] = BSplineCore::knots_[i].to(options);
3481
3482 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3483 result.coeffs_[i] = BSplineCore::coeffs_[i].to(options);
3484
3485 return result;
3486 }
3487
3489 inline auto to(torch::Device device) const {
3490 BSplineCommon result(BSplineCore::options_.device(device));
3491
3492 result.nknots_ = BSplineCore::nknots_;
3493 result.ncoeffs_ = BSplineCore::ncoeffs_;
3494 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3495
3496 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3497 result.knots_[i] = BSplineCore::knots_[i].to(device);
3498
3499 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3500 result.coeffs_[i] = BSplineCore::coeffs_[i].to(device);
3501
3502 return result;
3503 }
3504
3506 template <typename real_t> inline auto to() const {
3507 return to(BSplineCore::options_.template dtype<real_t>());
3508 }
3509
3516 inline auto diff(const BSplineCommon &other, int dim = -1) {
3517
3518 bool compatible(true);
3519
3520 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3521 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
3522
3523 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3524 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
3525
3526 if (!compatible)
3527 throw std::runtime_error("B-splines are not compatible");
3528
3529 if (dim == -1) {
3530 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3531 BSplineCore::coeffs(i) -= other.coeffs(i);
3532 } else
3533 BSplineCore::coeffs(dim) -= other.coeffs(dim);
3534
3535 return *this;
3536 }
3537
3544 inline auto abs_diff(const BSplineCommon &other, int dim = -1) {
3545
3546 bool compatible(true);
3547
3548 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3549 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
3550
3551 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3552 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
3553
3554 if (!compatible)
3555 throw std::runtime_error("B-splines are not compatible");
3556
3557 if (dim == -1) {
3558 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3559 BSplineCore::coeffs(i) =
3560 torch::abs(BSplineCore::coeffs(i) - other.coeffs(i));
3561 } else
3562 BSplineCore::coeffs(dim) =
3563 torch::abs(BSplineCore::coeffs(dim) - other.coeffs(dim));
3564
3565 return *this;
3566 }
3567
3569 inline auto scale(typename BSplineCore::value_type s, int dim = -1) {
3570 if (dim == -1)
3571 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3572 BSplineCore::coeffs(i) *= s;
3573 else
3574 BSplineCore::coeffs(dim) *= s;
3575 return *this;
3576 }
3577
3579 inline auto
3580 scale(std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
3581 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3582 BSplineCore::coeffs(i) *= v[i];
3583 return *this;
3584 }
3585
3587 inline auto translate(
3588 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
3589 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3590 BSplineCore::coeffs(i) += v[i];
3591 return *this;
3592 }
3593
3595 inline auto rotate(typename BSplineCore::value_type angle) {
3596
3597 static_assert(BSplineCore::geoDim() == 2,
3598 "Rotation about one angle is only available in 2D");
3599
3600 utils::TensorArray<2> coeffs;
3601 coeffs[0] = std::cos(angle) * BSplineCore::coeffs(0) -
3602 std::sin(angle) * BSplineCore::coeffs(1);
3603 coeffs[1] = std::sin(angle) * BSplineCore::coeffs(0) +
3604 std::cos(angle) * BSplineCore::coeffs(1);
3605
3606 BSplineCore::coeffs().swap(coeffs);
3607 return *this;
3608 }
3609
3611 inline auto rotate(std::array<typename BSplineCore::value_type, 3> angle) {
3612
3613 static_assert(BSplineCore::geoDim() == 3,
3614 "Rotation about two angles is only available in 3D");
3615
3616 utils::TensorArray<3> coeffs;
3617 coeffs[0] =
3618 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(0) +
3619 (std::sin(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) -
3620 std::cos(angle[0]) * std::sin(angle[2])) *
3621 BSplineCore::coeffs(1) +
3622 (std::cos(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) +
3623 std::sin(angle[0]) * std::sin(angle[2])) *
3624 BSplineCore::coeffs(2);
3625
3626 coeffs[1] =
3627 std::cos(angle[1]) * std::sin(angle[2]) * BSplineCore::coeffs(0) +
3628 (std::sin(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) +
3629 std::cos(angle[0]) * std::cos(angle[2])) *
3630 BSplineCore::coeffs(1) +
3631 (std::cos(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) -
3632 std::sin(angle[0]) * std::cos(angle[2])) *
3633 BSplineCore::coeffs(2);
3634
3635 coeffs[2] =
3636 -std::sin(angle[1]) * BSplineCore::coeffs(0) +
3637 std::sin(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(1) +
3638 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(2);
3639
3640 BSplineCore::coeffs().swap(coeffs);
3641 return *this;
3642 }
3643
3645 inline auto boundingBox() const {
3646
3647 // Lambda expression to compute the minimum value of all dimensions
3648 auto min_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
3649 return torch::stack({BSplineCore::coeffs(Is).min()...});
3650 };
3651
3652 // Lambda expression to compute the maximum value of all dimensions
3653 auto max_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
3654 return torch::stack({BSplineCore::coeffs(Is).max()...});
3655 };
3656
3657 std::pair<torch::Tensor, torch::Tensor> bbox;
3658 bbox.first = min_(std::make_index_sequence<BSplineCore::geoDim_>{});
3659 bbox.second = max_(std::make_index_sequence<BSplineCore::geoDim_>{});
3660 return bbox;
3661 }
3662
3663 // clang-format off
3684 // clang-format off
3686 template <bool memory_optimized = false>
3687 auto curl(const torch::Tensor &xi) const {
3689 }
3690
3691 template <bool memory_optimized = false>
3693 return curl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
3694 }
3696
3697 // clang-format off
3720 // clang-format on
3721 template <bool memory_optimized = false>
3722 inline auto
3730
3757 template <bool memory_optimized = false>
3760 const torch::Tensor &coeff_indices) const {
3761
3762 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
3763 "curl(.) requires that parametric and geometric dimension "
3764 "are the same");
3765
3766 // Check compatibility of arguments
3767 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3768 assert(xi[i].sizes() == knot_indices[i].sizes());
3769 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
3770 assert(xi[0].sizes() == xi[i].sizes());
3771
3772 if constexpr (BSplineCore::parDim_ == 2)
3773
3780 *BSplineCore::template eval<deriv::dx, memory_optimized>(
3782 *BSplineCore::template eval<deriv::dy, memory_optimized>(
3784
3785 else if constexpr (BSplineCore::parDim_ == 3)
3786
3791 *BSplineCore::template eval<deriv::dy, memory_optimized>(
3793 *BSplineCore::template eval<deriv::dz, memory_optimized>(
3795 *BSplineCore::template eval<deriv::dz, memory_optimized>(
3797 *BSplineCore::template eval<deriv::dx, memory_optimized>(
3799 *BSplineCore::template eval<deriv::dx, memory_optimized>(
3801 *BSplineCore::template eval<deriv::dy, memory_optimized>(
3803
3804 else {
3805 throw std::runtime_error("Unsupported parametric/geometric dimension");
3807 }
3808 }
3809
3831 template <bool memory_optimized = false, typename Geometry>
3832 auto icurl(const Geometry &G, const torch::Tensor &xi) const {
3833 if constexpr (BSplineCore::parDim_ == 0)
3835 torch::zeros_like(BSplineCore::coeffs_[0])};
3836 else
3838 }
3839
3840 template <bool memory_optimized = false, typename Geometry>
3841 inline auto icurl(const Geometry &G,
3843 if constexpr (BSplineCore::parDim_ == 0)
3845 torch::zeros_like(BSplineCore::coeffs_[0])};
3846 else
3848 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
3849 }
3851
3875 template <bool memory_optimized = false, typename Geometry>
3876 inline auto
3879 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
3880 if constexpr (BSplineCore::parDim_ == 0)
3882 torch::zeros_like(BSplineCore::coeffs_[0])};
3883 else
3885 G, xi, knot_indices,
3886 BSplineCore::template find_coeff_indices<memory_optimized>(
3887 knot_indices),
3890 }
3891
3922 template <bool memory_optimized = false, typename Geometry>
3923 inline auto
3926 const torch::Tensor &coeff_indices,
3927 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
3928 const torch::Tensor &coeff_indices_G) const {
3929
3930 if constexpr (BSplineCore::parDim_ == 0)
3932 torch::zeros_like(BSplineCore::coeffs_[0])};
3933 else {
3935 det[0] = std::make_shared<torch::Tensor>(torch::reciprocal(
3937 .det()));
3938
3942 }
3943 }
3944
3966 template <bool memory_optimized = false>
3967 auto div(const torch::Tensor &xi) const {
3968 if constexpr (BSplineCore::parDim_ == 0)
3970 torch::zeros_like(BSplineCore::coeffs_[0])};
3972 }
3973
3974 template <bool memory_optimized = false>
3976 if constexpr (BSplineCore::parDim_ == 0)
3978 torch::zeros_like(BSplineCore::coeffs_[0])};
3979 return div<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
3980 }
3982
4005 template <bool memory_optimized = false>
4006 inline auto
4009 if constexpr (BSplineCore::parDim_ == 0)
4011 torch::zeros_like(BSplineCore::coeffs_[0])};
4012 return div<memory_optimized>(
4014 BSplineCore::template find_coeff_indices<memory_optimized>(
4015 knot_indices));
4016 }
4017
4043 template <bool memory_optimized = false>
4046 const torch::Tensor &coeff_indices) const {
4047
4048 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
4049 "div(.) requires parDim == geoDim");
4050
4051 if constexpr (BSplineCore::parDim_ == 0)
4053 torch::zeros_like(BSplineCore::coeffs_[0])};
4054 // return torch::zeros_like(BSplineCore::coeffs_[0]);
4055
4056 else {
4057 // Check compatibility of arguments
4058 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4059 assert(xi[i].sizes() == knot_indices[i].sizes());
4060 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4061 assert(xi[0].sizes() == xi[i].sizes());
4062
4063 // Lambda expression to evaluate the divergence
4064 auto div_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4066 (*BSplineCore::template eval<
4069 ...)};
4070 };
4071
4072 return div_(std::make_index_sequence<BSplineCore::parDim_>{});
4073 }
4074 }
4075
4098 template <bool memory_optimized = false, typename Geometry>
4099 auto idiv(const Geometry &G, const torch::Tensor &xi) {
4100 if constexpr (BSplineCore::parDim_ == 0)
4102 torch::zeros_like(BSplineCore::coeffs_[0])};
4103 else
4105 }
4106
4107 template <bool memory_optimized = false, typename Geometry>
4108 inline auto idiv(const Geometry &G,
4110 if constexpr (BSplineCore::parDim_ == 0)
4112 torch::zeros_like(BSplineCore::coeffs_[0])};
4113 else
4115 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4116 }
4118
4144 template <bool memory_optimized = false, typename Geometry>
4145 inline auto
4148 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4149 if constexpr (BSplineCore::parDim_ == 0)
4151 torch::zeros_like(BSplineCore::coeffs_[0])};
4152 else
4154 G, xi, knot_indices,
4155 BSplineCore::template find_coeff_indices<memory_optimized>(
4156 knot_indices),
4159 }
4160
4192 template <bool memory_optimized = false, typename Geometry>
4193 inline auto idiv(const Geometry &G,
4196 const torch::Tensor &coeff_indices,
4197 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4198 const torch::Tensor &coeff_indices_G) const {
4199 if constexpr (BSplineCore::parDim_ == 0)
4201 torch::zeros_like(BSplineCore::coeffs_[0])};
4202 else
4206 .trace();
4207 }
4208
4229 template <bool memory_optimized = false>
4230 inline auto grad(const torch::Tensor &xi) const {
4231
4232 static_assert(BSplineCore::geoDim_ == 1,
4233 "grad(.) requires 1D variable, use jac(.) instead");
4234
4235 if constexpr (BSplineCore::parDim_ == 0)
4237 torch::zeros_like(BSplineCore::coeffs_[0])};
4238 else
4240 }
4241
4242 template <bool memory_optimized = false>
4244
4245 static_assert(BSplineCore::geoDim_ == 1,
4246 "grad(.) requires 1D variable, use jac(.) instead");
4247
4248 if constexpr (BSplineCore::parDim_ == 0)
4250 torch::zeros_like(BSplineCore::coeffs_[0])};
4251 else
4252 return grad<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4253 }
4255
4277 template <bool memory_optimized = false>
4278 inline auto
4281
4282 static_assert(BSplineCore::geoDim_ == 1,
4283 "grad(.) requires 1D variable, use jac(.) instead");
4284
4285 if constexpr (BSplineCore::parDim_ == 0)
4287 torch::zeros_like(BSplineCore::coeffs_[0])};
4288 else
4291 BSplineCore::template find_coeff_indices<memory_optimized>(
4292 knot_indices));
4293 }
4294
4319 template <bool memory_optimized = false>
4322 const torch::Tensor &coeff_indices) const {
4323
4324 static_assert(BSplineCore::geoDim_ == 1,
4325 "grad(.) requires 1D variable, use jac(.) instead");
4326
4327 if constexpr (BSplineCore::parDim_ == 0)
4329 torch::zeros_like(BSplineCore::coeffs_[0])};
4330
4331 else {
4332 // Check compatibility of arguments
4333 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4334 assert(xi[i].sizes() == knot_indices[i].sizes());
4335 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4336 assert(xi[0].sizes() == xi[i].sizes());
4337
4338 // Lambda expression to evaluate the gradient
4339 auto grad_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4341 BSplineCore::template eval<(deriv)utils::integer_pow<10, Is>::value,
4343 coeff_indices)...};
4344 };
4345
4346 return grad_(std::make_index_sequence<BSplineCore::parDim_>{});
4347 }
4348 }
4349
4371 template <bool memory_optimized = false, typename Geometry>
4372 auto igrad(const Geometry &G, const torch::Tensor &xi) const {
4373 if constexpr (BSplineCore::parDim_ == 0)
4375 torch::zeros_like(BSplineCore::coeffs_[0])};
4376 else
4378 }
4379
4380 template <bool memory_optimized = false, typename Geometry>
4381 inline auto igrad(const Geometry &G,
4383 if constexpr (BSplineCore::parDim_ == 0)
4385 torch::zeros_like(BSplineCore::coeffs_[0])};
4386 else
4388 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4389 }
4391
4415 template <bool memory_optimized = false, typename Geometry>
4416 inline auto
4419 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4420 if constexpr (BSplineCore::parDim_ == 0)
4422 torch::zeros_like(BSplineCore::coeffs_[0])};
4423 else
4425 G, xi, knot_indices,
4426 BSplineCore::template find_coeff_indices<memory_optimized>(
4427 knot_indices),
4430 }
4431
4462 template <bool memory_optimized = false, typename Geometry>
4463 inline auto
4466 const torch::Tensor &coeff_indices,
4467 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4468 const torch::Tensor &coeff_indices_G) const {
4469 if constexpr (BSplineCore::parDim_ == 0)
4471 torch::zeros_like(BSplineCore::coeffs_[0])};
4472 else
4476 .ginv();
4477 }
4478
4479 // clang-format off
4512 // clang-format on
4515 template <bool memory_optimized = false>
4516 inline auto hess(const torch::Tensor &xi) const {
4517 if constexpr (BSplineCore::parDim_ == 0)
4519 torch::zeros_like(BSplineCore::coeffs_[0])};
4520 else
4522 }
4523
4524 template <bool memory_optimized = false>
4526 if constexpr (BSplineCore::parDim_ == 0)
4528 torch::zeros_like(BSplineCore::coeffs_[0])};
4529 else
4530 return hess<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4531 }
4533
4534 // clang-format off
4569 // clang-format on
4570 template <bool memory_optimized = false>
4571 inline auto
4574 if constexpr (BSplineCore::parDim_ == 0)
4576 torch::zeros_like(BSplineCore::coeffs_[0])};
4577 else
4580 BSplineCore::template find_coeff_indices<memory_optimized>(
4581 knot_indices));
4582 }
4583
4584 // clang-format off
4621 // clang-format on
4622 template <bool memory_optimized = false>
4625 const torch::Tensor &coeff_indices) const {
4626
4627 if constexpr (BSplineCore::parDim_ == 0)
4629 torch::zeros_like(BSplineCore::coeffs_[0])};
4630
4631 else {
4632 // Check compatibility of arguments
4633 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4634 assert(xi[i].sizes() == knot_indices[i].sizes());
4635 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4636 assert(xi[0].sizes() == xi[i].sizes());
4637
4638 // Lambda expression to evaluate the hessian
4639 auto hess_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4640 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
4641 BSplineCore::geoDim_, BSplineCore::parDim_>{
4642 BSplineCore::template eval<
4644 Is / BSplineCore::parDim_>::value +
4646 Is % BSplineCore::parDim_>::value,
4648 .reorder_ikj();
4649 };
4650
4651 return hess_(std::make_index_sequence<BSplineCore::parDim_ *
4652 BSplineCore::parDim_>{});
4653 }
4654 }
4655
4683 template <bool memory_optimized = false, typename Geometry>
4684 auto ihess(const Geometry &G, const torch::Tensor &xi) const {
4685 if constexpr (BSplineCore::parDim_ == 0)
4687 torch::zeros_like(BSplineCore::coeffs_[0])};
4688 else
4690 }
4691
4692 template <bool memory_optimized = false, typename Geometry>
4693 inline auto ihess(const Geometry &G,
4695 if constexpr (BSplineCore::parDim_ == 0)
4697 torch::zeros_like(BSplineCore::coeffs_[0])};
4698 else
4700 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4701 }
4703
4733 template <bool memory_optimized = false, typename Geometry>
4734 inline auto
4737 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4738 if constexpr (BSplineCore::parDim_ == 0)
4740 torch::zeros_like(BSplineCore::coeffs_[0])};
4741 else
4743 G, xi, knot_indices,
4744 BSplineCore::template find_coeff_indices<memory_optimized>(
4745 knot_indices),
4748 }
4749
4785 template <bool memory_optimized = false, typename Geometry>
4786 inline auto
4789 const torch::Tensor &coeff_indices,
4790 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4791 const torch::Tensor &coeff_indices_G) const {
4792
4793 if constexpr (BSplineCore::parDim_ == 0)
4795 torch::zeros_like(BSplineCore::coeffs_[0])};
4796 else {
4798
4799 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
4803
4804 for (short_t component = 0; component < BSplineCore::geoDim_; ++component) {
4806
4807 for (short_t k = 0; k < hessG.slices(); ++k) {
4808 hess_component -= ijacG(component, k) * hessG.slice(k);
4809 }
4810
4811 auto jacInv = G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G).ginv();
4813
4814 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4815 for (short_t j = 0; j < BSplineCore::parDim_; ++j)
4816 hessu.set(i, j, component, hessu_component(i, j));
4817 }
4818
4819 return hessu;
4820
4821 }
4822 }
4823
4824 // clang-format off
4852 // clang-format on
4855 template <bool memory_optimized = false>
4856 inline auto jac(const torch::Tensor &xi) const {
4857 if constexpr (BSplineCore::parDim_ == 0)
4859 torch::zeros_like(BSplineCore::coeffs_[0])};
4860 else
4862 }
4863
4864 template <bool memory_optimized = false>
4866 if constexpr (BSplineCore::parDim_ == 0)
4868 torch::zeros_like(BSplineCore::coeffs_[0])};
4869 else
4870 return jac<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4871 }
4873
4874 // clang-format off
4904 // clang-format on
4905 template <bool memory_optimized = false>
4906 inline auto
4909 if constexpr (BSplineCore::parDim_ == 0)
4911 torch::zeros_like(BSplineCore::coeffs_[0])};
4912 else
4913 return jac<memory_optimized>(
4915 BSplineCore::template find_coeff_indices<memory_optimized>(
4916 knot_indices));
4917 }
4918
4919 // clang-format off
4957 // clang-format on
4958 template <bool memory_optimized = false>
4961 const torch::Tensor &coeff_indices) const {
4962
4963 if constexpr (BSplineCore::parDim_ == 0)
4965 torch::zeros_like(BSplineCore::coeffs_[0])};
4966
4967 else {
4968 // Check compatibility of arguments
4969 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4970 assert(xi[i].sizes() == knot_indices[i].sizes());
4971 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4972 assert(xi[0].sizes() == xi[i].sizes());
4973
4974 // Lambda expression to evaluate the jacobian
4975 auto jac_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4976 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
4977 BSplineCore::geoDim_>{
4978 BSplineCore::template eval<(deriv)utils::integer_pow<10, Is>::value,
4980 coeff_indices)...}
4981 .tr();
4982 };
4983
4984 return jac_(std::make_index_sequence<BSplineCore::parDim_>{});
4985 }
4986 }
4987
5009 template <bool memory_optimized = false, typename Geometry>
5010 auto ijac(const Geometry &G, const torch::Tensor &xi) const {
5011 if constexpr (BSplineCore::parDim_ == 0)
5013 torch::zeros_like(BSplineCore::coeffs_[0])};
5014 else
5016 }
5017
5018 template <bool memory_optimized = false, typename Geometry>
5019 inline auto ijac(const Geometry &G,
5021 if constexpr (BSplineCore::parDim_ == 0)
5023 torch::zeros_like(BSplineCore::coeffs_[0])};
5024 else
5026 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5027 }
5029
5054 template <bool memory_optimized = false, typename Geometry>
5055 inline auto
5058 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5059 if constexpr (BSplineCore::parDim_ == 0)
5061 torch::zeros_like(BSplineCore::coeffs_[0])};
5062 else
5064 G, xi, knot_indices,
5065 BSplineCore::template find_coeff_indices<memory_optimized>(
5066 knot_indices),
5069 }
5070
5101 template <bool memory_optimized = false, typename Geometry>
5102 inline auto ijac(const Geometry &G,
5105 const torch::Tensor &coeff_indices,
5106 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5107 const torch::Tensor &coeff_indices_G) const {
5108 if constexpr (BSplineCore::parDim_ == 0)
5110 torch::zeros_like(BSplineCore::coeffs_[0])};
5111 else
5115 .ginv();
5116 }
5117
5118 // clang-format off
5135 // clang-format on
5138 template <bool memory_optimized = false>
5139 inline auto lapl(const torch::Tensor &xi) const {
5140 if constexpr (BSplineCore::parDim_ == 0)
5142 torch::zeros_like(BSplineCore::coeffs_[0])};
5143 else
5145 }
5146
5147 template <bool memory_optimized = false>
5149 if constexpr (BSplineCore::parDim_ == 0)
5151 torch::zeros_like(BSplineCore::coeffs_[0])};
5152 else
5153 return lapl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5154 }
5156
5157 // clang-format off
5177 // clang-format on
5178 template <bool memory_optimized = false>
5179 inline auto
5182 if constexpr (BSplineCore::parDim_ == 0)
5184 torch::zeros_like(BSplineCore::coeffs_[0])};
5185 else
5188 BSplineCore::template find_coeff_indices<memory_optimized>(
5189 knot_indices));
5190 }
5191
5192 // clang-format off
5215 // clang-format on
5216 template <bool memory_optimized = false>
5219 const torch::Tensor &coeff_indices) const {
5220
5221 if constexpr (BSplineCore::parDim_ == 0)
5223 torch::zeros_like(BSplineCore::coeffs_[0])};
5224
5225 else {
5226 // Check compatibility of arguments
5227 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5228 assert(xi[i].sizes() == knot_indices[i].sizes());
5229 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5230 assert(xi[0].sizes() == xi[i].sizes());
5231
5232 // Lambda expression to evaluate the laplacian
5233 auto lapl_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5235 (BSplineCore::template eval<
5238 ...)}
5239 .reorder_ikj();
5240 };
5241
5242 return lapl_(std::make_index_sequence<BSplineCore::parDim_>{});
5243 }
5244 }
5245
5274 template <bool memory_optimized = false, typename Geometry>
5275 auto ilapl(const Geometry &G, const torch::Tensor &xi) const {
5276 if constexpr (BSplineCore::parDim_ == 0)
5278 torch::zeros_like(BSplineCore::coeffs_[0])};
5279 else
5281 }
5282
5283 template <bool memory_optimized = false, typename Geometry>
5284 inline auto ilapl(const Geometry &G,
5286 if constexpr (BSplineCore::parDim_ == 0)
5288 torch::zeros_like(BSplineCore::coeffs_[0])};
5289 else
5291 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5292 }
5294
5325 template <bool memory_optimized = false, typename Geometry>
5326 inline auto
5329 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5330 if constexpr (BSplineCore::parDim_ == 0)
5332 torch::zeros_like(BSplineCore::coeffs_[0])};
5333 else
5335 G, xi, knot_indices,
5336 BSplineCore::template find_coeff_indices<memory_optimized>(
5337 knot_indices),
5340 }
5341
5379 template <bool memory_optimized = false, typename Geometry>
5380 inline auto
5383 const torch::Tensor &coeff_indices,
5384 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5385 const torch::Tensor &coeff_indices_G) const {
5386
5387 if constexpr (BSplineCore::parDim_ == 0)
5389 torch::zeros_like(BSplineCore::coeffs_[0])};
5390
5391 else {
5392 auto hessu =
5394
5395 {
5396 auto igradG =
5399 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
5401 assert(igradG.cols() == hessG.slices());
5402 for (short_t k = 0; k < hessG.slices(); ++k)
5403 hessu -= igradG(0, k) * hessG.slice(k);
5404 }
5405
5406 auto jacInv =
5408 .ginv();
5409
5410 return (jacInv.tr() * hessu * jacInv).trace();
5411 }
5412 }
5413
5419#ifdef IGANET_WITH_MATPLOT
5420 template <typename Backend = matplot::backend::gnuplot>
5421#else
5422 template <typename Backend = void>
5423#endif
5424 inline auto plot(const nlohmann::json &json = {}) const {
5425 return plot<Backend>(*this, json);
5426 }
5427
5435#ifdef IGANET_WITH_MATPLOT
5436 template <typename Backend = matplot::backend::gnuplot>
5437#else
5438 template <typename Backend = void>
5439#endif
5441 const nlohmann::json &json = {}) const {
5442
5443 return plot<Backend>(*this, xi, json);
5444 }
5445
5453#ifdef IGANET_WITH_MATPLOT
5454 template <typename Backend = matplot::backend::gnuplot>
5455#else
5456 template <typename Backend = void>
5457#endif
5458 inline auto plot(
5459 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
5460 const nlohmann::json &json = {}) const {
5461
5462 return plot<Backend>(*this, xi, json);
5463 }
5464
5472#ifdef IGANET_WITH_MATPLOT
5473 template <typename Backend = matplot::backend::gnuplot,
5474 typename BSplineCoreColor>
5475#else
5476 template <typename Backend = void, typename BSplineCoreColor>
5477#endif
5479 const nlohmann::json &json = {}) const {
5480#ifdef IGANET_WITH_MATPLOT
5481 static_assert(BSplineCore::parDim() == BSplineCoreColor::parDim(),
5482 "Parametric dimensions must match");
5483
5484 if ((void *)this != (void *)&color && BSplineCoreColor::geoDim() > 1)
5485 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5486
5487 if constexpr (BSplineCore::parDim() == 1 && BSplineCore::geoDim() == 1) {
5488
5489 //
5490 // mapping: [0,1] -> R^1
5491 //
5492
5493 int64_t res0 = BSplineCore::ncoeffs(0);
5494 if (json.contains("res0"))
5495 res0 = json["res0"].get<int64_t>();
5496
5497 // Create figure with specified backend
5498 auto f = matplot::figure<Backend>(true);
5499 auto ax = f->current_axes();
5500
5501 // Create line
5502 auto Coords =
5503 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5504#ifdef __clang__
5505 auto Coords_cpu =
5506 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5507 Coords(0), torch::kCPU);
5508 auto XAccessor = std::get<1>(Coords_cpu);
5509#else
5510 auto [Coords_cpu, XAccessor] =
5511 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5512 Coords(0), torch::kCPU);
5513#endif
5514
5515 matplot::vector_1d Xfine(res0, 0.0);
5516 matplot::vector_1d Yfine(res0, 0.0);
5517
5518#pragma omp parallel for simd
5519 for (int64_t i = 0; i < res0; ++i)
5520 Xfine[i] = XAccessor[i];
5521
5522 // Plot (colored) line
5523 if ((void *)this != (void *)&color) {
5524 if constexpr (BSplineCoreColor::geoDim_ == 1) {
5525
5526 // Create colors
5527 auto Color =
5528 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5529#ifdef __clang__
5530 auto Color_cpu =
5531 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5532 1>(Color(0), torch::kCPU);
5533 auto CAccessor = std::get<1>(Color_cpu);
5534#else
5535 auto [Color_cpu, CAccessor] =
5536 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5537 1>(Color(0), torch::kCPU);
5538#endif
5539
5540 matplot::vector_1d Cfine(res0, 0.0);
5541
5542#pragma omp parallel for simd
5543 for (int64_t i = 0; i < res0; ++i)
5544 Cfine[i] = CAccessor[i];
5545
5546 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
5547 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
5548
5549 auto Cmap = matplot::colormap();
5550
5551 auto a = Cmap.size() / (Cmax - Cmin);
5552 auto b = -a * Cmin;
5553
5554 // Plot colored line
5555 ax->hold(matplot::on);
5556 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
5557 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
5558 ->line_width(2)
5559 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
5560 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
5561 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
5562 ax->hold(matplot::off);
5563 matplot::colorbar(ax);
5564 } else
5565 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5566 } else {
5567 // Plot unicolor line
5568 ax->plot(Xfine, Yfine, "b-")->line_width(2);
5569 }
5570
5571 bool cnet = false;
5572 if (json.contains("cnet"))
5573 cnet = json["cnet"].get<bool>();
5574
5575 if (cnet) {
5576 // Create control net
5577#ifdef __clang__
5578 auto coeffs_cpu =
5579 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5580 BSplineCore::coeffs(0), torch::kCPU);
5581 auto xAccessor = std::get<1>(coeffs_cpu);
5582#else
5583 auto [coeffs_cpu, xAccessor] =
5584 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5585 BSplineCore::coeffs(0), torch::kCPU);
5586#endif
5587 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
5588 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
5589
5590#pragma omp parallel for simd
5591 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
5592 X[i] = xAccessor[i];
5593 }
5594
5595 // Plot control net
5596 ax->hold(matplot::on);
5597 ax->plot(X, Y, ".k-")->line_width(1);
5598 ax->hold(matplot::off);
5599 }
5600
5601 // Title
5602 if (json.contains("title"))
5603 ax->title(json["title"].get<std::string>());
5604 else
5605 ax->title("BSpline: [0,1] -> R");
5606
5607 // X-axis label
5608 if (json.contains("xlabel"))
5609 ax->xlabel(json["xlabel"].get<std::string>());
5610 else
5611 ax->xlabel("x");
5612
5613 // Y-axis label
5614 if (json.contains("ylabel"))
5615 ax->ylabel(json["ylabel"].get<std::string>());
5616 else
5617 ax->ylabel("y");
5618
5619 return f;
5620 }
5621
5622 else if constexpr (BSplineCore::parDim_ == 1 && BSplineCore::geoDim_ == 2) {
5623
5624 //
5625 // mapping: [0,1] -> R^2
5626 //
5627
5628 int64_t res0 = BSplineCore::ncoeffs(0);
5629 if (json.contains("res0"))
5630 res0 = json["res0"].get<int64_t>();
5631
5632 // Create figure with specified backend
5633 auto f = matplot::figure<Backend>(true);
5634 auto ax = f->current_axes();
5635
5636 // Create curve
5637 auto Coords =
5638 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5639#ifdef __clang__
5640 auto Coords_cpu =
5641 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5642 Coords, torch::kCPU);
5643 auto XAccessor = std::get<1>(Coords_cpu)[0];
5644 auto YAccessor = std::get<1>(Coords_cpu)[1];
5645#else
5646 auto [Coords0_cpu, XAccessor] =
5647 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5648 Coords(0), torch::kCPU);
5649 auto [Coords1_cpu, YAccessor] =
5650 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5651 Coords(1), torch::kCPU);
5652#endif
5653
5654 matplot::vector_1d Xfine(res0, 0.0);
5655 matplot::vector_1d Yfine(res0, 0.0);
5656
5657#pragma omp parallel for simd
5658 for (int64_t i = 0; i < res0; ++i) {
5659 Xfine[i] = XAccessor[i];
5660 Yfine[i] = YAccessor[i];
5661 }
5662
5663 // Plot (colored) curve
5664 if ((void *)this != (void *)&color) {
5665 if constexpr (BSplineCoreColor::geoDim() == 1) {
5666
5667 // Create colors
5668 auto Color =
5669 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5670#ifdef __clang__
5671 auto Color_cpu =
5672 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5673 1>(Color(0), torch::kCPU);
5674 auto CAccessor = std::get<1>(Color_cpu);
5675#else
5676 auto [Color_cpu, CAccessor] =
5677 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5678 1>(Color(0), torch::kCPU);
5679#endif
5680
5681 matplot::vector_1d Cfine(res0, 0.0);
5682
5683#pragma omp parallel for simd
5684 for (int64_t i = 0; i < res0; ++i) {
5685 Cfine[i] = CAccessor[i];
5686 }
5687
5688 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
5689 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
5690
5691 auto Cmap = matplot::colormap();
5692
5693 auto a = Cmap.size() / (Cmax - Cmin);
5694 auto b = -a * Cmin;
5695
5696 // Plot colored curve
5697 ax->hold(matplot::on);
5698 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
5699 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
5700 ->line_width(2)
5701 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
5702 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
5703 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
5704 ax->hold(matplot::off);
5705 matplot::colorbar(ax);
5706 } else
5707 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5708 } else {
5709 // Plot unicolor curve
5710 ax->plot(Xfine, Yfine, "b-")->line_width(2);
5711 }
5712
5713 bool cnet = false;
5714 if (json.contains("cnet"))
5715 cnet = json["cnet"].get<bool>();
5716
5717 if (cnet) {
5718 // Create control net
5719#ifdef __clang__
5720 auto coeffs_cpu =
5721 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5722 BSplineCore::coeffs(), torch::kCPU);
5723 auto xAccessor = std::get<1>(coeffs_cpu)[0];
5724 auto yAccessor = std::get<1>(coeffs_cpu)[1];
5725#else
5726 auto [coeffs0_cpu, xAccessor] =
5727 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5728 BSplineCore::coeffs(0), torch::kCPU);
5729 auto [coeffs1_cpu, yAccessor] =
5730 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5731 BSplineCore::coeffs(1), torch::kCPU);
5732#endif
5733
5734 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
5735 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
5736
5737#pragma omp parallel for simd
5738 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
5739 X[i] = xAccessor[i];
5740 Y[i] = yAccessor[i];
5741 }
5742
5743 // Plot control net
5744 ax->hold(matplot::on);
5745 ax->plot(X, Y, ".k-")->line_width(1);
5746 ax->hold(matplot::off);
5747 }
5748
5749 // Title
5750 if (json.contains("title"))
5751 ax->title(json["title"].get<std::string>());
5752 else
5753 ax->title("BSpline: [0,1] -> R^2");
5754
5755 // X-axis label
5756 if (json.contains("xlabel"))
5757 ax->xlabel(json["xlabel"].get<std::string>());
5758 else
5759 ax->xlabel("x");
5760
5761 // Y-axis label
5762 if (json.contains("ylabel"))
5763 ax->ylabel(json["ylabel"].get<std::string>());
5764 else
5765 ax->ylabel("y");
5766
5767 return f;
5768 }
5769
5770 else if constexpr (BSplineCore::parDim() == 1 &&
5771 BSplineCore::geoDim() == 3) {
5772
5773 //
5774 // mapping: [0,1] -> R^3
5775 //
5776
5777 int64_t res0 = BSplineCore::ncoeffs(0);
5778 if (json.contains("res0"))
5779 res0 = json["res0"].get<int64_t>();
5780
5781 // Create figure with specified backend
5782 auto f = matplot::figure<Backend>(true);
5783 auto ax = f->current_axes();
5784
5785 auto Coords =
5786 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5787#ifdef __clang__
5788 auto Coords_cpu =
5789 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5790 Coords, torch::kCPU);
5791 auto XAccessor = std::get<1>(Coords_cpu)[0];
5792 auto YAccessor = std::get<1>(Coords_cpu)[1];
5793 auto ZAccessor = std::get<1>(Coords_cpu)[2];
5794#else
5795 auto [Coords0_cpu, XAccessor] =
5796 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5797 Coords(0), torch::kCPU);
5798 auto [Coords1_cpu, YAccessor] =
5799 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5800 Coords(1), torch::kCPU);
5801 auto [Coords2_cpu, ZAccessor] =
5802 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5803 Coords(2), torch::kCPU);
5804#endif
5805
5806 // Create curve
5807 matplot::vector_1d Xfine(res0, 0.0);
5808 matplot::vector_1d Yfine(res0, 0.0);
5809 matplot::vector_1d Zfine(res0, 0.0);
5810
5811#pragma omp parallel for simd
5812 for (int64_t i = 0; i < res0; ++i) {
5813 Xfine[i] = XAccessor[i];
5814 Yfine[i] = YAccessor[i];
5815 Zfine[i] = ZAccessor[i];
5816 }
5817
5818 // Plot (colored) curve
5819 if ((void *)this != (void *)&color) {
5820 if constexpr (BSplineCoreColor::geoDim() == 1) {
5821
5822 auto Color =
5823 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5824#ifdef __clang__
5825 auto Color_cpu =
5826 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5827 1>(Color(0), torch::kCPU);
5828 auto CAccessor = std::get<1>(Color_cpu);
5829#else
5830 auto [Color_cpu, CAccessor] =
5831 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5832 1>(Color(0), torch::kCPU);
5833#endif
5834
5835 // Create colors
5836 matplot::vector_1d Cfine(matplot::vector_1d(res0, 0.0));
5837
5838#pragma omp parallel for simd
5839 for (int64_t i = 0; i < res0; ++i) {
5840 Cfine[i] = CAccessor[i];
5841 }
5842
5843 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
5844 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
5845
5846 auto Cmap = matplot::colormap();
5847
5848 auto a = Cmap.size() / (Cmax - Cmin);
5849 auto b = -a * Cmin;
5850
5851 // Plot colored line
5852 ax->hold(matplot::on);
5853 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
5854 ax->plot3({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]},
5855 {Zfine[i], Zfine[i + 1]})
5856 ->line_width(2)
5857 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
5858 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
5859 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
5860 ax->hold(matplot::off);
5861 matplot::colorbar(ax);
5862 } else
5863 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5864 } else {
5865 // Plot curve
5866 ax->plot3(Xfine, Yfine, Zfine, "b-")->line_width(2);
5867 }
5868
5869 bool cnet = false;
5870 if (json.contains("cnet"))
5871 cnet = json["cnet"].get<bool>();
5872
5873 if (cnet) {
5874 // Create control net
5875#ifdef __clang__
5876 auto coeffs_cpu =
5877 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5878 BSplineCore::coeffs(), torch::kCPU);
5879 auto xAccessor = std::get<1>(coeffs_cpu)[0];
5880 auto yAccessor = std::get<1>(coeffs_cpu)[1];
5881 auto zAccessor = std::get<1>(coeffs_cpu)[2];
5882#else
5883 auto [coeffs0_cpu, xAccessor] =
5884 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5885 BSplineCore::coeffs(0), torch::kCPU);
5886 auto [coeffs1_cpu, yAccessor] =
5887 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5888 BSplineCore::coeffs(1), torch::kCPU);
5889 auto [coeffs2_cpu, zAccessor] =
5890 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5891 BSplineCore::coeffs(2), torch::kCPU);
5892#endif
5893
5894 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
5895 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
5896 matplot::vector_1d Z(BSplineCore::ncoeffs(0), 0.0);
5897
5898#pragma omp parallel for simd
5899 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
5900 X[i] = xAccessor[i];
5901 Y[i] = yAccessor[i];
5902 Z[i] = zAccessor[i];
5903 }
5904
5905 // Plot control net
5906 ax->hold(matplot::on);
5907 ax->plot3(X, Y, Z, ".k-")->line_width(1);
5908 ax->hold(matplot::off);
5909 }
5910
5911 // Title
5912 if (json.contains("title"))
5913 ax->title(json["title"].get<std::string>());
5914 else
5915 ax->title("BSpline: [0,1] -> R^3");
5916
5917 // X-axis label
5918 if (json.contains("xlabel"))
5919 ax->xlabel(json["xlabel"].get<std::string>());
5920 else
5921 ax->xlabel("x");
5922
5923 // Y-axis label
5924 if (json.contains("ylabel"))
5925 ax->ylabel(json["ylabel"].get<std::string>());
5926 else
5927 ax->ylabel("y");
5928
5929 // Z-axis label
5930 if (json.contains("zlabel"))
5931 ax->zlabel(json["zlabel"].get<std::string>());
5932 else
5933 ax->zlabel("z");
5934
5935 return f;
5936 }
5937
5938 else if constexpr (BSplineCore::parDim() == 2 &&
5939 BSplineCore::geoDim() == 2) {
5940
5941 //
5942 // mapping: [0,1]^2 -> R^2
5943 //
5944
5945 int64_t res0 = BSplineCore::ncoeffs(0);
5946 int64_t res1 = BSplineCore::ncoeffs(1);
5947 if (json.contains("res0"))
5948 res0 = json["res0"].get<int64_t>();
5949 if (json.contains("res1"))
5950 res1 = json["res1"].get<int64_t>();
5951
5952 // Create figure with specified backend
5953 auto f = matplot::figure<Backend>(true);
5954 auto ax = f->current_axes();
5955
5956 // Create mesh
5957 utils::TensorArray<2> meshgrid = utils::to_array<2>(
5958 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
5959 torch::linspace(0, 1, res1, BSplineCore::options_)},
5960 "xy"));
5961 auto Coords = BSplineCore::eval(meshgrid);
5962#ifdef __clang__
5963 auto Coords_cpu =
5964 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
5965 Coords, torch::kCPU);
5966 auto XAccessor = std::get<1>(Coords_cpu)[0];
5967 auto YAccessor = std::get<1>(Coords_cpu)[1];
5968#else
5969 auto [Coords0_cpu, XAccessor] =
5970 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
5971 Coords(0), torch::kCPU);
5972 auto [Coords1_cpu, YAccessor] =
5973 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
5974 Coords(1), torch::kCPU);
5975#endif
5976
5977 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
5978 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
5979 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
5980
5981#pragma omp parallel for simd collapse(2)
5982 for (int64_t i = 0; i < res0; ++i)
5983 for (int64_t j = 0; j < res1; ++j) {
5984 Xfine[j][i] = XAccessor[j][i];
5985 Yfine[j][i] = YAccessor[j][i];
5986 }
5987
5988 // Plot (colored) mesh
5989 if ((void *)this != (void *)&color) {
5990 if constexpr (BSplineCoreColor::geoDim() == 1) {
5991
5992 // Create colors
5993 auto Color = color.eval(meshgrid);
5994#ifdef __clang__
5995 auto Color_cpu =
5996 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5997 2>(Color, torch::kCPU);
5998 auto CAccessor = std::get<1>(Color_cpu)[0];
5999#else
6000 auto [Color0_cpu, CAccessor] =
6001 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6002 2>(Color(0), torch::kCPU);
6003#endif
6004
6005 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
6006
6007#pragma omp parallel for simd collapse(2)
6008 for (int64_t i = 0; i < res0; ++i)
6009 for (int64_t j = 0; j < res1; ++j)
6010 Cfine[j][i] = CAccessor[j][i];
6011
6012 // Plot colored mesh
6013 matplot::view(2);
6014 ax->mesh(Xfine, Yfine, Cfine)->hidden_3d(false);
6015 matplot::colorbar(ax);
6016 } else
6017 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6018 } else {
6019 // Plot unicolor mesh
6020 matplot::view(2);
6021 matplot::colormap(std::vector<std::vector<double>>{{ 0.0, 0.0, 1.0 }});
6022 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
6023 }
6024
6025 bool cnet = false;
6026 if (json.contains("cnet"))
6027 cnet = json["cnet"].get<bool>();
6028
6029 if (cnet) {
6030 // Create control net
6031#ifdef __clang__
6032 auto coeffs_cpu =
6033 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6034 BSplineCore::coeffs(), torch::kCPU);
6035 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6036 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6037#else
6038 auto [coeffs0_cpu, xAccessor] =
6039 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6040 BSplineCore::coeffs(0), torch::kCPU);
6041 auto [coeffs1_cpu, yAccessor] =
6042 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6043 BSplineCore::coeffs(1), torch::kCPU);
6044#endif
6045
6046 matplot::vector_2d X(BSplineCore::ncoeffs(1),
6047 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6048 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
6049 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6050 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
6051 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6052
6053#pragma omp parallel for simd collapse(2)
6054 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
6055 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
6056 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
6057 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
6058 }
6059
6060 // Plot control net
6061 ax->hold(matplot::on);
6062 ax->surf(X, Y, Z)
6063 ->palette_map_at_surface(true)
6064 .face_alpha(0)
6065 .line_width(1);
6066 for (std::size_t i = 0; i < X.size(); ++i)
6067 ax->scatter3(X[i], Y[i], Z[i], "k.");
6068 ax->hold(matplot::off);
6069 }
6070
6071 // Title
6072 if (json.contains("title"))
6073 ax->title(json["title"].get<std::string>());
6074 else
6075 ax->title("BSpline: [0,1]^2 -> R^2");
6076
6077 // X-axis label
6078 if (json.contains("xlabel"))
6079 ax->xlabel(json["xlabel"].get<std::string>());
6080 else
6081 ax->xlabel("x");
6082
6083 // Y-axis label
6084 if (json.contains("ylabel"))
6085 ax->ylabel(json["ylabel"].get<std::string>());
6086 else
6087 ax->ylabel("y");
6088
6089 // Z-axis label
6090 if (json.contains("zlabel"))
6091 ax->zlabel(json["zlabel"].get<std::string>());
6092 else
6093 ax->zlabel("z");
6094
6095 return f;
6096 }
6097
6098 else if constexpr (BSplineCore::parDim() == 2 &&
6099 BSplineCore::geoDim() == 3) {
6100
6102 // mapping: [0,1]^2 -> R^3
6104
6105 int64_t res0 = BSplineCore::ncoeffs(0);
6106 int64_t res1 = BSplineCore::ncoeffs(1);
6107 if (json.contains("res0"))
6108 res0 = json["res0"].get<int64_t>();
6109 if (json.contains("res1"))
6110 res1 = json["res1"].get<int64_t>();
6111
6112 // Create figure with specified backend
6113 auto f = matplot::figure<Backend>(true);
6114 auto ax = f->current_axes();
6115
6116 // Create surface
6117 utils::TensorArray<2> meshgrid = utils::to_array<2>(
6118 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
6119 torch::linspace(0, 1, res1, BSplineCore::options_)},
6120 "xy"));
6121 auto Coords = BSplineCore::eval(meshgrid);
6122#ifdef __clang__
6123 auto Coords_cpu =
6124 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6125 Coords, torch::kCPU);
6126 auto XAccessor = std::get<1>(Coords_cpu)[0];
6127 auto YAccessor = std::get<1>(Coords_cpu)[1];
6128 auto ZAccessor = std::get<1>(Coords_cpu)[2];
6129#else
6130 auto [Coords0_cpu, XAccessor] =
6131 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6132 Coords(0), torch::kCPU);
6133 auto [Coords1_cpu, YAccessor] =
6134 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6135 Coords(1), torch::kCPU);
6136 auto [Coords2_cpu, ZAccessor] =
6137 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6138 Coords(2), torch::kCPU);
6139#endif
6140
6141 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
6142 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
6143 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
6144
6145#pragma omp parallel for simd collapse(2)
6146 for (int64_t i = 0; i < res0; ++i)
6147 for (int64_t j = 0; j < res1; ++j) {
6148 Xfine[j][i] = XAccessor[j][i];
6149 Yfine[j][i] = YAccessor[j][i];
6150 Zfine[j][i] = ZAccessor[j][i];
6151 }
6152
6153 // Plot (colored) surface
6154 if ((void *)this != (void *)&color) {
6155 if constexpr (BSplineCoreColor::geoDim() == 1) {
6156
6157 // Create colors
6158 auto Color = color.eval(meshgrid);
6159#ifdef __clang__
6160 auto Color_cpu =
6161 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6162 2>(Color, torch::kCPU);
6163 auto CAccessor = std::get<1>(Color_cpu)[0];
6164#else
6165 auto [Color_cpu, CAccessor] =
6166 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6167 2>(Color(0), torch::kCPU);
6168#endif
6169
6170 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
6171
6172#pragma omp parallel for simd collapse(2)
6173 for (int64_t i = 0; i < res0; ++i)
6174 for (int64_t j = 0; j < res1; ++j) {
6175 Cfine[j][i] = CAccessor[j][i];
6176 }
6177
6178 // Plot colored surface
6179 ax->mesh(Xfine, Yfine, Zfine, Cfine)->hidden_3d(false);
6180 matplot::colorbar(ax);
6181 } else
6182 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6183 } else {
6184 // Plot unicolor surface
6185 matplot::colormap(std::vector<std::vector<double>>{{ 0.0, 0.0, 1.0 }});
6186 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
6187 }
6188
6189 bool cnet = false;
6190 if (json.contains("cnet"))
6191 cnet = json["cnet"].get<bool>();
6192
6193 if (cnet) {
6194 // Create control net
6195#ifdef __clang__
6196 auto coeffs_cpu =
6197 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6198 BSplineCore::coeffs(), torch::kCPU);
6199 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6200 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6201 auto zAccessor = std::get<1>(coeffs_cpu)[2];
6202#else
6203 auto [coeffs0_cpu, xAccessor] =
6204 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6205 BSplineCore::coeffs(0), torch::kCPU);
6206 auto [coeffs1_cpu, yAccessor] =
6207 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6208 BSplineCore::coeffs(1), torch::kCPU);
6209 auto [coeffs2_cpu, zAccessor] =
6210 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6211 BSplineCore::coeffs(2), torch::kCPU);
6212#endif
6213
6214 matplot::vector_2d X(BSplineCore::ncoeffs(1),
6215 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6216 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
6217 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6218 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
6219 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6220
6221#pragma omp parallel for simd collapse(2)
6222 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
6223 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
6224 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
6225 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
6226 Z[j][i] = zAccessor[j * BSplineCore::ncoeffs(0) + i];
6227 }
6228
6229 // Plot control net
6230 ax->hold(matplot::on);
6231 ax->surf(X, Y, Z)
6232 ->palette_map_at_surface(true)
6233 .face_alpha(0)
6234 .line_width(1);
6235 for (std::size_t i = 0; i < X.size(); ++i)
6236 ax->scatter3(X[i], Y[i], Z[i], "k.");
6237 ax->hold(matplot::off);
6238 }
6239
6240 // Title
6241 if (json.contains("title"))
6242 ax->title(json["title"].get<std::string>());
6243 else
6244 ax->title("BSpline: [0,1]^2 -> R^3");
6245
6246 // X-axis label
6247 if (json.contains("xlabel"))
6248 ax->xlabel(json["xlabel"].get<std::string>());
6249 else
6250 ax->xlabel("x");
6251
6252 // Y-axis label
6253 if (json.contains("ylabel"))
6254 ax->ylabel(json["ylabel"].get<std::string>());
6255 else
6256 ax->ylabel("y");
6257
6258 // Z-axis label
6259 if (json.contains("zlabel"))
6260 ax->zlabel(json["zlabel"].get<std::string>());
6261 else
6262 ax->zlabel("z");
6263
6264 return f;
6265 }
6266
6267 else
6268 throw std::runtime_error(
6269 "Unsupported combination of parametric/geometric dimensions");
6270#else
6271 throw std::runtime_error(
6272 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6273#endif
6274 }
6275
6286#ifdef IGANET_WITH_MATPLOT
6287 template <typename Backend = matplot::backend::gnuplot,
6288 typename BSplineCoreColor>
6289#else
6290 template <typename Backend = void, typename BSplineCoreColor>
6291#endif
6292 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
6294 const nlohmann::json &json = {}) const {
6295
6296#ifdef IGANET_WITH_MATPLOT
6297 auto f = plot<Backend>(color, json);
6298 auto ax = f->current_axes();
6299
6300#ifdef __clang__
6301 auto xi_cpu =
6302 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6303 xi, torch::kCPU);
6304 auto xiAccessor = std::get<1>(xi_cpu);
6305#else
6306 auto [xi_cpu, xiAccessor] =
6307 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6308 xi, torch::kCPU);
6309#endif
6310
6311 if constexpr (BSplineCore::parDim_ == 1) {
6312 matplot::vector_1d X(xi[0].size(0), 0.0);
6313 matplot::vector_1d Y(xi[0].size(0), 0.0);
6314
6315#pragma omp parallel for simd
6316 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6317 X[i] = xiAccessor[0][i];
6318 }
6319
6320 ax->hold(matplot::on);
6321 ax->scatter(X, Y, ".");
6322 ax->hold(matplot::off);
6323 } else if constexpr (BSplineCore::parDim_ == 2) {
6324 matplot::vector_1d X(xi[0].size(0), 0.0);
6325 matplot::vector_1d Y(xi[0].size(0), 0.0);
6326 matplot::vector_1d Z(xi[0].size(0), 0.0);
6327
6328#pragma omp parallel for simd
6329 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6330 X[i] = xiAccessor[0][i];
6331 Y[i] = xiAccessor[1][i];
6332 }
6333
6334 ax->hold(matplot::on);
6335 ax->scatter3(X, Y, Z, ".");
6336 ax->hold(matplot::off);
6337 } else if constexpr (BSplineCore::parDim_ == 3) {
6338 matplot::vector_1d X(xi[0].size(0), 0.0);
6339 matplot::vector_1d Y(xi[0].size(0), 0.0);
6340 matplot::vector_1d Z(xi[0].size(0), 0.0);
6341
6342#pragma omp parallel for simd
6343 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6344 X[i] = xiAccessor[0][i];
6345 Y[i] = xiAccessor[1][i];
6346 Z[i] = xiAccessor[2][i];
6347 }
6348
6349 ax->hold(matplot::on);
6350 ax->scatter3(X, Y, Z, ".");
6351 ax->hold(matplot::off);
6352 } else
6353 throw std::runtime_error("Invalid parametric dimension");
6354
6355 return f;
6356#else
6357 throw std::runtime_error(
6358 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6359#endif
6360 }
6361
6372#ifdef IGANET_WITH_MATPLOT
6373 template <typename Backend = matplot::backend::gnuplot,
6374 typename BSplineCoreColor>
6375#else
6376 template <typename Backend = void, typename BSplineCoreColor>
6377#endif
6378 inline auto plot(
6380 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
6381 const nlohmann::json &json = {}) const {
6382
6383#ifdef IGANET_WITH_MATPLOT
6384 auto f = plot<Backend>(color, json);
6385 auto ax = f->current_axes();
6386
6387 for (const auto &xi : xi) {
6388#ifdef __clang__
6389 auto xi_cpu =
6390 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6391 xi, torch::kCPU);
6392 auto xiAccessor = std::get<1>(xi_cpu);
6393#else
6394 auto [xi_cpu, xiAccessor] =
6395 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6396 xi, torch::kCPU);
6397#endif
6398
6399 if constexpr (BSplineCore::parDim_ == 1) {
6400 matplot::vector_1d X(xi[0].size(0), 0.0);
6401 matplot::vector_1d Y(xi[0].size(0), 0.0);
6402
6403#pragma omp parallel for simd
6404 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6405 X[i] = xiAccessor[0][i];
6406 }
6407
6408 ax->hold(matplot::on);
6409 ax->scatter(X, Y, ".");
6410 ax->hold(matplot::off);
6411 } else if constexpr (BSplineCore::parDim_ == 2) {
6412 matplot::vector_1d X(xi[0].size(0), 0.0);
6413 matplot::vector_1d Y(xi[0].size(0), 0.0);
6414 matplot::vector_1d Z(xi[0].size(0), 0.0);
6415
6416#pragma omp parallel for simd
6417 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6418 X[i] = xiAccessor[0][i];
6419 Y[i] = xiAccessor[1][i];
6420 }
6421
6422 ax->hold(matplot::on);
6423 ax->scatter3(X, Y, Z, ".");
6424 ax->hold(matplot::off);
6425 } else if constexpr (BSplineCore::parDim_ == 3) {
6426 matplot::vector_1d X(xi[0].size(0), 0.0);
6427 matplot::vector_1d Y(xi[0].size(0), 0.0);
6428 matplot::vector_1d Z(xi[0].size(0), 0.0);
6429
6430#pragma omp parallel for simd
6431 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6432 X[i] = xiAccessor[0][i];
6433 Y[i] = xiAccessor[1][i];
6434 Z[i] = xiAccessor[2][i];
6435 }
6436
6437 ax->hold(matplot::on);
6438 ax->scatter3(X, Y, Z, ".");
6439 ax->hold(matplot::off);
6440
6441 } else
6442 throw std::runtime_error("Invalid parametric dimension");
6443 }
6444 return f;
6445#else
6446 throw std::runtime_error(
6447 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6448#endif
6449 }
6450
6452 inline virtual void
6453 pretty_print(std::ostream &os = Log(log::info)) const noexcept override {
6454 os << name() << "(\nparDim = " << BSplineCore::parDim()
6455 << ", geoDim = " << BSplineCore::geoDim() << ", degrees = ";
6456
6457 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6458 os << BSplineCore::degree(i) << "x";
6459 if (BSplineCore::parDim() > 0)
6460 os << BSplineCore::degree(BSplineCore::parDim() - 1);
6461 else
6462 os << 0;
6463
6464 os << ", knots = ";
6465 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6466 os << BSplineCore::nknots(i) << "x";
6467 if (BSplineCore::parDim() > 0)
6468 os << BSplineCore::nknots(BSplineCore::parDim() - 1);
6469 else
6470 os << 0;
6471
6472 os << ", coeffs = ";
6473 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6474 os << BSplineCore::ncoeffs(i) << "x";
6475 if (BSplineCore::parDim() > 0)
6476 os << BSplineCore::ncoeffs(BSplineCore::parDim() - 1);
6477 else
6478 os << 1;
6479
6480 os << ", options = "
6481 << static_cast<torch::TensorOptions>(BSplineCore::options_);
6482
6483 if (is_verbose(os)) {
6484 os << "\nknots [ ";
6485 for (const torch::Tensor &knots : BSplineCore::knots()) {
6486 os << (knots.is_view() ? "view/" : "owns/");
6487 os << (knots.is_contiguous() ? "cont " : "non-cont ");
6488 }
6489 if (BSplineCore::parDim() > 0)
6490 os << "] = " << BSplineCore::knots();
6491 else
6492 os << "] = {}";
6493
6494 os << "\ncoeffs [ ";
6495 for (const torch::Tensor &coeffs : BSplineCore::coeffs()) {
6496 os << (coeffs.is_view() ? "view/" : "owns/");
6497 os << (coeffs.is_contiguous() ? "cont " : "non-cont ");
6498 }
6499 if (BSplineCore::ncumcoeffs() > 0)
6500 os << "] = " << BSplineCore::coeffs_view();
6501 else
6502 os << "] = {}";
6503 }
6504
6505 os << "\n)";
6506 }
6507
6516
6517 BSplineCommon result{*this};
6518
6519 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6520 result.coeffs(i) += other.coeffs(i);
6521
6522 return result;
6523 }
6524
6534
6535 BSplineCommon result{*this};
6536
6537 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6538 result.coeffs(i) -= other.coeffs(i);
6539
6540 return result;
6541 }
6542
6545 BSplineCommon operator*(typename BSplineCore::value_type s) const {
6546
6547 BSplineCommon result{*this};
6548
6549 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6550 result.coeffs(i) *= s;
6551
6552 return result;
6553 }
6554
6558 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
6559 const {
6560
6561 BSplineCommon result{*this};
6562
6563 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6564 result.coeffs(i) *= v[i];
6565
6566 return result;
6567 }
6568
6571 BSplineCommon operator/(typename BSplineCore::value_type s) const {
6572
6573 BSplineCommon result{*this};
6574
6575 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6576 result.coeffs(i) /= s;
6577
6578 return result;
6579 }
6580
6584 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
6585 const {
6586
6587 BSplineCommon result{*this};
6588
6589 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6590 result.coeffs(i) /= v[i];
6591
6592 return result;
6593 }
6594
6602
6603 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6604 BSplineCore::coeffs(i) += other.coeffs(i);
6605
6606 return *this;
6607 }
6608
6616
6617 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6618 BSplineCore::coeffs(i) -= other.coeffs(i);
6619
6620 return *this;
6621 }
6622
6624 BSplineCommon &operator*=(typename BSplineCore::value_type s) {
6625
6626 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6627 BSplineCore::coeffs(i) *= s;
6628
6629 return *this;
6630 }
6631
6634 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
6635
6636 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6637 BSplineCore::coeffs(i) *= v[i];
6638
6639 return *this;
6640 }
6641
6643 BSplineCommon &operator/=(typename BSplineCore::value_type s) {
6644
6645 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6646 BSplineCore::coeffs(i) /= s;
6647
6648 return *this;
6649 }
6650
6653 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
6654
6655 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6656 BSplineCore::coeffs(i) /= v[i];
6657
6658 return *this;
6659 }
6660};
6661
6663template <typename real_t, short_t GeoDim, short_t... Degrees>
6665 BSplineCommon<UniformBSplineCore<real_t, GeoDim, Degrees...>>;
6666
6668template <typename real_t, short_t GeoDim, short_t... Degrees>
6669inline std::ostream &
6670operator<<(std::ostream &os,
6672 obj.pretty_print(os);
6673 return os;
6674}
6675
6677template <typename real_t, short_t GeoDim, short_t... Degrees>
6679 BSplineCommon<NonUniformBSplineCore<real_t, GeoDim, Degrees...>>;
6680
6682template <typename real_t, short_t GeoDim, short_t... Degrees>
6683inline std::ostream &
6684operator<<(std::ostream &os,
6686 obj.pretty_print(os);
6687 return os;
6688}
6689} // namespace iganet
Compile-time block tensor.
B-spline (common high-level functionality)
Definition bspline.hpp:3261
auto ijac(const Geometry G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:5056
auto scale(typename BSplineCore::value_type s, int dim=-1)
Scales the B-spline object by a scalar.
Definition bspline.hpp:3569
auto translate(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Translates the B-spline object by a vector.
Definition bspline.hpp:3587
BSplineCommon operator/(typename BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:6571
auto diff(const BSplineCommon &other, int dim=-1)
Computes the difference between two compatible B-spline objects.
Definition bspline.hpp:3516
auto scale(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the B-spline object by a vector.
Definition bspline.hpp:3580
BSplineCommon & operator*=(typename BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:6624
auto icurl(const Geometry G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G) const
Returns a block-tensor with the curl of the B-spline object in the points xi with respect to the phys...
Definition bspline.hpp:3877
auto ihess(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4693
auto curl(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns a block-tensor with the curl of the B-spline object with respect to the parametric variables.
Definition bspline.hpp:3758
auto curl(const torch::Tensor &xi) const
Returns a block-tensor with the curl of the B-spline object with respect to the parametric variables.
Definition bspline.hpp:3687
auto ihess(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G, const torch::Tensor &coeff_indices_G) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4787
auto ilapl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5284
auto ihess(const Geometry &G, const torch::Tensor &xi) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4684
BSplineCommon & operator*=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:6633
auto idiv(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the divergence of the B-spline object with respect to the physical variab...
Definition bspline.hpp:4108
auto grad(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4320
auto plot(const BSplineCommon< BSplineCoreColor > &color, const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:6378
auto div(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the divergence of the B-spline object with respect to the parametric vari...
Definition bspline.hpp:3975
auto rotate(std::array< typename BSplineCore::value_type, 3 > angle)
Rotates the B-spline object by three angles in 3d.
Definition bspline.hpp:3611
BSplineCommon operator*(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v) const
Returns a new B-spline object whose coefficients are scaled by a vector.
Definition bspline.hpp:6557
auto rotate(typename BSplineCore::value_type angle)
Rotates the B-spline object by an angle in 2d.
Definition bspline.hpp:3595
BSplineCommon & operator/=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:6652
auto lapl(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5217
auto ijac(const Geometry &G, const torch::Tensor &xi) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:5010
auto icurl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G, const torch::Tensor &coeff_indices_G) const
Returns a block-tensor with the curl of the B-spline object in the points xi with respect to the phys...
Definition bspline.hpp:3924
auto ijac(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G, const torch::Tensor &coeff_indices_G) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:5102
auto jac(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4907
auto jac(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4959
auto hess(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4525
std::unique_ptr< BSplineCommon > uPtr
Unique pointer for BSplineCommon.
Definition bspline.hpp:3300
BSplineCommon(const BSplineCommon &other, bool clone)
Copy/clone constructor.
Definition bspline.hpp:3306
auto idiv(const Geometry G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G) const
Returns a block-tensor with the divergence of the B-spline object with respect to the physical variab...
Definition bspline.hpp:4146
static Ptr make_unique(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3345
auto lapl(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5148
auto ilapl(const Geometry G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5327
static Ptr make_shared(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3423
auto grad(const torch::Tensor &xi) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4230
auto idiv(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G, const torch::Tensor &coeff_indices_G) const
Returns a block-tensor with the divergence of the B-spline object with respect to the physical variab...
Definition bspline.hpp:4193
auto curl(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the curl of the B-spline object with respect to the parametric variables.
Definition bspline.hpp:3692
auto hess(const torch::Tensor &xi) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4516
auto igrad(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4381
static Ptr make_unique(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3362
auto abs_diff(const BSplineCommon &other, int dim=-1)
Computes the absolute difference between two compatible B-spline objects.
Definition bspline.hpp:3544
auto igrad(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G, const torch::Tensor &coeff_indices_G) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4464
auto div(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns a block-tensor with the divergence of the B-spline object with respect to the parametric vari...
Definition bspline.hpp:4044
std::shared_ptr< BSplineCommon > Ptr
Shared pointer for BSplineCommon.
Definition bspline.hpp:3297
auto hess(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4623
auto plot(const BSplineCommon< BSplineCoreColor > &color, const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:6292
static Ptr make_unique(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3353
static Ptr make_shared(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3415
BSplineCommon(const BSplineCommon &)=default
Copy constructor.
auto hess(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4572
auto plot(const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:5440
static Ptr make_unique(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3379
auto curl(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the curl of the B-spline object with respect to the parametric variables.
Definition bspline.hpp:3723
static Ptr make_unique(Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3339
BSplineCommon(BSplineCommon &&)=default
Move constructor.
auto plot(const nlohmann::json &json={}) const
Definition bspline.hpp:5424
BSplineCommon(BSplineCommon &&other, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs)
Move constructor with external coefficients.
Definition bspline.hpp:3329
BSplineCommon & operator+=(const BSplineCommon &other)
Adds the coefficients of another B-spline object.
Definition bspline.hpp:6601
auto ilapl(const Geometry &G, const torch::Tensor &xi) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5275
auto plot(const BSplineCommon< BSplineCoreColor > &color, const nlohmann::json &json={}) const
Definition bspline.hpp:5478
auto to() const
Returns a copy of the B-spline object with real_t type.
Definition bspline.hpp:3506
BSplineCommon operator+(const BSplineCommon &other) const
Returns a new B-spline object whose coefficients are the sum of that of two compatible B-spline objec...
Definition bspline.hpp:6515
auto icurl(const Geometry &G, const torch::Tensor &xi) const
Returns a block-tensor with the curl of the B-spline object in the points xi with respect to the phys...
Definition bspline.hpp:3832
auto ihess(const Geometry G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4735
auto idiv(const Geometry &G, const torch::Tensor &xi)
Returns a block-tensor with the divergence of the B-spline object with respect to the physical variab...
Definition bspline.hpp:4099
auto ijac(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:5019
auto boundingBox() const
Computes the bounding box of the B-spline object.
Definition bspline.hpp:3645
static Ptr make_shared(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3398
auto div(const torch::Tensor &xi) const
Returns a block-tensor with the divergence of the B-spline object with respect to the parametric vari...
Definition bspline.hpp:3967
auto icurl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the curl of the B-spline object in the points xi with respect to the phys...
Definition bspline.hpp:3841
auto grad(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4279
auto igrad(const Geometry &G, const torch::Tensor &xi) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4372
auto jac(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4865
BSplineCommon & operator/=(typename BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:6643
static Ptr make_shared(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3406
static Ptr make_unique(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3370
BSplineCommon & operator-=(const BSplineCommon &other)
Substracts the coefficients of another B-spline object.
Definition bspline.hpp:6615
BSplineCommon operator-(const BSplineCommon &other) const
Returns a new B-spline object whose coefficients are the difference of that of two compatible B-splin...
Definition bspline.hpp:6533
BSplineCommon(const BSplineCommon &other, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false)
Copy constructor with external coefficients.
Definition bspline.hpp:3313
auto ilapl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const torch::Tensor &coeff_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G, const torch::Tensor &coeff_indices_G) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5381
auto grad(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4243
auto to(torch::Device device) const
Returns a copy of the B-spline object with settings from device.
Definition bspline.hpp:3489
auto igrad(const Geometry G, const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices, const utils::TensorArray< Geometry::parDim()> &knot_indices_G) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4417
auto lapl(const torch::Tensor &xi) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5139
auto clone() const
Returns a clone of the B-spline object.
Definition bspline.hpp:3454
BSplineCommon operator/(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v) const
Returns a new B-spline object whose coefficients are scaled by a vector.
Definition bspline.hpp:6583
virtual void pretty_print(std::ostream &os=Log(log::info)) const noexcept override
Returns a string representation of the BSplineCommon object.
Definition bspline.hpp:6453
static Ptr make_shared(Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3392
auto jac(const torch::Tensor &xi) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4856
static Ptr make_shared(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3432
auto lapl(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5180
auto div(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the divergence of the B-spline object with respect to the parametric vari...
Definition bspline.hpp:4007
BSplineCommon & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:3448
auto plot(const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:5458
auto to(Options< real_t > options) const
Returns a copy of the B-spline object with settings from options.
Definition bspline.hpp:3471
BSplineCommon operator*(typename BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:6545
Abstract patch function base class.
Definition patch.hpp:25
Tensor-product non-uniform B-spline (core functionality)
Definition bspline.hpp:2720
typename Base::template derived_type< NonUniformBSplineCore, degree_elevate > self_type
Deduces the self-type possibly degrees (de-)elevated by the additive constant degree_elevate
Definition bspline.hpp:2742
static constexpr bool is_nonuniform()
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:2761
auto eval(const utils::TensorArray< Base::parDim_ > &xi, const utils::TensorArray< Base::parDim_ > &knot_indices) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2862
auto & from_gismo(BSpline &bspline, bool, bool)
Definition bspline.hpp:3222
auto eval(const torch::Tensor &xi) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2841
auto find_knot_indices(const torch::Tensor &xi) const
Returns the indices of knot spans containing xi
Definition bspline.hpp:2907
real_t value_type
Value type.
Definition bspline.hpp:2727
auto eval(const utils::TensorArray< Base::parDim_ > &xi) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2846
NonUniformBSplineCore & reduce_continuity(int numReduce=1, int dim=-1)
Returns the B-spline object with updated knot and coefficient vectors with reduced continuity.
Definition bspline.hpp:3045
NonUniformBSplineCore(const std::array< std::vector< typename Base::value_type >, Base::parDim_ > &kv, const utils::TensorArray< Base::geoDim_ > &coeffs, bool clone=false, Options< real_t > options=Options< real_t >{})
Constructor for non-equidistant knot vectors.
Definition bspline.hpp:2795
NonUniformBSplineCore & insert_knots(const utils::TensorArray< Base::parDim_ > &knots)
Returns the B-spline object with refined knot and coefficient vectors.
Definition bspline.hpp:3002
auto eval(const utils::TensorArray< Base::parDim_ > &xi, const utils::TensorArray< Base::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2877
UniformBSplineCore< real_t, GeoDim, Degrees... > Base
Base type.
Definition bspline.hpp:2723
auto find_knot_indices(const utils::TensorArray< Base::parDim_ > &xi) const
Returns the indices of knot spans containing xi
Definition bspline.hpp:2915
static constexpr bool is_uniform()
Returns true if the B-spline is uniform.
Definition bspline.hpp:2758
NonUniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:2936
NonUniformBSplineCore(const std::array< std::vector< typename Base::value_type >, Base::parDim_ > &kv, enum init init=init::greville, Options< real_t > options=Options< real_t >{})
Constructor for non-equidistant knot vectors.
Definition bspline.hpp:2773
void init_knots(const std::array< std::vector< typename Base::value_type >, Base::parDim_ > &kv)
Initializes the B-spline knots.
Definition bspline.hpp:2817
BSpline< real_t, GeoDim,(Degrees+degree_elevate)... > derived_type
Deduces the type of the template template parameter BSpline when exposed to the class template parame...
Definition bspline.hpp:2736
The Options class handles the automated determination of dtype from the template argument and the sel...
Definition options.hpp:90
Tensor-product uniform B-spline (core functionality)
Definition bspline.hpp:182
bool pinned_memory() const noexcept override
Returns the pinned_memory property.
Definition bspline.hpp:280
std::array< int64_t, parDim_ > ncoeffs_reverse_
Array storing the sizes of the coefficients of the control net in reverse order (needed for coeffs_v...
Definition bspline.hpp:210
utils::TensorArray< parDim_ > find_knot_indices(const utils::TensorArray< parDim_ > &xi) const noexcept
Returns the indices of knot spans containing xi
Definition bspline.hpp:1028
utils::TensorArray< geoDim_ > coeffs_view() const noexcept
Returns an array of views to the coefficient vectors.
Definition bspline.hpp:569
real_t value_type
Value type.
Definition bspline.hpp:226
bool requires_grad() const noexcept override
Returns the requires_grad property.
Definition bspline.hpp:275
utils::TensorArray< parDim_ > knots_
Array storing the knot vectors .
Definition bspline.hpp:214
auto & from_gismo(BSpline &bspline, bool, bool)
Definition bspline.hpp:2688
auto eval(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Returns the value of the univariate B-spline object in the points xi
Definition bspline.hpp:889
utils::TensorArray< parDim_ > & knots() noexcept
Returns a non-constant reference to the array of knot vectors.
Definition bspline.hpp:498
torch::Tensor as_tensor_(std::index_sequence< Is... >) const noexcept
Returns all coefficients as a single tensor.
Definition bspline.hpp:629
void load(const std::string &filename, const std::string &key="bspline")
Loads the B-spline from file.
Definition bspline.hpp:1788
UniformBSplineCore(const UniformBSplineCore< other_t, GeoDim, Degrees... > &other, Options< real_t > options=Options< real_t >{})
Copy constructor.
Definition bspline.hpp:423
torch::Layout layout() const noexcept override
Returns the layout property.
Definition bspline.hpp:270
const Options< real_t > & options() const noexcept
Returns a constant reference to the B-spline object's options.
Definition bspline.hpp:321
static constexpr const short_t & degree(short_t i) noexcept
Returns a constant reference to the degree in the -th dimension.
Definition bspline.hpp:470
const std::array< int64_t, parDim_ > & nknots() const noexcept
Returns a constant reference to the array of knot vector dimensions.
Definition bspline.hpp:515
derived_type< UniformBSplineCore, degree_elevate > self_type
Deduces the self-type possibly degrees (de-)elevated by the additive constant degree_elevate
Definition bspline.hpp:240
torch::Tensor as_tensor() const noexcept override
Returns all coefficients as a single tensor.
Definition bspline.hpp:637
auto find_coeff_indices(const torch::Tensor &indices) const
Returns the indices of the coefficients corresponding to the knot indices indices
Definition bspline.hpp:1055
UniformBSplineCore & from_json(const nlohmann::json &json)
Updates the B-spline object from JSON object.
Definition bspline.hpp:1417
utils::BlockTensor< torch::Tensor, 1, geoDim_ > eval_from_precomputed(const torch::Tensor &basfunc, const torch::Tensor &coeff_indices, int64_t numeval, torch::IntArrayRef sizes) const override
Returns the value of the B-spline object from precomputed basis function.
Definition bspline.hpp:758
auto eval_basfunc(const torch::Tensor &xi, const torch::Tensor &knot_indices) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1128
const torch::Tensor & coeffs(short_t i) const noexcept
Returns a constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:544
utils::TensorArray< geoDim_ > coeffs_
Array storing the coefficients of the control net , .
Definition bspline.hpp:219
static constexpr short_t parDim() noexcept
Returns the parametric dimension.
Definition bspline.hpp:449
static constexpr bool is_uniform() noexcept
Returns true if the B-spline is uniform.
Definition bspline.hpp:290
int64_t ncoeffs(short_t i) const noexcept
Returns the total number of coefficients in the -th direction.
Definition bspline.hpp:619
auto find_coeff_indices(const utils::TensorArray< parDim_ > &indices) const
Returns the indices of the coefficients corresponding to the knot indices indices
Definition bspline.hpp:1065
auto eval(const utils::TensorArray< parDim_ > &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:868
const auto coeffs_view(short_t i) const noexcept
Returns a view to the coefficient vector in the -th dimension.
Definition bspline.hpp:582
void update_coeffs(const utils::TensorArray< parDim_ > &knots, const utils::TensorArray< parDim_ > &knot_indices)
Updates the B-spline coefficients after knot insertion.
Definition bspline.hpp:2177
UniformBSplineCore & from_xml(const pugi::xml_document &doc, int id=0, std::string label="", int index=-1)
Updates the B-spline object from XML object.
Definition bspline.hpp:1574
torch::Dtype dtype() const noexcept override
Returns the dtype property.
Definition bspline.hpp:265
UniformBSplineCore & from_xml(const pugi::xml_node &root, int id=0, std::string label="", int index=-1)
Updates the B-spline object from XML node.
Definition bspline.hpp:1580
const utils::TensorArray< parDim_ > & knots() const noexcept
Returns a constant reference to the array of knot vectors.
Definition bspline.hpp:479
int64_t nknots(short_t i) const noexcept
Returns the dimension of the knot vector in the -th dimension.
Definition bspline.hpp:525
BSpline & to_gismo(BSpline &bspline, bool, bool) const
Definition bspline.hpp:2606
int64_t constexpr eval_prefactor() const
Computes the prefactor .
Definition bspline.hpp:2021
BSpline< real_t, GeoDim,(Degrees+degree_elevate)... > derived_type
Deduces the type of the template template parameter BSpline when exposed to the class template parame...
Definition bspline.hpp:235
std::array< int64_t, parDim_ > nknots_
Array storing the sizes of the knot vectors .
Definition bspline.hpp:201
static constexpr const std::array< short_t, parDim_ > degrees_
Array storing the degrees .
Definition bspline.hpp:197
static constexpr const short_t parDim_
Dimension of the parametric space .
Definition bspline.hpp:189
utils::BlockTensor< torch::Tensor, 1, geoDim_ > eval_from_precomputed(const utils::TensorArray< parDim_ > &basfunc, const torch::Tensor &coeff_indices, int64_t numeval, torch::IntArrayRef sizes) const override
Returns the value of the B-spline object from precomputed basis function.
Definition bspline.hpp:774
UniformBSplineCore(const std::array< int64_t, parDim_ > &ncoeffs, enum init init=init::greville, Options< real_t > options=Options< real_t >{})
Constructor for equidistant knot vectors.
Definition bspline.hpp:340
Options< real_t > options_
Options.
Definition bspline.hpp:222
auto greville(bool interior=false) const
Returns the Greville abscissae.
Definition bspline.hpp:689
utils::TensorArray< geoDim_ > & coeffs() noexcept
Returns a non-constant reference to the array of coefficient vectors.
Definition bspline.hpp:553
pugi::xml_node & to_xml(pugi::xml_node &root, int id=0, std::string label="", int index=-1) const
Returns the B-spline object as XML node.
Definition bspline.hpp:1461
static constexpr const std::array< short_t, parDim_ > & degrees() noexcept
Returns a constant reference to the array of degrees.
Definition bspline.hpp:460
int32_t device_index() const noexcept override
Returns the device_index property.
Definition bspline.hpp:260
torch::Device device() const noexcept override
Returns the device property.
Definition bspline.hpp:255
UniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:1949
UniformBSplineCore & set_requires_grad(bool requires_grad) noexcept override
Sets the B-spline object's requires_grad property.
Definition bspline.hpp:303
UniformBSplineCore & transform(const std::function< std::array< real_t, geoDim_ >(const std::array< real_t, parDim_ > &)> transformation)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1214
auto to_gismo() const
Converts the B-spline object into a gsBSpline object of the parametric dimension is one and a gsTenso...
Definition bspline.hpp:2472
UniformBSplineCore & from_tensor(const torch::Tensor &tensor) noexcept override
Sets all coefficients from a single tensor.
Definition bspline.hpp:663
UniformBSplineCore(Options< real_t > options=Options< real_t >{})
Default constructor.
Definition bspline.hpp:326
torch::serialize::OutputArchive & write(torch::serialize::OutputArchive &archive, const std::string &key="bspline") const
Writes the B-spline into a torch::serialize::OutputArchive object.
Definition bspline.hpp:1843
torch::serialize::InputArchive & read(torch::serialize::InputArchive &archive, const std::string &key="bspline")
Reads the B-spline from a torch::serialize::InputArchive object.
Definition bspline.hpp:1797
auto eval_basfunc_univariate(const torch::Tensor &xi, const torch::Tensor &knot_indices) const
Returns the vector of univariate B-spline basis functions (or their derivatives) evaluated in the poi...
Definition bspline.hpp:2348
bool operator!=(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are different.
Definition bspline.hpp:1936
bool isclose(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other, real_t rtol=real_t{1e-5}, real_t atol=real_t{1e-8}) const
Returns true if both B-spline objects are close up to the given tolerances.
Definition bspline.hpp:1872
bool operator==(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are the same.
Definition bspline.hpp:1904
nlohmann::json knots_to_json() const
Returns the B-spline object's knots as JSON object.
Definition bspline.hpp:1389
auto eval_basfunc(const utils::TensorArray< parDim_ > &xi) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1104
static constexpr const short_t geoDim_
Dimension of the geometric space .
Definition bspline.hpp:193
const utils::TensorArray< geoDim_ > & coeffs() const noexcept
Returns a constant reference to the array of coefficient vectors.
Definition bspline.hpp:534
int64_t ncumcoeffs() const noexcept
Returns the total number of coefficients.
Definition bspline.hpp:596
void save(const std::string &filename, const std::string &key="bspline") const
Saves the B-spline to file.
Definition bspline.hpp:1835
static constexpr bool is_nonuniform() noexcept
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:293
UniformBSplineCore(const std::array< int64_t, parDim_ > &ncoeffs, const utils::TensorArray< geoDim_ > &coeffs, bool clone=false, Options< real_t > options=Options< real_t >{})
Constructor for equidistant knot vectors.
Definition bspline.hpp:367
UniformBSplineCore & transform(const std::function< std::array< real_t, N >(const std::array< real_t, parDim_ > &)> transformation, std::array< short_t, N > dims)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1295
UniformBSplineCore(const std::array< int64_t, parDim_ > &ncoeffs, utils::TensorArray< geoDim_ > &&coeffs, Options< real_t > options=Options< real_t >{})
Constructor for equidistant knot vectors.
Definition bspline.hpp:405
const torch::Tensor & knots(short_t i) const noexcept
Returns a constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:489
auto find_knot_indices(const torch::Tensor &xi) const noexcept
Returns the indices of knot spans containing xi
Definition bspline.hpp:1020
auto update_coeffs_univariate(const torch::Tensor &knots, const torch::Tensor &knot_indices) const
Returns the knot insertion matrix.
Definition bspline.hpp:2425
std::array< int64_t, parDim_ > ncoeffs_
Array storing the sizes of the coefficients of the control net .
Definition bspline.hpp:205
nlohmann::json to_json() const override
Returns the B-spline object as JSON object.
Definition bspline.hpp:1375
torch::Tensor & coeffs(short_t i) noexcept
Returns a non-constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:561
void init_coeffs(enum init init)
Initializes the B-spline coefficients.
Definition bspline.hpp:2057
auto eval(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns the value of the univariate B-spline object in the points xi
Definition bspline.hpp:914
UniformBSplineCore & from_tensor_(std::index_sequence< Is... >, const torch::Tensor &tensor) noexcept
Sets all coefficients from a single tensor.
Definition bspline.hpp:647
auto eval_basfunc(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1142
static constexpr short_t geoDim() noexcept
Returns the geometric dimension.
Definition bspline.hpp:454
const std::array< int64_t, parDim_ > & ncoeffs() const noexcept
Returns a constant reference to the array of coefficient vector dimensions.
Definition bspline.hpp:609
torch::Tensor & knots(short_t i) noexcept
Returns a non-constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:506
void init_knots()
Initializes the B-spline knots.
Definition bspline.hpp:2030
auto eval(const torch::Tensor &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:860
auto eval_basfunc(const torch::Tensor &xi) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1093
bool is_sparse() const noexcept override
Returns true if the layout is sparse.
Definition bspline.hpp:285
int64_t as_tensor_size() const noexcept override
Returns the size of the single tensor representation of all coefficients.
Definition bspline.hpp:671
pugi::xml_document to_xml(int id=0, std::string label="", int index=-1) const
Returns the B-spline object as XML object.
Definition bspline.hpp:1451
nlohmann::json coeffs_to_json() const
Returns the B-spline object's coefficients as JSON object.
Definition bspline.hpp:1394
Spline type.
Definition bspline.hpp:3233
Full qualified name descriptor.
Definition fqn.hpp:26
Container utility functions.
Core components.
Full qualified name utility functions.
Integer sequence utility function.
Integer power utility function.
Linear algebra utility functions.
auto kron(T0 &&t0, T1 &&t1)
Computes the Kronecker-product between two or more tensors.
Definition linalg.hpp:210
std::array< torch::Tensor, N > TensorArray
Definition tensorarray.hpp:28
auto to_tensor(const std::array< T, N > &array, torch::IntArrayRef sizes=torch::IntArrayRef{-1}, const iganet::Options< T > &options=iganet::Options< T >{})
Converts an std::array to torch::Tensor.
Definition container.hpp:60
auto kronproduct(T0 &&t0, T1 &&t1)
Computes the directional Kronecker-product between two tensors along the given dimension.
Definition linalg.hpp:61
TensorArray< 1 > TensorArray1
Definition tensorarray.hpp:31
auto to_tensorAccessor(const torch::Tensor &tensor)
Converts a torch::Tensor object to a torch::TensorAccessor object.
Definition tensorarray.hpp:80
T prod(std::array< T, N > array, std::size_t start_index=0, std::size_t stop_index=N - 1)
Computes the (partial) product of all std::array entries.
Definition linalg.hpp:221
constexpr std::array< T, N - M > remove_from_back(std::array< T, N > array)
Derives an std::array object from a given std::array object dropping the last M entries.
Definition container.hpp:376
auto VSlice(torch::Tensor index, int64_t start_offset, int64_t stop_offset)
Vectorized version of torch::indexing::Slice (see https://pytorch.org/cppdocs/notes/tensor_indexing....
Definition vslice.hpp:47
auto dotproduct(T0 &&t0, T1 &&t1)
Computes the directional dot-product between two tensors with summation along the given dimension.
Definition linalg.hpp:37
auto to_ArrayRef(const std::array< T, N > &array)
Converts an std::array<int64_t, N> to a at::IntArrayRef object.
Definition container.hpp:192
Forward declaration of BlockTensor.
Definition blocktensor.hpp:46
Definition boundary.hpp:22
deriv
Enumerator for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:74
constexpr auto operator+(deriv lhs, deriv rhs)
Adds two enumerators for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:91
std::conjunction< std::is_base_of< detail::SplineType, T >... > is_SplineType
Type trait to check if T is a valid Spline type.
Definition bspline.hpp:3239
bool is_verbose(std::ostream &os)
Definition core.hpp:831
constexpr bool is_SplineType_v
Alias to the value of is_SplineType.
Definition bspline.hpp:3243
std::ostream & operator<<(std::ostream &os, const Boundary< Spline > &obj)
Print (as string) a Boundary object.
Definition boundary.hpp:1978
constexpr auto operator^(deriv lhs, short_t rhs)
Raises an enumerator for specifying the derivative of B-spline evaluation to a higher exponent.
Definition bspline.hpp:103
struct iganet::@0 Log
Logger.
init
Enumerator for specifying the initialization of B-spline coefficients.
Definition bspline.hpp:55
torch::serialize::InputArchive & operator>>(torch::serialize::InputArchive &archive, UniformBSplineCore< real_t, GeoDim, Degrees... > &obj)
De-serializes a B-spline object.
Definition bspline.hpp:2708
@ none
Definition boundary.hpp:38
short int short_t
Definition core.hpp:74
STL namespace.
Options.
Serialization utility functions.
Serialization prototype.
Definition serialize.hpp:31
Computes the power of integer E to the N at compile time.
Definition integer_pow.hpp:22
Reverse index sequence.
Definition index_sequence.hpp:37
TensorArray utility functions.
#define TENSORARRAY_FORALL(obj, func,...)
Definition tensorarray.hpp:159
VSlice utility functions.