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#include <string_view>
23
24#include <iganet/core/core.hpp>
30#include <iganet/utils/fqn.hpp>
33#include <iganet/utils/jit.hpp>
38
43#define GENERATE_EXPR_SEQ (curl)(div)(grad)(hess)(jac)(lapl)
44
49#define GENERATE_IEXPR_SEQ (icurl)(idiv)(igrad)(ihess)(ijac)(ilapl)
50
51namespace iganet {
52
53using namespace literals;
54using utils::operator+;
55
56// clang-format off
58enum class init : short_t {
59 none = 0,
60 zeros = 1,
61 ones = 2,
62 linear =
63 3,
64 random = 4,
65 greville = 5,
66 linspace = 6
68};
69
76enum class deriv : short_t {
77 func = 0,
78 dx = 1,
79 dy = 10,
80 dz = 100,
81 dt = 1000,
82};
83// clang-format on
84
93inline constexpr auto operator+(deriv lhs, deriv rhs) {
94 return static_cast<deriv>(static_cast<short_t>(lhs) +
95 static_cast<short_t>(rhs));
96}
97
106inline constexpr auto operator^(deriv lhs, short_t rhs) {
107 return static_cast<deriv>(static_cast<short_t>(lhs) *
108 static_cast<short_t>(rhs));
109}
110
111namespace detail {
112
113// @brief Concept to identify template parameters that have a
114// find_knot_indices function
115template <typename T>
116concept HasFindKnotIndices = requires(T t, typename T::eval_type x) {
117 { t.find_knot_indices(x) };
118};
119
120// @brief Concept to identify template parameters that have a
121// find_coeff_indices function
122template <typename T>
123concept HasFindCoeffIndices = requires(T t, typename T::eval_type x) {
124 { t.find_coeff_indices(x) };
125};
126
127} // namespace detail
128
130class SplineCore_ {};
131
134
137
140template <typename T>
141concept SplineCoreType = std::is_base_of_v<SplineCore_, T>;
142
145template <typename T>
146concept UniformSplineCoreType = std::is_base_of_v<UniformSplineCore_, T>;
147
150template <typename T>
151concept NonUniformSplineCoreType = std::is_base_of_v<NonUniformSplineCore_, T>;
152
225template <typename real_t, short_t GeoDim, short_t... Degrees>
227 : public UniformSplineCore_,
228 public utils::Serializable,
229 public BSplinePatch<real_t, GeoDim, sizeof...(Degrees)> {
231 template <typename BSplineCore>
233 friend class BSplineCommon;
234
235protected:
238 static constexpr const short_t parDim_ = sizeof...(Degrees);
239
242 static constexpr const short_t geoDim_ = GeoDim;
243
246 static constexpr const std::array<short_t, parDim_> degrees_ = {Degrees...};
247
250 std::array<int64_t, parDim_> nknots_;
251
254 std::array<int64_t, parDim_> ncoeffs_;
255
259 std::array<int64_t, parDim_> ncoeffs_reverse_;
260
264
269
272
273public:
275 using value_type = real_t;
276
282 template <template <typename, short_t, short_t...> class BSpline,
283 std::make_signed_t<short_t> degree_elevate = 0>
284 using derived_type = BSpline<real_t, GeoDim, (Degrees + degree_elevate)...>;
285
288 template <std::make_signed_t<short_t> degree_elevate = 0>
290
294 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
295 using derived_self_type = UniformBSplineCore<other_t, GeoDim_, Degrees_...>;
296
299 template <typename other_t>
301 UniformBSplineCore<other_t, GeoDim, Degrees...>;
302
306 [[nodiscard]] inline torch::Device device() const noexcept override {
307 return options_.device();
308 }
309
312 [[nodiscard]] inline int32_t device_index() const noexcept override {
313 return options_.device_index();
314 }
315
318 [[nodiscard]] inline torch::Dtype dtype() const noexcept override {
319 return options_.dtype();
320 }
321
324 [[nodiscard]] inline torch::Layout layout() const noexcept override {
325 return options_.layout();
326 }
327
330 [[nodiscard]] inline bool requires_grad() const noexcept override {
331 return options_.requires_grad();
332 }
333
336 [[nodiscard]] inline bool pinned_memory() const noexcept override {
337 return options_.pinned_memory();
338 }
339
342 [[nodiscard]] inline bool is_sparse() const noexcept override {
343 return options_.is_sparse();
344 }
345
348 inline static constexpr bool is_uniform() noexcept { return true; }
349
352 inline static constexpr bool is_nonuniform() noexcept { return false; }
353
363 inline UniformBSplineCore &
364 set_requires_grad(bool requires_grad) noexcept override {
365 if (options_.requires_grad() == requires_grad)
366 return *this;
367
368 for (short_t i = 0; i < parDim_; ++i)
370
371 for (short_t i = 0; i < geoDim_; ++i)
373
374 Options<real_t> tmp(options_.requires_grad(requires_grad));
375 options_.~Options<real_t>();
376 new (&options_) Options<real_t>(tmp);
377
378 return *this;
379 }
380
383 inline const Options<real_t> &options() const noexcept { return options_; }
384
389 : options_(options) {
390 nknots_.fill(0);
391 ncoeffs_.fill(0);
392 ncoeffs_reverse_.fill(0);
393 }
394
402 explicit UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
403 enum init init = init::greville,
406 // Reverse ncoeffs
407 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
408
409 // Initialize knot vectors
410 init_knots();
411
412 // Initialize coefficients
414 }
415
429 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
431 bool clone = false,
434 // Reverse ncoeffs
435 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
436
437 // Initialize knot vectors
438 init_knots();
439
440 // Check compatibility
441 for (short_t i = 0; i < geoDim_; ++i)
442 if (coeffs[i].numel() != ncumcoeffs())
443 throw std::runtime_error("Invalid number of coefficients");
444
445 // Copy/clone coefficients
446 if (clone)
447 for (short_t i = 0; i < geoDim_; ++i)
448 coeffs_[i] = coeffs[i]
449 .clone()
450 .to(options.requires_grad(false))
451 .requires_grad_(options.requires_grad());
452 else
453 for (short_t i = 0; i < geoDim_; ++i)
454 coeffs_[i] = coeffs[i];
455 }
456
467 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
471 coeffs_(std::move(coeffs)), options_(options) {
472 // Reverse ncoeffs
473 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
474
475 // Initialize knot vectors
476 init_knots();
477 }
478
484 template <typename other_t>
488 : nknots_(other.nknots()), ncoeffs_(other.ncoeffs()),
490 // Reverse ncoeffs
491 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
492
493 // Clone coefficients
494 for (short_t i = 0; i < geoDim_; ++i)
495 coeffs_[i] = other.coeffs(i)
496 .clone()
497 .to(options.requires_grad(false))
498 .requires_grad_(options.requires_grad());
499
500 // Clone knot vectors
501 for (short_t i = 0; i < parDim_; ++i)
502 knots_[i] = other.knots(i)
503 .clone()
504 .to(options.requires_grad(false))
505 .requires_grad_(options.requires_grad());
506 }
507
510
513
516 UniformBSplineCore &operator=(const UniformBSplineCore &) = default;
517
520 UniformBSplineCore &operator=(UniformBSplineCore &&) noexcept = default;
521
523 ~UniformBSplineCore() override = default;
524
528 inline static constexpr short_t parDim() noexcept { return parDim_; }
529
533 inline static constexpr short_t geoDim() noexcept { return geoDim_; }
534
538 inline static constexpr const std::array<short_t, parDim_> &
539 degrees() noexcept {
540 return degrees_;
541 }
542
549 inline static constexpr short_t degree(short_t i) noexcept {
550 assert(i >= 0 && i < parDim_);
551 return degrees_[i];
552 }
553
558 inline const utils::TensorArray<parDim_> &knots() const noexcept {
559 return knots_;
560 }
561
568 [[nodiscard]] inline const torch::Tensor &knots(short_t i) const noexcept {
569 assert(i >= 0 && i < parDim_);
570 return knots_[i];
571 }
572
577 inline utils::TensorArray<parDim_> &knots() noexcept { return knots_; }
578
585 inline torch::Tensor &knots(short_t i) noexcept {
586 assert(i >= 0 && i < parDim_);
587 return knots_[i];
588 }
589
594 inline const std::array<int64_t, parDim_> &nknots() const noexcept {
595 return nknots_;
596 }
597
604 [[nodiscard]] inline int64_t nknots(short_t i) const noexcept {
605 assert(i >= 0 && i < parDim_);
606 return nknots_[i];
607 }
608
613 inline const utils::TensorArray<geoDim_> &coeffs() const noexcept {
614 return coeffs_;
615 }
616
623 [[nodiscard]] inline const torch::Tensor &coeffs(short_t i) const noexcept {
624 assert(i >= 0 && i < geoDim_);
625 return coeffs_[i];
626 }
627
632 inline utils::TensorArray<geoDim_> &coeffs() noexcept { return coeffs_; }
633
640 inline torch::Tensor &coeffs(short_t i) noexcept {
641 assert(i >= 0 && i < geoDim_);
642 return coeffs_[i];
643 }
644
648 inline utils::TensorArray<geoDim_> coeffs_view() const noexcept {
650 for (short_t i = 0; i < geoDim_; ++i)
651 coeffs[i] = coeffs_view(i);
652 return coeffs;
653 }
654
661 inline const auto coeffs_view(short_t i) const noexcept {
662 assert(i >= 0 && i < geoDim_);
663 if constexpr (parDim_ > 1)
664 if (coeffs_[i].dim() > 1)
665 return coeffs_[i].view(utils::to_ArrayRef(ncoeffs_reverse_) + (-1_i64));
666 else
668 else
669 return coeffs_[i];
670 }
671
675 [[nodiscard]] inline int64_t ncumcoeffs() const noexcept {
676 int64_t s = 1;
677
678 for (short_t i = 0; i < parDim_; ++i)
679 s *= ncoeffs(i);
680
681 return s;
682 }
683
688 inline const std::array<int64_t, parDim_> &ncoeffs() const noexcept {
689 return ncoeffs_;
690 }
691
698 [[nodiscard]] inline int64_t ncoeffs(short_t i) const noexcept {
699 assert(i >= 0 && i < parDim_);
700 return ncoeffs_[i];
701 }
702
703private:
707 template <std::size_t... Is>
708 inline torch::Tensor as_tensor_(std::index_sequence<Is...>) const noexcept {
709 return torch::cat({coeffs_[Is]...});
710 }
711
712public:
716 [[nodiscard]] inline torch::Tensor as_tensor() const noexcept override {
717 return as_tensor_(std::make_index_sequence<geoDim_>{});
718 }
719
720private:
724 template <std::size_t... Is>
725 inline UniformBSplineCore &
726 from_tensor_(std::index_sequence<Is...>,
727 const torch::Tensor &tensor) noexcept {
728 ((coeffs_[Is] = tensor.index(
729 {torch::indexing::Slice(Is * ncumcoeffs(), (Is + 1) * ncumcoeffs()),
730 "..."})),
731 ...);
732 return *this;
733 }
734
735public:
741 inline UniformBSplineCore &
742 from_tensor(const torch::Tensor &tensor) noexcept override {
743 return from_tensor_(std::make_index_sequence<geoDim_>{}, tensor);
744 }
745
748 //
750 [[nodiscard]] inline int64_t as_tensor_size() const noexcept override {
751 return geoDim_ * ncumcoeffs();
752 }
753
768 inline auto greville(bool interior = false) const {
769 if constexpr (parDim_ == 0) {
770 return torch::zeros(1, options_);
771 } else {
773
774 // Fill coefficients with the tensor-product of Greville
775 // abscissae values per univariate dimension
776 for (short_t i = 0; i < parDim_; ++i) {
777 coeffs[i] = torch::ones(1, options_);
778
779 for (short_t j = 0; j < parDim_; ++j) {
780 if (i == j) {
781
782 int64_t offset = interior ? 1 : 0;
783 int64_t count = ncoeffs_[j] - (interior ? 2 : 0);
784
785 // idx_base: (count, 1)
786 auto idx_base =
787 torch::arange(
788 count,
789 options_.requires_grad(false).template dtype<int64_t>())
790 .unsqueeze(1);
791
792 // offsets: (1, degree)
793 auto offsets =
794 torch::arange(
795 1, degrees_[j] + 1,
796 options_.requires_grad(false).template dtype<int64_t>())
797 .unsqueeze(0);
798
799 // indices: (count, degree)
800 auto indices = idx_base + offset + offsets;
801
802 // Gather relevant knot values: shape (count, degree)
803 auto gathered = knots_[j]
804 .index_select(0, indices.flatten())
805 .view({count, degrees_[j]});
806
807 // Compute mean along degree dimension (dim=1)
808 auto greville_ = gathered.mean(1);
809
810 coeffs[i] = torch::kron(greville_, coeffs[i]);
811 } else
812 coeffs[i] = torch::kron(
813 torch::ones(ncoeffs_[j] - (interior ? 2 : 0), options_),
814 coeffs[i]);
815 }
816
817 // Enable gradient calculation for non-leaf tensor
818 if (options_.requires_grad())
819 coeffs[i].retain_grad();
820 }
821
822 return coeffs;
823 }
824 }
825
848 eval_from_precomputed(const torch::Tensor &basfunc,
849 const torch::Tensor &coeff_indices, int64_t numeval,
850 torch::IntArrayRef sizes) const override {
851
853
854 for (short_t i = 0; i < geoDim_; ++i)
855 result.set(
857 basfunc,
858 coeffs(i).index_select(0, coeff_indices).view({-1, numeval}))
859 .view(sizes));
860 return result;
861 }
862
871 const torch::Tensor &coeff_indices, int64_t numeval,
872 torch::IntArrayRef sizes) const override {
873
875
876 if constexpr (parDim_ == 0) {
877 for (short_t i = 0; i < geoDim_; ++i)
878 result.set(i, coeffs_[i]);
879 }
880
881 else {
882 // Lambda expression to evaluate the spline function
883 std::function<torch::Tensor(short_t, short_t)> eval_;
884
885 eval_ = [&, this](short_t i, short_t dim) {
886 if (dim == 0) {
887 return torch::matmul(coeffs(i)
888 .index_select(0, coeff_indices)
889 .view({numeval, -1, degrees_[0] + 1}),
890 basfunc[0].view({numeval, -1, 1}));
891 } else {
892 return torch::matmul(
893 (eval_(i, dim - 1)).view({numeval, -1, degrees_[dim] + 1}),
894 basfunc[dim].view({numeval, -1, 1}));
895 }
896 };
897
898 for (short_t i = 0; i < geoDim_; ++i)
899 result.set(i, (eval_(i, parDim_ - 1)).view(sizes));
900 }
901 return result;
902 }
904
955 template <deriv deriv = deriv::func, bool memory_optimized = false>
956 inline auto eval(const torch::Tensor &xi) const {
957 if constexpr (parDim_ == 1)
958 return eval<deriv, memory_optimized>(utils::TensorArray1({xi}));
959 else
960 throw std::runtime_error("Invalid parametric dimension");
961 }
962
968 template <deriv deriv = deriv::func, bool memory_optimized = false>
969 inline auto eval(const utils::TensorArray<parDim_> &xi) const {
970 return eval<deriv, memory_optimized>(xi, find_knot_indices(xi));
971 }
972
980 template <deriv deriv = deriv::func, bool memory_optimized = false>
981 inline auto eval_tr(const torch::Tensor &xi) const {
982 if constexpr (parDim_ == 1)
983 return eval_tr<deriv, memory_optimized>(utils::TensorArray1({xi}));
984 else
985 throw std::runtime_error("Invalid parametric dimension");
986 }
987
993 template <deriv deriv = deriv::func, bool memory_optimized = false>
994 inline auto eval_tr(const utils::TensorArray<parDim_> &xi) const {
995 return eval_tr<deriv, memory_optimized>(xi, find_knot_indices(xi));
996 }
998
1014 template <deriv deriv = deriv::func, bool memory_optimized = false>
1015 inline auto eval(const utils::TensorArray<parDim_> &xi,
1016 const utils::TensorArray<parDim_> &knot_indices) const {
1017 return eval<deriv, memory_optimized>(
1018 xi, knot_indices, find_coeff_indices<memory_optimized>(knot_indices));
1019 }
1020
1027 template <deriv deriv = deriv::func, bool memory_optimized = false>
1029 const utils::TensorArray<parDim_> &knot_indices) const {
1030 return eval_tr<deriv, memory_optimized>(
1031 xi, knot_indices, find_coeff_indices<memory_optimized>(knot_indices));
1032 }
1033
1052 template <deriv deriv = deriv::func, bool memory_optimized = false>
1053 inline auto eval(const utils::TensorArray<parDim_> &xi,
1054 const utils::TensorArray<parDim_> &knot_indices,
1055 const torch::Tensor &coeff_indices) const {
1056
1058
1059 if constexpr (parDim_ == 0) {
1060 for (short_t i = 0; i < geoDim_; ++i)
1061 if constexpr (deriv == deriv::func)
1062 result.set(i, coeffs_[i]);
1063 else
1064 result.set(i, torch::zeros_like(coeffs_[i]));
1065 return result;
1066 } // parDim == 0
1067
1068 else {
1069
1070 // Check compatibility of arguments
1071 for (short_t i = 0; i < parDim_; ++i)
1072 assert(xi[i].sizes() == knot_indices[i].sizes());
1073 for (short_t i = 1; i < parDim_; ++i)
1074 assert(xi[0].sizes() == xi[i].sizes());
1075
1076 if constexpr (memory_optimized) {
1077 // memory-optimized
1078
1079 if (coeffs(0).dim() > 1)
1080 throw std::runtime_error(
1081 "Memory-optimized evaluation requires single-valued coefficient");
1082
1083 else {
1084 auto basfunc =
1085 eval_basfunc<deriv, memory_optimized>(xi, knot_indices);
1086
1087 // Lambda expression to evaluate the spline function
1088 std::function<torch::Tensor(short_t, short_t)> eval_;
1089
1090 eval_ = [&, this](short_t i, short_t dim) {
1091 if (dim == 0) {
1092 return torch::matmul(
1093 coeffs(i)
1094 .index_select(0, coeff_indices)
1095 .view({xi[0].numel(), -1, degrees_[0] + 1}),
1096 basfunc[0].view({xi[0].numel(), -1, 1}));
1097 } else {
1098 return torch::matmul(
1099 (eval_(i, dim - 1))
1100 .view({xi[0].numel(), -1, degrees_[dim] + 1}),
1101 basfunc[dim].view({xi[0].numel(), -1, 1}));
1102 }
1103 };
1104
1105 for (short_t i = 0; i < geoDim_; ++i)
1106 result.set(i, (eval_(i, parDim_ - 1)).view(xi[0].sizes()));
1107
1108 return result;
1109 } // coeffs(0).dim() > 1
1110 }
1111
1112 else {
1113 // not memory-optimized
1114
1115 auto basfunc = eval_basfunc<deriv, memory_optimized>(xi, knot_indices);
1116
1117 if (coeffs(0).dim() > 1) {
1118 // coeffs has extra dimension
1119 auto sizes = xi[0].sizes() + (-1_i64);
1120 for (short_t i = 0; i < geoDim_; ++i)
1121 result.set(i, utils::dotproduct(basfunc.unsqueeze(-1),
1122 coeffs(i)
1123 .index_select(0, coeff_indices)
1124 .view({-1, xi[0].numel(),
1125 coeffs(i).size(-1)}))
1126 .view(sizes));
1127 } else {
1128 // coeffs does not have extra dimension
1129 for (short_t i = 0; i < geoDim_; ++i)
1130 result.set(i, utils::dotproduct(basfunc,
1131 coeffs(i)
1132 .index_select(0, coeff_indices)
1133 .view({-1, xi[0].numel()}))
1134 .view(xi[0].sizes()));
1135 }
1136 return result;
1137 }
1138 }
1139 }
1140
1148 template <deriv deriv = deriv::func, bool memory_optimized = false>
1150 const utils::TensorArray<parDim_> &knot_indices,
1151 const torch::Tensor &coeff_indices) const {
1152
1154
1155 if constexpr (parDim_ == 0) {
1156 for (short_t i = 0; i < geoDim_; ++i)
1157 if constexpr (deriv == deriv::func)
1158 result.set(i, coeffs_[i]);
1159 else
1160 result.set(i, torch::zeros_like(coeffs_[i]));
1161 return result;
1162 } // parDim == 0
1163
1164 else {
1165
1166 // Check compatibility of arguments
1167 for (short_t i = 0; i < parDim_; ++i)
1168 assert(xi[i].sizes() == knot_indices[i].sizes());
1169 for (short_t i = 1; i < parDim_; ++i)
1170 assert(xi[0].sizes() == xi[i].sizes());
1171
1172 if constexpr (memory_optimized) {
1173 // memory-optimized
1174
1175 if (coeffs(0).dim() > 1)
1176 throw std::runtime_error(
1177 "Memory-optimized evaluation requires single-valued coefficient");
1178
1179 else {
1180 auto basfunc =
1181 eval_basfunc_tr<deriv, memory_optimized>(xi, knot_indices);
1182
1183 // Lambda expression to evaluate the spline function
1184 std::function<torch::Tensor(short_t, short_t)> eval_;
1185
1186 eval_ = [&, this](short_t i, short_t dim) {
1187 if (dim == 0) {
1188 return torch::matmul(
1189 coeffs(i)
1190 .index_select(0, coeff_indices)
1191 .view({xi[0].numel(), -1, degrees_[0] + 1}),
1192 basfunc[0].view({xi[0].numel(), -1, 1}));
1193 } else {
1194 return torch::matmul(
1195 (eval_(i, dim - 1))
1196 .view({xi[0].numel(), -1, degrees_[dim] + 1}),
1197 basfunc[dim].view({xi[0].numel(), -1, 1}));
1198 }
1199 };
1200
1201 for (short_t i = 0; i < geoDim_; ++i)
1202 result.set(i, (eval_(i, parDim_ - 1)).view(xi[0].sizes()));
1203
1204 return result;
1205 } // coeffs(0).dim() > 1
1206 }
1207
1208 else {
1209 // not memory-optimized
1210
1211 auto basfunc =
1212 eval_basfunc_tr<deriv, memory_optimized>(xi, knot_indices);
1213
1214 if (coeffs(0).dim() > 1) {
1215 // coeffs has extra dimension
1216 auto sizes = xi[0].sizes() + (-1_i64);
1217 for (short_t i = 0; i < geoDim_; ++i)
1218 result.set(i, utils::dotproduct(basfunc.unsqueeze(-1),
1219 coeffs(i)
1220 .index_select(0, coeff_indices)
1221 .view({-1, xi[0].numel(),
1222 coeffs(i).size(-1)}))
1223 .view(sizes));
1224 } else {
1225 // coeffs does not have extra dimension
1226 for (short_t i = 0; i < geoDim_; ++i)
1227 result.set(i, utils::dotproduct(basfunc,
1228 coeffs(i)
1229 .index_select(0, coeff_indices)
1230 .view({-1, xi[0].numel()}))
1231 .view(xi[0].sizes()));
1232 }
1233 return result;
1234 }
1235 }
1236 }
1237
1256 inline auto find_knot_indices(const torch::Tensor &xi) const noexcept {
1257 if constexpr (parDim_ == 0)
1258 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1259 else
1261 }
1262
1268 if constexpr (parDim_ == 0)
1270 else {
1272
1273 for (short_t i = 0; i < parDim_; ++i)
1274 result[i] =
1275 torch::min(
1276 torch::full_like(xi[i], ncoeffs_[i] - 1, options_),
1277 torch::floor(xi[i] * (ncoeffs_[i] - degrees_[i]) + degrees_[i]))
1278 .to(torch::kInt64);
1279
1280 return result;
1281 }
1282 }
1284
1293 template <bool memory_optimized = false>
1294 inline auto find_coeff_indices(const torch::Tensor &indices) const {
1295 if constexpr (parDim_ == 0)
1296 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1297 else
1298 return find_coeff_indices<memory_optimized>(
1299 utils::TensorArray1({indices}));
1300 }
1301
1306 template <bool memory_optimized = false>
1307 inline auto
1309 using utils::operator-;
1310
1311 if constexpr (parDim_ == 0)
1312 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1313 else if constexpr (parDim_ == 1)
1314 return utils::VSlice<memory_optimized>(indices[0].flatten(), -degrees_[0],
1315 1);
1316 else {
1317 return utils::VSlice<memory_optimized>(
1318 TENSORARRAY_FORALL(indices, flatten),
1319 utils::make_array<int64_t>(-degrees_),
1320 utils::make_array<int64_t, parDim_>(1),
1322 }
1323 }
1325
1335 template <deriv deriv = deriv::func, bool memory_optimized = false>
1336 inline auto eval_basfunc(const torch::Tensor &xi) const {
1337 if constexpr (parDim_ == 0) {
1338 if constexpr (deriv == deriv::func)
1339 return torch::ones_like(coeffs_[0]);
1340 else
1341 return torch::zeros_like(coeffs_[0]);
1342 } else
1343 return eval_basfunc<deriv, memory_optimized>(utils::TensorArray1({xi}));
1344 }
1345
1351 template <deriv deriv = deriv::func, bool memory_optimized = false>
1352 inline auto eval_basfunc(const utils::TensorArray<parDim_> &xi) const {
1353 if constexpr (parDim_ == 0) {
1354 if constexpr (deriv == deriv::func)
1355 return torch::ones_like(coeffs_[0]);
1356 else
1357 return torch::zeros_like(coeffs_[0]);
1358 } else
1359 return eval_basfunc<deriv, memory_optimized>(xi, find_knot_indices(xi));
1360 }
1361
1367 template <deriv deriv = deriv::func, bool memory_optimized = false>
1368 inline auto eval_basfunc_tr(const torch::Tensor &xi) const {
1369 if constexpr (parDim_ == 0) {
1370 if constexpr (deriv == deriv::func)
1371 return torch::ones_like(coeffs_[0]);
1372 else
1373 return torch::zeros_like(coeffs_[0]);
1374 } else
1375 return eval_basfunc_tr<deriv, memory_optimized>(
1376 utils::TensorArray1({xi}));
1377 }
1378
1384 template <deriv deriv = deriv::func, bool memory_optimized = false>
1385 inline auto eval_basfunc_tr(const utils::TensorArray<parDim_> &xi) const {
1386 if constexpr (parDim_ == 0) {
1387 if constexpr (deriv == deriv::func)
1388 return torch::ones_like(coeffs_[0]);
1389 else
1390 return torch::zeros_like(coeffs_[0]);
1391 } else
1392 return eval_basfunc_tr<deriv, memory_optimized>(xi,
1393 find_knot_indices(xi));
1394 }
1396
1409 template <deriv deriv = deriv::func, bool memory_optimized = false>
1410 inline auto eval_basfunc(const torch::Tensor &xi,
1411 const torch::Tensor &knot_indices) const {
1412 if constexpr (parDim_ == 0) {
1413 if constexpr (deriv == deriv::func)
1414 return torch::ones_like(coeffs_[0]);
1415 else
1416 return torch::zeros_like(coeffs_[0]);
1417 } else
1418 return eval_basfunc<deriv, memory_optimized>(
1419 utils::TensorArray1({xi}), utils::TensorArray1({knot_indices}));
1420 }
1421
1428 template <deriv deriv = deriv::func, bool memory_optimized = false>
1429 inline auto eval_basfunc_tr(const torch::Tensor &xi,
1430 const torch::Tensor &knot_indices) const {
1431 if constexpr (parDim_ == 0) {
1432 if constexpr (deriv == deriv::func)
1433 return torch::ones_like(coeffs_[0]);
1434 else
1435 return torch::zeros_like(coeffs_[0]);
1436 } else
1437 return eval_basfunc_tr<deriv, memory_optimized>(
1438 utils::TensorArray1({xi}), utils::TensorArray1({knot_indices}));
1439 }
1440
1447 template <deriv deriv = deriv::func, bool memory_optimized = false>
1448 inline auto
1450 const utils::TensorArray<parDim_> &knot_indices) const {
1451
1452 if constexpr (parDim_ == 0) {
1453 if constexpr (deriv == deriv::func)
1454 return torch::ones_like(coeffs_[0]);
1455 else
1456 return torch::zeros_like(coeffs_[0]);
1457 }
1458
1459 else {
1460 // Check compatibility of arguments
1461 for (short_t i = 0; i < parDim_; ++i)
1462 assert(xi[i].sizes() == knot_indices[i].sizes());
1463 for (short_t i = 1; i < parDim_; ++i)
1464 assert(xi[0].sizes() == xi[i].sizes());
1465
1466 if constexpr (memory_optimized) {
1467
1468 // Lambda expression to evaluate the vector of basis functions
1469 auto basfunc_ = [&,
1470 this]<std::size_t... Is>(std::index_sequence<Is...>) {
1473 static_cast<short_t>(deriv) /
1476 static_cast<short_t>(deriv) /
1478 10>(xi[Is].flatten(),
1479 knot_indices[Is].flatten())
1480 .transpose(0, 1))...};
1481 };
1482
1483 return basfunc_(std::make_index_sequence<parDim_>{});
1484
1485 }
1486
1487 else /* not memory optimize */ {
1488
1489 if constexpr (parDim_ == 1) {
1490 return eval_prefactor<degrees_[0],
1491 static_cast<short_t>(deriv) % 10>() *
1493 static_cast<short_t>(deriv) % 10>(
1494 xi[0].flatten(), knot_indices[0].flatten());
1495
1496 } else {
1497
1498 // Lambda expression to evaluate the cumulated basis function
1499 auto basfunc_ = [&, this]<std::size_t... Is>(
1500 std::index_sequence<Is...>) {
1501 return (1 * ... *
1503 static_cast<short_t>(deriv) /
1505 10>())) *
1508 degrees_[Is], Is,
1509 static_cast<short_t>(deriv) /
1511 xi[Is].flatten(), knot_indices[Is].flatten())...);
1512 };
1513
1514 // Note that the kronecker product must be called in reverse order
1516 }
1517 }
1518 }
1519 }
1520
1527 template <deriv deriv = deriv::func, bool memory_optimized = false>
1528 inline auto
1530 const utils::TensorArray<parDim_> &knot_indices) const {
1531
1532 if constexpr (parDim_ == 0) {
1533 if constexpr (deriv == deriv::func)
1534 return torch::ones_like(coeffs_[0]);
1535 else
1536 return torch::zeros_like(coeffs_[0]);
1537 }
1538
1539 else {
1540 // Check compatibility of arguments
1541 for (short_t i = 0; i < parDim_; ++i)
1542 assert(xi[i].sizes() == knot_indices[i].sizes());
1543 for (short_t i = 1; i < parDim_; ++i)
1544 assert(xi[0].sizes() == xi[i].sizes());
1545
1546 if constexpr (memory_optimized) {
1547
1548 // Lambda expression to evaluate the vector of basis functions
1549 auto basfunc_ = [&,
1550 this]<std::size_t... Is>(std::index_sequence<Is...>) {
1553 static_cast<short_t>(deriv) /
1556 static_cast<short_t>(deriv) /
1558 10>(xi[Is].flatten(),
1559 knot_indices[Is].flatten())
1560 .transpose(0, 1))...};
1561 };
1562
1563 return basfunc_(std::make_index_sequence<parDim_>{});
1564
1565 }
1566
1567 else /* not memory optimize */ {
1568
1569 if constexpr (parDim_ == 1) {
1570 return eval_prefactor<degrees_[0],
1571 static_cast<short_t>(deriv) % 10>() *
1573 static_cast<short_t>(deriv) % 10>(
1574 xi[0].flatten(), knot_indices[0].flatten());
1575
1576 } else {
1577
1578 // Lambda expression to evaluate the cumulated basis function
1579 auto basfunc_ = [&, this]<std::size_t... Is>(
1580 std::index_sequence<Is...>) {
1581 return (1 * ... *
1583 static_cast<short_t>(deriv) /
1585 10>())) *
1586 utils::kronproduct<-1>(
1588 degrees_[Is], Is,
1589 static_cast<short_t>(deriv) /
1591 xi[Is].flatten(), knot_indices[Is].flatten())...);
1592 };
1593
1594 // Note that the kronecker product must be called in reverse order
1596 }
1597 }
1598 }
1599 }
1601
1605 inline UniformBSplineCore &
1606 transform(const std::function<
1607 std::array<real_t, geoDim_>(const std::array<real_t, parDim_> &)>
1608 mapping) {
1609
1610 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1611
1612 // 0D
1613 if constexpr (parDim_ == 0) {
1614 auto c = mapping(std::array<real_t, parDim_>{});
1615 for (short_t d = 0; d < geoDim_; ++d)
1616 coeffs_[d].detach()[0] = c[d];
1617 }
1618
1619 // 1D
1620 else if constexpr (parDim_ == 1) {
1621#pragma omp parallel for
1622 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1623 auto c =
1624 mapping(std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1625 for (short_t d = 0; d < geoDim_; ++d)
1626 coeffs_[d].detach()[i] = c[d];
1627 }
1628 }
1629
1630 // 2D
1631 else if constexpr (parDim_ == 2) {
1632#pragma omp parallel for collapse(2)
1633 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1634 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1635 auto c = mapping(std::array<real_t, parDim_>{
1636 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1637 for (short_t d = 0; d < geoDim_; ++d)
1638 coeffs_[d].detach()[j * ncoeffs_[0] + i] = c[d];
1639 }
1640 }
1641 }
1642
1643 // 3D
1644 else if constexpr (parDim_ == 3) {
1645#pragma omp parallel for collapse(3)
1646 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1647 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1648 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1649 auto c = mapping(std::array<real_t, parDim_>{
1650 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1651 k / real_t(ncoeffs_[2] - 1)});
1652 for (short_t d = 0; d < geoDim_; ++d)
1653 coeffs_[d].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1654 j * ncoeffs_[0] + i] = c[d];
1655 }
1656 }
1657 }
1658 }
1659
1660 // 4D
1661 else if constexpr (parDim_ == 4) {
1662#pragma omp parallel for collapse(4)
1663 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1664 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1665 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1666 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1667 auto c = mapping(std::array<real_t, parDim_>{
1668 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1669 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1670 for (short_t d = 0; d < geoDim_; ++d)
1671 coeffs_[d]
1672 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1673 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1674 i] = c[d];
1675 }
1676 }
1677 }
1678 }
1679 } else
1680 throw std::runtime_error("Unsupported parametric dimension");
1681
1682 return *this;
1683 }
1684
1690 template <std::size_t N>
1691 inline UniformBSplineCore &
1692 transform(const std::function<
1693 std::array<real_t, N>(const std::array<real_t, parDim_> &)>
1694 mapping,
1695 std::array<short_t, N> dims) {
1696
1697 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1698
1699 // 0D
1700 if constexpr (parDim_ == 0) {
1701 auto c = mapping(std::array<real_t, parDim_>{});
1702 for (std::size_t d = 0; d < N; ++d)
1703 coeffs_[dims[d]].detach()[0] = c[d];
1704 }
1705
1706 // 1D
1707 else if constexpr (parDim_ == 1) {
1708#pragma omp parallel for
1709 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1710 auto c =
1711 mapping(std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1712 for (std::size_t d = 0; d < N; ++d)
1713 coeffs_[dims[d]].detach()[i] = c[d];
1714 }
1715 }
1716
1717 // 2D
1718 else if constexpr (parDim_ == 2) {
1719#pragma omp parallel for collapse(2)
1720 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1721 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1722 auto c = mapping(std::array<real_t, parDim_>{
1723 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1724 for (std::size_t d = 0; d < N; ++d)
1725 coeffs_[dims[d]].detach()[j * ncoeffs_[0] + i] = c[d];
1726 }
1727 }
1728 }
1729
1730 // 3D
1731 else if constexpr (parDim_ == 3) {
1732#pragma omp parallel for collapse(3)
1733 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1734 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1735 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1736 auto c = mapping(std::array<real_t, parDim_>{
1737 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1738 k / real_t(ncoeffs_[2] - 1)});
1739 for (std::size_t d = 0; d < N; ++d)
1740 coeffs_[dims[d]].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1741 j * ncoeffs_[0] + i] = c[d];
1742 }
1743 }
1744 }
1745 }
1746
1747 // 4D
1748 else if constexpr (parDim_ == 4) {
1749#pragma omp parallel for collapse(4)
1750 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1751 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1752 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1753 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1754 auto c = mapping(std::array<real_t, parDim_>{
1755 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1756 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1757 for (std::size_t d = 0; d < N; ++d)
1758 coeffs_[dims[d]]
1759 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1760 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1761 i] = c[d];
1762 }
1763 }
1764 }
1765 }
1766 } else
1767 throw std::runtime_error("Unsupported parametric dimension");
1768
1769 return *this;
1770 }
1771
1774 [[nodiscard]] inline nlohmann::json to_json() const override {
1775 nlohmann::json json;
1776 json["degrees"] = degrees_;
1777 json["geoDim"] = geoDim_;
1778 json["parDim"] = parDim_;
1779 json["ncoeffs"] = ncoeffs_;
1780 json["nknots"] = nknots_;
1781 json["knots"] = knots_to_json();
1782 json["coeffs"] = coeffs_to_json();
1783
1784 return json;
1785 }
1786
1789 [[nodiscard]] inline nlohmann::json knots_to_json() const {
1790 return ::iganet::utils::to_json<real_t, 1>(knots_);
1791 }
1792
1795 [[nodiscard]] inline nlohmann::json coeffs_to_json() const {
1796 auto coeffs_json = nlohmann::json::array();
1797 for (short_t g = 0; g < geoDim_; ++g) {
1798 auto [coeffs_cpu, coeffs_accessor] =
1799 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
1800
1801 auto json = nlohmann::json::array();
1802
1803 if constexpr (parDim_ == 0) {
1804 json.push_back(coeffs_accessor[0]);
1805 }
1806
1807 else {
1808 for (int64_t i = 0; i < ncumcoeffs(); ++i)
1809 json.push_back(coeffs_accessor[i]);
1810 }
1811
1812 coeffs_json.push_back(json);
1813 }
1814 return coeffs_json;
1815 }
1816
1820 inline UniformBSplineCore &from_json(const nlohmann::json &json) override {
1821
1822 if (json["geoDim"].get<short_t>() != geoDim_)
1823 throw std::runtime_error(
1824 "JSON object provides incompatible geometric dimensions");
1825
1826 if (json["parDim"].get<short_t>() != parDim_)
1827 throw std::runtime_error(
1828 "JSON object provides incompatible parametric dimensions");
1829
1830 if (json["degrees"].get<std::array<short_t, parDim_>>() != degrees_)
1831 throw std::runtime_error("JSON object provides incompatible degrees");
1832
1833 nknots_ = json["nknots"].get<std::array<int64_t, parDim_>>();
1834 ncoeffs_ = json["ncoeffs"].get<std::array<int64_t, parDim_>>();
1835
1836 // Reverse ncoeffs
1838 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1839
1840 auto kv = json["knots"].get<std::array<std::vector<real_t>, parDim_>>();
1841
1842 for (short_t i = 0; i < parDim_; ++i)
1843 knots_[i] = utils::to_tensor(kv[i], options_);
1844
1845 auto c = json["coeffs"].get<std::array<std::vector<real_t>, geoDim_>>();
1846
1847 for (short_t i = 0; i < geoDim_; ++i)
1848 coeffs_[i] = utils::to_tensor(c[i], options_);
1849
1850 return *this;
1851 }
1852
1858 [[nodiscard]] inline pugi::xml_document
1859 to_xml(int id = 0, const std::string &label = "",
1860 int index = -1) const override {
1861 pugi::xml_document doc;
1862 pugi::xml_node root = doc.append_child("xml");
1863 to_xml(root, id, label, index);
1864
1865 return doc;
1866 }
1867
1874 inline pugi::xml_node &to_xml(pugi::xml_node &root, int id = 0,
1875 const std::string &label = "",
1876 int index = -1) const override {
1877 // add Geometry node
1878 pugi::xml_node geo = root.append_child("Geometry");
1879
1880 // 0D parametric dimension
1881 if constexpr (parDim_ == 0) {
1882 geo.append_attribute("type") = "Point";
1883
1884 if (id >= 0)
1885 geo.append_attribute("id") = id;
1886
1887 if (index >= 0)
1888 geo.append_attribute("index") = index;
1889
1890 if (!label.empty())
1891 geo.append_attribute("label") = label.c_str();
1892 }
1893
1894 // 1D parametric dimension
1895 else if constexpr (parDim_ == 1) {
1896 geo.append_attribute("type") = "BSpline";
1897
1898 if (id >= 0)
1899 geo.append_attribute("id") = id;
1900
1901 if (index >= 0)
1902 geo.append_attribute("index") = index;
1903
1904 if (!label.empty())
1905 geo.append_attribute("label") = label.c_str();
1906
1907 // add Basis node
1908 pugi::xml_node basis = geo.append_child("Basis");
1909 basis.append_attribute("type") = "BSplineBasis";
1910
1911 // add KnotVector node
1912 pugi::xml_node knots = basis.append_child("KnotVector");
1913 knots.append_attribute("degree") = degrees_[0];
1914
1915 std::stringstream ss;
1916 auto [knots_cpu, knots_accessor] =
1917 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
1918 for (int64_t i = 0; i < nknots_[0]; ++i)
1919 ss << std::to_string(knots_accessor[i])
1920 << (i < nknots_[0] - 1 ? " " : "");
1921 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1922 }
1923
1924 // >1D parametric dimension
1925 else {
1926 geo.append_attribute("type") =
1927 std::string("TensorBSpline").append(std::to_string(parDim_)).c_str();
1928
1929 if (id >= 0)
1930 geo.append_attribute("id") = id;
1931
1932 if (index >= 0)
1933 geo.append_attribute("index") = index;
1934
1935 if (!label.empty())
1936 geo.append_attribute("label") = label.c_str();
1937
1938 // add Basis node
1939 pugi::xml_node bases = geo.append_child("Basis");
1940 bases.append_attribute("type") = std::string("TensorBSplineBasis")
1941 .append(std::to_string(parDim_))
1942 .c_str();
1943
1944 for (short_t index = 0; index < parDim_; ++index) {
1945 pugi::xml_node basis = bases.append_child("Basis");
1946 basis.append_attribute("type") = "BSplineBasis";
1947 basis.append_attribute("index") = index;
1948
1949 // add KnotVector node
1950 pugi::xml_node knots = basis.append_child("KnotVector");
1951 knots.append_attribute("degree") = degrees_[index];
1952
1953 std::stringstream ss;
1954 auto [knots_cpu, knots_accessor] =
1955 utils::to_tensorAccessor<real_t, 1>(knots_[index], torch::kCPU);
1956 for (int64_t i = 0; i < nknots_[index]; ++i)
1957 ss << std::to_string(knots_accessor[i])
1958 << (i < nknots_[index] - 1 ? " " : "");
1959 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1960 }
1961
1962 } // parametric dimension
1963
1964 // add Coefs node
1965 pugi::xml_node coefs = geo.append_child("coefs");
1966 coefs.append_attribute("geoDim") = geoDim_;
1967
1968 auto [coeffs_cpu, coeffs_accessors] =
1969 utils::to_tensorAccessor<real_t, 1>(coeffs_, torch::kCPU);
1970 std::stringstream ss;
1971
1972 if constexpr (parDim_ == 0) {
1973 for (short_t g = 0; g < geoDim_; ++g)
1974 ss << std::to_string(coeffs_accessors[g][0]) << " ";
1975
1976 } else {
1977 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
1978 for (short_t g = 0; g < geoDim_; ++g)
1979 ss << std::to_string(coeffs_accessors[g][i]) << " ";
1980 }
1981
1982 coefs.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1983
1984 return root;
1985 }
1986
1993 inline UniformBSplineCore &from_xml(const pugi::xml_document &doc, int id = 0,
1994 const std::string &label = "",
1995 int index = -1) override {
1996 return from_xml(doc.child("xml"), id, label, index);
1997 }
1998
2005 inline UniformBSplineCore &from_xml(const pugi::xml_node &root, int id = 0,
2006 const std::string &label = "",
2007 int index = -1) override {
2008
2009 std::array<bool, std::max(parDim_, short_t{1})> nknots_found{false},
2010 ncoeffs_found{false};
2011
2012 // Loop through all geometry nodes
2013 for (pugi::xml_node geo : root.children("Geometry")) {
2014
2015 // 0D parametric dimension
2016 if constexpr (parDim_ == 0) {
2017
2018 // Check for "Point" with given id, index, label
2019 if (std::string_view{geo.attribute("type").value()} == "Point" &&
2020 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
2021 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
2022 (!label.empty() ? geo.attribute("label").value() == label : true)) {
2023
2024 nknots_found[0] = true;
2025 ncoeffs_found[0] = true;
2026 } // "Point"
2027 else
2028 continue; // try next "Geometry"
2029 }
2030
2031 // 1D parametric dimension
2032 else if constexpr (parDim_ == 1) {
2033
2034 // Check for "BSpline" with given id, index, label
2035 if (std::string_view{geo.attribute("type").value()} == "BSpline" &&
2036 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
2037 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
2038 (!label.empty() ? geo.attribute("label").value() == label : true)) {
2039
2040 // Check for "BSplineBasis"
2041 if (pugi::xml_node basis = geo.child("Basis");
2042 std::string_view{basis.attribute("type").value()} ==
2043 "BSplineBasis") {
2044
2045 // Check for "KnotVector"
2046 if (pugi::xml_node knots = basis.child("KnotVector");
2047 knots.attribute("degree").as_int() == degrees_[0]) {
2048
2049 std::vector<real_t> kv;
2050 std::string values = std::regex_replace(
2051 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
2052 for (auto value = strtok(&values[0], " "); value != nullptr;
2053 value = strtok(nullptr, " "))
2054 kv.push_back(static_cast<real_t>(std::stod(value)));
2055
2057 nknots_[0] = kv.size();
2058 ncoeffs_[0] = nknots_[0] - degrees_[0] - 1;
2059
2060 nknots_found[0] = true;
2061 ncoeffs_found[0] = true;
2062
2063 } // "KnotVector"
2064
2065 } // "BSplineBasis"
2066
2067 } // "Bspline"
2068 else
2069 continue; // try next "Geometry"
2070 }
2071
2072 // >1D parametric dimension
2073 else {
2074
2075 // Check for "TensorBSpline<parDim>" with given id, index, label
2076 if (geo.attribute("type").value() ==
2077 std::string("TensorBSpline").append(std::to_string(parDim_)) &&
2078 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
2079 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
2080 (!label.empty() ? geo.attribute("label").value() == label : true)) {
2081
2082 // Check for "TensorBSplineBasis<parDim>"
2083 if (pugi::xml_node bases = geo.child("Basis");
2084 bases.attribute("type").value() ==
2085 std::string("TensorBSplineBasis")
2086 .append(std::to_string(parDim_))) {
2087
2088 // Loop through all basis nodes
2089 for (pugi::xml_node basis : bases.children("Basis")) {
2090
2091 // Check for "BSplineBasis"
2092 if (std::string_view{basis.attribute("type").value()} ==
2093 "BSplineBasis") {
2094
2095 int index = basis.attribute("index").as_int();
2096
2097 // Check for "KnotVector"
2098 if (pugi::xml_node knots = basis.child("KnotVector");
2099 knots.attribute("degree").as_int() == degrees_[index]) {
2100
2101 std::vector<real_t> kv;
2102 std::string values = std::regex_replace(
2103 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
2104
2105 for (auto value = strtok(&values[0], " "); value != nullptr;
2106 value = strtok(nullptr, " "))
2107 kv.push_back(static_cast<real_t>(std::stod(value)));
2108
2109 knots_[index] = utils::to_tensor(kv, options_);
2110 nknots_[index] = kv.size();
2111 ncoeffs_[index] = nknots_[index] - degrees_[index] - 1;
2112
2113 nknots_found[index] = true;
2114 ncoeffs_found[index] = true;
2115
2116 } // "KnotVector"
2117
2118 } // "BSplineBasis"
2119
2120 } // "Basis"
2121
2122 } // "TensorBSplineBasis<parDim>"
2123
2124 } // "TensorBSpline<parDim>"
2125 else
2126 continue; // try next "Geometry"
2127
2128 } // parametric dimension
2129
2130 if (std::any_of(std::begin(nknots_found), std::end(nknots_found),
2131 [](bool i) { return !i; }))
2132 throw std::runtime_error(
2133 "XML object is not compatible with B-spline object");
2134
2135 // Reverse ncoeffs
2137 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2138
2139 // Fill coefficients with zeros
2140 int64_t size = ncumcoeffs();
2141 for (short_t i = 0; i < geoDim_; ++i)
2142 coeffs_[i] = torch::zeros(size, options_.device(torch::kCPU));
2143
2144 // Check for "coefs"
2145 if (pugi::xml_node coefs = geo.child("coefs")) {
2146
2147 std::string values = std::regex_replace(
2148 coefs.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
2149 auto coeffs_accessors = utils::to_tensorAccessor<real_t, 1>(coeffs_);
2150
2151 if constexpr (parDim_ == 0) {
2152 auto value = strtok(&values[0], " ");
2153
2154 for (short_t g = 0; g < geoDim_; ++g) {
2155 if (value == nullptr)
2156 throw std::runtime_error(
2157 "XML object does not provide enough coefficients");
2158
2159 coeffs_accessors[g][0] = static_cast<real_t>(std::stod(value));
2160 value = strtok(nullptr, " ");
2161 }
2162
2163 if (value != nullptr)
2164 throw std::runtime_error(
2165 "XML object provides too many coefficients");
2166
2167 } else {
2168 auto value = strtok(&values[0], " ");
2169
2170 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
2171 for (short_t g = 0; g < geoDim_; ++g) {
2172 if (value == nullptr)
2173 throw std::runtime_error(
2174 "XML object does not provide enough coefficients");
2175
2176 coeffs_accessors[g][i] = static_cast<real_t>(std::stod(value));
2177 value = strtok(nullptr, " ");
2178 }
2179
2180 if (value != nullptr)
2181 throw std::runtime_error(
2182 "XML object provides too many coefficients");
2183 }
2184
2185 // Copy coefficients to device (if needed)
2186 for (short_t i = 0; i < geoDim_; ++i)
2187 coeffs_[i] = coeffs_[i].to(options_.device());
2188
2189 if constexpr (parDim_ == 0) {
2190 if (nknots_found[0] && ncoeffs_found[0])
2191 return *this;
2192 } else if (std::all_of(std::begin(nknots_found), std::end(nknots_found),
2193 [](bool i) { return i; }) &&
2194 std::all_of(std::begin(ncoeffs_found),
2195 std::end(ncoeffs_found),
2196 [](bool i) { return i; }))
2197 return *this;
2198
2199 else
2200 throw std::runtime_error(
2201 "XML object is not compatible with B-spline object");
2202
2203 } // Coefs
2204 else
2205 throw std::runtime_error("XML object does not provide coefficients");
2206
2207 } // "Geometry"
2208
2209 throw std::runtime_error("XML object does not provide geometry with given "
2210 "id, index, and/or label");
2211 return *this;
2212 }
2213
2217 inline void load(const std::string &filename,
2218 const std::string &key = "bspline") {
2219 torch::serialize::InputArchive archive;
2220 archive.load_from(filename);
2221 read(archive, key);
2222 }
2223
2228 inline torch::serialize::InputArchive &
2229 read(torch::serialize::InputArchive &archive,
2230 const std::string &key = "bspline") {
2231 torch::Tensor tensor;
2232
2233 archive.read(key + ".parDim", tensor);
2234 if (tensor.item<int64_t>() != parDim_)
2235 throw std::runtime_error("parDim mismatch");
2236
2237 archive.read(key + ".geoDim", tensor);
2238 if (tensor.item<int64_t>() != geoDim_)
2239 throw std::runtime_error("geoDim mismatch");
2240
2241 for (short_t i = 0; i < parDim_; ++i) {
2242 archive.read(key + ".degree[" + std::to_string(i) + "]", tensor);
2243 if (tensor.item<int64_t>() != degrees_[i])
2244 throw std::runtime_error("degrees mismatch");
2245 }
2246
2247 for (short_t i = 0; i < parDim_; ++i) {
2248 archive.read(key + ".nknots[" + std::to_string(i) + "]", tensor);
2249 nknots_[i] = tensor.item<int64_t>();
2250 }
2251
2252 for (short_t i = 0; i < parDim_; ++i)
2253 archive.read(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
2254
2255 for (short_t i = 0; i < parDim_; ++i) {
2256 archive.read(key + ".ncoeffs[" + std::to_string(i) + "]", tensor);
2257 ncoeffs_[i] = tensor.item<int64_t>();
2258 }
2259
2261 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2262
2263 for (short_t i = 0; i < geoDim_; ++i)
2264 archive.read(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
2265
2266 return archive;
2267 }
2268
2272 inline void save(const std::string &filename,
2273 const std::string &key = "bspline") const {
2274 torch::serialize::OutputArchive archive;
2275 write(archive, key).save_to(filename);
2276 }
2277
2282 inline torch::serialize::OutputArchive &
2283 write(torch::serialize::OutputArchive &archive,
2284 const std::string &key = "bspline") const {
2285 archive.write(key + ".parDim", torch::full({1}, parDim_));
2286 archive.write(key + ".geoDim", torch::full({1}, geoDim_));
2287
2288 for (short_t i = 0; i < parDim_; ++i)
2289 archive.write(key + ".degree[" + std::to_string(i) + "]",
2290 torch::full({1}, degrees_[i]));
2291
2292 for (short_t i = 0; i < parDim_; ++i)
2293 archive.write(key + ".nknots[" + std::to_string(i) + "]",
2294 torch::full({1}, nknots_[i]));
2295
2296 for (short_t i = 0; i < parDim_; ++i)
2297 archive.write(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
2298
2299 for (short_t i = 0; i < parDim_; ++i)
2300 archive.write(key + ".ncoeffs[" + std::to_string(i) + "]",
2301 torch::full({1}, ncoeffs_[i]));
2302
2303 for (short_t i = 0; i < geoDim_; ++i)
2304 archive.write(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
2305
2306 return archive;
2307 }
2308
2318 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2320 real_t rtol = real_t{1e-5}, real_t atol = real_t{1e-8}) const {
2321 if constexpr (!std::is_same_v<real_t, other_t>)
2322 return false;
2323 bool result(true);
2324
2325 result *= (parDim_ == other.parDim());
2326 result *= (geoDim_ == other.geoDim());
2327
2328 for (short_t i = 0; i < parDim_; ++i)
2329 result *= (degree(i) == other.degree(i));
2330
2331 for (short_t i = 0; i < parDim_; ++i)
2332 result *= (nknots(i) == other.nknots(i));
2333
2334 for (short_t i = 0; i < parDim_; ++i)
2335 result *= (ncoeffs(i) == other.ncoeffs(i));
2336
2337 if (!result)
2338 return result;
2339
2340 for (short_t i = 0; i < parDim_; ++i)
2341 result *= torch::allclose(knots(i), other.knots(i), rtol, atol);
2342
2343 for (short_t i = 0; i < geoDim_; ++i)
2344 result *= torch::allclose(coeffs(i), other.coeffs(i), rtol, atol);
2345
2346 return result;
2347 }
2348
2355 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2358 if constexpr (!std::is_same_v<real_t, other_t>)
2359 return false;
2360 bool result(true);
2361
2362 result *= (parDim_ == other.parDim());
2363 result *= (geoDim_ == other.geoDim());
2364
2365 if (!result)
2366 return result;
2367
2368 for (short_t i = 0; i < parDim_; ++i)
2369 result *= (degree(i) == other.degree(i));
2370
2371 for (short_t i = 0; i < parDim_; ++i)
2372 result *= (nknots(i) == other.nknots(i));
2373
2374 for (short_t i = 0; i < parDim_; ++i)
2375 result *= (ncoeffs(i) == other.ncoeffs(i));
2376
2377 for (short_t i = 0; i < parDim_; ++i)
2378 result *= torch::equal(knots(i), other.knots(i));
2379
2380 for (short_t i = 0; i < geoDim_; ++i)
2381 result *= torch::equal(coeffs(i), other.coeffs(i));
2382
2383 return result;
2384 }
2385
2392 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2395 return !(
2396 *this ==
2397 other); // Do not change this to (*this != other) is it does not work
2398 }
2399
2409 inline UniformBSplineCore &uniform_refine(int numRefine = 1, int dim = -1) {
2410 assert(numRefine > 0);
2411 assert(dim == -1 || (dim >= 0 && dim < parDim_));
2412
2413 // Update number of knots and coefficients
2414 std::array<int64_t, parDim_> nknots(nknots_);
2415 std::array<int64_t, parDim_> ncoeffs(ncoeffs_);
2416
2417 for (int refine = 0; refine < numRefine; ++refine) {
2418 if (dim == -1)
2419 for (short_t i = 0; i < parDim_; ++i) {
2420 ncoeffs[i] += nknots[i] - 2 * degrees_[i] - 1; // must be done first
2421 nknots[i] += nknots[i] - 2 * degrees_[i] - 1;
2422 }
2423 else {
2424 ncoeffs[dim] +=
2425 nknots[dim] - 2 * degrees_[dim] - 1; // must be done first
2426 nknots[dim] += nknots[dim] - 2 * degrees_[dim] - 1;
2427 }
2428 }
2429
2430 // Update knot vectors
2431 utils::TensorArray<parDim_> knots, knots_indices;
2432
2433 for (short_t i = 0; i < parDim_; ++i) {
2434 std::vector<real_t> kv;
2435 kv.reserve(nknots[i]);
2436
2437 for (int64_t j = 0; j < degrees_[i]; ++j)
2438 kv.push_back(static_cast<real_t>(0));
2439
2440 for (int64_t j = 0; j < ncoeffs[i] - degrees_[i] + 1; ++j)
2441 kv.push_back(static_cast<real_t>(j) /
2442 static_cast<real_t>(ncoeffs[i] - degrees_[i]));
2443
2444 for (int64_t j = 0; j < degrees_[i]; ++j)
2445 kv.push_back(static_cast<real_t>(1));
2446
2448 }
2449
2450 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
2451 // \f$m_d\f$ is the number of coefficients after the update. To
2452 // update the coefficients using the Oslo algorithm (Algorithm
2453 // 4.11 from \cite Lyche:2011) we need to neglect the last
2454 // \f$p_d+1\f$ knots in what follows
2455 for (short_t i = 0; i < parDim_; ++i)
2456 knots_indices[i] = knots[i].index(
2457 {torch::indexing::Slice(0, knots[i].numel() - degrees_[i] - 1)});
2458
2459 // Get indices of the first \f$m_d\f$ new knots relative to old
2460 // knot vectors
2461 auto new_knot_indices = find_knot_indices(knots_indices);
2462
2463 // Update coefficient vector
2464 update_coeffs(knots, new_knot_indices);
2465
2466 // Swap old and new data
2467 knots.swap(knots_);
2468 nknots.swap(nknots_);
2469 ncoeffs.swap(ncoeffs_);
2470
2472 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2473
2474 return *this;
2475 }
2476
2477private:
2480 template <int64_t degree, int64_t deriv, int64_t terminal = degree - deriv>
2481 [[nodiscard]] inline int64_t constexpr eval_prefactor() const {
2482 if constexpr (degree > terminal)
2483 return degree * eval_prefactor<degree - 1, deriv, terminal>();
2484 else
2485 return 1;
2486 }
2487
2488public:
2490 inline void init_knots() {
2491
2492 for (short_t i = 0; i < parDim_; ++i) {
2493
2494 // Check that open knot vector can be created
2495 if ((ncoeffs_[i] < degrees_[i] + 1) || (ncoeffs_[i] < 2))
2496 throw std::runtime_error(
2497 "Not enough coefficients to create open knot vector");
2498
2499 nknots_[i] = ncoeffs_[i] + degrees_[i] + 1;
2500 int64_t num_inner = ncoeffs_[i] - degrees_[i];
2501
2502 auto start = torch::zeros({degrees_[i]}, options_);
2503 auto end = torch::ones({degrees_[i]}, options_);
2504 auto inner = torch::empty({0}, options_);
2505
2506 if (num_inner > 0) {
2507 inner = torch::arange(0, num_inner + 1, options_);
2508 inner = inner / static_cast<real_t>(num_inner);
2509 }
2510
2511 knots_[i] = torch::cat({start, inner, end});
2512 }
2513 }
2514
2517 inline void init_coeffs(enum init init) {
2518 switch (init) {
2519
2520 case (init::none): {
2521 break;
2522 }
2523
2524 case (init::zeros): {
2525
2526 // Fill coefficients with zeros
2527 int64_t size = ncumcoeffs();
2528 for (short_t i = 0; i < geoDim_; ++i)
2529 coeffs_[i] = torch::zeros(size, options_);
2530
2531 break;
2532 }
2533
2534 case (init::ones): {
2535
2536 // Fill coefficients with ones
2537 int64_t size = ncumcoeffs();
2538 for (short_t i = 0; i < geoDim_; ++i)
2539 coeffs_[i] = torch::ones(size, options_);
2540
2541 break;
2542 }
2543
2544 case (init::random): {
2545
2546 // Fill coefficients with random values
2547 int64_t size = ncumcoeffs();
2548 for (short_t i = 0; i < geoDim_; ++i)
2549 coeffs_[i] = torch::rand(size, options_);
2550
2551 break;
2552 }
2553
2554 case (init::linear): {
2555
2556 // Fill coefficients with the tensor-product of linearly
2557 // increasing values between 0 and 1 per univariate dimension
2558 for (short_t i = 0; i < geoDim_; ++i) {
2559 coeffs_[i] = torch::ones(1, options_);
2560
2561 for (short_t j = 0; j < parDim_; ++j) {
2562 if (i == j)
2563 coeffs_[i] = torch::kron(torch::linspace(static_cast<real_t>(0),
2564 static_cast<real_t>(1),
2565 ncoeffs_[j], options_),
2566 coeffs_[i]);
2567 else
2568 coeffs_[i] =
2569 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2570 }
2571
2572 // Enable gradient calculation for non-leaf tensor
2573 if (options_.requires_grad())
2574 coeffs_[i].retain_grad();
2575 }
2576 break;
2577 }
2578
2579 case (init::greville): {
2580
2581 // Fill coefficients with the tensor-product of Greville
2582 // abscissae values per univariate dimension
2583 for (short_t i = 0; i < geoDim_; ++i) {
2584 coeffs_[i] = torch::ones(1, options_);
2585
2586 for (short_t j = 0; j < parDim_; ++j) {
2587 if (i == j) {
2588
2589 int64_t count = ncoeffs_[j];
2590
2591 // idx_base: (count, 1)
2592 auto idx_base =
2593 torch::arange(
2594 count,
2595 options_.requires_grad(false).template dtype<int64_t>())
2596 .unsqueeze(1);
2597
2598 // offsets: (1, degree)
2599 auto offsets =
2600 torch::arange(
2601 1, degrees_[j] + 1,
2602 options_.requires_grad(false).template dtype<int64_t>())
2603 .unsqueeze(0);
2604
2605 // indices: (count, degree)
2606 auto indices = idx_base + offsets;
2607
2608 // Gather relevant knot values: shape (count, degree)
2609 auto gathered = knots_[j]
2610 .index_select(0, indices.flatten())
2611 .view({count, degrees_[j]});
2612
2613 // Compute mean along degree dimension (dim=1)
2614 auto greville_ = gathered.mean(1);
2615
2616 coeffs_[i] = torch::kron(greville_, coeffs_[i]);
2617 } else
2618 coeffs_[i] =
2619 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2620 }
2621
2622 // Enable gradient calculation for non-leaf tensor
2623 if (options_.requires_grad())
2624 coeffs_[i].retain_grad();
2625 }
2626 break;
2627 }
2628
2629 case (init::linspace): {
2630
2631 // Fill coefficients with increasing values
2632 int64_t size = ncumcoeffs();
2633 for (short_t i = 0; i < geoDim_; ++i)
2634 coeffs_[i] = torch::linspace(
2635 std::pow(10, i) * 0, std::pow(10, i) * (size - 1), size, options_);
2636
2637 break;
2638 }
2639
2640 default:
2641 throw std::runtime_error("Unsupported init option");
2642 }
2643 }
2644
2645protected:
2650 const utils::TensorArray<parDim_> &knot_indices) {
2651
2652 // Check compatibility of arguments
2653 for (short_t i = 0; i < parDim_; ++i)
2654 assert(knots[i].numel() == knot_indices[i].numel() + degrees_[i] + 1);
2655
2656 if constexpr (parDim_ == 1) {
2657
2658 auto basfunc = update_coeffs_univariate<degrees_[0], 0>(
2659 knots[0].flatten(), knot_indices[0].flatten());
2660
2661 auto coeff_indices = find_coeff_indices(knot_indices);
2662
2663 for (short_t i = 0; i < geoDim_; ++i)
2664 coeffs(i) =
2665 utils::dotproduct(basfunc, coeffs(i)
2666 .index_select(0, coeff_indices)
2667 .view({-1, knot_indices[0].numel()}))
2668 .view(knot_indices[0].sizes());
2669
2670 } else {
2671
2672 // Lambda expressions to evaluate the basis functions
2673 auto basfunc_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
2674 if constexpr (sizeof...(Is) == 1)
2675 return (update_coeffs_univariate<degrees_[Is], Is>(
2676 knots[Is].flatten(), knot_indices[Is].flatten()),
2677 ...);
2678 else
2680 knots[Is].flatten(), knot_indices[Is].flatten())...);
2681 };
2682
2683 auto basfunc = basfunc_(utils::make_reverse_index_sequence<parDim_>{});
2684
2685 // Lambda expression to calculate the partial product of array
2686 // entry from start_index to stop_index (including the latter)
2687 auto prod_ = [](utils::TensorArray<parDim_> array, short_t start_index,
2688 short_t stop_index) {
2689 int64_t result{1};
2690 for (short_t i = start_index; i <= stop_index; ++i)
2691 result *= array[i].numel();
2692 return result;
2693 };
2694
2695 utils::TensorArray<parDim_> knot_indices_;
2696
2697 for (short_t i = 0; i < parDim_; ++i)
2698 knot_indices_[i] =
2699 knot_indices[i]
2700 .repeat_interleave(prod_(knot_indices, 0, i - 1), 0)
2701 .repeat(prod_(knot_indices, i + 1, parDim_ - 1));
2702
2703 auto coeff_indices = find_coeff_indices(knot_indices_);
2704
2705 for (short_t i = 0; i < geoDim_; ++i)
2706 coeffs(i) = utils::dotproduct(basfunc,
2707 coeffs(i)
2708 .index_select(0, coeff_indices)
2709 .view({-1, knot_indices_[0].numel()}))
2710 .view(knot_indices_[0].sizes());
2711 }
2712 }
2713
2714 // clang-format off
2821 // clang-format on
2822 template <short_t degree, short_t dim, short_t deriv>
2823 inline auto eval_basfunc_univariate(const torch::Tensor &xi,
2824 const torch::Tensor &knot_indices) const {
2825 assert(xi.sizes() == knot_indices.sizes());
2826
2827 if constexpr (deriv > degree) {
2828 return torch::zeros({degree + 1, xi.numel()}, options_);
2829 } else {
2830 // Algorithm 2.22 from \cite Lyche:2011
2831 torch::Tensor b = torch::ones({xi.numel()}, options_);
2832
2833 // Calculate R_k, k = 1, ..., p_d-r_d
2834 for (short_t k = 1; k <= degree - deriv; ++k) {
2835
2836 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2837 auto t1 =
2838 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2839 auto t21 =
2840 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2841 t1;
2842
2843 // We handle the special case 0/0:=0 by first creating a
2844 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2845 // we do not have to take the absolute value as t2 >= t1.
2846 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2847 .to(::iganet::dtype_v<real_t>);
2848
2849 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2850 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2851 // equals the original expression if the mask is 0, i.e.,
2852 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2853 auto w = torch::div(xi.repeat(k) - t1 - mask, t21 - mask);
2854
2855 // Calculate the vector of B-splines evaluated at xi
2856 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2857 torch::zeros_like(xi, options_)},
2858 0) +
2859 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2860 }
2861
2862 // Calculate DR_k, k = p_d-r_d+1, ..., p_d
2863 for (short_t k = degree - deriv + 1; k <= degree; ++k) {
2864
2865 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2866 auto t21 =
2867 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2868 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2869
2870 // We handle the special case 0/0:=0 by first creating a
2871 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2872 // we do not have to take the absolute value as t2 >= t1.
2873 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2874 .to(::iganet::dtype_v<real_t>);
2875
2876 // Instead of computing 1/(t2-t1) which is prone to yielding
2877 // 0/0 we compute (1-mask)/(t2-t1-mask) which equals the
2878 // original expression if the mask is 0, i.e., t2-t1 >= eps
2879 // and 1 otherwise since t1 <= xi < t2.
2880 auto w = torch::div(torch::ones_like(t21, options_) - mask, t21 - mask);
2881
2882 // Calculate the vector of B-splines evaluated at xi
2883 b = torch::cat({torch::mul(-w, b), torch::zeros_like(xi, options_)},
2884 0) +
2885 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2886 }
2887
2888 return b.view({degree + 1, xi.numel()});
2889 }
2890 }
2891
2899 template <short_t degree, short_t dim, short_t deriv>
2900 inline auto
2901 eval_basfunc_univariate_tr(const torch::Tensor &xi,
2902 const torch::Tensor &knot_indices) const {
2903 assert(xi.sizes() == knot_indices.sizes());
2904
2905 if constexpr (deriv > degree) {
2906 return torch::zeros({xi.numel(), degree + 1}, options_); // swapped shape
2907 } else {
2908 torch::Tensor b = torch::ones({xi.numel()}, options_);
2909
2910 // Calculate R_k, k = 1, ..., degree-deriv
2911 for (short_t k = 1; k <= degree - deriv; ++k) {
2912 auto t1 =
2913 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2914 auto t21 =
2915 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2916 t1;
2917
2918 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2919 .to(::iganet::dtype_v<real_t>);
2920
2921 auto w = torch::div(xi.repeat(k) - t1 - mask, t21 - mask);
2922
2923 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2924 torch::zeros_like(xi, options_)},
2925 0) +
2926 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2927 }
2928
2929 // Calculate DR_k, k = degree-deriv+1, ..., degree
2930 for (short_t k = degree - deriv + 1; k <= degree; ++k) {
2931 auto t21 =
2932 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2933 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2934
2935 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2936 .to(::iganet::dtype_v<real_t>);
2937
2938 auto w = torch::div(torch::ones_like(t21, options_) - mask, t21 - mask);
2939
2940 b = torch::cat({torch::mul(-w, b), torch::zeros_like(xi, options_)},
2941 0) +
2942 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2943 }
2944
2945 // Swap axes: shape [degree+1, xi.numel()] → [xi.numel(), degree+1]
2946 return b.view({degree + 1, xi.numel()}).transpose(0, 1);
2947 }
2948 }
2949
2961 template <short_t degree, short_t dim>
2962 inline auto
2963 update_coeffs_univariate(const torch::Tensor &knots,
2964 const torch::Tensor &knot_indices) const {
2965 // Algorithm 2.22 from \cite Lyche:2011 modified to implement
2966 // the Oslo algorithm (Algorithm 4.11 from \cite Lyche:2011)
2967 torch::Tensor b = torch::ones({knot_indices.numel()}, options_);
2968
2969 // Calculate R_k, k = 1, ..., p_d
2970 for (short_t k = 1; k <= degree; ++k) {
2971
2972 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2973 auto t1 =
2974 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2975 auto t21 =
2976 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2977 t1;
2978
2979 // We handle the special case 0/0:=0 by first creating a
2980 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2981 // we do not have to take the absolute value as t2 >= t1.
2982 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2983 .to(::iganet::dtype_v<real_t>);
2984
2985 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2986 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2987 // equals the original expression if the mask is 0, i.e.,
2988 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2989 auto w = torch::div(
2990 knots.index({torch::indexing::Slice(k, knot_indices.numel() + k)})
2991 .repeat(k) -
2992 t1 - mask,
2993 t21 - mask);
2994
2995 // Calculate the vector of B-splines evaluated at xi
2996 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2997 torch::zeros_like(knot_indices, options_)},
2998 0) +
2999 torch::cat(
3000 {torch::zeros_like(knot_indices, options_), torch::mul(w, b)}, 0);
3001 }
3002
3003 return b.view({degree + 1, knot_indices.numel()});
3004 }
3005
3006public:
3011 auto to_gismo() const {
3012
3013#ifdef IGANET_WITH_GISMO
3014
3015 gismo::gsMatrix<real_t> coefs(ncumcoeffs(), geoDim_);
3016
3017 for (short_t g = 0; g < geoDim_; ++g) {
3018 auto [coeffs_cpu, coeffs_accessor] =
3019 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
3020 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
3021 coefs.col(g) =
3022 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
3023 }
3024
3025 std::array<gismo::gsKnotVector<real_t>, parDim_> kv;
3026
3027 for (short_t i = 0; i < parDim_; ++i) {
3028 auto [knots_cpu, knots_accessor] =
3029 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
3030 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
3031 kv[i] = gismo::gsKnotVector<real_t>(degrees_[i], knots_cpu_ptr,
3032 knots_cpu_ptr + knots_cpu.size(0));
3033 }
3034
3035 if constexpr (parDim_ == 1) {
3036
3037 return gismo::gsBSpline<real_t>(gismo::give(kv[0]), gismo::give(coefs));
3038
3039 } else if constexpr (parDim_ == 2) {
3040
3041 return gismo::gsTensorBSpline<parDim_, real_t>(
3042 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(coefs));
3043
3044 } else if constexpr (parDim_ == 3) {
3045
3046 return gismo::gsTensorBSpline<parDim_, real_t>(
3047 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
3048 gismo::give(coefs));
3049
3050 } else if constexpr (parDim_ == 4) {
3051
3052 return gismo::gsTensorBSpline<parDim_, real_t>(
3053 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
3054 gismo::give(kv[3]), gismo::give(coefs));
3055
3056 } else
3057 throw std::runtime_error("Invalid parametric dimension");
3058
3059#else
3060 throw std::runtime_error(
3061 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3062#endif
3063 }
3064
3065#ifdef IGANET_WITH_GISMO
3066
3067 // @brief Updates a given gsBSpline object from the B-spline object
3068 gismo::gsBSpline<real_t> &to_gismo(gismo::gsBSpline<real_t> &bspline,
3069 bool updateKnotVector = true,
3070 bool updateCoeffs = true) const {
3071
3072 if (updateKnotVector) {
3073
3074 if constexpr (parDim_ == 1) {
3075
3076 if (bspline.degree(0) != degrees_[0])
3077 throw std::runtime_error("Degrees mismatch");
3078
3079 auto [knots_cpu, knots_accessor] =
3080 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
3081 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
3082
3083 gismo::gsKnotVector<real_t> kv(degrees_[0], knots_cpu_ptr,
3084 knots_cpu_ptr + knots_cpu.size(0));
3085
3086 bspline.knots(0).swap(kv);
3087
3088 } else
3089 throw std::runtime_error("Invalid parametric dimension");
3090 }
3091
3092 if (updateCoeffs) {
3093
3094 for (short_t g = 0; g < geoDim_; ++g) {
3095 auto [coeffs_cpu, coeffs_accessor] =
3096 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
3097 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
3098 bspline.coefs().col(g) =
3099 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
3100 }
3101 }
3102
3103 return bspline;
3104 }
3105
3106 // @brief Updates a given gsTensorBSpline object from the B-spline object
3107 gismo::gsTensorBSpline<parDim_, real_t> &
3108 to_gismo(gismo::gsTensorBSpline<parDim_, real_t> &bspline,
3109 bool updateKnotVector = true, bool updateCoeffs = true) const {
3110
3111 if (updateKnotVector) {
3112
3113 // Check compatibility of arguments
3114 for (short_t i = 0; i < parDim_; ++i)
3115 assert(bspline.degree(i) == degrees_[i]);
3116
3117 for (short_t i = 0; i < parDim_; ++i) {
3118 auto [knots_cpu, knots_accessor] =
3119 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
3120 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
3121
3122 gismo::gsKnotVector<real_t> kv(degrees_[i], knots_cpu_ptr,
3123 knots_cpu_ptr + knots_cpu.size(0));
3124 bspline.knots(i).swap(kv);
3125 }
3126 }
3127
3128 if (updateCoeffs) {
3129
3130 for (short_t g = 0; g < geoDim_; ++g) {
3131 auto [coeffs_cpu, coeffs_accessor] =
3132 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
3133 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
3134 bspline.coefs().col(g) =
3135 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
3136 }
3137 }
3138
3139 return bspline;
3140 }
3141
3142#else // IGANET_WITH_GISMO
3143
3150 template <typename BSpline>
3151 BSpline &to_gismo(BSpline &bspline, bool updateKnotVector,
3152 bool updateCoeffs) const {
3153 throw std::runtime_error(
3154 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3155 return bspline;
3156 }
3157
3158#endif // IGANET_WITH_GISMO
3159
3160#ifdef IGANET_WITH_GISMO
3161
3162 // @brief Updates the B-spline object from a given gsBSpline object
3163 auto &from_gismo(const gismo::gsBSpline<real_t> &bspline,
3164 bool updateCoeffs = true, bool updateKnotVector = false) {
3165
3166 if (updateKnotVector) {
3167
3168 throw std::runtime_error(
3169 "Knot vectors can only be updated for Non-uniform B-splines");
3170 }
3171
3172 if (updateCoeffs) {
3173
3174 if (bspline.coefs().cols() != geoDim_)
3175 throw std::runtime_error("Geometric dimensions mismatch");
3176
3177 if (bspline.coefs().rows() != ncumcoeffs())
3178 throw std::runtime_error("Coefficient vector dimensions mismatch");
3179
3180 for (short_t g = 0; g < geoDim_; ++g) {
3181
3182 auto [coeffs_cpu, coeffs_accessor] =
3183 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
3184
3185 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
3186
3187 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
3188 coeffs_accessor[i] = coeffs_ptr[i];
3189
3190 coeffs_[g] = coeffs_[g].to(options_.device());
3191 }
3192 }
3193
3194 return *this;
3195 }
3196
3197 // @brief Updates the B-spline object from a given gsTensorBSpline object
3198 auto &from_gismo(const gismo::gsTensorBSpline<parDim_, real_t> &bspline,
3199 bool updateCoeffs = true, bool updateKnotVector = false) {
3200
3201 if (updateKnotVector) {
3202
3203 throw std::runtime_error(
3204 "Knot vectors can only be updated for Non-uniform B-splines");
3205 }
3206
3207 if (updateCoeffs) {
3208
3209 if (bspline.coefs().cols() != geoDim_)
3210 throw std::runtime_error("Geometric dimensions mismatch");
3211
3212 if (bspline.coefs().rows() != ncumcoeffs())
3213 throw std::runtime_error("Coefficient vector dimensions mismatch");
3214
3215 for (short_t g = 0; g < geoDim_; ++g) {
3216
3217 auto [coeffs_cpu, coeffs_accessor] =
3218 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
3219
3220 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
3221
3222 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
3223 coeffs_accessor[i] = coeffs_ptr[i];
3224
3225 coeffs_[g] = coeffs_[g].to(options_.device());
3226 }
3227 }
3228
3229 return *this;
3230 }
3231
3232#else // IGANET_WITH_GISMO
3233
3240 template <typename BSpline>
3241 auto &from_gismo(BSpline &bspline, bool updateCoeffs,
3242 bool updateKnotVector) {
3243 throw std::runtime_error(
3244 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3245 return *this;
3246 }
3247
3248#endif // IGANET_WITH_GISMO
3249};
3250
3258template <typename real_t, short_t GeoDim, short_t... Degrees>
3259inline torch::serialize::OutputArchive &
3260operator<<(torch::serialize::OutputArchive &archive,
3262 return obj.write(archive);
3263}
3264
3272template <typename real_t, short_t GeoDim, short_t... Degrees>
3273inline torch::serialize::InputArchive &
3274operator>>(torch::serialize::InputArchive &archive,
3276 return obj.read(archive);
3277}
3278
3284template <typename real_t, short_t GeoDim, short_t... Degrees>
3286 : public NonUniformSplineCore_,
3287 public UniformBSplineCore<real_t, GeoDim, Degrees...> {
3288private:
3290 using Base = UniformBSplineCore<real_t, GeoDim, Degrees...>;
3291
3292public:
3294 using value_type = real_t;
3295
3301 template <template <typename, short_t, short_t...> class BSpline,
3302 std::make_signed_t<short_t> degree_elevate = 0>
3303 using derived_type = BSpline<real_t, GeoDim, (Degrees + degree_elevate)...>;
3304
3307 template <std::make_signed_t<short_t> degree_elevate = 0>
3310
3314 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
3316 NonUniformBSplineCore<other_t, GeoDim_, Degrees_...>;
3317
3320 template <typename other_t>
3322 NonUniformBSplineCore<other_t, GeoDim, Degrees...>;
3323
3327 inline static constexpr bool is_uniform() { return false; }
3328
3331 inline static constexpr bool is_nonuniform() { return true; }
3332
3334 using UniformBSplineCore<real_t, GeoDim, Degrees...>::UniformBSplineCore;
3335
3338 explicit NonUniformBSplineCore(Base &&other) noexcept
3339 : Base(std::move(other)) {}
3340
3349 const std::array<std::vector<typename Base::value_type>, Base::parDim_>
3350 &kv,
3351 enum init init = init::greville,
3353 : Base(options) {
3354 init_knots(kv);
3356 }
3357
3371 NonUniformBSplineCore(const std::array<std::vector<typename Base::value_type>,
3372 Base::parDim_> &kv,
3374 bool clone = false,
3376 : Base(options) {
3377 init_knots(kv);
3378
3379 // Copy/clone coefficients
3380 if (clone)
3381 for (short_t i = 0; i < Base::geoDim_; ++i)
3382 Base::coeffs_[i] = coeffs[i]
3383 .clone()
3384 .to(options.requires_grad(false))
3385 .requires_grad_(Base::options.requires_grad());
3386 else
3387 for (short_t i = 0; i < Base::geoDim_; ++i)
3388 Base::coeffs_[i] = coeffs[i];
3389 }
3390
3391private:
3393 inline void init_knots(
3394 const std::array<std::vector<typename Base::value_type>, Base::parDim_>
3395 &kv) {
3396 for (short_t i = 0; i < Base::parDim_; ++i) {
3397
3398 // Check that knot vector has enough (n+p+1) entries
3399 if (2 * Base::degrees_[i] > kv[i].size() - 2)
3400 throw std::runtime_error("Knot vector is too short for an open knot "
3401 "vector (n+p+1 > 2*(p+1))");
3402
3404 Base::nknots_[i] = Base::knots_[i].size(0);
3407 }
3408 // Reverse ncoeffs
3409 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3410 }
3411
3412public:
3420 template <deriv deriv = deriv::func, bool memory_optimized = false>
3421 inline auto eval(const torch::Tensor &xi) const {
3422 return eval<deriv, memory_optimized>(utils::TensorArray1({xi}));
3423 }
3424
3432 template <deriv deriv = deriv::func, bool memory_optimized = false>
3433 inline auto eval(const utils::TensorArray<Base::parDim_> &xi) const {
3434 if constexpr (Base::parDim_ == 0) {
3436 for (short_t i = 0; i < Base::geoDim_; ++i)
3437 if constexpr (deriv == deriv::func)
3438 result.set(i, Base::coeffs_[i]);
3439 else
3440 result.set(i, torch::zeros_like(Base::coeffs_[i]));
3441 return result;
3442 } else
3443 return Base::template eval<deriv, memory_optimized>(
3444 xi, find_knot_indices(xi));
3445 }
3446
3453 template <deriv deriv = deriv::func, bool memory_optimized = false>
3454 inline auto
3456 const utils::TensorArray<Base::parDim_> &knot_indices) const {
3457 if constexpr (Base::parDim_ == 0) {
3459 for (short_t i = 0; i < Base::geoDim_; ++i)
3460 if constexpr (deriv == deriv::func)
3461 result.set(i, Base::coeffs_[i]);
3462 else
3463 result.set(i, torch::zeros_like(Base::coeffs_[i]));
3464 return result;
3465 } else
3466 return Base::template eval<deriv, memory_optimized>(xi, knot_indices);
3467 }
3468
3476 template <deriv deriv = deriv::func, bool memory_optimized = false>
3478 const utils::TensorArray<Base::parDim_> &knot_indices,
3479 const torch::Tensor &coeff_indices) const {
3480 if constexpr (Base::parDim_ == 0) {
3482 for (short_t i = 0; i < Base::geoDim_; ++i)
3483 if constexpr (deriv == deriv::func)
3484 result.set(i, Base::coeffs_[i]);
3485 else
3486 result.set(i, torch::zeros_like(Base::coeffs_[i]));
3487 return result;
3488 } else
3489 return Base::template eval<deriv, memory_optimized>(xi, knot_indices,
3490 coeff_indices);
3491 }
3493
3509 inline auto find_knot_indices(const torch::Tensor &xi) const {
3510 if constexpr (Base::parDim_ == 0)
3511 return torch::zeros_like(Base::coeffs_[0]).to(torch::kInt64);
3512 else
3514 }
3515
3519 inline auto
3521
3523 for (short_t i = 0; i < Base::parDim_; ++i) {
3524 auto nnz = Base::knots_[i].repeat({xi[i].numel(), 1}) >
3525 xi[i].flatten().view({-1, 1});
3526 indices[i] =
3527 torch::remainder(std::get<1>(((nnz.cumsum(1) == 1) & nnz).max(1)) - 1,
3528 Base::nknots_[i] - Base::degrees_[i] - 1)
3529 .view(xi[i].sizes());
3530 }
3531 return indices;
3532 }
3534
3544 inline NonUniformBSplineCore &uniform_refine(int numRefine = 1,
3545 int dim = -1) {
3546 assert(numRefine > 0);
3547 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
3548
3549 // Update knot vectors, number of knots and coefficients
3550 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
3552
3553 for (short_t i = 0; i < Base::parDim_; ++i) {
3554 auto [kv_cpu, kv_accessor] =
3555 utils::to_tensorAccessor<typename Base::value_type, 1>(
3556 Base::knots_[i], torch::kCPU);
3557
3558 std::vector<typename Base::value_type> kv;
3559 kv.reserve(Base::nknots_[i]);
3560 kv.push_back(kv_accessor[0]);
3561
3562 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
3563
3564 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]))
3565 for (int refine = 1; refine < (2 << (numRefine - 1)); ++refine)
3566 kv.push_back(
3567 kv_accessor[j - 1] +
3568 static_cast<Base::value_type>(refine) /
3569 static_cast<Base::value_type>(2 << (numRefine - 1)) *
3570 (kv_accessor[j] - kv_accessor[j - 1]));
3571
3572 kv.push_back(kv_accessor[j]);
3573 }
3574
3576 nknots[i] = kv.size();
3577 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
3578 }
3579
3580 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3581 // \f$m_d\f$ is the number of coefficients after the update. To
3582 // update the coefficients using the Oslo algorithm (Algorithm
3583 // 4.11 from \cite Lyche:2011) we need to neglect the last
3584 // \f$p_d+1\f$ knots in what follows
3585 for (short_t i = 0; i < Base::parDim_; ++i)
3586 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3587 0, knots[i].numel() - Base::degrees_[i] - 1)});
3588
3589 // Get indices of the first \f$m_d\f$ new knots relative to old
3590 // knot vectors
3591 auto new_knot_indices = find_knot_indices(knots_indices);
3592
3593 // Update coefficient vector
3594 Base::update_coeffs(knots, new_knot_indices);
3595
3596 // Swap old and new data
3597 knots.swap(Base::knots_);
3598 nknots.swap(Base::nknots_);
3599 ncoeffs.swap(Base::ncoeffs_);
3600
3602 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3603
3604 return *this;
3605 }
3606
3611 inline NonUniformBSplineCore &
3613 std::array<int64_t, Base::parDim_> nknots(Base::nknots_);
3614 std::array<int64_t, Base::parDim_> ncoeffs(Base::ncoeffs_);
3616
3617 // Update number of knots and coefficients and generate new knot
3618 // vectors
3619 for (short_t i = 0; i < Base::parDim_; ++i) {
3620 nknots[i] += knots[i].numel();
3621 ncoeffs[i] += knots[i].numel();
3622 knots_[i] =
3623 std::get<0>(torch::sort(torch::cat({Base::knots_[i], knots[i]})));
3624 }
3625
3626 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3627 // \f$m_d\f$ is the number of coefficients after the update. To
3628 // update the coefficients using the Oslo algorithm (Algorithm
3629 // 4.11 from \cite Lyche:2011) we need to neglect the last
3630 // \f$p_d+1\f$ knots in what follows
3631 for (short_t i = 0; i < Base::parDim_; ++i)
3632 knots_indices[i] = knots_[i].index({torch::indexing::Slice(
3633 0, knots_[i].numel() - Base::degrees_[i] - 1)});
3634
3635 // Get indices of the first \f$m_d\f$ new knots relative to old
3636 // knot vectors
3637 auto new_knot_indices = find_knot_indices(knots_indices);
3638
3639 // Update coefficient vector
3640 Base::update_coeffs(knots_, new_knot_indices);
3641
3642 // Swap old and new data
3643 knots_.swap(Base::knots_);
3644 nknots.swap(Base::nknots_);
3645 ncoeffs.swap(Base::ncoeffs_);
3646
3648 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3649
3650 return *this;
3651 }
3652
3658 inline NonUniformBSplineCore &reduce_continuity(int numReduce = 1,
3659 int dim = -1) {
3660 assert(numReduce > 0);
3661 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
3662
3663 // Update knot vectors, number of knots and coefficients
3664 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
3666
3667 for (short_t i = 0; i < Base::parDim_; ++i) {
3668 auto [kv_cpu, kv_accessor] =
3669 utils::to_tensorAccessor<typename Base::value_type, 1>(
3670 Base::knots_[i], torch::kCPU);
3671
3672 std::vector<typename Base::value_type> kv;
3673 kv.reserve(Base::nknots_[i]);
3674 kv.push_back(kv_accessor[0]);
3675
3676 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
3677
3678 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]) &&
3679 (kv_accessor[j] < kv_accessor[kv_accessor.size(0) - 1]))
3680 for (int reduce = 0; reduce < numReduce; ++reduce)
3681 kv.push_back(kv_accessor[j]);
3682
3683 kv.push_back(kv_accessor[j]);
3684 }
3685
3687 nknots[i] = kv.size();
3688 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
3689 }
3690
3691 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3692 // \f$m_d\f$ is the number of coefficients after the update. To
3693 // update the coefficients using the Oslo algorithm (Algorithm
3694 // 4.11 from \cite Lyche:2011) we need to neglect the last
3695 // \f$p_d+1\f$ knots in what follows
3696 for (short_t i = 0; i < Base::parDim_; ++i)
3697 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3698 0, knots[i].numel() - Base::degrees_[i] - 1)});
3699
3700 // Get indices of the first \f$m_d\f$ new knots relative to old
3701 // knot vectors
3702 auto new_knot_indices = find_knot_indices(knots_indices);
3703
3704 // Update coefficient vector
3705 Base::update_coeffs(knots, new_knot_indices);
3706
3707 // Swap old and new data
3708 knots.swap(Base::knots_);
3709 nknots.swap(Base::nknots_);
3710 ncoeffs.swap(Base::ncoeffs_);
3711
3713 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3714
3715 return *this;
3716 }
3717
3718#ifdef IGANET_WITH_GISMO
3719
3720 // @brief Updates the B-spline object from a given gsBSpline object
3721 auto &from_gismo(const gismo::gsBSpline<typename Base::value_type> &bspline,
3722 bool updateCoeffs = true, bool updateKnotVector = false) {
3723
3724 if (updateKnotVector) {
3725
3726 if constexpr (Base::parDim_ == 1) {
3727
3728 if (bspline.degree(0) != Base::degrees_[0])
3729 throw std::runtime_error("Degrees mismatch");
3730
3731 if (bspline.knots(0).size() != Base::nknots_[0])
3732 throw std::runtime_error("Knot vector dimensions mismatch");
3733
3734 auto [knots0_cpu, knots0_accessor] =
3735 utils::to_tensorAccessor<typename Base::value_type, 1>(
3736 Base::knots_[0], torch::kCPU);
3737
3738 const typename Base::value_type *knots0_ptr =
3739 bspline.knots(0).asMatrix().data();
3740
3741 for (int64_t i = 0; i < Base::nknots_[0]; ++i)
3742 knots0_accessor[i] = knots0_ptr[i];
3743
3745
3746 } else
3747 throw std::runtime_error("Invalid parametric dimension");
3748 }
3749
3750 if (updateCoeffs) {
3751
3752 if (bspline.coefs().rows() != Base::geoDim_)
3753 throw std::runtime_error("Geometric dimensions mismatch");
3754
3755 if (bspline.coefs().cols() != Base::ncumcoeffs())
3756 throw std::runtime_error("Coefficient vector dimensions mismatch");
3757
3758 for (short_t g = 0; g < Base::geoDim_; ++g) {
3759
3760 auto [coeffs_cpu, coeffs_accessor] =
3761 utils::to_tensorAccessor<typename Base::value_type, 1>(
3762 Base::coeffs_[g], torch::kCPU);
3763
3764 const typename Base::value_type *coeffs_ptr =
3765 bspline.coefs().row(g).data();
3766
3767 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3768 coeffs_accessor[i] = coeffs_ptr[i];
3769
3771 }
3772 }
3773
3774 return *this;
3775 }
3776
3777 // @brief Updates the B-spline object from a given gsTensorBSpline object
3778 auto &from_gismo(
3779 const gismo::gsTensorBSpline<Base::parDim_, typename Base::value_type>
3780 &bspline,
3781 bool updateCoeffs = true, bool updateKnotVector = false) {
3782
3783 if (updateKnotVector) {
3784
3785 for (short_t i = 0; i < Base::parDim_; ++i) {
3786 if (bspline.degree(i) != Base::degrees_[i])
3787 throw std::runtime_error("Degrees mismatch");
3788
3789 if (bspline.knots(i).size() != Base::nknots_[i])
3790 throw std::runtime_error("Knot vector dimensions mismatch");
3791
3792 auto [knots_cpu, knots_accessor] =
3793 utils::to_tensorAccessor<typename Base::value_type, 1>(
3794 Base::knots_[i], torch::kCPU);
3795
3796 const typename Base::value_type *knots_ptr =
3797 bspline.knots(i).asMatrix().data();
3798
3799 for (int64_t i = 0; i < Base::nknots_[i]; ++i)
3800 knots_accessor[i] = knots_ptr[i];
3801
3803 }
3804 }
3805
3806 if (updateCoeffs) {
3807
3808 if (bspline.coefs().rows() != Base::geoDim_)
3809 throw std::runtime_error("Geometric dimensions mismatch");
3810
3811 if (bspline.coefs().cols() != Base::ncumcoeffs())
3812 throw std::runtime_error("Coefficient vector dimensions mismatch");
3813
3814 for (short_t g = 0; g < Base::geoDim_; ++g) {
3815
3816 auto [coeffs_cpu, coeffs_accessor] =
3817 utils::to_tensorAccessor<typename Base::value_type, 1>(
3818 Base::coeffs_[g], torch::kCPU);
3819
3820 const typename Base::value_type *coeffs_ptr =
3821 bspline.coefs().row(g).data();
3822
3823 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3824 coeffs_accessor[i] = coeffs_ptr[i];
3825
3827 }
3828 }
3829
3830 return *this;
3831 }
3832
3833#else // IGANET_WITH_GISMO
3834
3841 template <typename BSpline>
3842 auto &from_gismo(BSpline &bspline, bool updateCoeffs,
3843 bool updateKnotVector) {
3844 throw std::runtime_error(
3845 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3846 return *this;
3847 }
3848
3849#endif // IGANET_WITH_GISMO
3850};
3851
3853class Spline_ {};
3854
3857template <typename T>
3858concept SplineType = std::is_base_of_v<Spline_, T>;
3859
3862template <typename T>
3864 std::is_base_of_v<Spline_, T> && std::is_base_of_v<UniformSplineCore_, T> &&
3865 !std::is_base_of_v<NonUniformSplineCore_, T>;
3866
3869template <typename T>
3870concept NonUniformSplineType = std::is_base_of_v<Spline_, T> &&
3871 std::is_base_of_v<NonUniformSplineCore_, T>;
3872
3886template <typename BSplineCore>
3888class BSplineCommon : public Spline_,
3889 public BSplineCore,
3890 protected utils::FullQualifiedName {
3891public:
3893 using BSplineCore::BSplineCore;
3894
3900 template <template <typename, short_t, short_t...> class T,
3901 std::make_signed_t<short_t> degree_elevate = 0>
3903 typename BSplineCore::template derived_type<T, degree_elevate>>;
3904
3907 template <std::make_signed_t<short_t> degree_elevate = 0>
3910
3914 template <typename real_t, short_t GeoDim, short_t... Degrees>
3916 BSplineCommon<typename BSplineCore::template derived_self_type<
3917 real_t, GeoDim, Degrees...>>;
3918
3921 template <typename other_t>
3923 typename BSplineCore::template real_derived_self_type<other_t>>;
3924
3926 using Ptr = std::shared_ptr<BSplineCommon>;
3927
3929 using uPtr = std::unique_ptr<BSplineCommon>;
3930
3932 BSplineCommon(const BSplineCommon &) = default;
3933
3937 BSplineCommon(const BSplineCommon &other, bool clone) : BSplineCommon(other) {
3938 if (clone)
3939 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3940 BSplineCore::coeffs_[i] = other.coeffs(i).clone();
3941 }
3942
3949 bool clone = false)
3950 : BSplineCommon(other) {
3951 if (clone)
3952 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3953 BSplineCore::coeffs_[i] = coeffs[i].clone();
3954 else
3955 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3956 BSplineCore::coeffs_[i] = coeffs[i];
3957 }
3958
3961
3965 template <typename OtherCore>
3967 explicit BSplineCommon(OtherCore &&core)
3968 : BSplineCore(std::forward<OtherCore>(core)) {}
3969
3976 [[nodiscard]] auto to_nonuniform() &&
3979 {
3980 using target_core =
3981 typename BSplineCore::template derived_type<NonUniformBSplineCore>;
3983 std::move(static_cast<BSplineCore &>(*this)));
3984 }
3985
3991 [[nodiscard]] auto to_uniform() &&
3993 {
3994 using target_core =
3995 typename BSplineCore::template derived_type<UniformBSplineCore>;
3996
3997 BSplineCommon<target_core> canonical(BSplineCore::ncoeffs(), init::none,
3998 BSplineCore::options());
3999 for (short_t i = 0; i < BSplineCore::parDim(); ++i)
4000 if (!torch::equal(BSplineCore::knots(i), canonical.knots(i)))
4001 throw std::runtime_error(
4002 "Cannot convert a non-uniform B-spline to a uniform B-spline");
4003
4005 std::move(static_cast<target_core &>(*this)));
4006 }
4007
4013 : BSplineCommon(std::move(other)) {
4014 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
4015 BSplineCore::coeffs_[i] = std::move(coeffs[i]);
4016 }
4017
4022 inline static Ptr
4027
4033 inline static Ptr
4034 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
4035 enum init init = init::greville,
4038 return uPtr(new BSplineCommon(ncoeffs, init, options));
4039 }
4040
4047 inline static Ptr
4048 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
4050 bool clone = false,
4053 return uPtr(new BSplineCommon(ncoeffs, coeffs, clone, options));
4054 }
4055
4061 inline static Ptr
4062 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
4066 return uPtr(new BSplineCommon(ncoeffs, coeffs, options));
4067 }
4068
4074 inline static Ptr
4075 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
4076 BSplineCore::parDim_> &kv,
4077 enum init init = init::greville,
4080 return uPtr(new BSplineCommon(kv, init, options));
4081 }
4082
4089 inline static Ptr
4090 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
4091 BSplineCore::parDim_> &kv,
4093 bool clone = false,
4096 return uPtr(new BSplineCommon(kv, coeffs, clone, options));
4097 }
4099
4104 inline static Ptr
4107 return std::make_shared<BSplineCommon>(options);
4108 }
4109
4115 inline static Ptr
4116 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
4117 enum init init = init::greville,
4120 return std::make_shared<BSplineCommon>(ncoeffs, init, options);
4121 }
4122
4129 inline static Ptr
4130 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
4132 bool clone = false,
4135 return std::make_shared<BSplineCommon>(ncoeffs, coeffs, clone, options);
4136 }
4137
4143 inline static Ptr
4144 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
4148 return std::make_shared<BSplineCommon>(ncoeffs, coeffs, options);
4149 }
4150
4156 inline static Ptr
4157 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
4158 BSplineCore::parDim_> &kv,
4159 enum init init = init::greville,
4162 return std::make_shared<BSplineCommon>(kv, init, options);
4163 }
4164
4171 inline static Ptr
4172 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
4173 BSplineCore::parDim_> &kv,
4175 bool clone = false,
4178 return std::make_shared<BSplineCommon>(kv, coeffs, clone, options);
4179 }
4181
4191 inline BSplineCommon &uniform_refine(int numRefine = 1, int dim = -1) {
4192 BSplineCore::uniform_refine(numRefine, dim);
4193 return *this;
4194 }
4195
4198 inline auto clone() const {
4199 BSplineCommon result;
4200
4201 result.nknots_ = BSplineCore::nknots_;
4202 result.ncoeffs_ = BSplineCore::ncoeffs_;
4203 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
4204
4205 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4206 result.knots_[i] = BSplineCore::knots_[i].clone();
4207
4208 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
4209 result.coeffs_[i] = BSplineCore::coeffs_[i].clone();
4210
4211 return result;
4212 }
4213
4218 template <typename real_t> inline auto to(Options<real_t> options) const {
4220 result(options);
4221
4222 result.nknots_ = BSplineCore::nknots_;
4223 result.ncoeffs_ = BSplineCore::ncoeffs_;
4224 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
4225
4226 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4227 result.knots_[i] = BSplineCore::knots_[i].to(options);
4228
4229 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
4230 result.coeffs_[i] = BSplineCore::coeffs_[i].to(options);
4231
4232 return result;
4233 }
4234
4238 inline auto to(torch::Device device) const {
4239 BSplineCommon result(BSplineCore::options_.device(device));
4240
4241 result.nknots_ = BSplineCore::nknots_;
4242 result.ncoeffs_ = BSplineCore::ncoeffs_;
4243 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
4244
4245 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4246 result.knots_[i] = BSplineCore::knots_[i].to(device);
4247
4248 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
4249 result.coeffs_[i] = BSplineCore::coeffs_[i].to(device);
4250
4251 return result;
4252 }
4253
4257 template <typename real_t> inline auto to() const {
4258 return to(BSplineCore::options_.template dtype<real_t>());
4259 }
4260
4270 inline auto diff(const BSplineCommon &other, int dim = -1) const {
4271 return this->clone().diff_(other, dim);
4272 }
4273
4283 inline auto diff_(const BSplineCommon &other, int dim = -1) {
4284
4285 bool compatible(true);
4286
4287 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4288 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
4289
4290 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4291 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
4292
4293 if (!compatible)
4294 throw std::runtime_error("B-splines are not compatible");
4295
4296 if (dim == -1) {
4297 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
4298 BSplineCore::coeffs(i) -= other.coeffs(i);
4299 } else
4300 BSplineCore::coeffs(dim) -= other.coeffs(dim);
4301
4302 return *this;
4303 }
4304
4314 inline auto abs_diff(const BSplineCommon &other, int dim = -1) const {
4315 return this->clone().abs_diff_(other, dim);
4316 }
4317
4327 inline auto abs_diff_(const BSplineCommon &other, int dim = -1) {
4328
4329 bool compatible(true);
4330
4331 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4332 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
4333
4334 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4335 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
4336
4337 if (!compatible)
4338 throw std::runtime_error("B-splines are not compatible");
4339
4340 if (dim == -1) {
4341 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
4342 BSplineCore::coeffs(i) =
4343 torch::abs(BSplineCore::coeffs(i) - other.coeffs(i));
4344 } else
4345 BSplineCore::coeffs(dim) =
4346 torch::abs(BSplineCore::coeffs(dim) - other.coeffs(dim));
4347
4348 return *this;
4349 }
4350
4355 inline auto norm() const {
4356 return torch::mean(
4357 torch::pow(BSplineCore::eval(BSplineCore::greville())(0), 2));
4358 }
4359
4364 inline auto scale(BSplineCore::value_type s, int dim = -1) const {
4365 return this->clone().scale_(s, dim);
4366 }
4367
4372 inline auto scale_(BSplineCore::value_type s, int dim = -1) {
4373 if (dim == -1)
4374 for (int i = 0; i < BSplineCore::geoDim(); ++i)
4375 BSplineCore::coeffs(i) *= s;
4376 else
4377 BSplineCore::coeffs(dim) *= s;
4378 return *this;
4379 }
4380
4384 inline auto
4385 scale(std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
4386 const {
4387 return this->clone().scale_(v);
4388 }
4389
4393 inline auto scale_(
4394 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
4395 for (int i = 0; i < BSplineCore::geoDim(); ++i)
4396 BSplineCore::coeffs(i) *= v[i];
4397 return *this;
4398 }
4399
4403 inline auto translate(
4404 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
4405 const {
4406 return this->clone().translate_(v);
4407 }
4408
4412 inline auto translate_(
4413 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
4414 for (int i = 0; i < BSplineCore::geoDim(); ++i)
4415 BSplineCore::coeffs(i) += v[i];
4416 return *this;
4417 }
4418
4422 inline auto rotate(BSplineCore::value_type angle) const {
4423 return this->clone().rotate_(angle);
4424 }
4425
4429 inline auto rotate_(BSplineCore::value_type angle) {
4430
4431 static_assert(BSplineCore::geoDim() == 2,
4432 "Rotation about one angle is only available in 2D");
4433
4434 utils::TensorArray<2> coeffs;
4435 coeffs[0] = std::cos(angle) * BSplineCore::coeffs(0) -
4436 std::sin(angle) * BSplineCore::coeffs(1);
4437 coeffs[1] = std::sin(angle) * BSplineCore::coeffs(0) +
4438 std::cos(angle) * BSplineCore::coeffs(1);
4439
4440 BSplineCore::coeffs().swap(coeffs);
4441 return *this;
4442 }
4443
4447 inline auto
4448 rotate(std::array<typename BSplineCore::value_type, 3> angle) const {
4449 return this->clone().rotate_(angle);
4450 }
4451
4455 inline auto rotate_(std::array<typename BSplineCore::value_type, 3> angle) {
4456
4457 static_assert(BSplineCore::geoDim() == 3,
4458 "Rotation about two angles is only available in 3D");
4459
4460 utils::TensorArray<3> coeffs;
4461 coeffs[0] =
4462 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(0) +
4463 (std::sin(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) -
4464 std::cos(angle[0]) * std::sin(angle[2])) *
4465 BSplineCore::coeffs(1) +
4466 (std::cos(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) +
4467 std::sin(angle[0]) * std::sin(angle[2])) *
4468 BSplineCore::coeffs(2);
4469
4470 coeffs[1] =
4471 std::cos(angle[1]) * std::sin(angle[2]) * BSplineCore::coeffs(0) +
4472 (std::sin(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) +
4473 std::cos(angle[0]) * std::cos(angle[2])) *
4474 BSplineCore::coeffs(1) +
4475 (std::cos(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) -
4476 std::sin(angle[0]) * std::cos(angle[2])) *
4477 BSplineCore::coeffs(2);
4478
4479 coeffs[2] =
4480 -std::sin(angle[1]) * BSplineCore::coeffs(0) +
4481 std::sin(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(1) +
4482 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(2);
4483
4484 BSplineCore::coeffs().swap(coeffs);
4485 return *this;
4486 }
4487
4490 inline auto boundingBox() const {
4491
4492 // Lambda expression to compute the minimum value of all dimensions
4493 auto min_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4494 return torch::stack({BSplineCore::coeffs(Is).min()...});
4495 };
4496
4497 // Lambda expression to compute the maximum value of all dimensions
4498 auto max_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4499 return torch::stack({BSplineCore::coeffs(Is).max()...});
4500 };
4501
4502 std::pair<torch::Tensor, torch::Tensor> bbox;
4503 bbox.first = min_(std::make_index_sequence<BSplineCore::geoDim_>{});
4504 bbox.second = max_(std::make_index_sequence<BSplineCore::geoDim_>{});
4505 return bbox;
4506 }
4507
4515 template <bool memory_optimized = false>
4516 inline auto nv(const torch::Tensor &xi) const {
4517 return nv<memory_optimized>(utils::TensorArray1({xi}));
4518 }
4519
4525 template <bool memory_optimized = false>
4526 inline auto nv(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4527 return nv<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4528 }
4530
4539 template <bool memory_optimized = false>
4540 inline auto
4542 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4543 return nv<memory_optimized>(
4544 xi, knot_indices,
4545 BSplineCore::template find_coeff_indices<memory_optimized>(
4546 knot_indices));
4547 }
4548
4560 template <bool memory_optimized = false>
4562 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4563 const torch::Tensor &coeff_indices) const {
4564
4565 if constexpr (BSplineCore::parDim_ == 1 && BSplineCore::geoDim_ == 2) {
4566 // Compute the perpendicular vector
4567 auto eval_ = BSplineCore::template eval<deriv::dx, memory_optimized>(
4568 xi, knot_indices, coeff_indices);
4569 return utils::BlockTensor<torch::Tensor, 1, 2>(*eval_[1], -*eval_[0]);
4570 } else if constexpr (BSplineCore::parDim_ == 1 &&
4571 BSplineCore::geoDim_ == 3) {
4572 // Compute the Frenet normal vector
4573 auto t_ = BSplineCore::template eval<deriv::dx, memory_optimized>(
4574 xi, knot_indices, coeff_indices)
4575 .normalize();
4576 auto a_ = BSplineCore::template eval<deriv::dx ^ 2, memory_optimized>(
4577 xi, knot_indices, coeff_indices);
4578 auto n_ = a_ - a_.dot(t_) * t_;
4580 n_(0, 0), n_(0, 1), n_(0, 2), t_(0, 0), t_(0, 1), t_(0, 2));
4581 } else if constexpr (BSplineCore::parDim_ == 2 &&
4582 BSplineCore::geoDim_ == 3) {
4583 // Compute the cross product of tangent vectors
4584 auto jac_ = jac<memory_optimized>(xi, knot_indices, coeff_indices);
4586 jac_(1, 0) * jac_(2, 1) - jac_(2, 0) * jac_(1, 1),
4587 jac_(2, 0) * jac_(0, 1) - jac_(0, 0) * jac_(2, 1),
4588 jac_(0, 0) * jac_(1, 1) - jac_(1, 0) * jac_(0, 1));
4589 } else {
4590 throw std::runtime_error("Unsupported parametric/geometric dimension");
4592 }
4593 }
4594
4595 // clang-format off
4616 // clang-format off
4618 template <bool memory_optimized = false>
4619 inline auto curl(const torch::Tensor &xi) const {
4620 return curl<memory_optimized>(utils::TensorArray1({xi}));
4621 }
4622
4628 template <bool memory_optimized = false>
4629 inline auto curl(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4630 return curl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4631 }
4633
4634 // clang-format off
4657 // clang-format on
4658 template <bool memory_optimized = false>
4659 inline auto
4661 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4662 return curl<memory_optimized>(
4663 xi, knot_indices,
4664 BSplineCore::template find_coeff_indices<memory_optimized>(
4665 knot_indices));
4666 }
4667
4694 template <bool memory_optimized = false>
4696 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4697 const torch::Tensor &coeff_indices) const {
4698
4699 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
4700 "curl(.) requires that parametric and geometric dimension "
4701 "are the same");
4702
4703 // Check compatibility of arguments
4704 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4705 assert(xi[i].sizes() == knot_indices[i].sizes());
4706 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4707 assert(xi[0].sizes() == xi[i].sizes());
4708
4709 if constexpr (BSplineCore::parDim_ == 2)
4710
4717 *BSplineCore::template eval<deriv::dx, memory_optimized>(
4718 xi, knot_indices, coeff_indices)[1] -
4719 *BSplineCore::template eval<deriv::dy, memory_optimized>(
4720 xi, knot_indices, coeff_indices)[0]);
4721
4722 else if constexpr (BSplineCore::parDim_ == 3)
4723
4728 *BSplineCore::template eval<deriv::dy, memory_optimized>(
4729 xi, knot_indices, coeff_indices)[2] -
4730 *BSplineCore::template eval<deriv::dz, memory_optimized>(
4731 xi, knot_indices, coeff_indices)[1],
4732 *BSplineCore::template eval<deriv::dz, memory_optimized>(
4733 xi, knot_indices, coeff_indices)[0] +
4734 *BSplineCore::template eval<deriv::dx, memory_optimized>(
4735 xi, knot_indices, coeff_indices)[2],
4736 *BSplineCore::template eval<deriv::dx, memory_optimized>(
4737 xi, knot_indices, coeff_indices)[1] +
4738 *BSplineCore::template eval<deriv::dy, memory_optimized>(
4739 xi, knot_indices, coeff_indices)[0]);
4740
4741 else {
4742 throw std::runtime_error("Unsupported parametric/geometric dimension");
4744 }
4745 }
4746
4768 template <bool memory_optimized = false, typename Geometry>
4769 inline auto icurl(const Geometry &G, const torch::Tensor &xi) const {
4770 if constexpr (BSplineCore::parDim_ == 0)
4772 torch::zeros_like(BSplineCore::coeffs_[0])};
4773 else
4774 return icurl<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4775 }
4776
4783 template <bool memory_optimized = false, typename Geometry>
4784 inline auto icurl(const Geometry &G,
4786 if constexpr (BSplineCore::parDim_ == 0)
4788 torch::zeros_like(BSplineCore::coeffs_[0])};
4789 else
4790 return icurl<memory_optimized, Geometry>(
4791 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4792 }
4794
4818 template <bool memory_optimized = false, typename Geometry>
4819 inline auto
4821 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4822 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4823 if constexpr (BSplineCore::parDim_ == 0)
4825 torch::zeros_like(BSplineCore::coeffs_[0])};
4826 else
4827 return icurl<memory_optimized, Geometry>(
4828 G, xi, knot_indices,
4829 BSplineCore::template find_coeff_indices<memory_optimized>(
4830 knot_indices),
4831 knot_indices_G,
4832 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4833 }
4834
4865 template <bool memory_optimized = false, typename Geometry>
4866 inline auto
4868 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4869 const torch::Tensor &coeff_indices,
4870 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4871 const torch::Tensor &coeff_indices_G) const {
4872
4873 if constexpr (BSplineCore::parDim_ == 0)
4875 torch::zeros_like(BSplineCore::coeffs_[0])};
4876 else {
4878 det[0] = std::make_shared<torch::Tensor>(torch::reciprocal(
4879 G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G)
4880 .det()));
4881
4882 return det * (curl<memory_optimized>(xi, knot_indices, coeff_indices) *
4883 G.template jac<memory_optimized>(xi, knot_indices_G,
4884 coeff_indices_G));
4885 }
4886 }
4887
4909 template <bool memory_optimized = false>
4910 inline auto div(const torch::Tensor &xi) const {
4911 if constexpr (BSplineCore::parDim_ == 0)
4913 torch::zeros_like(BSplineCore::coeffs_[0])};
4914 return div<memory_optimized>(utils::TensorArray1({xi}));
4915 }
4916
4921 template <bool memory_optimized = false>
4922 inline auto div(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4923 if constexpr (BSplineCore::parDim_ == 0)
4925 torch::zeros_like(BSplineCore::coeffs_[0])};
4926 return div<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4927 }
4929
4952 template <bool memory_optimized = false>
4953 inline auto
4955 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4956 if constexpr (BSplineCore::parDim_ == 0)
4958 torch::zeros_like(BSplineCore::coeffs_[0])};
4959 return div<memory_optimized>(
4960 xi, knot_indices,
4961 BSplineCore::template find_coeff_indices<memory_optimized>(
4962 knot_indices));
4963 }
4964
4990 template <bool memory_optimized = false>
4992 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4993 const torch::Tensor &coeff_indices) const {
4994
4995 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
4996 "div(.) requires parDim == geoDim");
4997
4998 if constexpr (BSplineCore::parDim_ == 0)
5000 torch::zeros_like(BSplineCore::coeffs_[0])};
5001 // return torch::zeros_like(BSplineCore::coeffs_[0]);
5002
5003 else {
5004 // Check compatibility of arguments
5005 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5006 assert(xi[i].sizes() == knot_indices[i].sizes());
5007 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5008 assert(xi[0].sizes() == xi[i].sizes());
5009
5010 // Lambda expression to evaluate the divergence
5011 auto div_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5013 (*BSplineCore::template eval<
5015 memory_optimized>(xi, knot_indices, coeff_indices)[Is] +
5016 ...)};
5017 };
5018
5019 return div_(std::make_index_sequence<BSplineCore::parDim_>{});
5020 }
5021 }
5022
5045 template <bool memory_optimized = false, typename Geometry>
5046 inline auto idiv(const Geometry &G, const torch::Tensor &xi) {
5047 if constexpr (BSplineCore::parDim_ == 0)
5049 torch::zeros_like(BSplineCore::coeffs_[0])};
5050 else
5051 return idiv<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5052 }
5053
5060 template <bool memory_optimized = false, typename Geometry>
5061 inline auto idiv(const Geometry &G,
5063 if constexpr (BSplineCore::parDim_ == 0)
5065 torch::zeros_like(BSplineCore::coeffs_[0])};
5066 else
5067 return idiv<memory_optimized, Geometry>(
5068 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5069 }
5071
5097 template <bool memory_optimized = false, typename Geometry>
5098 inline auto
5100 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5101 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5102 if constexpr (BSplineCore::parDim_ == 0)
5104 torch::zeros_like(BSplineCore::coeffs_[0])};
5105 else
5106 return idiv<memory_optimized, Geometry>(
5107 G, xi, knot_indices,
5108 BSplineCore::template find_coeff_indices<memory_optimized>(
5109 knot_indices),
5110 knot_indices_G,
5111 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5112 }
5113
5145 template <bool memory_optimized = false, typename Geometry>
5146 inline auto idiv(const Geometry &G,
5148 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5149 const torch::Tensor &coeff_indices,
5150 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5151 const torch::Tensor &coeff_indices_G) const {
5152 if constexpr (BSplineCore::parDim_ == 0)
5154 torch::zeros_like(BSplineCore::coeffs_[0])};
5155 else
5156 return ijac<memory_optimized, Geometry>(G, xi, knot_indices,
5157 coeff_indices, knot_indices_G,
5158 coeff_indices_G)
5159 .trace();
5160 }
5161
5182 template <bool memory_optimized = false>
5183 inline auto grad(const torch::Tensor &xi) const {
5184
5185 static_assert(BSplineCore::geoDim_ == 1,
5186 "grad(.) requires 1D variable, use jac(.) instead");
5187
5188 if constexpr (BSplineCore::parDim_ == 0)
5190 torch::zeros_like(BSplineCore::coeffs_[0])};
5191 else
5192 return grad<memory_optimized>(utils::TensorArray1({xi}));
5193 }
5194
5199 template <bool memory_optimized = false>
5200 inline auto grad(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5201
5202 static_assert(BSplineCore::geoDim_ == 1,
5203 "grad(.) requires 1D variable, use jac(.) instead");
5204
5205 if constexpr (BSplineCore::parDim_ == 0)
5207 torch::zeros_like(BSplineCore::coeffs_[0])};
5208 else
5209 return grad<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5210 }
5212
5234 template <bool memory_optimized = false>
5235 inline auto
5237 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5238
5239 static_assert(BSplineCore::geoDim_ == 1,
5240 "grad(.) requires 1D variable, use jac(.) instead");
5241
5242 if constexpr (BSplineCore::parDim_ == 0)
5244 torch::zeros_like(BSplineCore::coeffs_[0])};
5245 else
5246 return grad<memory_optimized>(
5247 xi, knot_indices,
5248 BSplineCore::template find_coeff_indices<memory_optimized>(
5249 knot_indices));
5250 }
5251
5276 template <bool memory_optimized = false>
5278 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5279 const torch::Tensor &coeff_indices) const {
5280
5281 static_assert(BSplineCore::geoDim_ == 1,
5282 "grad(.) requires 1D variable, use jac(.) instead");
5283
5284 if constexpr (BSplineCore::parDim_ == 0)
5286 torch::zeros_like(BSplineCore::coeffs_[0])};
5287
5288 else {
5289 // Check compatibility of arguments
5290 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5291 assert(xi[i].sizes() == knot_indices[i].sizes());
5292 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5293 assert(xi[0].sizes() == xi[i].sizes());
5294
5295 // Lambda expression to evaluate the gradient
5296 auto grad_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5298 BSplineCore::template eval<
5300 memory_optimized>(xi, knot_indices, coeff_indices)...};
5301 };
5302
5303 return grad_(std::make_index_sequence<BSplineCore::parDim_>{});
5304 }
5305 }
5306
5328 template <bool memory_optimized = false, typename Geometry>
5329 inline auto igrad(const Geometry &G, const torch::Tensor &xi) const {
5330 if constexpr (BSplineCore::parDim_ == 0)
5332 torch::zeros_like(BSplineCore::coeffs_[0])};
5333 else
5334 return igrad<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5335 }
5336
5343 template <bool memory_optimized = false, typename Geometry>
5344 inline auto igrad(const Geometry &G,
5346 if constexpr (BSplineCore::parDim_ == 0)
5348 torch::zeros_like(BSplineCore::coeffs_[0])};
5349 else
5350 return igrad<memory_optimized, Geometry>(
5351 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5352 }
5354
5378 template <bool memory_optimized = false, typename Geometry>
5379 inline auto
5381 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5382 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5383 if constexpr (BSplineCore::parDim_ == 0)
5385 torch::zeros_like(BSplineCore::coeffs_[0])};
5386 else
5387 return igrad<memory_optimized, Geometry>(
5388 G, xi, knot_indices,
5389 BSplineCore::template find_coeff_indices<memory_optimized>(
5390 knot_indices),
5391 knot_indices_G,
5392 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5393 }
5394
5425 template <bool memory_optimized = false, typename Geometry>
5426 inline auto
5428 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5429 const torch::Tensor &coeff_indices,
5430 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5431 const torch::Tensor &coeff_indices_G) const {
5432 if constexpr (BSplineCore::parDim_ == 0)
5434 torch::zeros_like(BSplineCore::coeffs_[0])};
5435 else
5436 return grad<memory_optimized>(xi, knot_indices, coeff_indices) *
5437 G.template jac<memory_optimized>(xi, knot_indices_G,
5438 coeff_indices_G)
5439 .ginv();
5440 }
5441
5442 // clang-format off
5475 // clang-format on
5478 template <bool memory_optimized = false>
5479 inline auto hess(const torch::Tensor &xi) const {
5480 if constexpr (BSplineCore::parDim_ == 0)
5482 torch::zeros_like(BSplineCore::coeffs_[0])};
5483 else
5484 return hess<memory_optimized>(utils::TensorArray1({xi}));
5485 }
5486
5491 template <bool memory_optimized = false>
5492 inline auto hess(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5493 if constexpr (BSplineCore::parDim_ == 0)
5495 torch::zeros_like(BSplineCore::coeffs_[0])};
5496 else
5497 return hess<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5498 }
5500
5501 // clang-format off
5536 // clang-format on
5537 template <bool memory_optimized = false>
5538 inline auto
5540 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5541 if constexpr (BSplineCore::parDim_ == 0)
5543 torch::zeros_like(BSplineCore::coeffs_[0])};
5544 else
5545 return hess<memory_optimized>(
5546 xi, knot_indices,
5547 BSplineCore::template find_coeff_indices<memory_optimized>(
5548 knot_indices));
5549 }
5550
5551 // clang-format off
5588 // clang-format on
5589 template <bool memory_optimized = false>
5591 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5592 const torch::Tensor &coeff_indices) const {
5593
5594 if constexpr (BSplineCore::parDim_ == 0)
5596 torch::zeros_like(BSplineCore::coeffs_[0])};
5597
5598 else {
5599 // Check compatibility of arguments
5600 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5601 assert(xi[i].sizes() == knot_indices[i].sizes());
5602 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5603 assert(xi[0].sizes() == xi[i].sizes());
5604
5605 // Lambda expression to evaluate the hessian
5606 auto hess_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5607 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5608 BSplineCore::geoDim_, BSplineCore::parDim_>{
5609 BSplineCore::template eval<
5610 static_cast<deriv>(
5611 utils::integer_pow<10, Is / BSplineCore::parDim_>::value) +
5612 static_cast<deriv>(utils::integer_pow<
5613 10, Is % BSplineCore::parDim_>::value),
5614 memory_optimized>(xi, knot_indices, coeff_indices)...}
5615 .reorder_ikj();
5616 };
5617
5618 return hess_(std::make_index_sequence<BSplineCore::parDim_ *
5619 BSplineCore::parDim_>{});
5620 }
5621 }
5622
5650 template <bool memory_optimized = false, typename Geometry>
5651 inline auto ihess(const Geometry &G, const torch::Tensor &xi) const {
5652 if constexpr (BSplineCore::parDim_ == 0)
5654 torch::zeros_like(BSplineCore::coeffs_[0])};
5655 else
5656 return ihess<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5657 }
5658
5665 template <bool memory_optimized = false, typename Geometry>
5666 inline auto ihess(const Geometry &G,
5668 if constexpr (BSplineCore::parDim_ == 0)
5670 torch::zeros_like(BSplineCore::coeffs_[0])};
5671 else
5672 return ihess<memory_optimized, Geometry>(
5673 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5674 }
5676
5706 template <bool memory_optimized = false, typename Geometry>
5707 inline auto
5709 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5710 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5711 if constexpr (BSplineCore::parDim_ == 0)
5713 torch::zeros_like(BSplineCore::coeffs_[0])};
5714 else
5715 return ihess<memory_optimized, Geometry>(
5716 G, xi, knot_indices,
5717 BSplineCore::template find_coeff_indices<memory_optimized>(
5718 knot_indices),
5719 knot_indices_G,
5720 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5721 }
5722
5758 template <bool memory_optimized = false, typename Geometry>
5759 inline auto
5761 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5762 const torch::Tensor &coeff_indices,
5763 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5764 const torch::Tensor &coeff_indices_G) const {
5765
5766 if constexpr (BSplineCore::parDim_ == 0)
5768 torch::zeros_like(BSplineCore::coeffs_[0])};
5769 else {
5770 utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5771 BSplineCore::parDim_, BSplineCore::geoDim_>
5772 hessu;
5773
5774 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
5775 coeff_indices_G);
5776 auto ijacG = ijac<memory_optimized>(G, xi, knot_indices, coeff_indices,
5777 knot_indices_G, coeff_indices_G);
5778
5779 for (short_t component = 0; component < BSplineCore::geoDim_;
5780 ++component) {
5781 auto hess_component =
5782 hess<memory_optimized>(xi, knot_indices, coeff_indices)
5783 .slice(component);
5784
5785 for (short_t k = 0; k < hessG.slices(); ++k) {
5786 hess_component -= ijacG(component, k) * hessG.slice(k);
5787 }
5788
5789 auto jacInv = G.template jac<memory_optimized>(xi, knot_indices_G,
5790 coeff_indices_G)
5791 .ginv();
5792 auto hessu_component = jacInv.tr() * hess_component * jacInv;
5793
5794 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5795 for (short_t j = 0; j < BSplineCore::parDim_; ++j)
5796 hessu.set(i, j, component, hessu_component(i, j));
5797 }
5798
5799 return hessu;
5800 }
5801 }
5802
5803 // clang-format off
5831 // clang-format on
5834 template <bool memory_optimized = false>
5835 inline auto jac(const torch::Tensor &xi) const {
5836 if constexpr (BSplineCore::parDim_ == 0)
5838 torch::zeros_like(BSplineCore::coeffs_[0])};
5839 else
5840 return jac<memory_optimized>(utils::TensorArray1({xi}));
5841 }
5842
5847 template <bool memory_optimized = false>
5848 inline auto jac(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5849 if constexpr (BSplineCore::parDim_ == 0)
5851 torch::zeros_like(BSplineCore::coeffs_[0])};
5852 else
5853 return jac<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5854 }
5856
5857 // clang-format off
5887 // clang-format on
5888 template <bool memory_optimized = false>
5889 inline auto
5891 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5892 if constexpr (BSplineCore::parDim_ == 0)
5894 torch::zeros_like(BSplineCore::coeffs_[0])};
5895 else
5896 return jac<memory_optimized>(
5897 xi, knot_indices,
5898 BSplineCore::template find_coeff_indices<memory_optimized>(
5899 knot_indices));
5900 }
5901
5902 // clang-format off
5940 // clang-format on
5941 template <bool memory_optimized = false>
5943 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5944 const torch::Tensor &coeff_indices) const {
5945
5946 if constexpr (BSplineCore::parDim_ == 0)
5948 torch::zeros_like(BSplineCore::coeffs_[0])};
5949
5950 else {
5951 // Check compatibility of arguments
5952 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5953 assert(xi[i].sizes() == knot_indices[i].sizes());
5954 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5955 assert(xi[0].sizes() == xi[i].sizes());
5956
5957 // Lambda expression to evaluate the jacobian
5958 auto jac_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5959 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5960 BSplineCore::geoDim_>{
5961 BSplineCore::template eval<
5963 memory_optimized>(xi, knot_indices, coeff_indices)...}
5964 .tr();
5965 };
5966
5967 return jac_(std::make_index_sequence<BSplineCore::parDim_>{});
5968 }
5969 }
5970
5992 template <bool memory_optimized = false, typename Geometry>
5993 inline auto ijac(const Geometry &G, const torch::Tensor &xi) const {
5994 if constexpr (BSplineCore::parDim_ == 0)
5996 torch::zeros_like(BSplineCore::coeffs_[0])};
5997 else
5998 return ijac<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5999 }
6000
6007 template <bool memory_optimized = false, typename Geometry>
6008 inline auto ijac(const Geometry &G,
6010 if constexpr (BSplineCore::parDim_ == 0)
6012 torch::zeros_like(BSplineCore::coeffs_[0])};
6013 else
6014 return ijac<memory_optimized, Geometry>(
6015 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
6016 }
6018
6043 template <bool memory_optimized = false, typename Geometry>
6044 inline auto
6046 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
6047 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
6048 if constexpr (BSplineCore::parDim_ == 0)
6050 torch::zeros_like(BSplineCore::coeffs_[0])};
6051 else
6052 return ijac<memory_optimized, Geometry>(
6053 G, xi, knot_indices,
6054 BSplineCore::template find_coeff_indices<memory_optimized>(
6055 knot_indices),
6056 knot_indices_G,
6057 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
6058 }
6059
6090 template <bool memory_optimized = false, typename Geometry>
6091 inline auto ijac(const Geometry &G,
6093 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
6094 const torch::Tensor &coeff_indices,
6095 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
6096 const torch::Tensor &coeff_indices_G) const {
6097 if constexpr (BSplineCore::parDim_ == 0)
6099 torch::zeros_like(BSplineCore::coeffs_[0])};
6100 else
6101 return jac<memory_optimized>(xi, knot_indices, coeff_indices) *
6102 G.template jac<memory_optimized>(xi, knot_indices_G,
6103 coeff_indices_G)
6104 .ginv();
6105 }
6106
6107 // clang-format off
6124 // clang-format on
6127 template <bool memory_optimized = false>
6128 inline auto lapl(const torch::Tensor &xi) const {
6129 if constexpr (BSplineCore::parDim_ == 0)
6131 torch::zeros_like(BSplineCore::coeffs_[0])};
6132 else
6133 return lapl<memory_optimized>(utils::TensorArray1({xi}));
6134 }
6135
6140 template <bool memory_optimized = false>
6141 inline auto lapl(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
6142 if constexpr (BSplineCore::parDim_ == 0)
6144 torch::zeros_like(BSplineCore::coeffs_[0])};
6145 else
6146 return lapl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
6147 }
6149
6150 // clang-format off
6170 // clang-format on
6171 template <bool memory_optimized = false>
6172 inline auto
6174 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
6175 if constexpr (BSplineCore::parDim_ == 0)
6177 torch::zeros_like(BSplineCore::coeffs_[0])};
6178 else
6179 return lapl<memory_optimized>(
6180 xi, knot_indices,
6181 BSplineCore::template find_coeff_indices<memory_optimized>(
6182 knot_indices));
6183 }
6184
6185 // clang-format off
6208 // clang-format on
6209 template <bool memory_optimized = false>
6211 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
6212 const torch::Tensor &coeff_indices) const {
6213
6214 if constexpr (BSplineCore::parDim_ == 0)
6216 torch::zeros_like(BSplineCore::coeffs_[0])};
6217
6218 else {
6219 // Check compatibility of arguments
6220 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
6221 assert(xi[i].sizes() == knot_indices[i].sizes());
6222 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
6223 assert(xi[0].sizes() == xi[i].sizes());
6224
6225 // Lambda expression to evaluate the laplacian
6226 auto lapl_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
6228 (BSplineCore::template eval<
6229 static_cast<deriv>(utils::integer_pow<10, Is>::value) ^ 2,
6230 memory_optimized>(xi, knot_indices, coeff_indices) +
6231 ...)}
6232 .reorder_ikj();
6233 };
6234
6235 return lapl_(std::make_index_sequence<BSplineCore::parDim_>{});
6236 }
6237 }
6238
6267 template <bool memory_optimized = false, typename Geometry>
6268 auto ilapl(const Geometry &G, const torch::Tensor &xi) const {
6269 if constexpr (BSplineCore::parDim_ == 0)
6271 torch::zeros_like(BSplineCore::coeffs_[0])};
6272 else
6273 return ilapl<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
6274 }
6275
6282 template <bool memory_optimized = false, typename Geometry>
6283 inline auto ilapl(const Geometry &G,
6285 if constexpr (BSplineCore::parDim_ == 0)
6287 torch::zeros_like(BSplineCore::coeffs_[0])};
6288 else
6289 return ilapl<memory_optimized, Geometry>(
6290 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
6291 }
6293
6324 template <bool memory_optimized = false, typename Geometry>
6325 inline auto
6327 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
6328 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
6329 if constexpr (BSplineCore::parDim_ == 0)
6331 torch::zeros_like(BSplineCore::coeffs_[0])};
6332 else
6333 return ilapl<memory_optimized, Geometry>(
6334 G, xi, knot_indices,
6335 BSplineCore::template find_coeff_indices<memory_optimized>(
6336 knot_indices),
6337 knot_indices_G,
6338 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
6339 }
6340
6378 template <bool memory_optimized = false, typename Geometry>
6379 inline auto
6381 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
6382 const torch::Tensor &coeff_indices,
6383 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
6384 const torch::Tensor &coeff_indices_G) const {
6385
6386 if constexpr (BSplineCore::parDim_ == 0)
6388 torch::zeros_like(BSplineCore::coeffs_[0])};
6389
6390 else {
6391 auto hessu =
6392 hess<memory_optimized>(xi, knot_indices, coeff_indices).slice(0);
6393
6394 {
6395 auto igradG =
6396 igrad<memory_optimized>(G, xi, knot_indices, coeff_indices,
6397 knot_indices_G, coeff_indices_G);
6398 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
6399 coeff_indices_G);
6400 assert(igradG.cols() == hessG.slices());
6401 for (short_t k = 0; k < hessG.slices(); ++k)
6402 hessu -= igradG(0, k) * hessG.slice(k);
6403 }
6404
6405 auto jacInv =
6406 G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G)
6407 .ginv();
6408
6409 return (jacInv.tr() * hessu * jacInv).trace();
6410 }
6411 }
6412
6418#ifdef IGANET_WITH_MATPLOT
6419 template <typename Backend = matplot::backend::gnuplot>
6420#else
6421 template <typename Backend = void>
6422#endif
6423 inline auto plot(const nlohmann::json &json = {}) const {
6424 return plot<Backend>(*this, json);
6425 }
6426
6434#ifdef IGANET_WITH_MATPLOT
6435 template <typename Backend = matplot::backend::gnuplot>
6436#else
6437 template <typename Backend = void>
6438#endif
6440 const nlohmann::json &json = {}) const {
6441
6442 return plot<Backend>(*this, xi, json);
6443 }
6444
6452#ifdef IGANET_WITH_MATPLOT
6453 template <typename Backend = matplot::backend::gnuplot>
6454#else
6455 template <typename Backend = void>
6456#endif
6457 inline auto plot(
6458 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
6459 const nlohmann::json &json = {}) const {
6460
6461 return plot<Backend>(*this, xi, json);
6462 }
6463
6471#ifdef IGANET_WITH_MATPLOT
6472 template <typename Backend = matplot::backend::gnuplot,
6473 typename BSplineCoreColor>
6474#else
6475 template <typename Backend = void, typename BSplineCoreColor>
6476#endif
6477 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
6478 const nlohmann::json &json = {}) const {
6479#ifdef IGANET_WITH_MATPLOT
6480 static_assert(BSplineCore::parDim() == BSplineCoreColor::parDim(),
6481 "Parametric dimensions must match");
6482
6483 if ((void *)this != (void *)&color && BSplineCoreColor::geoDim() > 1)
6484 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6485
6486 if constexpr (BSplineCore::parDim() == 1 && BSplineCore::geoDim() == 1) {
6487
6488 //
6489 // mapping: [0,1] -> R^1
6490 //
6491
6492 int64_t res0 = BSplineCore::ncoeffs(0);
6493 if (json.contains("res0"))
6494 res0 = json["res0"].get<int64_t>();
6495
6496 // Create figure with specified backend
6497 auto f = matplot::figure<Backend>(false);
6498 f->backend()->run_command("unset warnings");
6499 f->ioff();
6500 auto ax = f->current_axes();
6501
6502 // Create line
6503 auto Coords =
6504 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6505#ifdef __clang__
6506 auto Coords_cpu =
6507 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6508 Coords(0), torch::kCPU);
6509 auto XAccessor = std::get<1>(Coords_cpu);
6510#else
6511 auto [Coords_cpu, XAccessor] =
6512 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6513 Coords(0), torch::kCPU);
6514#endif
6515
6516 matplot::vector_1d Xfine(res0, 0.0);
6517 matplot::vector_1d Yfine(res0, 0.0);
6518
6519#pragma omp parallel for simd
6520 for (int64_t i = 0; i < res0; ++i)
6521 Xfine[i] = XAccessor[i];
6522
6523 // Plot (colored) line
6524 if ((void *)this != (void *)&color) {
6525 if constexpr (BSplineCoreColor::geoDim_ == 1) {
6526
6527 // Create colors
6528 auto Color =
6529 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6530#ifdef __clang__
6531 auto Color_cpu =
6532 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6533 1>(Color(0), torch::kCPU);
6534 auto CAccessor = std::get<1>(Color_cpu);
6535#else
6536 auto [Color_cpu, CAccessor] =
6537 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6538 1>(Color(0), torch::kCPU);
6539#endif
6540
6541 matplot::vector_1d Cfine(res0, 0.0);
6542
6543#pragma omp parallel for simd
6544 for (int64_t i = 0; i < res0; ++i)
6545 Cfine[i] = CAccessor[i];
6546
6547 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
6548 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
6549
6550 auto Cmap = matplot::colormap();
6551
6552 auto a = Cmap.size() / (Cmax - Cmin);
6553 auto b = -a * Cmin;
6554
6555 // Plot colored line
6556 ax->hold(matplot::on);
6557 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
6558 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
6559 ->line_width(2)
6560 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
6561 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
6562 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
6563 ax->hold(matplot::off);
6564 matplot::colorbar(ax);
6565 } else
6566 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6567 } else {
6568 // Plot unicolor line
6569 ax->plot(Xfine, Yfine, "b-")->line_width(2);
6570 }
6571
6572 bool cnet = false;
6573 if (json.contains("cnet"))
6574 cnet = json["cnet"].get<bool>();
6575
6576 if (cnet) {
6577 // Create control net
6578#ifdef __clang__
6579 auto coeffs_cpu =
6580 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6581 BSplineCore::coeffs(0), torch::kCPU);
6582 auto xAccessor = std::get<1>(coeffs_cpu);
6583#else
6584 auto [coeffs_cpu, xAccessor] =
6585 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6586 BSplineCore::coeffs(0), torch::kCPU);
6587#endif
6588 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6589 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6590
6591#pragma omp parallel for simd
6592 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6593 X[i] = xAccessor[i];
6594 }
6595
6596 // Plot control net
6597 ax->hold(matplot::on);
6598 ax->plot(X, Y, ".k-")->line_width(1);
6599 ax->hold(matplot::off);
6600 }
6601
6602 // Title
6603 if (json.contains("title"))
6604 ax->title(json["title"].get<std::string>());
6605 else
6606 ax->title("BSpline: [0,1] -> R");
6607
6608 // X-axis label
6609 if (json.contains("xlabel"))
6610 ax->xlabel(json["xlabel"].get<std::string>());
6611 else
6612 ax->xlabel("x");
6613
6614 // Y-axis label
6615 if (json.contains("ylabel"))
6616 ax->ylabel(json["ylabel"].get<std::string>());
6617 else
6618 ax->ylabel("y");
6619
6620 return f;
6621 }
6622
6623 else if constexpr (BSplineCore::parDim_ == 1 && BSplineCore::geoDim_ == 2) {
6624
6625 //
6626 // mapping: [0,1] -> R^2
6627 //
6628
6629 int64_t res0 = BSplineCore::ncoeffs(0);
6630 if (json.contains("res0"))
6631 res0 = json["res0"].get<int64_t>();
6632
6633 // Create figure with specified backend
6634 auto f = matplot::figure<Backend>(false);
6635 f->backend()->run_command("unset warnings");
6636 f->ioff();
6637 auto ax = f->current_axes();
6638
6639 // Create curve
6640 auto Coords =
6641 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6642#ifdef __clang__
6643 auto Coords_cpu =
6644 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6645 Coords, torch::kCPU);
6646 auto XAccessor = std::get<1>(Coords_cpu)[0];
6647 auto YAccessor = std::get<1>(Coords_cpu)[1];
6648#else
6649 auto [Coords0_cpu, XAccessor] =
6650 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6651 Coords(0), torch::kCPU);
6652 auto [Coords1_cpu, YAccessor] =
6653 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6654 Coords(1), torch::kCPU);
6655#endif
6656
6657 matplot::vector_1d Xfine(res0, 0.0);
6658 matplot::vector_1d Yfine(res0, 0.0);
6659
6660#pragma omp parallel for simd
6661 for (int64_t i = 0; i < res0; ++i) {
6662 Xfine[i] = XAccessor[i];
6663 Yfine[i] = YAccessor[i];
6664 }
6665
6666 // Plot (colored) curve
6667 if ((void *)this != (void *)&color) {
6668 if constexpr (BSplineCoreColor::geoDim() == 1) {
6669
6670 // Create colors
6671 auto Color =
6672 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6673#ifdef __clang__
6674 auto Color_cpu =
6675 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6676 1>(Color(0), torch::kCPU);
6677 auto CAccessor = std::get<1>(Color_cpu);
6678#else
6679 auto [Color_cpu, CAccessor] =
6680 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6681 1>(Color(0), torch::kCPU);
6682#endif
6683
6684 matplot::vector_1d Cfine(res0, 0.0);
6685
6686#pragma omp parallel for simd
6687 for (int64_t i = 0; i < res0; ++i) {
6688 Cfine[i] = CAccessor[i];
6689 }
6690
6691 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
6692 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
6693
6694 auto Cmap = matplot::colormap();
6695
6696 auto a = Cmap.size() / (Cmax - Cmin);
6697 auto b = -a * Cmin;
6698
6699 // Plot colored curve
6700 ax->hold(matplot::on);
6701 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
6702 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
6703 ->line_width(2)
6704 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
6705 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
6706 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
6707 ax->hold(matplot::off);
6708 matplot::colorbar(ax);
6709 } else
6710 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6711 } else {
6712 // Plot unicolor curve
6713 ax->plot(Xfine, Yfine, "b-")->line_width(2);
6714 }
6715
6716 bool cnet = false;
6717 if (json.contains("cnet"))
6718 cnet = json["cnet"].get<bool>();
6719
6720 if (cnet) {
6721 // Create control net
6722#ifdef __clang__
6723 auto coeffs_cpu =
6724 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6725 BSplineCore::coeffs(), torch::kCPU);
6726 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6727 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6728#else
6729 auto [coeffs0_cpu, xAccessor] =
6730 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6731 BSplineCore::coeffs(0), torch::kCPU);
6732 auto [coeffs1_cpu, yAccessor] =
6733 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6734 BSplineCore::coeffs(1), torch::kCPU);
6735#endif
6736
6737 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6738 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6739
6740#pragma omp parallel for simd
6741 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6742 X[i] = xAccessor[i];
6743 Y[i] = yAccessor[i];
6744 }
6745
6746 // Plot control net
6747 ax->hold(matplot::on);
6748 ax->plot(X, Y, ".k-")->line_width(1);
6749 ax->hold(matplot::off);
6750 }
6751
6752 // Title
6753 if (json.contains("title"))
6754 ax->title(json["title"].get<std::string>());
6755 else
6756 ax->title("BSpline: [0,1] -> R^2");
6757
6758 // X-axis label
6759 if (json.contains("xlabel"))
6760 ax->xlabel(json["xlabel"].get<std::string>());
6761 else
6762 ax->xlabel("x");
6763
6764 // Y-axis label
6765 if (json.contains("ylabel"))
6766 ax->ylabel(json["ylabel"].get<std::string>());
6767 else
6768 ax->ylabel("y");
6769
6770 return f;
6771 }
6772
6773 else if constexpr (BSplineCore::parDim() == 1 &&
6774 BSplineCore::geoDim() == 3) {
6775
6776 //
6777 // mapping: [0,1] -> R^3
6778 //
6779
6780 int64_t res0 = BSplineCore::ncoeffs(0);
6781 if (json.contains("res0"))
6782 res0 = json["res0"].get<int64_t>();
6783
6784 // Create figure with specified backend
6785 auto f = matplot::figure<Backend>(false);
6786 f->backend()->run_command("unset warnings");
6787 f->ioff();
6788 auto ax = f->current_axes();
6789
6790 auto Coords =
6791 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6792#ifdef __clang__
6793 auto Coords_cpu =
6794 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6795 Coords, torch::kCPU);
6796 auto XAccessor = std::get<1>(Coords_cpu)[0];
6797 auto YAccessor = std::get<1>(Coords_cpu)[1];
6798 auto ZAccessor = std::get<1>(Coords_cpu)[2];
6799#else
6800 auto [Coords0_cpu, XAccessor] =
6801 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6802 Coords(0), torch::kCPU);
6803 auto [Coords1_cpu, YAccessor] =
6804 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6805 Coords(1), torch::kCPU);
6806 auto [Coords2_cpu, ZAccessor] =
6807 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6808 Coords(2), torch::kCPU);
6809#endif
6810
6811 // Create curve
6812 matplot::vector_1d Xfine(res0, 0.0);
6813 matplot::vector_1d Yfine(res0, 0.0);
6814 matplot::vector_1d Zfine(res0, 0.0);
6815
6816#pragma omp parallel for simd
6817 for (int64_t i = 0; i < res0; ++i) {
6818 Xfine[i] = XAccessor[i];
6819 Yfine[i] = YAccessor[i];
6820 Zfine[i] = ZAccessor[i];
6821 }
6822
6823 // Plot (colored) curve
6824 if ((void *)this != (void *)&color) {
6825 if constexpr (BSplineCoreColor::geoDim() == 1) {
6826
6827 auto Color =
6828 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6829#ifdef __clang__
6830 auto Color_cpu =
6831 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6832 1>(Color(0), torch::kCPU);
6833 auto CAccessor = std::get<1>(Color_cpu);
6834#else
6835 auto [Color_cpu, CAccessor] =
6836 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6837 1>(Color(0), torch::kCPU);
6838#endif
6839
6840 // Create colors
6841 matplot::vector_1d Cfine(matplot::vector_1d(res0, 0.0));
6842
6843#pragma omp parallel for simd
6844 for (int64_t i = 0; i < res0; ++i) {
6845 Cfine[i] = CAccessor[i];
6846 }
6847
6848 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
6849 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
6850
6851 auto Cmap = matplot::colormap();
6852
6853 auto a = Cmap.size() / (Cmax - Cmin);
6854 auto b = -a * Cmin;
6855
6856 // Plot colored line
6857 ax->hold(matplot::on);
6858 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
6859 ax->plot3({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]},
6860 {Zfine[i], Zfine[i + 1]})
6861 ->line_width(2)
6862 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
6863 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
6864 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
6865 ax->hold(matplot::off);
6866 matplot::colorbar(ax);
6867 } else
6868 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6869 } else {
6870 // Plot curve
6871 ax->plot3(Xfine, Yfine, Zfine, "b-")->line_width(2);
6872 }
6873
6874 bool cnet = false;
6875 if (json.contains("cnet"))
6876 cnet = json["cnet"].get<bool>();
6877
6878 if (cnet) {
6879 // Create control net
6880#ifdef __clang__
6881 auto coeffs_cpu =
6882 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6883 BSplineCore::coeffs(), torch::kCPU);
6884 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6885 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6886 auto zAccessor = std::get<1>(coeffs_cpu)[2];
6887#else
6888 auto [coeffs0_cpu, xAccessor] =
6889 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6890 BSplineCore::coeffs(0), torch::kCPU);
6891 auto [coeffs1_cpu, yAccessor] =
6892 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6893 BSplineCore::coeffs(1), torch::kCPU);
6894 auto [coeffs2_cpu, zAccessor] =
6895 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6896 BSplineCore::coeffs(2), torch::kCPU);
6897#endif
6898
6899 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6900 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6901 matplot::vector_1d Z(BSplineCore::ncoeffs(0), 0.0);
6902
6903#pragma omp parallel for simd
6904 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6905 X[i] = xAccessor[i];
6906 Y[i] = yAccessor[i];
6907 Z[i] = zAccessor[i];
6908 }
6909
6910 // Plot control net
6911 ax->hold(matplot::on);
6912 ax->plot3(X, Y, Z, ".k-")->line_width(1);
6913 ax->hold(matplot::off);
6914 }
6915
6916 // Title
6917 if (json.contains("title"))
6918 ax->title(json["title"].get<std::string>());
6919 else
6920 ax->title("BSpline: [0,1] -> R^3");
6921
6922 // X-axis label
6923 if (json.contains("xlabel"))
6924 ax->xlabel(json["xlabel"].get<std::string>());
6925 else
6926 ax->xlabel("x");
6927
6928 // Y-axis label
6929 if (json.contains("ylabel"))
6930 ax->ylabel(json["ylabel"].get<std::string>());
6931 else
6932 ax->ylabel("y");
6933
6934 // Z-axis label
6935 if (json.contains("zlabel"))
6936 ax->zlabel(json["zlabel"].get<std::string>());
6937 else
6938 ax->zlabel("z");
6939
6940 return f;
6941 }
6942
6943 else if constexpr (BSplineCore::parDim() == 2 &&
6944 BSplineCore::geoDim() == 2) {
6945
6946 //
6947 // mapping: [0,1]^2 -> R^2
6948 //
6949
6950 int64_t res0 = BSplineCore::ncoeffs(0);
6951 int64_t res1 = BSplineCore::ncoeffs(1);
6952 if (json.contains("res0"))
6953 res0 = json["res0"].get<int64_t>();
6954 if (json.contains("res1"))
6955 res1 = json["res1"].get<int64_t>();
6956
6957 // Create figure with specified backend
6958 auto f = matplot::figure<Backend>(false);
6959 f->backend()->run_command("unset warnings");
6960 f->ioff();
6961 auto ax = f->current_axes();
6962
6963 // Create mesh
6964 utils::TensorArray<2> meshgrid = utils::to_array<2>(
6965 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
6966 torch::linspace(0, 1, res1, BSplineCore::options_)},
6967 "xy"));
6968 auto Coords = BSplineCore::eval(meshgrid);
6969#ifdef __clang__
6970 auto Coords_cpu =
6971 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6972 Coords, torch::kCPU);
6973 auto XAccessor = std::get<1>(Coords_cpu)[0];
6974 auto YAccessor = std::get<1>(Coords_cpu)[1];
6975#else
6976 auto [Coords0_cpu, XAccessor] =
6977 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6978 Coords(0), torch::kCPU);
6979 auto [Coords1_cpu, YAccessor] =
6980 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6981 Coords(1), torch::kCPU);
6982#endif
6983
6984 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
6985 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
6986 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
6987
6988#pragma omp parallel for simd collapse(2)
6989 for (int64_t i = 0; i < res0; ++i)
6990 for (int64_t j = 0; j < res1; ++j) {
6991 Xfine[j][i] = XAccessor[j][i];
6992 Yfine[j][i] = YAccessor[j][i];
6993 }
6994
6995 // Plot (colored) mesh
6996 if ((void *)this != (void *)&color) {
6997 if constexpr (BSplineCoreColor::geoDim() == 1) {
6998
6999 // Create colors
7000 auto Color = color.eval(meshgrid);
7001#ifdef __clang__
7002 auto Color_cpu =
7003 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
7004 2>(Color, torch::kCPU);
7005 auto CAccessor = std::get<1>(Color_cpu)[0];
7006#else
7007 auto [Color0_cpu, CAccessor] =
7008 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
7009 2>(Color(0), torch::kCPU);
7010#endif
7011
7012 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
7013
7014#pragma omp parallel for simd collapse(2)
7015 for (int64_t i = 0; i < res0; ++i)
7016 for (int64_t j = 0; j < res1; ++j)
7017 Cfine[j][i] = CAccessor[j][i];
7018
7019 // Plot colored mesh
7020 matplot::view(2);
7021 ax->mesh(Xfine, Yfine, Cfine)->hidden_3d(false);
7022 matplot::colorbar(ax);
7023 } else
7024 throw std::runtime_error("BSpline for coloring must have geoDim=1");
7025 } else {
7026 // Plot unicolor mesh
7027 matplot::view(2);
7028 matplot::colormap(std::vector<std::vector<double>>{{0.0, 0.0, 1.0}});
7029 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
7030 }
7031
7032 bool cnet = false;
7033 if (json.contains("cnet"))
7034 cnet = json["cnet"].get<bool>();
7035
7036 if (cnet) {
7037 // Create control net
7038#ifdef __clang__
7039 auto coeffs_cpu =
7040 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7041 BSplineCore::coeffs(), torch::kCPU);
7042 auto xAccessor = std::get<1>(coeffs_cpu)[0];
7043 auto yAccessor = std::get<1>(coeffs_cpu)[1];
7044#else
7045 auto [coeffs0_cpu, xAccessor] =
7046 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7047 BSplineCore::coeffs(0), torch::kCPU);
7048 auto [coeffs1_cpu, yAccessor] =
7049 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7050 BSplineCore::coeffs(1), torch::kCPU);
7051#endif
7052
7053 matplot::vector_2d X(BSplineCore::ncoeffs(1),
7054 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
7055 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
7056 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
7057 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
7058 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
7059
7060#pragma omp parallel for simd collapse(2)
7061 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
7062 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
7063 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
7064 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
7065 }
7066
7067 // Plot control net
7068 ax->hold(matplot::on);
7069 ax->surf(X, Y, Z)
7070 ->palette_map_at_surface(true)
7071 .face_alpha(0)
7072 .line_width(1);
7073 for (std::size_t i = 0; i < X.size(); ++i)
7074 ax->scatter3(X[i], Y[i], Z[i], "k.");
7075 ax->hold(matplot::off);
7076 }
7077
7078 // Title
7079 if (json.contains("title"))
7080 ax->title(json["title"].get<std::string>());
7081 else
7082 ax->title("BSpline: [0,1]^2 -> R^2");
7083
7084 // X-axis label
7085 if (json.contains("xlabel"))
7086 ax->xlabel(json["xlabel"].get<std::string>());
7087 else
7088 ax->xlabel("x");
7089
7090 // Y-axis label
7091 if (json.contains("ylabel"))
7092 ax->ylabel(json["ylabel"].get<std::string>());
7093 else
7094 ax->ylabel("y");
7095
7096 // Z-axis label
7097 if (json.contains("zlabel"))
7098 ax->zlabel(json["zlabel"].get<std::string>());
7099 else
7100 ax->zlabel("z");
7101
7102 return f;
7103 }
7104
7105 else if constexpr (BSplineCore::parDim() == 2 &&
7106 BSplineCore::geoDim() == 3) {
7107
7109 // mapping: [0,1]^2 -> R^3
7111
7112 int64_t res0 = BSplineCore::ncoeffs(0);
7113 int64_t res1 = BSplineCore::ncoeffs(1);
7114 if (json.contains("res0"))
7115 res0 = json["res0"].get<int64_t>();
7116 if (json.contains("res1"))
7117 res1 = json["res1"].get<int64_t>();
7118
7119 // Create figure with specified backend
7120 auto f = matplot::figure<Backend>(false);
7121 f->backend()->run_command("unset warnings");
7122 f->ioff();
7123 auto ax = f->current_axes();
7124
7125 // Create surface
7126 utils::TensorArray<2> meshgrid = utils::to_array<2>(
7127 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
7128 torch::linspace(0, 1, res1, BSplineCore::options_)},
7129 "xy"));
7130 auto Coords = BSplineCore::eval(meshgrid);
7131#ifdef __clang__
7132 auto Coords_cpu =
7133 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
7134 Coords, torch::kCPU);
7135 auto XAccessor = std::get<1>(Coords_cpu)[0];
7136 auto YAccessor = std::get<1>(Coords_cpu)[1];
7137 auto ZAccessor = std::get<1>(Coords_cpu)[2];
7138#else
7139 auto [Coords0_cpu, XAccessor] =
7140 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
7141 Coords(0), torch::kCPU);
7142 auto [Coords1_cpu, YAccessor] =
7143 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
7144 Coords(1), torch::kCPU);
7145 auto [Coords2_cpu, ZAccessor] =
7146 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
7147 Coords(2), torch::kCPU);
7148#endif
7149
7150 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
7151 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
7152 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
7153
7154#pragma omp parallel for simd collapse(2)
7155 for (int64_t i = 0; i < res0; ++i)
7156 for (int64_t j = 0; j < res1; ++j) {
7157 Xfine[j][i] = XAccessor[j][i];
7158 Yfine[j][i] = YAccessor[j][i];
7159 Zfine[j][i] = ZAccessor[j][i];
7160 }
7161
7162 // Plot (colored) surface
7163 if ((void *)this != (void *)&color) {
7164 if constexpr (BSplineCoreColor::geoDim() == 1) {
7165
7166 // Create colors
7167 auto Color = color.eval(meshgrid);
7168#ifdef __clang__
7169 auto Color_cpu =
7170 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
7171 2>(Color, torch::kCPU);
7172 auto CAccessor = std::get<1>(Color_cpu)[0];
7173#else
7174 auto [Color_cpu, CAccessor] =
7175 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
7176 2>(Color(0), torch::kCPU);
7177#endif
7178
7179 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
7180
7181#pragma omp parallel for simd collapse(2)
7182 for (int64_t i = 0; i < res0; ++i)
7183 for (int64_t j = 0; j < res1; ++j) {
7184 Cfine[j][i] = CAccessor[j][i];
7185 }
7186
7187 // Plot colored surface
7188 ax->mesh(Xfine, Yfine, Zfine, Cfine)->hidden_3d(false);
7189 matplot::colorbar(ax);
7190 } else
7191 throw std::runtime_error("BSpline for coloring must have geoDim=1");
7192 } else {
7193 // Plot unicolor surface
7194 matplot::colormap(std::vector<std::vector<double>>{{0.0, 0.0, 1.0}});
7195 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
7196 }
7197
7198 bool cnet = false;
7199 if (json.contains("cnet"))
7200 cnet = json["cnet"].get<bool>();
7201
7202 if (cnet) {
7203 // Create control net
7204#ifdef __clang__
7205 auto coeffs_cpu =
7206 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7207 BSplineCore::coeffs(), torch::kCPU);
7208 auto xAccessor = std::get<1>(coeffs_cpu)[0];
7209 auto yAccessor = std::get<1>(coeffs_cpu)[1];
7210 auto zAccessor = std::get<1>(coeffs_cpu)[2];
7211#else
7212 auto [coeffs0_cpu, xAccessor] =
7213 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7214 BSplineCore::coeffs(0), torch::kCPU);
7215 auto [coeffs1_cpu, yAccessor] =
7216 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7217 BSplineCore::coeffs(1), torch::kCPU);
7218 auto [coeffs2_cpu, zAccessor] =
7219 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7220 BSplineCore::coeffs(2), torch::kCPU);
7221#endif
7222
7223 matplot::vector_2d X(BSplineCore::ncoeffs(1),
7224 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
7225 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
7226 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
7227 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
7228 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
7229
7230#pragma omp parallel for simd collapse(2)
7231 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
7232 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
7233 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
7234 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
7235 Z[j][i] = zAccessor[j * BSplineCore::ncoeffs(0) + i];
7236 }
7237
7238 // Plot control net
7239 ax->hold(matplot::on);
7240 ax->surf(X, Y, Z)
7241 ->palette_map_at_surface(true)
7242 .face_alpha(0)
7243 .line_width(1);
7244 for (std::size_t i = 0; i < X.size(); ++i)
7245 ax->scatter3(X[i], Y[i], Z[i], "k.");
7246 ax->hold(matplot::off);
7247 }
7248
7249 // Title
7250 if (json.contains("title"))
7251 ax->title(json["title"].get<std::string>());
7252 else
7253 ax->title("BSpline: [0,1]^2 -> R^3");
7254
7255 // X-axis label
7256 if (json.contains("xlabel"))
7257 ax->xlabel(json["xlabel"].get<std::string>());
7258 else
7259 ax->xlabel("x");
7260
7261 // Y-axis label
7262 if (json.contains("ylabel"))
7263 ax->ylabel(json["ylabel"].get<std::string>());
7264 else
7265 ax->ylabel("y");
7266
7267 // Z-axis label
7268 if (json.contains("zlabel"))
7269 ax->zlabel(json["zlabel"].get<std::string>());
7270 else
7271 ax->zlabel("z");
7272
7273 return f;
7274 }
7275
7276 else
7277 throw std::runtime_error(
7278 "Unsupported combination of parametric/geometric dimensions");
7279#else
7280 throw std::runtime_error(
7281 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
7282#endif
7283 }
7284
7295#ifdef IGANET_WITH_MATPLOT
7296 template <typename Backend = matplot::backend::gnuplot,
7297 typename BSplineCoreColor>
7298#else
7299 template <typename Backend = void, typename BSplineCoreColor>
7300#endif
7301 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
7303 const nlohmann::json &json = {}) const {
7304
7305#ifdef IGANET_WITH_MATPLOT
7306 auto f = plot<Backend>(color, json);
7307 auto ax = f->current_axes();
7308
7309#ifdef __clang__
7310 auto xi_cpu =
7311 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7312 xi, torch::kCPU);
7313 auto xiAccessor = std::get<1>(xi_cpu);
7314#else
7315 auto [xi_cpu, xiAccessor] =
7316 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7317 xi, torch::kCPU);
7318#endif
7319
7320 if constexpr (BSplineCore::parDim_ == 1) {
7321 matplot::vector_1d X(xi[0].size(0), 0.0);
7322 matplot::vector_1d Y(xi[0].size(0), 0.0);
7323
7324#pragma omp parallel for simd
7325 for (int64_t i = 0; i < xi[0].size(0); ++i) {
7326 X[i] = xiAccessor[0][i];
7327 }
7328
7329 ax->hold(matplot::on);
7330 ax->scatter(X, Y, ".");
7331 ax->hold(matplot::off);
7332 } else if constexpr (BSplineCore::parDim_ == 2) {
7333 matplot::vector_1d X(xi[0].size(0), 0.0);
7334 matplot::vector_1d Y(xi[0].size(0), 0.0);
7335 matplot::vector_1d Z(xi[0].size(0), 0.0);
7336
7337#pragma omp parallel for simd
7338 for (int64_t i = 0; i < xi[0].size(0); ++i) {
7339 X[i] = xiAccessor[0][i];
7340 Y[i] = xiAccessor[1][i];
7341 }
7342
7343 ax->hold(matplot::on);
7344 ax->scatter3(X, Y, Z, ".");
7345 ax->hold(matplot::off);
7346 } else if constexpr (BSplineCore::parDim_ == 3) {
7347 matplot::vector_1d X(xi[0].size(0), 0.0);
7348 matplot::vector_1d Y(xi[0].size(0), 0.0);
7349 matplot::vector_1d Z(xi[0].size(0), 0.0);
7350
7351#pragma omp parallel for simd
7352 for (int64_t i = 0; i < xi[0].size(0); ++i) {
7353 X[i] = xiAccessor[0][i];
7354 Y[i] = xiAccessor[1][i];
7355 Z[i] = xiAccessor[2][i];
7356 }
7357
7358 ax->hold(matplot::on);
7359 ax->scatter3(X, Y, Z, ".");
7360 ax->hold(matplot::off);
7361 } else
7362 throw std::runtime_error("Invalid parametric dimension");
7363
7364 return f;
7365#else
7366 throw std::runtime_error(
7367 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
7368#endif
7369 }
7370
7381#ifdef IGANET_WITH_MATPLOT
7382 template <typename Backend = matplot::backend::gnuplot,
7383 typename BSplineCoreColor>
7384#else
7385 template <typename Backend = void, typename BSplineCoreColor>
7386#endif
7387 inline auto plot(
7389 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
7390 const nlohmann::json &json = {}) const {
7391
7392#ifdef IGANET_WITH_MATPLOT
7393 auto f = plot<Backend>(color, json);
7394 auto ax = f->current_axes();
7395
7396 for (const auto &xi : xi) {
7397#ifdef __clang__
7398 auto xi_cpu =
7399 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7400 xi, torch::kCPU);
7401 auto xiAccessor = std::get<1>(xi_cpu);
7402#else
7403 auto [xi_cpu, xiAccessor] =
7404 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
7405 xi, torch::kCPU);
7406#endif
7407
7408 if constexpr (BSplineCore::parDim_ == 1) {
7409 matplot::vector_1d X(xi[0].size(0), 0.0);
7410 matplot::vector_1d Y(xi[0].size(0), 0.0);
7411
7412#pragma omp parallel for simd
7413 for (int64_t i = 0; i < xi[0].size(0); ++i) {
7414 X[i] = xiAccessor[0][i];
7415 }
7416
7417 ax->hold(matplot::on);
7418 ax->scatter(X, Y, ".");
7419 ax->hold(matplot::off);
7420 } else if constexpr (BSplineCore::parDim_ == 2) {
7421 matplot::vector_1d X(xi[0].size(0), 0.0);
7422 matplot::vector_1d Y(xi[0].size(0), 0.0);
7423 matplot::vector_1d Z(xi[0].size(0), 0.0);
7424
7425#pragma omp parallel for simd
7426 for (int64_t i = 0; i < xi[0].size(0); ++i) {
7427 X[i] = xiAccessor[0][i];
7428 Y[i] = xiAccessor[1][i];
7429 }
7430
7431 ax->hold(matplot::on);
7432 ax->scatter3(X, Y, Z, ".");
7433 ax->hold(matplot::off);
7434 } else if constexpr (BSplineCore::parDim_ == 3) {
7435 matplot::vector_1d X(xi[0].size(0), 0.0);
7436 matplot::vector_1d Y(xi[0].size(0), 0.0);
7437 matplot::vector_1d Z(xi[0].size(0), 0.0);
7438
7439#pragma omp parallel for simd
7440 for (int64_t i = 0; i < xi[0].size(0); ++i) {
7441 X[i] = xiAccessor[0][i];
7442 Y[i] = xiAccessor[1][i];
7443 Z[i] = xiAccessor[2][i];
7444 }
7445
7446 ax->hold(matplot::on);
7447 ax->scatter3(X, Y, Z, ".");
7448 ax->hold(matplot::off);
7449
7450 } else
7451 throw std::runtime_error("Invalid parametric dimension");
7452 }
7453 return f;
7454#else
7455 throw std::runtime_error(
7456 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
7457#endif
7458 }
7459
7462 inline void pretty_print(std::ostream &os) const noexcept override {
7463 os << name() << "(\nparDim = " << BSplineCore::parDim()
7464 << ", geoDim = " << BSplineCore::geoDim() << ", degrees = ";
7465
7466 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
7467 os << BSplineCore::degree(i) << "x";
7468 if (BSplineCore::parDim() > 0)
7469 os << BSplineCore::degree(BSplineCore::parDim() - 1);
7470 else
7471 os << 0;
7472
7473 os << ", knots = ";
7474 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
7475 os << BSplineCore::nknots(i) << "x";
7476 if (BSplineCore::parDim() > 0)
7477 os << BSplineCore::nknots(BSplineCore::parDim() - 1);
7478 else
7479 os << 0;
7480
7481 os << ", coeffs = ";
7482 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
7483 os << BSplineCore::ncoeffs(i) << "x";
7484 if (BSplineCore::parDim() > 0)
7485 os << BSplineCore::ncoeffs(BSplineCore::parDim() - 1);
7486 else
7487 os << 1;
7488
7489 os << ", options = "
7490 << static_cast<torch::TensorOptions>(BSplineCore::options_);
7491
7492 if (is_verbose(os)) {
7493 os << "\nknots [ ";
7494 for (const torch::Tensor &knots : BSplineCore::knots()) {
7495 os << (knots.is_view() ? "view/" : "owns/");
7496 os << (knots.is_contiguous() ? "cont " : "non-cont ");
7497 }
7498 if (BSplineCore::parDim() > 0)
7499 os << "] = " << BSplineCore::knots();
7500 else
7501 os << "] = {}";
7502
7503 os << "\ncoeffs [ ";
7504 for (const torch::Tensor &coeffs : BSplineCore::coeffs()) {
7505 os << (coeffs.is_view() ? "view/" : "owns/");
7506 os << (coeffs.is_contiguous() ? "cont " : "non-cont ");
7507 }
7508 if (BSplineCore::ncumcoeffs() > 0)
7509 os << "] = " << BSplineCore::coeffs_view();
7510 else
7511 os << "] = {}";
7512 }
7513
7514 os << "\n)";
7515 }
7516
7527
7528 BSplineCommon result{*this};
7529
7530 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7531 result.coeffs(i) += other.coeffs(i);
7532
7533 return result;
7534 }
7535
7547
7548 BSplineCommon result{*this};
7549
7550 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7551 result.coeffs(i) -= other.coeffs(i);
7552
7553 return result;
7554 }
7555
7560 BSplineCommon operator*(BSplineCore::value_type s) const {
7561
7562 BSplineCommon result{*this};
7563
7564 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7565 result.coeffs(i) *= s;
7566
7567 return result;
7568 }
7569
7575 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
7576 const {
7577
7578 BSplineCommon result{*this};
7579
7580 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7581 result.coeffs(i) *= v[i];
7582
7583 return result;
7584 }
7585
7590 BSplineCommon operator/(BSplineCore::value_type s) const {
7591
7592 BSplineCommon result{*this};
7593
7594 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7595 result.coeffs(i) /= s;
7596
7597 return result;
7598 }
7599
7605 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
7606 const {
7607
7608 BSplineCommon result{*this};
7609
7610 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7611 result.coeffs(i) /= v[i];
7612
7613 return result;
7614 }
7615
7625
7626 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7627 BSplineCore::coeffs(i) += other.coeffs(i);
7628
7629 return *this;
7630 }
7631
7641
7642 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7643 BSplineCore::coeffs(i) -= other.coeffs(i);
7644
7645 return *this;
7646 }
7647
7651 BSplineCommon &operator*=(BSplineCore::value_type s) {
7652
7653 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7654 BSplineCore::coeffs(i) *= s;
7655
7656 return *this;
7657 }
7658
7663 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
7664
7665 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7666 BSplineCore::coeffs(i) *= v[i];
7667
7668 return *this;
7669 }
7670
7674 BSplineCommon &operator/=(BSplineCore::value_type s) {
7675
7676 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7677 BSplineCore::coeffs(i) /= s;
7678
7679 return *this;
7680 }
7681
7686 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
7687
7688 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7689 BSplineCore::coeffs(i) /= v[i];
7690
7691 return *this;
7692 }
7693};
7694
7696template <typename real_t, short_t GeoDim, short_t... Degrees>
7698 BSplineCommon<UniformBSplineCore<real_t, GeoDim, Degrees...>>;
7699
7710template <typename real_t, short_t GeoDim, short_t... Degrees>
7711inline std::ostream &
7712operator<<(std::ostream &os,
7714 obj.pretty_print(os);
7715 return os;
7716}
7717
7727template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
7728std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
7730 const std::array<iganet::short_t, ParDim> &degrees,
7731 const std::array<int64_t, ParDim> &ncoeffs,
7734 static_assert(std::is_same_v<real_t, float> ||
7735 std::is_same_v<real_t, double> ||
7736 std::is_same_v<real_t, long double>,
7737 "Unsupported scalar type for JIT source generation");
7738
7740
7741 std::string includes = R"(
7742#include <iganet/splines/bspline.hpp>
7743
7744#ifdef __clang__
7745#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
7746#endif
7747)";
7748
7749 std::ostringstream src;
7750 src << "using real_t = " << iganet::type_name_v<real_t> << ";\n"
7751 << "using patch_t = iganet::BSplinePatch<real_t, " << GeoDim << ", "
7752 << ParDim << ">;\n"
7753 << "using bspline_t = iganet::UniformBSpline<real_t, " << GeoDim;
7754 for (const auto degree : degrees)
7755 src << ", " << degree;
7756 src << ">;\n\n"
7757 << "EXPORT std::shared_ptr<patch_t> create_patch(\n"
7758 << " const std::array<int64_t, " << ParDim << "> &ncoeffs,\n"
7759 << " enum iganet::init init, iganet::Options<real_t> options) {\n"
7760 << " return std::make_shared<bspline_t>(ncoeffs, init, options);\n"
7761 << "}\n";
7762
7763 const auto libname =
7764 iganet::jit{}.compile(includes, src.str(), "CreateUniformBSpline");
7765 auto handler = std::make_shared<iganet::DLHandler>(libname);
7766
7767 using create_patch_fn =
7768 std::shared_ptr<patch_t> (*)(const std::array<int64_t, ParDim> &,
7770 const auto create_patch =
7771 reinterpret_cast<create_patch_fn>(handler->getSymbol("create_patch"));
7772 auto patch = create_patch(ncoeffs, init, options);
7773 auto *patch_ptr = patch.get();
7774
7775 // Keep the library loaded until after the dynamically created patch and its
7776 // shared_ptr control block have been destroyed.
7777 return std::shared_ptr<patch_t>(
7778 patch_ptr, [patch = std::move(patch),
7779 handler = std::move(handler)](patch_t *) mutable {
7780 patch.reset();
7781 handler.reset();
7782 });
7783}
7788
7790template <typename real_t, short_t GeoDim, short_t... Degrees>
7792 BSplineCommon<NonUniformBSplineCore<real_t, GeoDim, Degrees...>>;
7793
7804template <typename real_t, short_t GeoDim, short_t... Degrees>
7805inline std::ostream &
7806operator<<(std::ostream &os,
7808 obj.pretty_print(os);
7809 return os;
7810}
7811
7821template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
7822std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
7824 const std::array<iganet::short_t, ParDim> &degrees,
7825 const std::array<int64_t, ParDim> &ncoeffs,
7828 static_assert(std::is_same_v<real_t, float> ||
7829 std::is_same_v<real_t, double> ||
7830 std::is_same_v<real_t, long double>,
7831 "Unsupported scalar type for JIT source generation");
7832
7834
7835 std::string includes = R"(
7836#include <iganet/splines/bspline.hpp>
7837
7838#ifdef __clang__
7839#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
7840#endif
7841)";
7842
7843 std::ostringstream src;
7844 src << "using real_t = " << iganet::type_name_v<real_t> << ";\n"
7845 << "using patch_t = iganet::BSplinePatch<real_t, " << GeoDim << ", "
7846 << ParDim << ">;\n"
7847 << "using bspline_t = iganet::NonUniformBSpline<real_t, " << GeoDim;
7848 for (const auto degree : degrees)
7849 src << ", " << degree;
7850 src << ">;\n\n"
7851 << "EXPORT std::shared_ptr<patch_t> create_patch(\n"
7852 << " const std::array<int64_t, " << ParDim << "> &ncoeffs,\n"
7853 << " enum iganet::init init, iganet::Options<real_t> options) {\n"
7854 << " return std::make_shared<bspline_t>(ncoeffs, init, options);\n"
7855 << "}\n";
7856
7857 const auto libname =
7858 iganet::jit{}.compile(includes, src.str(), "CreateNonUniformBSpline");
7859 auto handler = std::make_shared<iganet::DLHandler>(libname);
7860
7861 using create_patch_fn =
7862 std::shared_ptr<patch_t> (*)(const std::array<int64_t, ParDim> &,
7864 const auto create_patch =
7865 reinterpret_cast<create_patch_fn>(handler->getSymbol("create_patch"));
7866 auto patch = create_patch(ncoeffs, init, options);
7867 auto *patch_ptr = patch.get();
7868
7869 // Keep the library loaded until after the dynamically created patch and its
7870 // shared_ptr control block have been destroyed.
7871 return std::shared_ptr<patch_t>(
7872 patch_ptr, [patch = std::move(patch),
7873 handler = std::move(handler)](patch_t *) mutable {
7874 patch.reset();
7875 handler.reset();
7876 });
7877}
7878
7879namespace detail {
7880
7881template <iganet::short_t ParDim> struct BSplineMetadata {
7882 std::array<iganet::short_t, ParDim> degrees{};
7883 std::array<int64_t, ParDim> ncoeffs{};
7884};
7885
7887
7897template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
7898BSplineMetadata<ParDim> parseBSplineMetadata(const pugi::xml_node &root, int id,
7899 const std::string &label,
7900 int index) {
7901 pugi::xml_node geometry;
7902 const std::string geometryType =
7903 ParDim == 0 ? "Point"
7904 : ParDim == 1
7905 ? "BSpline"
7906 : std::string("TensorBSpline").append(std::to_string(ParDim));
7907 for (const auto &candidate : root.children("Geometry"))
7908 if (std::string_view{candidate.attribute("type").value()} == geometryType &&
7909 (id < 0 || candidate.attribute("id").as_int() == id) &&
7910 (index < 0 || candidate.attribute("index").as_int() == index) &&
7911 (label.empty() || candidate.attribute("label").value() == label)) {
7912 geometry = candidate;
7913 break;
7914 }
7915 if (!geometry)
7916 throw std::runtime_error(
7917 "XML object does not provide geometry with given attributes");
7918 const auto coefs = geometry.child("coefs");
7919 if (!coefs || coefs.attribute("geoDim").as_int() != GeoDim)
7920 throw std::runtime_error(
7921 "XML object provides an incompatible geometric dimension");
7922
7923 BSplineMetadata<ParDim> metadata;
7924 const auto readBasis = [&](const pugi::xml_node &basis, int direction) {
7925 const auto knots = basis.child("KnotVector");
7926 if (std::string_view{basis.attribute("type").value()} != "BSplineBasis" ||
7927 !knots || !knots.attribute("degree"))
7928 throw std::runtime_error("XML object does not provide a valid basis");
7929 metadata.degrees[direction] = knots.attribute("degree").as_int();
7930 std::stringstream values(knots.child_value());
7931 real_t value;
7932 int64_t nknots = 0;
7933 while (values >> value)
7934 ++nknots;
7935 metadata.ncoeffs[direction] = nknots - metadata.degrees[direction] - 1;
7936 if (metadata.ncoeffs[direction] <= 0)
7937 throw std::runtime_error("XML object provides an invalid knot vector");
7938 };
7939 if constexpr (ParDim == 1)
7940 readBasis(geometry.child("Basis"), 0);
7941 else if constexpr (ParDim > 1) {
7942 const auto bases = geometry.child("Basis");
7943 const std::string basisType =
7944 std::string("TensorBSplineBasis").append(std::to_string(ParDim));
7945 if (std::string_view{bases.attribute("type").value()} != basisType)
7946 throw std::runtime_error("XML object does not provide a valid basis");
7947 std::array<bool, ParDim> found{};
7948 for (const auto &basis : bases.children("Basis")) {
7949 const int direction = basis.attribute("index").as_int(-1);
7951 if (direction < 0 || direction >= ParDim || found[direction])
7952 throw std::runtime_error("XML object does not provide a valid basis");
7953 readBasis(basis, direction);
7954 found[direction] = true;
7955 }
7956 if (std::any_of(found.begin(), found.end(),
7957 [](bool value) { return !value; }))
7958 throw std::runtime_error("XML object does not provide all bases");
7959 }
7960 return metadata;
7961}
7962
7969template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
7971 if (json.at("geoDim").get<iganet::short_t>() != GeoDim)
7972 throw std::runtime_error(
7973 "JSON object provides an incompatible geometric dimension");
7974 if (json.at("parDim").get<iganet::short_t>() != ParDim)
7975 throw std::runtime_error(
7976 "JSON object provides an incompatible parametric dimension");
7977 BSplineMetadata<ParDim> metadata{
7978 json.at("degrees").template get<std::array<iganet::short_t, ParDim>>(),
7979 json.at("ncoeffs").template get<std::array<int64_t, ParDim>>()};
7980 if (std::any_of(metadata.ncoeffs.begin(), metadata.ncoeffs.end(),
7981 [](int64_t count) { return count <= 0; }))
7982 throw std::runtime_error("JSON object provides invalid coefficient counts");
7983 return metadata;
7984}
7985
7986} // namespace detail
7987
7999template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8000std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8002 const pugi::xml_node &root, int id = 0, const std::string &label = "",
8003 int index = -1,
8005 const auto metadata = detail::parseBSplineMetadata<real_t, GeoDim, ParDim>(
8006 root, id, label, index);
8007
8008 auto patch = createNonUniformBSpline<real_t, GeoDim, ParDim>(
8009 metadata.degrees, metadata.ncoeffs, iganet::init::none, options);
8010 patch->from_xml(root, id, label, index);
8011 return patch;
8012}
8013
8025template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8026std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8028 const pugi::xml_document &doc, int id = 0, const std::string &label = "",
8029 int index = -1,
8031 return createNonUniformBSpline<real_t, GeoDim, ParDim>(doc.child("xml"), id,
8032 label, index, options);
8033}
8034
8043template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8044std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8046 const nlohmann::json &json,
8048 const auto metadata =
8049 detail::parseBSplineMetadata<real_t, GeoDim, ParDim>(json);
8050
8051 auto patch = createNonUniformBSpline<real_t, GeoDim, ParDim>(
8052 metadata.degrees, metadata.ncoeffs, iganet::init::none, options);
8053 patch->from_json(json);
8054 return patch;
8055}
8056
8057namespace detail {
8058
8072template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8073std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8075 const std::array<iganet::short_t, ParDim> &degrees,
8076 const std::string &serialized, SerializationFormat format, int id,
8077 const std::string &label, int index, iganet::Options<real_t> options) {
8079
8080 const std::string includes = R"(
8081#include <iganet/splines/bspline.hpp>
8082
8083#ifdef __clang__
8084#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
8085#endif
8086)";
8087
8088 std::ostringstream src;
8089 src << "using real_t = " << iganet::type_name_v<real_t> << ";\n"
8090 << "using patch_t = iganet::BSplinePatch<real_t, " << GeoDim << ", "
8091 << ParDim << ">;\n"
8092 << "using nonuniform_t = iganet::NonUniformBSpline<real_t, " << GeoDim;
8093 for (const auto degree : degrees)
8094 src << ", " << degree;
8095 src << ">;\n"
8096 << "using uniform_t = iganet::UniformBSpline<real_t, " << GeoDim;
8097 for (const auto degree : degrees)
8098 src << ", " << degree;
8099 src << ">;\n\n"
8100 << "EXPORT std::shared_ptr<patch_t> create_patch_from_xml(\n"
8101 << " const std::string &serialized, int id,\n"
8102 << " const std::string &label, int index,\n"
8103 << " iganet::Options<real_t> options) {\n"
8104 << " pugi::xml_document doc;\n"
8105 << " if (!doc.load_string(serialized.c_str()))\n"
8106 << " throw std::runtime_error(\"Unable to parse XML object\");\n"
8107 << " nonuniform_t spline(options);\n"
8108 << " spline.from_xml(doc, id, label, index);\n"
8109 << " return std::make_shared<uniform_t>(\n"
8110 << " std::move(spline).to_uniform());\n"
8111 << "}\n\n"
8112 << "EXPORT std::shared_ptr<patch_t> create_patch_from_json(\n"
8113 << " const std::string &serialized,\n"
8114 << " iganet::Options<real_t> options) {\n"
8115 << " nonuniform_t spline(options);\n"
8116 << " spline.from_json(nlohmann::json::parse(serialized));\n"
8117 << " return std::make_shared<uniform_t>(\n"
8118 << " std::move(spline).to_uniform());\n"
8119 << "}\n";
8120
8121 const auto libname = iganet::jit{}.compile(
8122 includes, src.str(), "CreateUniformBSplineFromSerialized");
8123 auto handler = std::make_shared<iganet::DLHandler>(libname);
8124 std::shared_ptr<patch_t> patch;
8125 if (format == SerializationFormat::xml) {
8126 using create_fn = std::shared_ptr<patch_t> (*)(const std::string &, int,
8127 const std::string &, int,
8129 const auto create = reinterpret_cast<create_fn>(
8130 handler->getSymbol("create_patch_from_xml"));
8131 patch = create(serialized, id, label, index, options);
8132 } else {
8133 using create_fn = std::shared_ptr<patch_t> (*)(const std::string &,
8135 const auto create = reinterpret_cast<create_fn>(
8136 handler->getSymbol("create_patch_from_json"));
8137 patch = create(serialized, options);
8138 }
8139
8140 auto *patch_ptr = patch.get();
8141 return std::shared_ptr<patch_t>(
8142 patch_ptr, [patch = std::move(patch),
8143 handler = std::move(handler)](patch_t *) mutable {
8144 patch.reset();
8145 handler.reset();
8146 });
8147}
8148
8149} // namespace detail
8150
8166template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8167std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8169 const pugi::xml_node &root, int id = 0, const std::string &label = "",
8170 int index = -1,
8172 const auto metadata = detail::parseBSplineMetadata<real_t, GeoDim, ParDim>(
8173 root, id, label, index);
8174
8175 std::ostringstream serialized;
8176 root.print(serialized);
8177 return detail::createUniformBSplineFromSerialized<real_t, GeoDim, ParDim>(
8178 metadata.degrees, serialized.str(), detail::SerializationFormat::xml, id,
8179 label, index, options);
8180}
8181
8193template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8194std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8196 const pugi::xml_document &doc, int id = 0, const std::string &label = "",
8197 int index = -1,
8199 return createUniformBSpline<real_t, GeoDim, ParDim>(doc.child("xml"), id,
8200 label, index, options);
8201}
8202
8209template <typename real_t, iganet::short_t GeoDim, iganet::short_t ParDim>
8210std::shared_ptr<iganet::BSplinePatch<real_t, GeoDim, ParDim>>
8212 const nlohmann::json &json,
8214 const auto metadata =
8215 detail::parseBSplineMetadata<real_t, GeoDim, ParDim>(json);
8216
8217 return detail::createUniformBSplineFromSerialized<real_t, GeoDim, ParDim>(
8218 metadata.degrees, json.dump(), detail::SerializationFormat::json, 0, "",
8219 -1, options);
8220}
8221} // namespace iganet
Compile-time block tensor.
B-spline (common high-level functionality).
Definition bspline.hpp:3890
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:6045
auto to_nonuniform() &&
Converts a uniform B-spline into a non-uniform B-spline.
Definition bspline.hpp:3976
auto rotate(std::array< typename BSplineCore::value_type, 3 > angle) const
Rotates the B-spline object by three angles in 3d.
Definition bspline.hpp:4448
BSplineCommon & operator*=(BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:7651
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:4820
auto ihess(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the ihess operation.
Definition bspline.hpp:5666
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:4695
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:4619
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:5760
auto diff_(const BSplineCommon &other, int dim=-1)
Computes the difference between two compatible B-spline objects in-place.
Definition bspline.hpp:4283
auto ilapl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the ilapl operation.
Definition bspline.hpp:6283
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:5651
auto rotate_(std::array< typename BSplineCore::value_type, 3 > angle)
Rotates the B-spline object by three angles in 3d in-place.
Definition bspline.hpp:4455
BSplineCommon & operator*=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:7662
auto rotate_(BSplineCore::value_type angle)
Rotates the B-spline object by an angle in 2d in-place.
Definition bspline.hpp:4429
auto idiv(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the idiv operation.
Definition bspline.hpp:5061
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:5277
auto plot(const BSplineCommon< BSplineCoreColor > &color, const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Plots the B-spline object colored by another B-spline object together with a set of sampling points.
Definition bspline.hpp:7387
auto div(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the div operation.
Definition bspline.hpp:4922
void pretty_print(std::ostream &os) const noexcept override
Returns a string representation of the BSplineCommon object.
Definition bspline.hpp:7462
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:7574
auto nv(const utils::TensorArray< BSplineCore::parDim_ > &xi, const utils::TensorArray< BSplineCore::parDim_ > &knot_indices) const
Returns a block-tensor with the outward pointing normal vector of the B-spline object.
Definition bspline.hpp:4541
BSplineCommon & operator/=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:7685
auto translate_(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Translates the B-spline object by a vector in-place.
Definition bspline.hpp:4412
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:6210
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:5993
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:4867
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:6091
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:5890
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:5942
auto hess(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the hess operation.
Definition bspline.hpp:5492
std::unique_ptr< BSplineCommon > uPtr
Unique pointer for BSplineCommon.
Definition bspline.hpp:3929
BSplineCommon(const BSplineCommon &other, bool clone)
Copy/clone constructor.
Definition bspline.hpp:3937
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:5099
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 >{})
Provides the make_unique operation.
Definition bspline.hpp:4034
auto lapl(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the lapl operation.
Definition bspline.hpp:6141
auto abs_diff_(const BSplineCommon &other, int dim=-1)
Computes the absolute difference between two compatible B-spline objects in-place.
Definition bspline.hpp:4327
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:6326
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 >{})
Provides the make_shared operation.
Definition bspline.hpp:4157
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:5183
auto diff(const BSplineCommon &other, int dim=-1) const
Computes the difference between two compatible B-spline objects.
Definition bspline.hpp:4270
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:5146
auto curl(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the curl operation.
Definition bspline.hpp:4629
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:5479
auto igrad(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the igrad operation.
Definition bspline.hpp:5344
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 >{})
Provides the make_unique operation.
Definition bspline.hpp:4062
auto to_uniform() &&
Converts a non-uniform B-spline into a uniform B-spline.
Definition bspline.hpp:3991
BSplineCommon operator*(BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:7560
auto scale(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v) const
Scales the B-spline object by a vector.
Definition bspline.hpp:4385
auto nv(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 outward pointing normal vector of the B-spline object.
Definition bspline.hpp:4561
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:5427
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:4991
std::shared_ptr< BSplineCommon > Ptr
Shared pointer for BSplineCommon.
Definition bspline.hpp:3926
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:5590
auto plot(const BSplineCommon< BSplineCoreColor > &color, const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Plots the B-spline object colored by another B-spline object together with a set of sampling points.
Definition bspline.hpp:7301
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 >{})
Provides the make_unique operation.
Definition bspline.hpp:4048
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 >{})
Provides the make_shared operation.
Definition bspline.hpp:4144
auto scale(BSplineCore::value_type s, int dim=-1) const
Scales the B-spline object by a scalar.
Definition bspline.hpp:4364
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:5539
auto plot(const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Plots the B-spline object together with a set of sampling points.
Definition bspline.hpp:6439
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 >{})
Provides the make_unique operation.
Definition bspline.hpp:4090
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:4660
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:4023
BSplineCommon(BSplineCommon &&)=default
Move constructor.
BSplineCommon operator/(BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:7590
auto norm() const
Computes the norm of the B-spline object by computing the mean-squared sum of the function values eva...
Definition bspline.hpp:4355
auto plot(const nlohmann::json &json={}) const
Plots the B-spline object.
Definition bspline.hpp:6423
BSplineCommon(BSplineCommon &&other, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs)
Move constructor with external coefficients.
Definition bspline.hpp:4011
BSplineCommon & operator+=(const BSplineCommon &other)
Adds the coefficients of another B-spline object.
Definition bspline.hpp:7624
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:6268
auto plot(const BSplineCommon< BSplineCoreColor > &color, const nlohmann::json &json={}) const
Plots the B-spline object colored by another B-spline object.
Definition bspline.hpp:6477
auto to() const
Returns a copy of the B-spline object with real_t type.
Definition bspline.hpp:4257
auto nv(const torch::Tensor &xi) const
Returns a block-tensor with the outward pointing normal vector of the B-spline object.
Definition bspline.hpp:4516
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:7526
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:4769
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:5708
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:5046
auto ijac(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the ijac operation.
Definition bspline.hpp:6008
auto translate(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v) const
Translates the B-spline object by a vector.
Definition bspline.hpp:4403
auto boundingBox() const
Computes the bounding box of the B-spline object.
Definition bspline.hpp:4490
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 >{})
Provides the make_shared operation.
Definition bspline.hpp:4116
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:4910
auto icurl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the icurl operation.
Definition bspline.hpp:4784
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:5236
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:5329
auto scale_(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the B-spline object by a vector in-place.
Definition bspline.hpp:4393
auto jac(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the jac operation.
Definition bspline.hpp:5848
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 >{})
Provides the make_shared operation.
Definition bspline.hpp:4130
BSplineCommon & operator/=(BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:7674
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 >{})
Provides the make_unique operation.
Definition bspline.hpp:4075
auto rotate(BSplineCore::value_type angle) const
Rotates the B-spline object by an angle in 2d.
Definition bspline.hpp:4422
auto nv(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the nv operation.
Definition bspline.hpp:4526
BSplineCommon & operator-=(const BSplineCommon &other)
Substracts the coefficients of another B-spline object.
Definition bspline.hpp:7640
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:7546
BSplineCommon(const BSplineCommon &other, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false)
Copy constructor with external coefficients.
Definition bspline.hpp:3947
auto scale_(BSplineCore::value_type s, int dim=-1)
Scales the B-spline object by a scalar in-place.
Definition bspline.hpp:4372
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:6380
auto grad(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Provides the grad operation.
Definition bspline.hpp:5200
auto to(torch::Device device) const
Returns a copy of the B-spline object with settings from device.
Definition bspline.hpp:4238
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:5380
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:6128
auto clone() const
Returns a clone of the B-spline object.
Definition bspline.hpp:4198
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:7604
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:4105
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:5835
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 >{})
Provides the make_shared operation.
Definition bspline.hpp:4172
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:6173
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:4954
BSplineCommon & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:4191
auto plot(const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Plots the B-spline object together with a set of sampling points.
Definition bspline.hpp:6457
auto abs_diff(const BSplineCommon &other, int dim=-1) const
Computes the absolute difference between two compatible B-spline objects.
Definition bspline.hpp:4314
auto to(Options< real_t > options) const
Returns a copy of the B-spline object with settings from options.
Definition bspline.hpp:4218
BSplineCommon(OtherCore &&core)
Constructs the high-level B-spline from a compatible core.
Definition bspline.hpp:3967
Abstract patch function base class.
Definition patch.hpp:53
Tensor-product non-uniform B-spline (core functionality).
Definition bspline.hpp:3287
static constexpr bool is_nonuniform()
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:3331
auto eval(const utils::TensorArray< Base::parDim_ > &xi, const utils::TensorArray< Base::parDim_ > &knot_indices) const
Provides the eval operation.
Definition bspline.hpp:3455
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:3309
auto eval(const torch::Tensor &xi) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:3421
auto find_knot_indices(const torch::Tensor &xi) const
Returns the indices of knot spans containing xi.
Definition bspline.hpp:3509
real_t value_type
Value type.
Definition bspline.hpp:3294
auto eval(const utils::TensorArray< Base::parDim_ > &xi) const
Provides the eval operation.
Definition bspline.hpp:3433
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:3658
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:3371
NonUniformBSplineCore(Base &&other) noexcept
Constructs a non-uniform B-spline by consuming a uniform one.
Definition bspline.hpp:3338
NonUniformBSplineCore & insert_knots(const utils::TensorArray< Base::parDim_ > &knots)
Returns the B-spline object with refined knot and coefficient vectors.
Definition bspline.hpp:3612
auto & from_gismo(BSpline &bspline, bool updateCoeffs, bool updateKnotVector)
Provides the from_gismo operation.
Definition bspline.hpp:3842
auto eval(const utils::TensorArray< Base::parDim_ > &xi, const utils::TensorArray< Base::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Provides the eval operation.
Definition bspline.hpp:3477
UniformBSplineCore< real_t, GeoDim, Degrees... > Base
Base type.
Definition bspline.hpp:3290
auto find_knot_indices(const utils::TensorArray< Base::parDim_ > &xi) const
Provides the find_knot_indices operation.
Definition bspline.hpp:3520
static constexpr bool is_uniform()
Returns true if the B-spline is uniform.
Definition bspline.hpp:3327
NonUniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:3544
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:3348
void init_knots(const std::array< std::vector< typename Base::value_type >, Base::parDim_ > &kv)
Initializes the B-spline knots.
Definition bspline.hpp:3393
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:3303
NonUniformSplineCore base class.
Definition bspline.hpp:136
The Options class handles the automated determination of dtype from the template argument and the sel...
Definition options.hpp:47
Spline base class.
Definition bspline.hpp:3853
SplineCore base class.
Definition bspline.hpp:130
Tensor-product uniform B-spline (core functionality).
Definition bspline.hpp:229
bool pinned_memory() const noexcept override
Returns the pinned_memory property.
Definition bspline.hpp:336
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:259
utils::TensorArray< parDim_ > find_knot_indices(const utils::TensorArray< parDim_ > &xi) const noexcept
Provides the find_knot_indices operation.
Definition bspline.hpp:1267
utils::TensorArray< geoDim_ > coeffs_view() const noexcept
Returns an array of views to the coefficient vectors.
Definition bspline.hpp:648
real_t value_type
Value type.
Definition bspline.hpp:275
bool requires_grad() const noexcept override
Returns the requires_grad property.
Definition bspline.hpp:330
utils::TensorArray< parDim_ > knots_
Array storing the knot vectors. .
Definition bspline.hpp:263
UniformBSplineCore & transform(const std::function< std::array< real_t, geoDim_ >(const std::array< real_t, parDim_ > &)> mapping)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1606
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:1015
utils::TensorArray< parDim_ > & knots() noexcept
Returns a non-constant reference to the array of knot vectors.
Definition bspline.hpp:577
torch::Tensor as_tensor_(std::index_sequence< Is... >) const noexcept
Returns all coefficients as a single tensor.
Definition bspline.hpp:708
void load(const std::string &filename, const std::string &key="bspline")
Loads the B-spline from file.
Definition bspline.hpp:2217
UniformBSplineCore(const UniformBSplineCore< other_t, GeoDim, Degrees... > &other, Options< real_t > options=Options< real_t >{})
Copy constructor.
Definition bspline.hpp:485
torch::Layout layout() const noexcept override
Returns the layout property.
Definition bspline.hpp:324
UniformBSplineCore(UniformBSplineCore &&) noexcept=default
Move constructor.
UniformBSplineCore & from_json(const nlohmann::json &json) override
Updates the B-spline object from JSON object.
Definition bspline.hpp:1820
auto eval_basfunc_tr(const torch::Tensor &xi) const
Provides the eval_basfunc_tr operation.
Definition bspline.hpp:1368
const Options< real_t > & options() const noexcept
Returns a constant reference to the B-spline object's options.
Definition bspline.hpp:383
const std::array< int64_t, parDim_ > & nknots() const noexcept
Returns a constant reference to the array of knot vector dimensions.
Definition bspline.hpp:594
UniformBSplineCore & transform(const std::function< std::array< real_t, N >(const std::array< real_t, parDim_ > &)> mapping, std::array< short_t, N > dims)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1692
auto eval_basfunc_tr(const torch::Tensor &xi, const torch::Tensor &knot_indices) const
Provides the eval_basfunc_tr operation.
Definition bspline.hpp:1429
auto eval_basfunc_univariate_tr(const torch::Tensor &xi, const torch::Tensor &knot_indices) const
Provides the eval_basfunc_univariate_tr operation.
Definition bspline.hpp:2901
derived_type< UniformBSplineCore, degree_elevate > self_type
Deduces the self-type possibly degrees (de-)elevated by the additive constant degree_elevate.
Definition bspline.hpp:289
torch::Tensor as_tensor() const noexcept override
Returns all coefficients as a single tensor.
Definition bspline.hpp:716
auto eval_tr(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Provides the eval_tr operation.
Definition bspline.hpp:1028
auto find_coeff_indices(const torch::Tensor &indices) const
Returns the indices of the coefficients corresponding to the knot indices indices.
Definition bspline.hpp:1294
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:848
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:1410
const torch::Tensor & coeffs(short_t i) const noexcept
Returns a constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:623
utils::TensorArray< geoDim_ > coeffs_
Array storing the coefficients of the control net. , .
Definition bspline.hpp:268
static constexpr short_t parDim() noexcept
Returns the parametric dimension.
Definition bspline.hpp:528
static constexpr bool is_uniform() noexcept
Returns true if the B-spline is uniform.
Definition bspline.hpp:348
int64_t ncoeffs(short_t i) const noexcept
Returns the total number of coefficients in the. -th direction.
Definition bspline.hpp:698
auto eval_tr(const torch::Tensor &xi) const
Provides the eval_tr operation.
Definition bspline.hpp:981
auto find_coeff_indices(const utils::TensorArray< parDim_ > &indices) const
Provides the find_coeff_indices operation.
Definition bspline.hpp:1308
auto eval(const utils::TensorArray< parDim_ > &xi) const
Provides the eval operation.
Definition bspline.hpp:969
const auto coeffs_view(short_t i) const noexcept
Returns a view to the coefficient vector in the. -th dimension.
Definition bspline.hpp:661
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:2649
pugi::xml_document to_xml(int id=0, const std::string &label="", int index=-1) const override
Returns the B-spline object as XML object.
Definition bspline.hpp:1859
static constexpr short_t degree(short_t i) noexcept
Returns a constant reference to the degree in the. -th dimension.
Definition bspline.hpp:549
torch::Dtype dtype() const noexcept override
Returns the dtype property.
Definition bspline.hpp:318
const utils::TensorArray< parDim_ > & knots() const noexcept
Returns a constant reference to the array of knot vectors.
Definition bspline.hpp:558
int64_t nknots(short_t i) const noexcept
Returns the dimension of the knot vector in the. -th dimension.
Definition bspline.hpp:604
int64_t constexpr eval_prefactor() const
Computes the prefactor .
Definition bspline.hpp:2481
BSpline< real_t, GeoDim,(Degrees+degree_elevate)... > derived_type
Deduces the type of the template parameter BSpline when exposed to the class template parameters real...
Definition bspline.hpp:284
std::array< int64_t, parDim_ > nknots_
Array storing the sizes of the knot vectors. .
Definition bspline.hpp:250
static constexpr const std::array< short_t, parDim_ > degrees_
Array storing the degrees. .
Definition bspline.hpp:246
pugi::xml_node & to_xml(pugi::xml_node &root, int id=0, const std::string &label="", int index=-1) const override
Returns the B-spline object as XML node.
Definition bspline.hpp:1874
static constexpr const short_t parDim_
Dimension of the parametric space. .
Definition bspline.hpp:238
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
Provides the eval_from_precomputed operation.
Definition bspline.hpp:870
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:402
Options< real_t > options_
Options.
Definition bspline.hpp:271
auto greville(bool interior=false) const
Returns the Greville abscissae.
Definition bspline.hpp:768
utils::TensorArray< geoDim_ > & coeffs() noexcept
Returns a non-constant reference to the array of coefficient vectors.
Definition bspline.hpp:632
static constexpr const std::array< short_t, parDim_ > & degrees() noexcept
Returns a constant reference to the array of degrees.
Definition bspline.hpp:539
int32_t device_index() const noexcept override
Returns the device_index property.
Definition bspline.hpp:312
torch::Device device() const noexcept override
Returns the device property.
Definition bspline.hpp:306
UniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:2409
UniformBSplineCore & set_requires_grad(bool requires_grad) noexcept override
Sets the B-spline object's requires_grad property.
Definition bspline.hpp:364
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:3011
UniformBSplineCore & from_tensor(const torch::Tensor &tensor) noexcept override
Sets all coefficients from a single tensor.
Definition bspline.hpp:742
UniformBSplineCore(Options< real_t > options=Options< real_t >{})
Default constructor.
Definition bspline.hpp:388
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:2283
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:2229
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:2823
bool operator!=(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are different.
Definition bspline.hpp:2393
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:2319
bool operator==(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are the same.
Definition bspline.hpp:2356
nlohmann::json knots_to_json() const
Returns the B-spline object's knots as JSON object.
Definition bspline.hpp:1789
auto eval_basfunc(const utils::TensorArray< parDim_ > &xi) const
Provides the eval_basfunc operation.
Definition bspline.hpp:1352
static constexpr const short_t geoDim_
Dimension of the geometric space. .
Definition bspline.hpp:242
auto eval_tr(const utils::TensorArray< parDim_ > &xi) const
Provides the eval_tr operation.
Definition bspline.hpp:994
const utils::TensorArray< geoDim_ > & coeffs() const noexcept
Returns a constant reference to the array of coefficient vectors.
Definition bspline.hpp:613
auto eval_basfunc_tr(const utils::TensorArray< parDim_ > &xi) const
Provides the eval_basfunc_tr operation.
Definition bspline.hpp:1385
int64_t ncumcoeffs() const noexcept
Returns the total number of coefficients.
Definition bspline.hpp:675
void save(const std::string &filename, const std::string &key="bspline") const
Saves the B-spline to file.
Definition bspline.hpp:2272
static constexpr bool is_nonuniform() noexcept
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:352
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:429
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:467
const torch::Tensor & knots(short_t i) const noexcept
Returns a constant reference to the knot vector in the. -th dimension.
Definition bspline.hpp:568
auto find_knot_indices(const torch::Tensor &xi) const noexcept
Returns the indices of knot spans containing xi.
Definition bspline.hpp:1256
auto update_coeffs_univariate(const torch::Tensor &knots, const torch::Tensor &knot_indices) const
Returns the knot insertion matrix.
Definition bspline.hpp:2963
std::array< int64_t, parDim_ > ncoeffs_
Array storing the sizes of the coefficients of the control net .
Definition bspline.hpp:254
nlohmann::json to_json() const override
Returns the B-spline object as JSON object.
Definition bspline.hpp:1774
torch::Tensor & coeffs(short_t i) noexcept
Returns a non-constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:640
UniformBSplineCore(const UniformBSplineCore &)=default
Copy constructor.
void init_coeffs(enum init init)
Initializes the B-spline coefficients.
Definition bspline.hpp:2517
auto & from_gismo(BSpline &bspline, bool updateCoeffs, bool updateKnotVector)
Provides the from_gismo operation.
Definition bspline.hpp:3241
auto eval_tr(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Provides the eval_tr operation.
Definition bspline.hpp:1149
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:1053
UniformBSplineCore & from_tensor_(std::index_sequence< Is... >, const torch::Tensor &tensor) noexcept
Sets all coefficients from a single tensor.
Definition bspline.hpp:726
auto eval_basfunc(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Provides the eval_basfunc operation.
Definition bspline.hpp:1449
static constexpr short_t geoDim() noexcept
Returns the geometric dimension.
Definition bspline.hpp:533
const std::array< int64_t, parDim_ > & ncoeffs() const noexcept
Returns a constant reference to the array of coefficient vector dimensions.
Definition bspline.hpp:688
torch::Tensor & knots(short_t i) noexcept
Returns a non-constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:585
UniformBSplineCore & from_xml(const pugi::xml_node &root, int id=0, const std::string &label="", int index=-1) override
Updates the B-spline object from XML node.
Definition bspline.hpp:2005
BSpline & to_gismo(BSpline &bspline, bool updateKnotVector, bool updateCoeffs) const
Provides the to_gismo operation.
Definition bspline.hpp:3151
auto eval_basfunc_tr(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Provides the eval_basfunc_tr operation.
Definition bspline.hpp:1529
void init_knots()
Initializes the B-spline knots.
Definition bspline.hpp:2490
auto eval(const torch::Tensor &xi) const
Returns the value of the B-spline object in the point xi.
Definition bspline.hpp:956
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:1336
bool is_sparse() const noexcept override
Returns true if the layout is sparse.
Definition bspline.hpp:342
UniformBSplineCore & from_xml(const pugi::xml_document &doc, int id=0, const std::string &label="", int index=-1) override
Updates the B-spline object from XML object.
Definition bspline.hpp:1993
int64_t as_tensor_size() const noexcept override
Returns the size of the single tensor representation of all coefficients.
Definition bspline.hpp:750
nlohmann::json coeffs_to_json() const
Returns the B-spline object's coefficients as JSON object.
Definition bspline.hpp:1795
UniformSplineCore base class.
Definition bspline.hpp:133
Full qualified name descriptor.
Definition fqn.hpp:22
Concept to identify template parameters that are derived from iganet::NonUniformSplineCore_.
Definition bspline.hpp:151
Concept to identify template parameters that are derived from iganet::Spline_ and iganet::NonUniformS...
Definition bspline.hpp:3870
Concept to identify template parameters that are derived from iganet::SplineCore_.
Definition bspline.hpp:141
Concept to identify template parameters that are derived from iganet::Spline_.
Definition bspline.hpp:3858
Concept to identify template parameters that are derived from iganet::UniformSplineCore_.
Definition bspline.hpp:146
Concept to identify template parameters that are derived from iganet::Spline_ and iganet::UniformSpli...
Definition bspline.hpp:3863
Definition bspline.hpp:123
Definition bspline.hpp:116
Container utility functions.
Core components.
Dynamic library loader.
Full qualified name utility functions.
Integer sequence utility function.
Integer power utility function.
Linear algebra utility functions.
SerializationFormat
Definition bspline.hpp:7886
std::shared_ptr< iganet::BSplinePatch< real_t, GeoDim, ParDim > > createUniformBSplineFromSerialized(const std::array< iganet::short_t, ParDim > &degrees, const std::string &serialized, SerializationFormat format, int id, const std::string &label, int index, iganet::Options< real_t > options)
Loads a non-uniform B-spline and converts it to a uniform B-spline inside one JIT-generated dynamic l...
Definition bspline.hpp:8074
std::array< int64_t, ParDim > ncoeffs
Definition bspline.hpp:7883
std::array< iganet::short_t, ParDim > degrees
Definition bspline.hpp:7882
BSplineMetadata< ParDim > parseBSplineMetadata(const pugi::xml_node &root, int id, const std::string &label, int index)
Parses B-spline metadata from an XML node.
Definition bspline.hpp:7898
Definition bspline.hpp:7881
auto kron(T0 &&t0, T1 &&t1)
Computes the Kronecker-product between two or more tensors.
Definition linalg.hpp:214
std::array< torch::Tensor, N > TensorArray
Definition tensorarray.hpp:26
auto to_tensor(const std::array< T, N > &array, torch::IntArrayRef sizes=torch::IntArrayRef{-1}, const iganet::Options< T > &options=iganet::Options< T >{})
Converts a std::array to torch::Tensor.
Definition container.hpp:79
auto kronproduct(T0 &&t0, T1 &&t1)
Computes the directional Kronecker-product between two tensors along the given dimension.
Definition linalg.hpp:60
TensorArray< 1 > TensorArray1
Definition tensorarray.hpp:29
auto to_tensorAccessor(const torch::Tensor &tensor)
Converts a torch::Tensor object to a torch::TensorAccessor object.
Definition tensorarray.hpp:95
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:239
constexpr std::array< T, N - M > remove_from_back(std::array< T, N > array)
Derives a std::array object from a given std::array object dropping the last M entries.
Definition container.hpp:510
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:48
auto dotproduct(T0 &&t0, T1 &&t1)
Computes the directional dot-product between two tensors with summation along the given dimension.
Definition linalg.hpp:36
auto to_ArrayRef(const std::array< T, N > &array)
Converts a std::array<int64_t, N> to an at::IntArrayRef object.
Definition container.hpp:241
Forward declaration of BlockTensor.
Definition blocktensor.hpp:47
Definition core.hpp:73
std::shared_ptr< iganet::BSplinePatch< real_t, GeoDim, ParDim > > createUniformBSpline(const std::array< iganet::short_t, ParDim > &degrees, const std::array< int64_t, ParDim > &ncoeffs, enum iganet::init init=iganet::init::greville, iganet::Options< real_t > options=iganet::Options< real_t >{})
Create tensor-product uniform B-spline.
Definition bspline.hpp:7729
std::shared_ptr< iganet::BSplinePatch< real_t, GeoDim, ParDim > > createNonUniformBSpline(const std::array< iganet::short_t, ParDim > &degrees, const std::array< int64_t, ParDim > &ncoeffs, enum iganet::init init=iganet::init::greville, iganet::Options< real_t > options=iganet::Options< real_t >{})
Create tensor-product non-uniform B-spline.
Definition bspline.hpp:7823
deriv
Enumerator for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:76
constexpr auto operator+(deriv lhs, deriv rhs)
Adds two enumerators for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:93
bool is_verbose(std::ostream &os)
Tests whether verbose output is enabled on a stream.
Definition core.hpp:871
std::ostream & operator<<(std::ostream &os, const MemoryDebugger< id > &obj)
Prints a memory debugger object.
Definition memory.hpp:145
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:106
init
Enumerator for specifying the initialization of B-spline coefficients.
Definition bspline.hpp:58
torch::serialize::InputArchive & operator>>(torch::serialize::InputArchive &archive, UniformBSplineCore< real_t, GeoDim, Degrees... > &obj)
Deserializes a B-spline object.
Definition bspline.hpp:3274
@ none
Definition boundary.hpp:38
short int short_t
Signed short integer type used by IgANet's compact enumerations.
Definition core.hpp:76
STL namespace.
Options.
Serialization utility functions.
Serialization prototype.
Definition serialize.hpp:29
Computes the power of integer E to the N at compile time.
Definition integer_pow.hpp:21
Reverse index sequence.
Definition index_sequence.hpp:36
TensorArray utility functions.
#define TENSORARRAY_FORALL(obj, func,...)
Definition tensorarray.hpp:220
VSlice utility functions.