IgANet
IgANets - Isogeometric Analysis Networks
Loading...
Searching...
No Matches
bspline.hpp
Go to the documentation of this file.
1
15#pragma once
16
17#include <algorithm>
18#include <exception>
19#include <filesystem>
20#include <functional>
21#include <regex>
22
23#include <core.hpp>
24#include <options.hpp>
25#include <patch.hpp>
26
27#include <utils/blocktensor.hpp>
28#include <utils/container.hpp>
29#include <utils/fqn.hpp>
31#include <utils/integer_pow.hpp>
32#include <utils/linalg.hpp>
33#include <utils/serialize.hpp>
34#include <utils/tensorarray.hpp>
35#include <utils/vslice.hpp>
36
41#define GENERATE_EXPR_SEQ (curl)(div)(grad)(hess)(jac)(lapl)
42
47#define GENERATE_IEXPR_SEQ (icurl)(idiv)(igrad)(ihess)(ijac)(ilapl)
48
49namespace iganet {
50using namespace literals;
51using utils::operator+;
52
53// clang-format off
55enum class init : short_t {
56 none = 0,
57 zeros = 1,
58 ones = 2,
59 linear =
60 3,
61 random = 4,
62 greville = 5,
63 linspace = 6
65};
66// clang-format on
67
74enum class deriv : short_t {
75 func = 0,
77 dx = 1,
78 dy = 10,
79 dz = 100,
80 dt = 1000,
81};
82
91inline constexpr auto operator+(deriv lhs, deriv rhs) {
92 return deriv(static_cast<short_t>(lhs) + static_cast<short_t>(rhs));
93}
94
103inline constexpr auto operator^(deriv lhs, short_t rhs) {
104 return deriv(static_cast<short_t>(lhs) * static_cast<short_t>(rhs));
105}
106
108 class SplineCore_ {};
109
112
115
117 template<typename T>
118 concept SplineCoreType = std::is_base_of_v<SplineCore_, T>;
119
121 template<typename T>
122 concept UniformSplineCoreType = std::is_base_of_v<UniformSplineCore_, T>;
123
125 template<typename T>
126 concept NonUniformSplineCoreType = std::is_base_of_v<NonUniformSplineCore_, T>;
127
200template <typename real_t, short_t GeoDim, short_t... Degrees>
202 : public UniformSplineCore_,
203 public utils::Serializable,
204 public BSplinePatch<real_t, GeoDim, sizeof...(Degrees)> {
206 template <typename BSplineCore> requires SplineCoreType<BSplineCore> friend class BSplineCommon;
207
208protected:
211 static constexpr const short_t parDim_ = sizeof...(Degrees);
212
215 static constexpr const short_t geoDim_ = GeoDim;
216
219 static constexpr const std::array<short_t, parDim_> degrees_ = {Degrees...};
220
223 std::array<int64_t, parDim_> nknots_;
224
227 std::array<int64_t, parDim_> ncoeffs_;
228
232 std::array<int64_t, parDim_> ncoeffs_reverse_;
233
237
242
245
246public:
248 using value_type = real_t;
249
255 template <template <typename, short_t, short_t...> class BSpline,
256 std::make_signed<short_t>::type degree_elevate = 0>
257 using derived_type = BSpline<real_t, GeoDim, (Degrees + degree_elevate)...>;
258
261 template <std::make_signed<short_t>::type degree_elevate = 0>
263
267 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
268 using derived_self_type = UniformBSplineCore<other_t, GeoDim_, Degrees_...>;
269
272 template <typename other_t>
274 UniformBSplineCore<other_t, GeoDim, Degrees...>;
275
277 inline torch::Device device() const noexcept override {
278 return options_.device();
279 }
280
282 inline int32_t device_index() const noexcept override {
283 return options_.device_index();
284 }
285
287 inline torch::Dtype dtype() const noexcept override {
288 return options_.dtype();
289 }
290
292 inline torch::Layout layout() const noexcept override {
293 return options_.layout();
294 }
295
297 inline bool requires_grad() const noexcept override {
298 return options_.requires_grad();
299 }
300
302 inline bool pinned_memory() const noexcept override {
303 return options_.pinned_memory();
304 }
305
307 inline bool is_sparse() const noexcept override {
308 return options_.is_sparse();
309 }
310
312 inline static constexpr bool is_uniform() noexcept { return true; }
313
315 inline static constexpr bool is_nonuniform() noexcept { return false; }
316
324 inline UniformBSplineCore &
325 set_requires_grad(bool requires_grad) noexcept override {
326 if (options_.requires_grad() == requires_grad)
327 return *this;
328
329 for (short_t i = 0; i < parDim_; ++i)
331
332 for (short_t i = 0; i < geoDim_; ++i)
334
335 Options<real_t> tmp(options_.requires_grad(requires_grad));
336 options_.~Options<real_t>();
337 new (&options_) Options<real_t>(tmp);
338
339 return *this;
340 }
341
343 inline const Options<real_t> &options() const noexcept { return options_; }
344
354
362 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
363 enum init init = init::greville,
366 // Reverse ncoeffs
367 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
368
369 // Initialize knot vectors
370 init_knots();
371
372 // Initialize coefficients
374 }
375
389 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
391 bool clone = false,
394 // Reverse ncoeffs
395 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
396
397 // Initialize knot vectors
398 init_knots();
399
400 // Check compatibility
401 for (short_t i = 0; i < geoDim_; ++i)
402 if (coeffs[i].numel() != ncumcoeffs())
403 throw std::runtime_error("Invalid number of coefficients");
404
405 // Copy/clone coefficients
406 if (clone)
407 for (short_t i = 0; i < geoDim_; ++i)
408 coeffs_[i] = coeffs[i]
409 .clone()
410 .to(options.requires_grad(false))
411 .requires_grad_(options.requires_grad());
412 else
413 for (short_t i = 0; i < geoDim_; ++i)
414 coeffs_[i] = coeffs[i];
415 }
416
427 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
431 coeffs_(std::move(coeffs)) {
432 // Reverse ncoeffs
433 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
434
435 // Initialize knot vectors
436 init_knots();
437 }
438
444 template <typename other_t>
448 : options_(options), ncoeffs_(other.ncoeffs()),
450 // Reverse ncoeffs
451 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
452
453 // Clone coefficients
454 for (short_t i = 0; i < geoDim_; ++i)
455 coeffs_[i] = other.coeffs(i)
456 .clone()
457 .to(options.requires_grad(false))
458 .requires_grad_(options.requires_grad());
459
460 // Clone knot vectors
461 for (short_t i = 0; i < parDim_; ++i)
462 knots_[i] = other.knots(i)
463 .clone()
464 .to(options.requires_grad(false))
465 .requires_grad_(options.requires_grad());
466 }
467
471 inline static constexpr short_t parDim() noexcept { return parDim_; }
472
476 inline static constexpr short_t geoDim() noexcept { return geoDim_; }
477
481 inline static constexpr const std::array<short_t, parDim_> &
482 degrees() noexcept {
483 return degrees_;
484 }
485
492 inline static constexpr const short_t &degree(short_t i) noexcept {
493 assert(i >= 0 && i < parDim_);
494 return degrees_[i];
495 }
496
501 inline const utils::TensorArray<parDim_> &knots() const noexcept {
502 return knots_;
503 }
504
511 inline const torch::Tensor &knots(short_t i) const noexcept {
512 assert(i >= 0 && i < parDim_);
513 return knots_[i];
514 }
515
520 inline utils::TensorArray<parDim_> &knots() noexcept { return knots_; }
521
528 inline torch::Tensor &knots(short_t i) noexcept {
529 assert(i >= 0 && i < parDim_);
530 return knots_[i];
531 }
532
537 inline const std::array<int64_t, parDim_> &nknots() const noexcept {
538 return nknots_;
539 }
540
547 inline int64_t nknots(short_t i) const noexcept {
548 assert(i >= 0 && i < parDim_);
549 return nknots_[i];
550 }
551
556 inline const utils::TensorArray<geoDim_> &coeffs() const noexcept {
557 return coeffs_;
558 }
559
566 inline const torch::Tensor &coeffs(short_t i) const noexcept {
567 assert(i >= 0 && i < geoDim_);
568 return coeffs_[i];
569 }
570
575 inline utils::TensorArray<geoDim_> &coeffs() noexcept { return coeffs_; }
576
583 inline torch::Tensor &coeffs(short_t i) noexcept {
584 assert(i >= 0 && i < geoDim_);
585 return coeffs_[i];
586 }
587
591 inline utils::TensorArray<geoDim_> coeffs_view() const noexcept {
593 for (short_t i = 0; i < geoDim_; ++i)
594 coeffs[i] = coeffs_view(i);
595 return coeffs;
596 }
597
604 inline const auto coeffs_view(short_t i) const noexcept {
605 assert(i >= 0 && i < geoDim_);
606 if constexpr (parDim_ > 1)
607 if (coeffs_[i].dim() > 1)
608 return coeffs_[i].view(utils::to_ArrayRef(ncoeffs_reverse_) + (-1_i64));
609 else
611 else
612 return coeffs_[i];
613 }
614
618 inline int64_t ncumcoeffs() const noexcept {
619 int64_t s = 1;
620
621 for (short_t i = 0; i < parDim_; ++i)
622 s *= ncoeffs(i);
623
624 return s;
625 }
626
631 inline const std::array<int64_t, parDim_> &ncoeffs() const noexcept {
632 return ncoeffs_;
633 }
634
641 inline int64_t ncoeffs(short_t i) const noexcept {
642 assert(i >= 0 && i < parDim_);
643 return ncoeffs_[i];
644 }
645
646private:
650 template <std::size_t... Is>
651 inline torch::Tensor as_tensor_(std::index_sequence<Is...>) const noexcept {
652 return torch::cat({coeffs_[Is]...});
653 }
654
655public:
659 inline torch::Tensor as_tensor() const noexcept override {
660 return as_tensor_(std::make_index_sequence<geoDim_>{});
661 }
662
663private:
667 template <std::size_t... Is>
668 inline UniformBSplineCore &
669 from_tensor_(std::index_sequence<Is...>,
670 const torch::Tensor &tensor) noexcept {
671 ((coeffs_[Is] = tensor.index(
672 {torch::indexing::Slice(Is * ncumcoeffs(), (Is + 1) * ncumcoeffs()),
673 "..."})),
674 ...);
675 return *this;
676 }
677
678public:
684 inline UniformBSplineCore &
685 from_tensor(const torch::Tensor &tensor) noexcept override {
686 return from_tensor_(std::make_index_sequence<geoDim_>{}, tensor);
687 }
688
691 //
693 inline int64_t as_tensor_size() const noexcept override {
694 return geoDim_ * ncumcoeffs();
695 }
696
711 inline auto greville(bool interior = false) const {
712 if constexpr (parDim_ == 0) {
713 return torch::zeros(1, options_);
714 } else {
716
717 // Fill coefficients with the tensor-product of Greville
718 // abscissae values per univariate dimension
719 for (short_t i = 0; i < parDim_; ++i) {
720 coeffs[i] = torch::ones(1, options_);
721
722 for (short_t j = 0; j < parDim_; ++j) {
723 if (i == j) {
724
725 int64_t offset = interior ? 1 : 0;
726 int64_t count = ncoeffs_[j] - (interior ? 2 : 0);
727
728 // idx_base: (count, 1)
729 auto idx_base = torch::arange(count, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(1);
730
731 // offsets: (1, degree)
732 auto offsets = torch::arange(1, degrees_[j] + 1, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(0);
733
734 // indices: (count, degree)
735 auto indices = idx_base + offset + offsets;
736
737 // Gather relevant knot values: shape (count, degree)
738 auto gathered = knots_[j].index_select(0, indices.flatten()).view({count, degrees_[j]});
739
740 // Compute mean along degree dimension (dim=1)
741 auto greville_ = gathered.mean(1);
742
743 coeffs[i] = torch::kron(greville_, coeffs[i]);
744 } else
745 coeffs[i] =
746 torch::kron(torch::ones(ncoeffs_[j] - (interior ? 2 : 0), options_), coeffs[i]);
747 }
748
749 // Enable gradient calculation for non-leaf tensor
750 if (options_.requires_grad())
751 coeffs[i].retain_grad();
752 }
753
754 return coeffs;
755 }
756 }
757
780 eval_from_precomputed(const torch::Tensor &basfunc,
781 const torch::Tensor &coeff_indices, int64_t numeval,
782 torch::IntArrayRef sizes) const override {
783
785
786 for (short_t i = 0; i < geoDim_; ++i)
787 result.set(
789 basfunc,
790 coeffs(i).index_select(0, coeff_indices).view({-1, numeval}))
791 .view(sizes));
792 return result;
793 }
794
797 const torch::Tensor &coeff_indices, int64_t numeval,
798 torch::IntArrayRef sizes) const override {
799
801
802 if constexpr (parDim_ == 0) {
803 for (short_t i = 0; i < geoDim_; ++i)
804 result.set(i, coeffs_[i]);
805 }
806
807 else {
808 // Lambda expression to evaluate the spline function
809 std::function<torch::Tensor(short_t, short_t)> eval_;
810
811 eval_ = [&, this](short_t i, short_t dim) {
812 if (dim == 0) {
813 return torch::matmul(coeffs(i)
814 .index_select(0, coeff_indices)
815 .view({numeval, -1, degrees_[0] + 1}),
816 basfunc[0].view({numeval, -1, 1}));
817 } else {
818 return torch::matmul(
819 (eval_(i, dim - 1)).view({numeval, -1, degrees_[dim] + 1}),
820 basfunc[dim].view({numeval, -1, 1}));
821 }
822 };
823
824 for (short_t i = 0; i < geoDim_; ++i)
825 result.set(i, (eval_(i, parDim_ - 1)).view(sizes));
826 }
827 return result;
828 }
830
881 template <deriv deriv = deriv::func, bool memory_optimized = false>
882 inline auto eval(const torch::Tensor &xi) const {
883 if constexpr (parDim_ == 1)
884 return eval<deriv, memory_optimized>(utils::TensorArray1({xi}));
885 else
886 throw std::runtime_error("Invalid parametric dimension");
887 }
888
889 template <deriv deriv = deriv::func, bool memory_optimized = false>
890 inline auto eval(const utils::TensorArray<parDim_> &xi) const {
891 return eval<deriv, memory_optimized>(xi, find_knot_indices(xi));
892 }
894
910 template <deriv deriv = deriv::func, bool memory_optimized = false>
911 inline auto eval(const utils::TensorArray<parDim_> &xi,
912 const utils::TensorArray<parDim_> &knot_indices) const {
913 return eval<deriv, memory_optimized>(
914 xi, knot_indices, find_coeff_indices<memory_optimized>(knot_indices));
915 }
916
935 template <deriv deriv = deriv::func, bool memory_optimized = false>
936 inline auto eval(const utils::TensorArray<parDim_> &xi,
937 const utils::TensorArray<parDim_> &knot_indices,
938 const torch::Tensor &coeff_indices) const {
939
941
942 if constexpr (parDim_ == 0) {
943 for (short_t i = 0; i < geoDim_; ++i)
944 if constexpr (deriv == deriv::func)
945 result.set(i, coeffs_[i]);
946 else
947 result.set(i, torch::zeros_like(coeffs_[i]));
948 return result;
949 } // parDim == 0
950
951 else {
952
953 // Check compatibility of arguments
954 for (short_t i = 0; i < parDim_; ++i)
955 assert(xi[i].sizes() == knot_indices[i].sizes());
956 for (short_t i = 1; i < parDim_; ++i)
957 assert(xi[0].sizes() == xi[i].sizes());
958
959 if constexpr (memory_optimized) {
960 // memory-optimized
961
962 if (coeffs(0).dim() > 1)
963 throw std::runtime_error(
964 "Memory-optimized evaluation requires single-valued coefficient");
965
966 else {
967 auto basfunc =
968 eval_basfunc<deriv, memory_optimized>(xi, knot_indices);
969
970 // Lambda expression to evaluate the spline function
971 std::function<torch::Tensor(short_t, short_t)> eval_;
972
973 eval_ = [&, this](short_t i, short_t dim) {
974 if (dim == 0) {
975 return torch::matmul(
976 coeffs(i)
977 .index_select(0, coeff_indices)
978 .view({xi[0].numel(), -1, degrees_[0] + 1}),
979 basfunc[0].view({xi[0].numel(), -1, 1}));
980 } else {
981 return torch::matmul(
982 (eval_(i, dim - 1))
983 .view({xi[0].numel(), -1, degrees_[dim] + 1}),
984 basfunc[dim].view({xi[0].numel(), -1, 1}));
985 }
986 };
987
988 for (short_t i = 0; i < geoDim_; ++i)
989 result.set(i, (eval_(i, parDim_ - 1)).view(xi[0].sizes()));
990
991 return result;
992 } // coeffs(0).dim() > 1
993 }
994
995 else {
996 // not memory-optimized
997
998 auto basfunc = eval_basfunc<deriv, memory_optimized>(xi, knot_indices);
999
1000 if (coeffs(0).dim() > 1) {
1001 // coeffs has extra dimension
1002 auto sizes = xi[0].sizes() + (-1_i64);
1003 for (short_t i = 0; i < geoDim_; ++i)
1004 result.set(i, utils::dotproduct(basfunc.unsqueeze(-1),
1005 coeffs(i)
1006 .index_select(0, coeff_indices)
1007 .view({-1, xi[0].numel(),
1008 coeffs(i).size(-1)}))
1009 .view(sizes));
1010 } else {
1011 // coeffs does not have extra dimension
1012 for (short_t i = 0; i < geoDim_; ++i)
1013 result.set(i, utils::dotproduct(basfunc,
1014 coeffs(i)
1015 .index_select(0, coeff_indices)
1016 .view({-1, xi[0].numel()}))
1017 .view(xi[0].sizes()));
1018 }
1019 return result;
1020 }
1021 }
1022 }
1023
1042 inline auto find_knot_indices(const torch::Tensor &xi) const noexcept {
1043 if constexpr (parDim_ == 0)
1044 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1045 else
1047 }
1048
1051 if constexpr (parDim_ == 0)
1053 else {
1055
1056 for (short_t i = 0; i < parDim_; ++i)
1057 result[i] =
1058 torch::min(
1059 torch::full_like(xi[i], ncoeffs_[i] - 1, options_),
1060 torch::floor(xi[i] * (ncoeffs_[i] - degrees_[i]) + degrees_[i]))
1061 .to(torch::kInt64);
1062
1063 return result;
1064 }
1065 }
1067
1076 template <bool memory_optimized = false>
1077 inline auto find_coeff_indices(const torch::Tensor &indices) const {
1078 if constexpr (parDim_ == 0)
1079 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1080 else
1081 return find_coeff_indices<memory_optimized>(
1082 utils::TensorArray1({indices}));
1083 }
1084
1085 template <bool memory_optimized = false>
1086 inline auto
1088 using utils::operator-;
1089
1090 if constexpr (parDim_ == 0)
1091 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1092 else if constexpr (parDim_ == 1)
1093 return utils::VSlice<memory_optimized>(indices[0].flatten(), -degrees_[0],
1094 1);
1095 else {
1096 return utils::VSlice<memory_optimized>(
1097 TENSORARRAY_FORALL(indices, flatten),
1098 utils::make_array<int64_t>(-degrees_),
1099 utils::make_array<int64_t, parDim_>(1),
1101 }
1102 }
1104
1114 template <deriv deriv = deriv::func, bool memory_optimized = false>
1115 inline auto eval_basfunc(const torch::Tensor &xi) const {
1116 if constexpr (parDim_ == 0) {
1117 if constexpr (deriv == deriv::func)
1118 return torch::ones_like(coeffs_[0]);
1119 else
1120 return torch::zeros_like(coeffs_[0]);
1121 } else
1122 return eval_basfunc<deriv, memory_optimized>(utils::TensorArray1({xi}));
1123 }
1124
1125 template <deriv deriv = deriv::func, bool memory_optimized = false>
1126 inline auto eval_basfunc(const utils::TensorArray<parDim_> &xi) const {
1127 if constexpr (parDim_ == 0) {
1128 if constexpr (deriv == deriv::func)
1129 return torch::ones_like(coeffs_[0]);
1130 else
1131 return torch::zeros_like(coeffs_[0]);
1132 } else
1133 return eval_basfunc<deriv, memory_optimized>(xi, find_knot_indices(xi));
1134 }
1136
1149 template <deriv deriv = deriv::func, bool memory_optimized = false>
1150 inline auto eval_basfunc(const torch::Tensor &xi,
1151 const torch::Tensor &knot_indices) const {
1152 if constexpr (parDim_ == 0) {
1153 if constexpr (deriv == deriv::func)
1154 return torch::ones_like(coeffs_[0]);
1155 else
1156 return torch::zeros_like(coeffs_[0]);
1157 } else
1158 return eval_basfunc<deriv, memory_optimized>(
1159 utils::TensorArray1({xi}), utils::TensorArray1({knot_indices}));
1160 }
1161
1162 template <deriv deriv = deriv::func, bool memory_optimized = false>
1163 inline auto
1165 const utils::TensorArray<parDim_> &knot_indices) const {
1166
1167 if constexpr (parDim_ == 0) {
1168 if constexpr (deriv == deriv::func)
1169 return torch::ones_like(coeffs_[0]);
1170 else
1171 return torch::zeros_like(coeffs_[0]);
1172 }
1173
1174 else {
1175 // Check compatibility of arguments
1176 for (short_t i = 0; i < parDim_; ++i)
1177 assert(xi[i].sizes() == knot_indices[i].sizes());
1178 for (short_t i = 1; i < parDim_; ++i)
1179 assert(xi[0].sizes() == xi[i].sizes());
1180
1181 if constexpr (memory_optimized) {
1182
1183 // Lambda expression to evaluate the vector of basis functions
1184 auto basfunc_ = [&,
1185 this]<std::size_t... Is>(std::index_sequence<Is...>) {
1188 (short_t)deriv /
1191 degrees_[Is], Is,
1193 xi[Is].flatten(), knot_indices[Is].flatten())
1194 .transpose(0, 1))...};
1195 };
1196
1197 return basfunc_(std::make_index_sequence<parDim_>{});
1198
1199 }
1200
1201 else /* not memory optimize */ {
1202
1203 if constexpr (parDim_ == 1) {
1204 return eval_prefactor<degrees_[0], (short_t)deriv % 10>() *
1205 eval_basfunc_univariate<degrees_[0], 0, (short_t)deriv % 10>(
1206 xi[0].flatten(), knot_indices[0].flatten());
1207
1208 } else {
1209
1210 // Lambda expression to evaluate the cumulated basis function
1211 auto basfunc_ = [&, this]<std::size_t... Is>(
1212 std::index_sequence<Is...>) {
1213 return (1 * ... *
1215 (short_t)deriv /
1217 10>())) *
1220 degrees_[Is], Is,
1222 10>(xi[Is].flatten(),
1223 knot_indices[Is].flatten())...);
1224 };
1225
1226 // Note that the kronecker product must be called in reverse order
1228 }
1229 }
1230 }
1231 }
1233
1235 inline UniformBSplineCore &
1236 transform(const std::function<
1237 std::array<real_t, geoDim_>(const std::array<real_t, parDim_> &)>
1238 transformation) {
1239 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1240
1241 // 0D
1242 if constexpr (parDim_ == 0) {
1243 auto c = transformation(std::array<real_t, parDim_>{});
1244 for (short_t d = 0; d < geoDim_; ++d)
1245 coeffs_[d].detach()[0] = c[d];
1246 }
1247
1248 // 1D
1249 else if constexpr (parDim_ == 1) {
1250#pragma omp parallel for
1251 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1252 auto c = transformation(
1253 std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1254 for (short_t d = 0; d < geoDim_; ++d)
1255 coeffs_[d].detach()[i] = c[d];
1256 }
1257 }
1258
1259 // 2D
1260 else if constexpr (parDim_ == 2) {
1261#pragma omp parallel for collapse(2)
1262 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1263 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1264 auto c = transformation(std::array<real_t, parDim_>{
1265 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1266 for (short_t d = 0; d < geoDim_; ++d)
1267 coeffs_[d].detach()[j * ncoeffs_[0] + i] = c[d];
1268 }
1269 }
1270 }
1271
1272 // 3D
1273 else if constexpr (parDim_ == 3) {
1274#pragma omp parallel for collapse(3)
1275 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1276 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1277 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1278 auto c = transformation(std::array<real_t, parDim_>{
1279 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1280 k / real_t(ncoeffs_[2] - 1)});
1281 for (short_t d = 0; d < geoDim_; ++d)
1282 coeffs_[d].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1283 j * ncoeffs_[0] + i] = c[d];
1284 }
1285 }
1286 }
1287 }
1288
1289 // 4D
1290 else if constexpr (parDim_ == 4) {
1291#pragma omp parallel for collapse(4)
1292 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1293 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1294 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1295 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1296 auto c = transformation(std::array<real_t, parDim_>{
1297 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1298 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1299 for (short_t d = 0; d < geoDim_; ++d)
1300 coeffs_[d]
1301 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1302 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1303 i] = c[d];
1304 }
1305 }
1306 }
1307 }
1308 } else
1309 throw std::runtime_error("Unsupported parametric dimension");
1310
1311 return *this;
1312 }
1313
1315 template <std::size_t N>
1316 inline UniformBSplineCore &
1317 transform(const std::function<
1318 std::array<real_t, N>(const std::array<real_t, parDim_> &)>
1319 transformation,
1320 std::array<short_t, N> dims) {
1321 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1322
1323 // 0D
1324 if constexpr (parDim_ == 0) {
1325 auto c = transformation(std::array<real_t, parDim_>{});
1326 for (std::size_t d = 0; d < N; ++d)
1327 coeffs_[dims[d]].detach()[0] = c[d];
1328 }
1329
1330 // 1D
1331 else if constexpr (parDim_ == 1) {
1332#pragma omp parallel for
1333 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1334 auto c = transformation(
1335 std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1336 for (std::size_t d = 0; d < N; ++d)
1337 coeffs_[dims[d]].detach()[i] = c[d];
1338 }
1339 }
1340
1341 // 2D
1342 else if constexpr (parDim_ == 2) {
1343#pragma omp parallel for collapse(2)
1344 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1345 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1346 auto c = transformation(std::array<real_t, parDim_>{
1347 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1348 for (std::size_t d = 0; d < N; ++d)
1349 coeffs_[dims[d]].detach()[j * ncoeffs_[0] + i] = c[d];
1350 }
1351 }
1352 }
1353
1354 // 3D
1355 else if constexpr (parDim_ == 3) {
1356#pragma omp parallel for collapse(3)
1357 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1358 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1359 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1360 auto c = transformation(std::array<real_t, parDim_>{
1361 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1362 k / real_t(ncoeffs_[2] - 1)});
1363 for (std::size_t d = 0; d < N; ++d)
1364 coeffs_[dims[d]].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1365 j * ncoeffs_[0] + i] = c[d];
1366 }
1367 }
1368 }
1369 }
1370
1371 // 4D
1372 else if constexpr (parDim_ == 4) {
1373#pragma omp parallel for collapse(4)
1374 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1375 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1376 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1377 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1378 auto c = transformation(std::array<real_t, parDim_>{
1379 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1380 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1381 for (std::size_t d = 0; d < N; ++d)
1382 coeffs_[dims[d]]
1383 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1384 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1385 i] = c[d];
1386 }
1387 }
1388 }
1389 }
1390 } else
1391 throw std::runtime_error("Unsupported parametric dimension");
1392
1393 return *this;
1394 }
1395
1397 inline nlohmann::json to_json() const override {
1398 nlohmann::json json;
1399 json["degrees"] = degrees_;
1400 json["geoDim"] = geoDim_;
1401 json["parDim"] = parDim_;
1402 json["ncoeffs"] = ncoeffs_;
1403 json["nknots"] = nknots_;
1404 json["knots"] = knots_to_json();
1405 json["coeffs"] = coeffs_to_json();
1406
1407 return json;
1408 }
1409
1411 inline nlohmann::json knots_to_json() const {
1412 return ::iganet::utils::to_json<real_t, 1>(knots_);
1413 }
1414
1416 inline nlohmann::json coeffs_to_json() const {
1417 auto coeffs_json = nlohmann::json::array();
1418 for (short_t g = 0; g < geoDim_; ++g) {
1419 auto [coeffs_cpu, coeffs_accessor] =
1420 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
1421
1422 auto json = nlohmann::json::array();
1423
1424 if constexpr (parDim_ == 0) {
1425 json.push_back(coeffs_accessor[0]);
1426 }
1427
1428 else {
1429 for (int64_t i = 0; i < ncumcoeffs(); ++i)
1430 json.push_back(coeffs_accessor[i]);
1431 }
1432
1433 coeffs_json.push_back(json);
1434 }
1435 return coeffs_json;
1436 }
1437
1439 inline UniformBSplineCore &from_json(const nlohmann::json &json) {
1440
1441 if (json["geoDim"].get<short_t>() != geoDim_)
1442 throw std::runtime_error(
1443 "JSON object provides incompatible geometric dimensions");
1444
1445 if (json["parDim"].get<short_t>() != parDim_)
1446 throw std::runtime_error(
1447 "JSON object provides incompatible parametric dimensions");
1448
1449 if (json["degrees"].get<std::array<short_t, parDim_>>() != degrees_)
1450 throw std::runtime_error("JSON object provides incompatible degrees");
1451
1452 nknots_ = json["nknots"].get<std::array<int64_t, parDim_>>();
1453 ncoeffs_ = json["ncoeffs"].get<std::array<int64_t, parDim_>>();
1454
1455 // Reverse ncoeffs
1457 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1458
1459 auto kv = json["knots"].get<std::array<std::vector<real_t>, parDim_>>();
1460
1461 for (short_t i = 0; i < parDim_; ++i)
1462 knots_[i] = utils::to_tensor(kv[i], options_);
1463
1464 auto c = json["coeffs"].get<std::array<std::vector<real_t>, geoDim_>>();
1465
1466 for (short_t i = 0; i < geoDim_; ++i)
1467 coeffs_[i] = utils::to_tensor(c[i], options_);
1468
1469 return *this;
1470 }
1471
1473 inline pugi::xml_document to_xml(int id = 0, std::string label = "",
1474 int index = -1) const {
1475 pugi::xml_document doc;
1476 pugi::xml_node root = doc.append_child("xml");
1477 to_xml(root, id, label, index);
1478
1479 return doc;
1480 }
1481
1483 inline pugi::xml_node &to_xml(pugi::xml_node &root, int id = 0,
1484 std::string label = "", int index = -1) const {
1485 // add Geometry node
1486 pugi::xml_node geo = root.append_child("Geometry");
1487
1488 // 0D parametric dimension
1489 if constexpr (parDim_ == 0) {
1490 geo.append_attribute("type") = "Point";
1491
1492 if (id >= 0)
1493 geo.append_attribute("id") = id;
1494
1495 if (index >= 0)
1496 geo.append_attribute("index") = index;
1497
1498 if (!label.empty())
1499 geo.append_attribute("label") = label.c_str();
1500 }
1501
1502 // 1D parametric dimension
1503 else if constexpr (parDim_ == 1) {
1504 geo.append_attribute("type") = "BSpline";
1505
1506 if (id >= 0)
1507 geo.append_attribute("id") = id;
1508
1509 if (index >= 0)
1510 geo.append_attribute("index") = index;
1511
1512 if (!label.empty())
1513 geo.append_attribute("label") = label.c_str();
1514
1515 // add Basis node
1516 pugi::xml_node basis = geo.append_child("Basis");
1517 basis.append_attribute("type") = "BSplineBasis";
1518
1519 // add KnotVector node
1520 pugi::xml_node knots = basis.append_child("KnotVector");
1521 knots.append_attribute("degree") = degrees_[0];
1522
1523 std::stringstream ss;
1524 auto [knots_cpu, knots_accessor] =
1525 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
1526 for (int64_t i = 0; i < nknots_[0]; ++i)
1527 ss << std::to_string(knots_accessor[i])
1528 << (i < nknots_[0] - 1 ? " " : "");
1529 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1530 }
1531
1532 // >1D parametric dimension
1533 else {
1534 geo.append_attribute("type") =
1535 std::string("TensorBSpline").append(std::to_string(parDim_)).c_str();
1536
1537 if (id >= 0)
1538 geo.append_attribute("id") = id;
1539
1540 if (index >= 0)
1541 geo.append_attribute("index") = index;
1542
1543 if (!label.empty())
1544 geo.append_attribute("label") = label.c_str();
1545
1546 // add Basis node
1547 pugi::xml_node bases = geo.append_child("Basis");
1548 bases.append_attribute("type") = std::string("TensorBSplineBasis")
1549 .append(std::to_string(parDim_))
1550 .c_str();
1551
1552 for (short_t index = 0; index < parDim_; ++index) {
1553 pugi::xml_node basis = bases.append_child("Basis");
1554 basis.append_attribute("type") = "BSplineBasis";
1555 basis.append_attribute("index") = index;
1556
1557 // add KnotVector node
1558 pugi::xml_node knots = basis.append_child("KnotVector");
1559 knots.append_attribute("degree") = degrees_[index];
1560
1561 std::stringstream ss;
1562 auto [knots_cpu, knots_accessor] =
1563 utils::to_tensorAccessor<real_t, 1>(knots_[index], torch::kCPU);
1564 for (int64_t i = 0; i < nknots_[index]; ++i)
1565 ss << std::to_string(knots_accessor[i])
1566 << (i < nknots_[index] - 1 ? " " : "");
1567 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1568 }
1569
1570 } // parametric dimension
1571
1572 // add Coefs node
1573 pugi::xml_node coefs = geo.append_child("coefs");
1574 coefs.append_attribute("geoDim") = geoDim_;
1575
1576 auto [coeffs_cpu, coeffs_accessors] =
1577 utils::to_tensorAccessor<real_t, 1>(coeffs_, torch::kCPU);
1578 std::stringstream ss;
1579
1580 if constexpr (parDim_ == 0) {
1581 for (short_t g = 0; g < geoDim_; ++g)
1582 ss << std::to_string(coeffs_accessors[g][0]) << " ";
1583
1584 } else {
1585 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
1586 for (short_t g = 0; g < geoDim_; ++g)
1587 ss << std::to_string(coeffs_accessors[g][i]) << " ";
1588 }
1589
1590 coefs.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1591
1592 return root;
1593 }
1594
1596 inline UniformBSplineCore &from_xml(const pugi::xml_document &doc, int id = 0,
1597 std::string label = "", int index = -1) {
1598 return from_xml(doc.child("xml"), id, label, index);
1599 }
1600
1602 inline UniformBSplineCore &from_xml(const pugi::xml_node &root, int id = 0,
1603 std::string label = "", int index = -1) {
1604
1605 std::array<bool, std::max(parDim_, short_t{1})> nknots_found{false},
1606 ncoeffs_found{false};
1607
1608 // Loop through all geometry nodes
1609 for (pugi::xml_node geo : root.children("Geometry")) {
1610
1611 // 0D parametric dimension
1612 if constexpr (parDim_ == 0) {
1613
1614 // Check for "Point" with given id, index, label
1615 if (geo.attribute("type").value() == std::string("Point") &&
1616 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1617 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1618 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1619
1620 nknots_found[0] = true;
1621 ncoeffs_found[0] = true;
1622 } // "Point"
1623 else
1624 continue; // try next "Geometry"
1625 }
1626
1627 // 1D parametric dimension
1628 else if constexpr (parDim_ == 1) {
1629
1630 // Check for "BSpline" with given id, index, label
1631 if (geo.attribute("type").value() == std::string("BSpline") &&
1632 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1633 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1634 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1635
1636 // Check for "BSplineBasis"
1637 if (pugi::xml_node basis = geo.child("Basis");
1638 basis.attribute("type").value() == std::string("BSplineBasis")) {
1639
1640 // Check for "KnotVector"
1641 if (pugi::xml_node knots = basis.child("KnotVector");
1642 knots.attribute("degree").as_int() == degrees_[0]) {
1643
1644 std::vector<real_t> kv;
1645 std::string values = std::regex_replace(
1646 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1647 for (auto value = strtok(&values[0], " "); value != NULL;
1648 value = strtok(NULL, " "))
1649 kv.push_back(static_cast<real_t>(std::stod(value)));
1650
1652 nknots_[0] = kv.size();
1653 ncoeffs_[0] = nknots_[0] - degrees_[0] - 1;
1654
1655 nknots_found[0] = true;
1656 ncoeffs_found[0] = true;
1657
1658 } // "KnotVector"
1659
1660 } // "BSplineBasis"
1661
1662 } // "Bspline"
1663 else
1664 continue; // try next "Geometry"
1665 }
1666
1667 // >1D parametric dimension
1668 else {
1669
1670 // Check for "TensorBSpline<parDim>" with given id, index, label
1671 if (geo.attribute("type").value() ==
1672 std::string("TensorBSpline").append(std::to_string(parDim_)) &&
1673 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1674 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1675 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1676
1677 // Check for "TensorBSplineBasis<parDim>"
1678 if (pugi::xml_node bases = geo.child("Basis");
1679 bases.attribute("type").value() ==
1680 std::string("TensorBSplineBasis")
1681 .append(std::to_string(parDim_))) {
1682
1683 // Loop through all basis nodes
1684 for (pugi::xml_node basis : bases.children("Basis")) {
1685
1686 // Check for "BSplineBasis"
1687 if (basis.attribute("type").value() ==
1688 std::string("BSplineBasis")) {
1689
1690 short_t index = basis.attribute("index").as_int();
1691
1692 // Check for "KnotVector"
1693 if (pugi::xml_node knots = basis.child("KnotVector");
1694 knots.attribute("degree").as_int() == degrees_[index]) {
1695
1696 std::vector<real_t> kv;
1697 std::string values = std::regex_replace(
1698 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1699
1700 for (auto value = strtok(&values[0], " "); value != NULL;
1701 value = strtok(NULL, " "))
1702 kv.push_back(static_cast<real_t>(std::stod(value)));
1703
1704 knots_[index] = utils::to_tensor(kv, options_);
1705 nknots_[index] = kv.size();
1706 ncoeffs_[index] = nknots_[index] - degrees_[index] - 1;
1707
1708 nknots_found[index] = true;
1709 ncoeffs_found[index] = true;
1710
1711 } // "KnotVector"
1712
1713 } // "BSplineBasis"
1714
1715 } // "Basis"
1716
1717 } // "TensorBSplineBasis<parDim>"
1718
1719 } // "TensorBSpline<parDim>"
1720 else
1721 continue; // try next "Geometry"
1722
1723 } // parametric dimension
1724
1725 if (std::any_of(std::begin(nknots_found), std::end(nknots_found),
1726 [](bool i) { return !i; }))
1727 throw std::runtime_error(
1728 "XML object is not compatible with B-spline object");
1729
1730 // Reverse ncoeffs
1732 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1733
1734 // Fill coefficients with zeros
1735 int64_t size = ncumcoeffs();
1736 for (short_t i = 0; i < geoDim_; ++i)
1737 coeffs_[i] = torch::zeros(size, options_.device(torch::kCPU));
1738
1739 // Check for "coefs"
1740 if (pugi::xml_node coefs = geo.child("coefs")) {
1741
1742 std::string values = std::regex_replace(
1743 coefs.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1744 auto coeffs_accessors = utils::to_tensorAccessor<real_t, 1>(coeffs_);
1745
1746 if constexpr (parDim_ == 0) {
1747 auto value = strtok(&values[0], " ");
1748
1749 for (short_t g = 0; g < geoDim_; ++g) {
1750 if (value == NULL)
1751 throw std::runtime_error(
1752 "XML object does not provide enough coefficients");
1753
1754 coeffs_accessors[g][0] = static_cast<real_t>(std::stod(value));
1755 value = strtok(NULL, " ");
1756 }
1757
1758 if (value != NULL)
1759 throw std::runtime_error(
1760 "XML object provides too many coefficients");
1761
1762 } else {
1763 auto value = strtok(&values[0], " ");
1764
1765 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
1766 for (short_t g = 0; g < geoDim_; ++g) {
1767 if (value == NULL)
1768 throw std::runtime_error(
1769 "XML object does not provide enough coefficients");
1770
1771 coeffs_accessors[g][i] = static_cast<real_t>(std::stod(value));
1772 value = strtok(NULL, " ");
1773 }
1774
1775 if (value != NULL)
1776 throw std::runtime_error(
1777 "XML object provides too many coefficients");
1778 }
1779
1780 // Copy coefficients to device (if needed)
1781 for (short_t i = 0; i < geoDim_; ++i)
1782 coeffs_[i] = coeffs_[i].to(options_.device());
1783
1784 if constexpr (parDim_ == 0) {
1785 if (nknots_found[0] && ncoeffs_found[0])
1786 return *this;
1787 } else if (std::all_of(std::begin(nknots_found), std::end(nknots_found),
1788 [](bool i) { return i; }) &&
1789 std::all_of(std::begin(ncoeffs_found),
1790 std::end(ncoeffs_found),
1791 [](bool i) { return i; }))
1792 return *this;
1793
1794 else
1795 throw std::runtime_error(
1796 "XML object is not compatible with B-spline object");
1797
1798 } // Coefs
1799 else
1800 throw std::runtime_error("XML object does not provide coefficients");
1801
1802 } // "Geometry"
1803
1804 throw std::runtime_error("XML object does not provide geometry with given "
1805 "id, index, and/or label");
1806 return *this;
1807 }
1808
1810 inline void load(const std::string &filename,
1811 const std::string &key = "bspline") {
1812 torch::serialize::InputArchive archive;
1813 archive.load_from(filename);
1814 read(archive, key);
1815 }
1816
1818 inline torch::serialize::InputArchive &
1819 read(torch::serialize::InputArchive &archive,
1820 const std::string &key = "bspline") {
1821 torch::Tensor tensor;
1822
1823 archive.read(key + ".parDim", tensor);
1824 if (tensor.item<int64_t>() != parDim_)
1825 throw std::runtime_error("parDim mismatch");
1826
1827 archive.read(key + ".geoDim", tensor);
1828 if (tensor.item<int64_t>() != geoDim_)
1829 throw std::runtime_error("geoDim mismatch");
1830
1831 for (short_t i = 0; i < parDim_; ++i) {
1832 archive.read(key + ".degree[" + std::to_string(i) + "]", tensor);
1833 if (tensor.item<int64_t>() != degrees_[i])
1834 throw std::runtime_error("degrees mismatch");
1835 }
1836
1837 for (short_t i = 0; i < parDim_; ++i) {
1838 archive.read(key + ".nknots[" + std::to_string(i) + "]", tensor);
1839 nknots_[i] = tensor.item<int64_t>();
1840 }
1841
1842 for (short_t i = 0; i < parDim_; ++i)
1843 archive.read(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
1844
1845 for (short_t i = 0; i < parDim_; ++i) {
1846 archive.read(key + ".ncoeffs[" + std::to_string(i) + "]", tensor);
1847 ncoeffs_[i] = tensor.item<int64_t>();
1848 }
1849
1850 for (short_t i = 0; i < geoDim_; ++i)
1851 archive.read(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
1852
1853 return archive;
1854 }
1855
1857 inline void save(const std::string &filename,
1858 const std::string &key = "bspline") const {
1859 torch::serialize::OutputArchive archive;
1860 write(archive, key).save_to(filename);
1861 }
1862
1864 inline torch::serialize::OutputArchive &
1865 write(torch::serialize::OutputArchive &archive,
1866 const std::string &key = "bspline") const {
1867 archive.write(key + ".parDim", torch::full({1}, parDim_));
1868 archive.write(key + ".geoDim", torch::full({1}, geoDim_));
1869
1870 for (short_t i = 0; i < parDim_; ++i)
1871 archive.write(key + ".degree[" + std::to_string(i) + "]",
1872 torch::full({1}, degrees_[i]));
1873
1874 for (short_t i = 0; i < parDim_; ++i)
1875 archive.write(key + ".nknots[" + std::to_string(i) + "]",
1876 torch::full({1}, nknots_[i]));
1877
1878 for (short_t i = 0; i < parDim_; ++i)
1879 archive.write(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
1880
1881 for (short_t i = 0; i < parDim_; ++i)
1882 archive.write(key + ".ncoeffs[" + std::to_string(i) + "]",
1883 torch::full({1}, ncoeffs_[i]));
1884
1885 for (short_t i = 0; i < geoDim_; ++i)
1886 archive.write(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
1887
1888 return archive;
1889 }
1890
1893 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
1895 real_t rtol = real_t{1e-5}, real_t atol = real_t{1e-8}) const {
1896 if constexpr (!std::is_same<real_t, other_t>::value)
1897 return false;
1898 bool result(true);
1899
1900 result *= (parDim_ == other.parDim());
1901 result *= (geoDim_ == other.geoDim());
1902
1903 for (short_t i = 0; i < parDim_; ++i)
1904 result *= (degree(i) == other.degree(i));
1905
1906 for (short_t i = 0; i < parDim_; ++i)
1907 result *= (nknots(i) == other.nknots(i));
1908
1909 for (short_t i = 0; i < parDim_; ++i)
1910 result *= (ncoeffs(i) == other.ncoeffs(i));
1911
1912 if (!result)
1913 return result;
1914
1915 for (short_t i = 0; i < parDim_; ++i)
1916 result *= torch::allclose(knots(i), other.knots(i), rtol, atol);
1917
1918 for (short_t i = 0; i < geoDim_; ++i)
1919 result *= torch::allclose(coeffs(i), other.coeffs(i), rtol, atol);
1920
1921 return result;
1922 }
1923
1925 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
1928 if constexpr (!std::is_same<real_t, other_t>::value)
1929 return false;
1930 bool result(true);
1931
1932 result *= (parDim_ == other.parDim());
1933 result *= (geoDim_ == other.geoDim());
1934
1935 if (!result)
1936 return result;
1937
1938 for (short_t i = 0; i < parDim_; ++i)
1939 result *= (degree(i) == other.degree(i));
1940
1941 for (short_t i = 0; i < parDim_; ++i)
1942 result *= (nknots(i) == other.nknots(i));
1943
1944 for (short_t i = 0; i < parDim_; ++i)
1945 result *= (ncoeffs(i) == other.ncoeffs(i));
1946
1947 for (short_t i = 0; i < parDim_; ++i)
1948 result *= torch::equal(knots(i), other.knots(i));
1949
1950 for (short_t i = 0; i < geoDim_; ++i)
1951 result *= torch::equal(coeffs(i), other.coeffs(i));
1952
1953 return result;
1954 }
1955
1957 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
1960 return !(
1961 *this ==
1962 other); // Do not change this to (*this != other) is it does not work
1963 }
1964
1971 inline UniformBSplineCore &uniform_refine(int numRefine = 1, int dim = -1) {
1972 assert(numRefine > 0);
1973 assert(dim == -1 || (dim >= 0 && dim < parDim_));
1974
1975 // Update number of knots and coefficients
1976 std::array<int64_t, parDim_> nknots(nknots_);
1977 std::array<int64_t, parDim_> ncoeffs(ncoeffs_);
1978
1979 for (short_t refine = 0; refine < numRefine; ++refine) {
1980 if (dim == -1)
1981 for (short_t i = 0; i < parDim_; ++i) {
1982 ncoeffs[i] += nknots[i] - 2 * degrees_[i] - 1; // must be done first
1983 nknots[i] += nknots[i] - 2 * degrees_[i] - 1;
1984 }
1985 else {
1986 ncoeffs[dim] +=
1987 nknots[dim] - 2 * degrees_[dim] - 1; // must be done first
1988 nknots[dim] += nknots[dim] - 2 * degrees_[dim] - 1;
1989 }
1990 }
1991
1992 // Update knot vectors
1993 utils::TensorArray<parDim_> knots, knots_indices;
1994
1995 for (short_t i = 0; i < parDim_; ++i) {
1996 std::vector<real_t> kv;
1997 kv.reserve(nknots[i]);
1998
1999 for (int64_t j = 0; j < degrees_[i]; ++j)
2000 kv.push_back(static_cast<real_t>(0));
2001
2002 for (int64_t j = 0; j < ncoeffs[i] - degrees_[i] + 1; ++j)
2003 kv.push_back(static_cast<real_t>(j) /
2004 static_cast<real_t>(ncoeffs[i] - degrees_[i]));
2005
2006 for (int64_t j = 0; j < degrees_[i]; ++j)
2007 kv.push_back(static_cast<real_t>(1));
2008
2010 }
2011
2012 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
2013 // \f$m_d\f$ is the number of coefficients after the update. To
2014 // update the coefficients using the Oslo algorithm (Algorithm
2015 // 4.11 from \cite Lyche:2011) we need to neglect the last
2016 // \f$p_d+1\f$ knots in what follows
2017 for (short_t i = 0; i < parDim_; ++i)
2018 knots_indices[i] = knots[i].index(
2019 {torch::indexing::Slice(0, knots[i].numel() - degrees_[i] - 1)});
2020
2021 // Get indices of the first \f$m_d\f$ new knots relative to old
2022 // knot vectors
2023 auto new_knot_indices = find_knot_indices(knots_indices);
2024
2025 // Update coefficient vector
2026 update_coeffs(knots, new_knot_indices);
2027
2028 // Swap old and new data
2029 knots.swap(knots_);
2030 nknots.swap(nknots_);
2031 ncoeffs.swap(ncoeffs_);
2032
2034 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2035
2036 return *this;
2037 }
2038
2039private:
2042 template <int64_t degree, int64_t deriv, int64_t terminal = degree - deriv>
2043 inline int64_t constexpr eval_prefactor() const {
2044 if constexpr (degree > terminal)
2045 return degree * eval_prefactor<degree - 1, deriv, terminal>();
2046 else
2047 return 1;
2048 }
2049
2050public:
2052 inline void init_knots() {
2053
2054 for (short_t i = 0; i < parDim_; ++i) {
2055
2056 // Check that open knot vector can be created
2057 if ((ncoeffs_[i] < degrees_[i] + 1) || (ncoeffs_[i] < 2))
2058 throw std::runtime_error(
2059 "Not enough coefficients to create open knot vector");
2060
2061 nknots_[i] = ncoeffs_[i] + degrees_[i] + 1;
2062 int64_t num_inner = ncoeffs_[i] - degrees_[i];
2063
2064 auto start = torch::zeros({degrees_[i]}, options_);
2065 auto end = torch::ones({degrees_[i]}, options_);
2066 auto inner = torch::empty({0}, options_);
2067
2068 if (num_inner > 0) {
2069 inner = torch::arange(0, num_inner + 1, options_);
2070 inner = inner / static_cast<real_t>(num_inner);
2071 }
2072
2073 knots_[i] = torch::cat({start, inner, end});
2074 }
2075
2076 }
2077
2079 inline void init_coeffs(enum init init) {
2080 switch (init) {
2081
2082 case (init::none): {
2083 break;
2084 }
2085
2086 case (init::zeros): {
2087
2088 // Fill coefficients with zeros
2089 int64_t size = ncumcoeffs();
2090 for (short_t i = 0; i < geoDim_; ++i)
2091 coeffs_[i] = torch::zeros(size, options_);
2092
2093 break;
2094 }
2095
2096 case (init::ones): {
2097
2098 // Fill coefficients with ones
2099 int64_t size = ncumcoeffs();
2100 for (short_t i = 0; i < geoDim_; ++i)
2101 coeffs_[i] = torch::ones(size, options_);
2102
2103 break;
2104 }
2105
2106 case (init::random): {
2107
2108 // Fill coefficients with random values
2109 int64_t size = ncumcoeffs();
2110 for (short_t i = 0; i < geoDim_; ++i)
2111 coeffs_[i] = torch::rand(size, options_);
2112
2113 break;
2114 }
2115
2116 case (init::linear): {
2117
2118 // Fill coefficients with the tensor-product of linearly
2119 // increasing values between 0 and 1 per univariate dimension
2120 for (short_t i = 0; i < geoDim_; ++i) {
2121 coeffs_[i] = torch::ones(1, options_);
2122
2123 for (short_t j = 0; j < parDim_; ++j) {
2124 if (i == j)
2125 coeffs_[i] = torch::kron(torch::linspace(static_cast<real_t>(0),
2126 static_cast<real_t>(1),
2127 ncoeffs_[j], options_),
2128 coeffs_[i]);
2129 else
2130 coeffs_[i] =
2131 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2132 }
2133
2134 // Enable gradient calculation for non-leaf tensor
2135 if (options_.requires_grad())
2136 coeffs_[i].retain_grad();
2137 }
2138 break;
2139 }
2140
2141 case (init::greville): {
2142
2143 // Fill coefficients with the tensor-product of Greville
2144 // abscissae values per univariate dimension
2145 for (short_t i = 0; i < geoDim_; ++i) {
2146 coeffs_[i] = torch::ones(1, options_);
2147
2148 for (short_t j = 0; j < parDim_; ++j) {
2149 if (i == j) {
2150
2151 int64_t count = ncoeffs_[j];
2152
2153 // idx_base: (count, 1)
2154 auto idx_base = torch::arange(count, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(1);
2155
2156 // offsets: (1, degree)
2157 auto offsets = torch::arange(1, degrees_[j] + 1, options_.requires_grad(false).template dtype<int64_t>()).unsqueeze(0);
2158
2159 // indices: (count, degree)
2160 auto indices = idx_base + offsets;
2161
2162 // Gather relevant knot values: shape (count, degree)
2163 auto gathered = knots_[j].index_select(0, indices.flatten()).view({count, degrees_[j]});
2164
2165 // Compute mean along degree dimension (dim=1)
2166 auto greville_ = gathered.mean(1);
2167
2168 coeffs_[i] = torch::kron(greville_, coeffs_[i]);
2169 } else
2170 coeffs_[i] =
2171 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2172 }
2173
2174 // Enable gradient calculation for non-leaf tensor
2175 if (options_.requires_grad())
2176 coeffs_[i].retain_grad();
2177 }
2178 break;
2179 }
2180
2181 case (init::linspace): {
2182
2183 // Fill coefficients with increasing values
2184 int64_t size = ncumcoeffs();
2185 for (short_t i = 0; i < geoDim_; ++i)
2186 coeffs_[i] = torch::linspace(
2187 std::pow(10, i) * 0, std::pow(10, i) * (size - 1), size, options_);
2188
2189 break;
2190 }
2191
2192 default:
2193 throw std::runtime_error("Unsupported init option");
2194 }
2195 }
2196
2197protected:
2200 const utils::TensorArray<parDim_> &knot_indices) {
2201
2202 // Check compatibility of arguments
2203 for (short_t i = 0; i < parDim_; ++i)
2204 assert(knots[i].numel() == knot_indices[i].numel() + degrees_[i] + 1);
2205
2206 if constexpr (parDim_ == 1) {
2207
2208 auto basfunc = update_coeffs_univariate<degrees_[0], 0>(
2209 knots[0].flatten(), knot_indices[0].flatten());
2210
2211 auto coeff_indices = find_coeff_indices(knot_indices);
2212
2213 for (short_t i = 0; i < geoDim_; ++i)
2214 coeffs(i) =
2215 utils::dotproduct(basfunc, coeffs(i)
2216 .index_select(0, coeff_indices)
2217 .view({-1, knot_indices[0].numel()}))
2218 .view(knot_indices[0].sizes());
2219
2220 } else {
2221
2222 // Lambda expressions to evaluate the basis functions
2223 auto basfunc_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
2224 if constexpr (sizeof...(Is) == 1)
2225 return (update_coeffs_univariate<degrees_[Is], Is>(
2226 knots[Is].flatten(), knot_indices[Is].flatten()),
2227 ...);
2228 else
2230 knots[Is].flatten(), knot_indices[Is].flatten())...);
2231 };
2232
2233 auto basfunc = basfunc_(utils::make_reverse_index_sequence<parDim_>{});
2234
2235 // Lambda expression to calculate the partial product of array
2236 // entry from start_index to stop_index (including the latter)
2237 auto prod_ = [](utils::TensorArray<parDim_> array, short_t start_index,
2238 short_t stop_index) {
2239 int64_t result{1};
2240 for (short_t i = start_index; i <= stop_index; ++i)
2241 result *= array[i].numel();
2242 return result;
2243 };
2244
2245 utils::TensorArray<parDim_> knot_indices_;
2246
2247 for (short_t i = 0; i < parDim_; ++i)
2248 knot_indices_[i] =
2249 knot_indices[i]
2250 .repeat_interleave(prod_(knot_indices, 0, i - 1), 0)
2251 .repeat(prod_(knot_indices, i + 1, parDim_ - 1));
2252
2253 auto coeff_indices = find_coeff_indices(knot_indices_);
2254
2255 for (short_t i = 0; i < geoDim_; ++i)
2256 coeffs(i) = utils::dotproduct(basfunc,
2257 coeffs(i)
2258 .index_select(0, coeff_indices)
2259 .view({-1, knot_indices_[0].numel()}))
2260 .view(knot_indices_[0].sizes());
2261 }
2262 }
2263
2264 // clang-format off
2368 // clang-format on
2369 template <short_t degree, short_t dim, short_t deriv>
2370 inline auto eval_basfunc_univariate(const torch::Tensor &xi,
2371 const torch::Tensor &knot_indices) const {
2372 assert(xi.sizes() == knot_indices.sizes());
2373
2374 if constexpr (deriv > degree) {
2375 return torch::zeros({degree + 1, xi.numel()}, options_);
2376 } else {
2377 // Algorithm 2.22 from \cite Lyche:2011
2378 torch::Tensor b = torch::ones({xi.numel()}, options_);
2379
2380 // Calculate R_k, k = 1, ..., p_d-r_d
2381 for (short_t k = 1; k <= degree - deriv; ++k) {
2382
2383 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2384 auto t1 =
2385 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2386 auto t21 =
2387 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2388 t1;
2389
2390 // We handle the special case 0/0:=0 by first creating a
2391 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2392 // we do not have to take the absolute value as t2 >= t1.
2393 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2394 .to(::iganet::dtype<real_t>());
2395
2396 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2397 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2398 // equals the original expression if the mask is 0, i.e.,
2399 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2400 auto w = torch::div(xi.repeat(k) - t1 - mask, t21 - mask);
2401
2402 // Calculate the vector of B-splines evaluated at xi
2403 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2404 torch::zeros_like(xi, options_)},
2405 0) +
2406 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2407 }
2408
2409 // Calculate DR_k, k = p_d-r_d+1, ..., p_d
2410 for (short_t k = degree - deriv + 1; k <= degree; ++k) {
2411
2412 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2413 auto t21 =
2414 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2415 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2416
2417 // We handle the special case 0/0:=0 by first creating a
2418 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2419 // we do not have to take the absolute value as t2 >= t1.
2420 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2421 .to(::iganet::dtype<real_t>());
2422
2423 // Instead of computing 1/(t2-t1) which is prone to yielding
2424 // 0/0 we compute (1-mask)/(t2-t1-mask) which equals the
2425 // original expression if the mask is 0, i.e., t2-t1 >= eps
2426 // and 1 otherwise since t1 <= xi < t2.
2427 auto w = torch::div(torch::ones_like(t21, options_) - mask, t21 - mask);
2428
2429 // Calculate the vector of B-splines evaluated at xi
2430 b = torch::cat({torch::mul(-w, b), torch::zeros_like(xi, options_)},
2431 0) +
2432 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2433 }
2434
2435 return b.view({degree + 1, xi.numel()});
2436 }
2437 }
2438
2445 template <short_t degree, short_t dim>
2446 inline auto
2447 update_coeffs_univariate(const torch::Tensor &knots,
2448 const torch::Tensor &knot_indices) const {
2449 // Algorithm 2.22 from \cite Lyche:2011 modified to implement
2450 // the Oslo algorithm (Algorithm 4.11 from \cite Lyche:2011)
2451 torch::Tensor b = torch::ones({knot_indices.numel()}, options_);
2452
2453 // Calculate R_k, k = 1, ..., p_d
2454 for (short_t k = 1; k <= degree; ++k) {
2455
2456 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2457 auto t1 =
2458 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2459 auto t21 =
2460 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2461 t1;
2462
2463 // We handle the special case 0/0:=0 by first creating a
2464 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2465 // we do not have to take the absolute value as t2 >= t1.
2466 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2467 .to(::iganet::dtype<real_t>());
2468
2469 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2470 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2471 // equals the original expression if the mask is 0, i.e.,
2472 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2473 auto w = torch::div(
2474 knots.index({torch::indexing::Slice(k, knot_indices.numel() + k)})
2475 .repeat(k) -
2476 t1 - mask,
2477 t21 - mask);
2478
2479 // Calculate the vector of B-splines evaluated at xi
2480 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2481 torch::zeros_like(knot_indices, options_)},
2482 0) +
2483 torch::cat(
2484 {torch::zeros_like(knot_indices, options_), torch::mul(w, b)}, 0);
2485 }
2486
2487 return b.view({degree + 1, knot_indices.numel()});
2488 }
2489
2490public:
2494 auto to_gismo() const {
2495
2496#ifdef IGANET_WITH_GISMO
2497
2498 gismo::gsMatrix<real_t> coefs(ncumcoeffs(), geoDim_);
2499
2500 for (short_t g = 0; g < geoDim_; ++g) {
2501 auto [coeffs_cpu, coeffs_accessor] =
2502 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2503 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2504 coefs.col(g) =
2505 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2506 }
2507
2508 std::array<gismo::gsKnotVector<real_t>, parDim_> kv;
2509
2510 for (short_t i = 0; i < parDim_; ++i) {
2511 auto [knots_cpu, knots_accessor] =
2512 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
2513 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2514 kv[i] = gismo::gsKnotVector<real_t>(degrees_[i], knots_cpu_ptr,
2515 knots_cpu_ptr + knots_cpu.size(0));
2516 }
2517
2518 if constexpr (parDim_ == 1) {
2519
2520 return gismo::gsBSpline<real_t>(gismo::give(kv[0]), gismo::give(coefs));
2521
2522 } else if constexpr (parDim_ == 2) {
2523
2524 return gismo::gsTensorBSpline<parDim_, real_t>(
2525 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(coefs));
2526
2527 } else if constexpr (parDim_ == 3) {
2528
2529 return gismo::gsTensorBSpline<parDim_, real_t>(
2530 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
2531 gismo::give(coefs));
2532
2533 } else if constexpr (parDim_ == 4) {
2534
2535 return gismo::gsTensorBSpline<parDim_, real_t>(
2536 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
2537 gismo::give(kv[3]), gismo::give(coefs));
2538
2539 } else
2540 throw std::runtime_error("Invalid parametric dimension");
2541
2542#else
2543 throw std::runtime_error(
2544 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2545#endif
2546 }
2547
2548#ifdef IGANET_WITH_GISMO
2549
2550 // @brief Updates a given gsBSpline object from the B-spline object
2551 gismo::gsBSpline<real_t> &to_gismo(gismo::gsBSpline<real_t> &bspline,
2552 bool updateKnotVector = true,
2553 bool updateCoeffs = true) const {
2554
2555 if (updateKnotVector) {
2556
2557 if constexpr (parDim_ == 1) {
2558
2559 if (bspline.degree(0) != degrees_[0])
2560 throw std::runtime_error("Degrees mismatch");
2561
2562 auto [knots_cpu, knots_accessor] =
2563 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
2564 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2565
2566 gismo::gsKnotVector<real_t> kv(degrees_[0], knots_cpu_ptr,
2567 knots_cpu_ptr + knots_cpu.size(0));
2568
2569 bspline.knots(0).swap(kv);
2570
2571 } else
2572 throw std::runtime_error("Invalid parametric dimension");
2573 }
2574
2575 if (updateCoeffs) {
2576
2577 for (short_t g = 0; g < geoDim_; ++g) {
2578 auto [coeffs_cpu, coeffs_accessor] =
2579 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2580 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2581 bspline.coefs().col(g) =
2582 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2583 }
2584 }
2585
2586 return bspline;
2587 }
2588
2589 // @brief Updates a given gsTensorBSpline object from the B-spline object
2590 gismo::gsTensorBSpline<parDim_, real_t> &
2591 to_gismo(gismo::gsTensorBSpline<parDim_, real_t> &bspline,
2592 bool updateKnotVector = true, bool updateCoeffs = true) const {
2593
2594 if (updateKnotVector) {
2595
2596 // Check compatibility of arguments
2597 for (short_t i = 0; i < parDim_; ++i)
2598 assert(bspline.degree(i) == degrees_[i]);
2599
2600 for (short_t i = 0; i < parDim_; ++i) {
2601 auto [knots_cpu, knots_accessor] =
2602 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
2603 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2604
2605 gismo::gsKnotVector<real_t> kv(degrees_[i], knots_cpu_ptr,
2606 knots_cpu_ptr + knots_cpu.size(0));
2607 bspline.knots(i).swap(kv);
2608 }
2609 }
2610
2611 if (updateCoeffs) {
2612
2613 for (short_t g = 0; g < geoDim_; ++g) {
2614 auto [coeffs_cpu, coeffs_accessor] =
2615 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2616 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2617 bspline.coefs().col(g) =
2618 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2619 }
2620 }
2621
2622 return bspline;
2623 }
2624
2625#else // IGANET_WITH_GISMO
2626
2627 template <typename BSpline>
2628 BSpline &to_gismo(BSpline &bspline, bool, bool) const {
2629 throw std::runtime_error(
2630 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2631 return bspline;
2632 }
2633
2634#endif // IGANET_WITH_GISMO
2635
2636#ifdef IGANET_WITH_GISMO
2637
2638 // @brief Updates the B-spline object from a given gsBSpline object
2639 auto &from_gismo(const gismo::gsBSpline<real_t> &bspline,
2640 bool updateCoeffs = true, bool updateKnotVector = false) {
2641
2642 if (updateKnotVector) {
2643
2644 throw std::runtime_error(
2645 "Knot vectors can only be updated for Non-uniform B-splines");
2646 }
2647
2648 if (updateCoeffs) {
2649
2650 if (bspline.coefs().cols() != geoDim_)
2651 throw std::runtime_error("Geometric dimensions mismatch");
2652
2653 if (bspline.coefs().rows() != ncumcoeffs())
2654 throw std::runtime_error("Coefficient vector dimensions mismatch");
2655
2656 for (short_t g = 0; g < geoDim_; ++g) {
2657
2658 auto [coeffs_cpu, coeffs_accessor] =
2659 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2660
2661 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
2662
2663 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
2664 coeffs_accessor[i] = coeffs_ptr[i];
2665
2666 coeffs_[g] = coeffs_[g].to(options_.device());
2667 }
2668 }
2669
2670 return *this;
2671 }
2672
2673 // @brief Updates the B-spline object from a given gsTensorBSpline object
2674 auto &from_gismo(const gismo::gsTensorBSpline<parDim_, real_t> &bspline,
2675 bool updateCoeffs = true, bool updateKnotVector = false) {
2676
2677 if (updateKnotVector) {
2678
2679 throw std::runtime_error(
2680 "Knot vectors can only be updated for Non-uniform B-splines");
2681 }
2682
2683 if (updateCoeffs) {
2684
2685 if (bspline.coefs().cols() != geoDim_)
2686 throw std::runtime_error("Geometric dimensions mismatch");
2687
2688 if (bspline.coefs().rows() != ncumcoeffs())
2689 throw std::runtime_error("Coefficient vector dimensions mismatch");
2690
2691 for (short_t g = 0; g < geoDim_; ++g) {
2692
2693 auto [coeffs_cpu, coeffs_accessor] =
2694 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2695
2696 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
2697
2698 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
2699 coeffs_accessor[i] = coeffs_ptr[i];
2700
2701 coeffs_[g] = coeffs_[g].to(options_.device());
2702 }
2703 }
2704
2705 return *this;
2706 }
2707
2708#else // IGANET_WITH_GISMO
2709
2710 template <typename BSpline> auto &from_gismo(BSpline &bspline, bool, bool) {
2711 throw std::runtime_error(
2712 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2713 return *this;
2714 }
2715
2716#endif // IGANET_WITH_GISMO
2717};
2718
2720template <typename real_t, short_t GeoDim, short_t... Degrees>
2721inline torch::serialize::OutputArchive &
2722operator<<(torch::serialize::OutputArchive &archive,
2724 return obj.write(archive);
2725}
2726
2728template <typename real_t, short_t GeoDim, short_t... Degrees>
2729inline torch::serialize::InputArchive &
2730operator>>(torch::serialize::InputArchive &archive,
2732 return obj.read(archive);
2733}
2734
2740template <typename real_t, short_t GeoDim, short_t... Degrees>
2742 : public NonUniformSplineCore_,
2743 public UniformBSplineCore<real_t, GeoDim, Degrees...> {
2744private:
2746 using Base = UniformBSplineCore<real_t, GeoDim, Degrees...>;
2747
2748public:
2750 using value_type = real_t;
2751
2757 template <template <typename, short_t, short_t...> class BSpline,
2758 std::make_signed<short_t>::type degree_elevate = 0>
2759 using derived_type = BSpline<real_t, GeoDim, (Degrees + degree_elevate)...>;
2760
2763 template <std::make_signed<short_t>::type degree_elevate = 0>
2764 using self_type = typename Base::template derived_type<NonUniformBSplineCore,
2765 degree_elevate>;
2766
2770 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2772 NonUniformBSplineCore<other_t, GeoDim_, Degrees_...>;
2773
2776 template <typename other_t>
2778 NonUniformBSplineCore<other_t, GeoDim, Degrees...>;
2779
2781 inline static constexpr bool is_uniform() { return false; }
2782
2784 inline static constexpr bool is_nonuniform() { return true; }
2785
2787 using UniformBSplineCore<real_t, GeoDim, Degrees...>::UniformBSplineCore;
2788
2796 NonUniformBSplineCore(const std::array<std::vector<typename Base::value_type>,
2797 Base::parDim_> &kv,
2798 enum init init = init::greville,
2800 : Base(options) {
2801 init_knots(kv);
2803 }
2804
2818 NonUniformBSplineCore(const std::array<std::vector<typename Base::value_type>,
2819 Base::parDim_> &kv,
2821 bool clone = false,
2823 : Base(options) {
2824 init_knots(kv);
2825
2826 // Copy/clone coefficients
2827 if (clone)
2828 for (short_t i = 0; i < Base::geoDim_; ++i)
2829 Base::coeffs_[i] = coeffs[i]
2830 .clone()
2831 .to(options.requires_grad(false))
2832 .requires_grad_(Base::options.requires_grad());
2833 else
2834 for (short_t i = 0; i < Base::geoDim_; ++i)
2835 Base::coeffs_[i] = coeffs[i];
2836 }
2837
2838private:
2840 inline void init_knots(
2841 const std::array<std::vector<typename Base::value_type>, Base::parDim_>
2842 &kv) {
2843 for (short_t i = 0; i < Base::parDim_; ++i) {
2844
2845 // Check that knot vector has enough (n+p+1) entries
2846 if (2 * Base::degrees_[i] > kv[i].size() - 2)
2847 throw std::runtime_error("Knot vector is too short for an open knot "
2848 "vector (n+p+1 > 2*(p+1))");
2849
2851 Base::nknots_[i] = Base::knots_[i].size(0);
2854 }
2855 // Reverse ncoeffs
2856 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
2857 }
2858
2859public:
2863 template <deriv deriv = deriv::func, bool memory_optimized = false>
2864 inline auto eval(const torch::Tensor &xi) const {
2865 return eval<deriv, memory_optimized>(utils::TensorArray1({xi}));
2866 }
2867
2868 template <deriv deriv = deriv::func, bool memory_optimized = false>
2869 inline auto eval(const utils::TensorArray<Base::parDim_> &xi) const {
2870 if constexpr (Base::parDim_ == 0) {
2872 for (short_t i = 0; i < Base::geoDim_; ++i)
2873 if constexpr (deriv == deriv::func)
2874 result.set(i, Base::coeffs_[i]);
2875 else
2876 result.set(i, torch::zeros_like(Base::coeffs_[i]));
2877 return result;
2878 } else
2879 return Base::template eval<deriv, memory_optimized>(
2880 xi, find_knot_indices(xi));
2881 }
2882
2883 template <deriv deriv = deriv::func, bool memory_optimized = false>
2884 inline auto
2886 const utils::TensorArray<Base::parDim_> &knot_indices) const {
2887 if constexpr (Base::parDim_ == 0) {
2889 for (short_t i = 0; i < Base::geoDim_; ++i)
2890 if constexpr (deriv == deriv::func)
2891 result.set(i, Base::coeffs_[i]);
2892 else
2893 result.set(i, torch::zeros_like(Base::coeffs_[i]));
2894 return result;
2895 } else
2896 return Base::template eval<deriv, memory_optimized>(xi, knot_indices);
2897 }
2898
2899 template <deriv deriv = deriv::func, bool memory_optimized = false>
2901 const utils::TensorArray<Base::parDim_> &knot_indices,
2902 const torch::Tensor &coeff_indices) const {
2903 if constexpr (Base::parDim_ == 0) {
2905 for (short_t i = 0; i < Base::geoDim_; ++i)
2906 if constexpr (deriv == deriv::func)
2907 result.set(i, Base::coeffs_[i]);
2908 else
2909 result.set(i, torch::zeros_like(Base::coeffs_[i]));
2910 return result;
2911 } else
2912 return Base::template eval<deriv, memory_optimized>(xi, knot_indices,
2913 coeff_indices);
2914 }
2916
2930 inline auto find_knot_indices(const torch::Tensor &xi) const {
2931 if constexpr (Base::parDim_ == 0)
2932 return torch::zeros_like(Base::coeffs_[0]).to(torch::kInt64);
2933 else
2935 }
2936
2937 inline auto
2939
2941 for (short_t i = 0; i < Base::parDim_; ++i) {
2942 auto nnz = Base::knots_[i].repeat({xi[i].numel(), 1}) >
2943 xi[i].flatten().view({-1, 1});
2944 indices[i] =
2945 torch::remainder(std::get<1>(((nnz.cumsum(1) == 1) & nnz).max(1)) - 1,
2946 Base::nknots_[i] - Base::degrees_[i] - 1)
2947 .view(xi[i].sizes());
2948 }
2949 return indices;
2950 }
2952
2959 inline NonUniformBSplineCore &uniform_refine(int numRefine = 1,
2960 int dim = -1) {
2961 assert(numRefine > 0);
2962 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
2963
2964 // Update knot vectors, number of knots and coefficients
2965 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
2967
2968 for (short_t i = 0; i < Base::parDim_; ++i) {
2969 auto [kv_cpu, kv_accessor] =
2970 utils::to_tensorAccessor<typename Base::value_type, 1>(
2971 Base::knots_[i], torch::kCPU);
2972
2973 std::vector<typename Base::value_type> kv;
2974 kv.reserve(Base::nknots_[i]);
2975 kv.push_back(kv_accessor[0]);
2976
2977 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
2978
2979 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]))
2980 for (short_t refine = 1; refine < (2 << (numRefine - 1)); ++refine)
2981 kv.push_back(kv_accessor[j - 1] +
2982 static_cast<typename Base::value_type>(refine) /
2983 static_cast<typename Base::value_type>(
2984 2 << (numRefine - 1)) *
2985 (kv_accessor[j] - kv_accessor[j - 1]));
2986
2987 kv.push_back(kv_accessor[j]);
2988 }
2989
2991 nknots[i] = kv.size();
2992 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
2993 }
2994
2995 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
2996 // \f$m_d\f$ is the number of coefficients after the update. To
2997 // update the coefficients using the Oslo algorithm (Algorithm
2998 // 4.11 from \cite Lyche:2011) we need to neglect the last
2999 // \f$p_d+1\f$ knots in what follows
3000 for (short_t i = 0; i < Base::parDim_; ++i)
3001 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3002 0, knots[i].numel() - Base::degrees_[i] - 1)});
3003
3004 // Get indices of the first \f$m_d\f$ new knots relative to old
3005 // knot vectors
3006 auto new_knot_indices = find_knot_indices(knots_indices);
3007
3008 // Update coefficient vector
3009 Base::update_coeffs(knots, new_knot_indices);
3010
3011 // Swap old and new data
3012 knots.swap(Base::knots_);
3013 nknots.swap(Base::nknots_);
3014 ncoeffs.swap(Base::ncoeffs_);
3015
3017 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3018
3019 return *this;
3020 }
3021
3024 inline NonUniformBSplineCore &
3026 std::array<int64_t, Base::parDim_> nknots(Base::nknots_);
3027 std::array<int64_t, Base::parDim_> ncoeffs(Base::ncoeffs_);
3029
3030 // Update number of knots and coefficients and generate new knot
3031 // vectors
3032 for (short_t i = 0; i < Base::parDim_; ++i) {
3033 nknots[i] += knots[i].numel();
3034 ncoeffs[i] += knots[i].numel();
3035 knots_[i] =
3036 std::get<0>(torch::sort(torch::cat({Base::knots_[i], knots[i]})));
3037 }
3038
3039 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3040 // \f$m_d\f$ is the number of coefficients after the update. To
3041 // update the coefficients using the Oslo algorithm (Algorithm
3042 // 4.11 from \cite Lyche:2011) we need to neglect the last
3043 // \f$p_d+1\f$ knots in what follows
3044 for (short_t i = 0; i < Base::parDim_; ++i)
3045 knots_indices[i] = knots_[i].index({torch::indexing::Slice(
3046 0, knots_[i].numel() - Base::degrees_[i] - 1)});
3047
3048 // Get indices of the first \f$m_d\f$ new knots relative to old
3049 // knot vectors
3050 auto new_knot_indices = find_knot_indices(knots_indices);
3051
3052 // Update coefficient vector
3053 Base::update_coeffs(knots_, new_knot_indices);
3054
3055 // Swap old and new data
3056 knots_.swap(Base::knots_);
3057 nknots.swap(Base::nknots_);
3058 ncoeffs.swap(Base::ncoeffs_);
3059
3061 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3062
3063 return *this;
3064 }
3065
3068 inline NonUniformBSplineCore &reduce_continuity(int numReduce = 1,
3069 int dim = -1) {
3070 assert(numReduce > 0);
3071 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
3072
3073 // Update knot vectors, number of knots and coefficients
3074 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
3076
3077 for (short_t i = 0; i < Base::parDim_; ++i) {
3078 auto [kv_cpu, kv_accessor] =
3079 utils::to_tensorAccessor<typename Base::value_type, 1>(
3080 Base::knots_[i], torch::kCPU);
3081
3082 std::vector<typename Base::value_type> kv;
3083 kv.reserve(Base::nknots_[i]);
3084 kv.push_back(kv_accessor[0]);
3085
3086 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
3087
3088 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]) &&
3089 (kv_accessor[j] < kv_accessor[kv_accessor.size(0) - 1]))
3090 for (short_t reduce = 0; reduce < numReduce; ++reduce)
3091 kv.push_back(kv_accessor[j]);
3092
3093 kv.push_back(kv_accessor[j]);
3094 }
3095
3097 nknots[i] = kv.size();
3098 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
3099 }
3100
3101 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3102 // \f$m_d\f$ is the number of coefficients after the update. To
3103 // update the coefficients using the Oslo algorithm (Algorithm
3104 // 4.11 from \cite Lyche:2011) we need to neglect the last
3105 // \f$p_d+1\f$ knots in what follows
3106 for (short_t i = 0; i < Base::parDim_; ++i)
3107 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3108 0, knots[i].numel() - Base::degrees_[i] - 1)});
3109
3110 // Get indices of the first \f$m_d\f$ new knots relative to old
3111 // knot vectors
3112 auto new_knot_indices = find_knot_indices(knots_indices);
3113
3114 // Update coefficient vector
3115 Base::update_coeffs(knots, new_knot_indices);
3116
3117 // Swap old and new data
3118 knots.swap(Base::knots_);
3119 nknots.swap(Base::nknots_);
3120 ncoeffs.swap(Base::ncoeffs_);
3121
3123 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3124
3125 return *this;
3126 }
3127
3128#ifdef IGANET_WITH_GISMO
3129
3130 // @brief Updates the B-spline object from a given gsBSpline object
3131 auto &from_gismo(const gismo::gsBSpline<typename Base::value_type> &bspline,
3132 bool updateCoeffs = true, bool updateKnotVector = false) {
3133
3134 if (updateKnotVector) {
3135
3136 if constexpr (Base::parDim_ == 1) {
3137
3138 if (bspline.degree(0) != Base::degrees_[0])
3139 throw std::runtime_error("Degrees mismatch");
3140
3141 if (bspline.knots(0).size() != Base::nknots_[0])
3142 throw std::runtime_error("Knot vector dimensions mismatch");
3143
3144 auto [knots0_cpu, knots0_accessor] =
3145 utils::to_tensorAccessor<typename Base::value_type, 1>(
3146 Base::knots_[0], torch::kCPU);
3147
3148 const typename Base::value_type *knots0_ptr =
3149 bspline.knots(0).asMatrix().data();
3150
3151 for (int64_t i = 0; i < Base::nknots_[0]; ++i)
3152 knots0_accessor[i] = knots0_ptr[i];
3153
3155
3156 } else
3157 throw std::runtime_error("Invalid parametric dimension");
3158 }
3159
3160 if (updateCoeffs) {
3161
3162 if (bspline.coefs().rows() != Base::geoDim_)
3163 throw std::runtime_error("Geometric dimensions mismatch");
3164
3165 if (bspline.coefs().cols() != Base::ncumcoeffs())
3166 throw std::runtime_error("Coefficient vector dimensions mismatch");
3167
3168 for (short_t g = 0; g < Base::geoDim_; ++g) {
3169
3170 auto [coeffs_cpu, coeffs_accessor] =
3171 utils::to_tensorAccessor<typename Base::value_type, 1>(
3172 Base::coeffs_[g], torch::kCPU);
3173
3174 const typename Base::value_type *coeffs_ptr =
3175 bspline.coefs().row(g).data();
3176
3177 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3178 coeffs_accessor[i] = coeffs_ptr[i];
3179
3181 }
3182 }
3183
3184 return *this;
3185 }
3186
3187 // @brief Updates the B-spline object from a given gsTensorBSpline object
3188 auto &from_gismo(
3189 const gismo::gsTensorBSpline<Base::parDim_, typename Base::value_type>
3190 &bspline,
3191 bool updateCoeffs = true, bool updateKnotVector = false) {
3192
3193 if (updateKnotVector) {
3194
3195 for (short_t i = 0; i < Base::parDim_; ++i) {
3196 if (bspline.degree(i) != Base::degrees_[i])
3197 throw std::runtime_error("Degrees mismatch");
3198
3199 if (bspline.knots(i).size() != Base::nknots_[i])
3200 throw std::runtime_error("Knot vector dimensions mismatch");
3201
3202 auto [knots_cpu, knots_accessor] =
3203 utils::to_tensorAccessor<typename Base::value_type, 1>(
3204 Base::knots_[i], torch::kCPU);
3205
3206 const typename Base::value_type *knots_ptr =
3207 bspline.knots(i).asMatrix().data();
3208
3209 for (int64_t i = 0; i < Base::nknots_[i]; ++i)
3210 knots_accessor[i] = knots_ptr[i];
3211
3213 }
3214 }
3215
3216 if (updateCoeffs) {
3217
3218 if (bspline.coefs().rows() != Base::geoDim_)
3219 throw std::runtime_error("Geometric dimensions mismatch");
3220
3221 if (bspline.coefs().cols() != Base::ncumcoeffs())
3222 throw std::runtime_error("Coefficient vector dimensions mismatch");
3223
3224 for (short_t g = 0; g < Base::geoDim_; ++g) {
3225
3226 auto [coeffs_cpu, coeffs_accessor] =
3227 utils::to_tensorAccessor<typename Base::value_type, 1>(
3228 Base::coeffs_[g], torch::kCPU);
3229
3230 const typename Base::value_type *coeffs_ptr =
3231 bspline.coefs().row(g).data();
3232
3233 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3234 coeffs_accessor[i] = coeffs_ptr[i];
3235
3237 }
3238 }
3239
3240 return *this;
3241 }
3242
3243#else // IGANET_WITH_GISMO
3244
3245 template <typename BSpline> auto &from_gismo(BSpline &bspline, bool, bool) {
3246 throw std::runtime_error(
3247 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3248 return *this;
3249 }
3250
3251#endif // IGANET_WITH_GISMO
3252};
3253
3255 class Spline_ {};
3256
3258 template<typename T>
3259 concept SplineType = std::is_base_of_v<Spline_, T>;
3260
3262 template<typename T>
3263 concept UniformSplineType = std::is_base_of_v<Spline_, T> &&
3264 std::is_base_of_v<UniformSplineCore_, T> && !std::is_base_of_v<NonUniformSplineCore_, T>;
3265
3267 template<typename T>
3268 concept NonUniformSplineType = std::is_base_of_v<Spline_, T> &&
3269 std::is_base_of_v<NonUniformSplineCore_, T>;
3270
3284 template <typename BSplineCore>
3286 class BSplineCommon : public Spline_,
3287 public BSplineCore,
3288 protected utils::FullQualifiedName {
3289public:
3291 using BSplineCore::BSplineCore;
3292
3298 template <template <typename, short_t, short_t...> class T,
3299 std::make_signed<short_t>::type degree_elevate = 0>
3301 typename BSplineCore::template derived_type<T, degree_elevate>>;
3302
3305 template <std::make_signed<short_t>::type degree_elevate = 0>
3308
3312 template <typename real_t, short_t GeoDim, short_t... Degrees>
3314 BSplineCommon<typename BSplineCore::template derived_self_type<
3315 real_t, GeoDim, Degrees...>>;
3316
3319 template <typename other_t>
3321 typename BSplineCore::template real_derived_self_type<other_t>>;
3322
3324 using Ptr = std::shared_ptr<BSplineCommon>;
3325
3327 using uPtr = std::unique_ptr<BSplineCommon>;
3328
3330 BSplineCommon(const BSplineCommon &) = default;
3331
3333 BSplineCommon(const BSplineCommon &other, bool clone) : BSplineCommon(other) {
3334 if (clone)
3335 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3336 BSplineCore::coeffs_[i] = other.coeffs(i).clone();
3337 }
3338
3342 bool clone = false)
3343 : BSplineCommon(other) {
3344 if (clone)
3345 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3346 BSplineCore::coeffs_[i] = coeffs[i].clone();
3347 else
3348 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3349 BSplineCore::coeffs_[i] = coeffs[i];
3350 }
3351
3354
3358 : BSplineCommon(std::move(other)) {
3359 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3360 BSplineCore::coeffs_[i] = std::move(coeffs[i]);
3361 }
3362
3365 inline static Ptr
3370
3371 inline static Ptr
3372 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3373 enum init init = init::greville,
3376 return uPtr(new BSplineCommon(ncoeffs, init, options));
3377 }
3378
3379 inline static Ptr
3380 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3382 bool clone = false,
3385 return uPtr(new BSplineCommon(ncoeffs, coeffs, clone, options));
3386 }
3387
3388 inline static Ptr
3389 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3393 return uPtr(new BSplineCommon(ncoeffs, coeffs, options));
3394 }
3395
3396 inline static Ptr
3397 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
3398 BSplineCore::parDim_> &kv,
3399 enum init init = init::greville,
3402 return uPtr(new BSplineCommon(kv, init, options));
3403 }
3404
3405 inline static Ptr
3406 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
3407 BSplineCore::parDim_> &kv,
3409 bool clone = false,
3412 return uPtr(new BSplineCommon(kv, coeffs, clone, options));
3413 }
3415
3418 inline static Ptr
3423
3424 inline static Ptr
3425 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3426 enum init init = init::greville,
3429 return Ptr(new BSplineCommon(ncoeffs, init, options));
3430 }
3431
3432 inline static Ptr
3433 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3435 bool clone = false,
3438 return Ptr(new BSplineCommon(ncoeffs, coeffs, clone, options));
3439 }
3440
3441 inline static Ptr
3442 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3446 return Ptr(new BSplineCommon(ncoeffs, coeffs, options));
3447 }
3448
3449 inline static Ptr
3450 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
3451 BSplineCore::parDim_> &kv,
3452 enum init init = init::greville,
3455 return Ptr(new BSplineCommon(kv, init, options));
3456 }
3457
3458 inline static Ptr
3459 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
3460 BSplineCore::parDim_> &kv,
3462 bool clone = false,
3465 return Ptr(new BSplineCommon(kv, coeffs, clone, options));
3466 }
3468
3475 inline BSplineCommon &uniform_refine(int numRefine = 1, int dim = -1) {
3476 BSplineCore::uniform_refine(numRefine, dim);
3477 return *this;
3478 }
3479
3481 inline auto clone() const {
3482 BSplineCommon result;
3483
3484 result.nknots_ = BSplineCore::nknots_;
3485 result.ncoeffs_ = BSplineCore::ncoeffs_;
3486 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3487
3488 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3489 result.knots_[i] = BSplineCore::knots_[i].clone();
3490
3491 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3492 result.coeffs_[i] = BSplineCore::coeffs_[i].clone();
3493
3494 return result;
3495 }
3496
3498 template <typename real_t> inline auto to(Options<real_t> options) const {
3500 result(options);
3501
3502 result.nknots_ = BSplineCore::nknots_;
3503 result.ncoeffs_ = BSplineCore::ncoeffs_;
3504 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3505
3506 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3507 result.knots_[i] = BSplineCore::knots_[i].to(options);
3508
3509 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3510 result.coeffs_[i] = BSplineCore::coeffs_[i].to(options);
3511
3512 return result;
3513 }
3514
3516 inline auto to(torch::Device device) const {
3517 BSplineCommon result(BSplineCore::options_.device(device));
3518
3519 result.nknots_ = BSplineCore::nknots_;
3520 result.ncoeffs_ = BSplineCore::ncoeffs_;
3521 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3522
3523 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3524 result.knots_[i] = BSplineCore::knots_[i].to(device);
3525
3526 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3527 result.coeffs_[i] = BSplineCore::coeffs_[i].to(device);
3528
3529 return result;
3530 }
3531
3533 template <typename real_t> inline auto to() const {
3534 return to(BSplineCore::options_.template dtype<real_t>());
3535 }
3536
3543 inline auto diff(const BSplineCommon &other, int dim = -1) {
3544
3545 bool compatible(true);
3546
3547 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3548 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
3549
3550 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3551 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
3552
3553 if (!compatible)
3554 throw std::runtime_error("B-splines are not compatible");
3555
3556 if (dim == -1) {
3557 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3558 BSplineCore::coeffs(i) -= other.coeffs(i);
3559 } else
3560 BSplineCore::coeffs(dim) -= other.coeffs(dim);
3561
3562 return *this;
3563 }
3564
3571 inline auto abs_diff(const BSplineCommon &other, int dim = -1) {
3572
3573 bool compatible(true);
3574
3575 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3576 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
3577
3578 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3579 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
3580
3581 if (!compatible)
3582 throw std::runtime_error("B-splines are not compatible");
3583
3584 if (dim == -1) {
3585 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3586 BSplineCore::coeffs(i) =
3587 torch::abs(BSplineCore::coeffs(i) - other.coeffs(i));
3588 } else
3589 BSplineCore::coeffs(dim) =
3590 torch::abs(BSplineCore::coeffs(dim) - other.coeffs(dim));
3591
3592 return *this;
3593 }
3594
3596 inline auto scale(typename BSplineCore::value_type s, int dim = -1) {
3597 if (dim == -1)
3598 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3599 BSplineCore::coeffs(i) *= s;
3600 else
3601 BSplineCore::coeffs(dim) *= s;
3602 return *this;
3603 }
3604
3606 inline auto
3607 scale(std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
3608 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3609 BSplineCore::coeffs(i) *= v[i];
3610 return *this;
3611 }
3612
3614 inline auto translate(
3615 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
3616 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3617 BSplineCore::coeffs(i) += v[i];
3618 return *this;
3619 }
3620
3622 inline auto rotate(typename BSplineCore::value_type angle) {
3623
3624 static_assert(BSplineCore::geoDim() == 2,
3625 "Rotation about one angle is only available in 2D");
3626
3627 utils::TensorArray<2> coeffs;
3628 coeffs[0] = std::cos(angle) * BSplineCore::coeffs(0) -
3629 std::sin(angle) * BSplineCore::coeffs(1);
3630 coeffs[1] = std::sin(angle) * BSplineCore::coeffs(0) +
3631 std::cos(angle) * BSplineCore::coeffs(1);
3632
3633 BSplineCore::coeffs().swap(coeffs);
3634 return *this;
3635 }
3636
3638 inline auto rotate(std::array<typename BSplineCore::value_type, 3> angle) {
3639
3640 static_assert(BSplineCore::geoDim() == 3,
3641 "Rotation about two angles is only available in 3D");
3642
3643 utils::TensorArray<3> coeffs;
3644 coeffs[0] =
3645 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(0) +
3646 (std::sin(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) -
3647 std::cos(angle[0]) * std::sin(angle[2])) *
3648 BSplineCore::coeffs(1) +
3649 (std::cos(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) +
3650 std::sin(angle[0]) * std::sin(angle[2])) *
3651 BSplineCore::coeffs(2);
3652
3653 coeffs[1] =
3654 std::cos(angle[1]) * std::sin(angle[2]) * BSplineCore::coeffs(0) +
3655 (std::sin(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) +
3656 std::cos(angle[0]) * std::cos(angle[2])) *
3657 BSplineCore::coeffs(1) +
3658 (std::cos(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) -
3659 std::sin(angle[0]) * std::cos(angle[2])) *
3660 BSplineCore::coeffs(2);
3661
3662 coeffs[2] =
3663 -std::sin(angle[1]) * BSplineCore::coeffs(0) +
3664 std::sin(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(1) +
3665 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(2);
3666
3667 BSplineCore::coeffs().swap(coeffs);
3668 return *this;
3669 }
3670
3672 inline auto boundingBox() const {
3673
3674 // Lambda expression to compute the minimum value of all dimensions
3675 auto min_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
3676 return torch::stack({BSplineCore::coeffs(Is).min()...});
3677 };
3678
3679 // Lambda expression to compute the maximum value of all dimensions
3680 auto max_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
3681 return torch::stack({BSplineCore::coeffs(Is).max()...});
3682 };
3683
3684 std::pair<torch::Tensor, torch::Tensor> bbox;
3685 bbox.first = min_(std::make_index_sequence<BSplineCore::geoDim_>{});
3686 bbox.second = max_(std::make_index_sequence<BSplineCore::geoDim_>{});
3687 return bbox;
3688 }
3689
3697 template <bool memory_optimized = false>
3698 inline auto nv(const torch::Tensor &xi) const {
3699 return nv<memory_optimized>(utils::TensorArray1({xi}));
3700 }
3701
3702 template <bool memory_optimized = false>
3703 inline auto nv(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
3704 return nv<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
3705 }
3707
3716 template <bool memory_optimized = false>
3717 inline auto
3719 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
3720 return nv<memory_optimized>(
3721 xi, knot_indices,
3722 BSplineCore::template find_coeff_indices<memory_optimized>(
3723 knot_indices));
3724 }
3725
3737 template <bool memory_optimized = false>
3739 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
3740 const torch::Tensor &coeff_indices) const {
3741
3742 if constexpr (BSplineCore::parDim_ == 1 &&
3743 BSplineCore::geoDim_ == 2) {
3744 // Compute the perpendicular vector
3745 auto eval_ = BSplineCore::template eval<deriv::dx, memory_optimized>(xi, knot_indices, coeff_indices);
3746 return utils::BlockTensor<torch::Tensor, 1, 2>(*eval_[1], - *eval_[0]);
3747 }
3748 else if constexpr (BSplineCore::parDim_ == 1 &&
3749 BSplineCore::geoDim_ == 3) {
3750 // Compute the Frenet normal vector
3751 auto t_ = BSplineCore::template eval<deriv::dx, memory_optimized>(xi, knot_indices, coeff_indices).normalize();
3752 auto a_ = BSplineCore::template eval<deriv::dx^2, memory_optimized>(xi, knot_indices, coeff_indices);
3753 auto n_ = a_ - a_.dot(t_) * t_;
3754 return utils::BlockTensor<torch::Tensor, 2, 3>(n_(0,0), n_(0,1), n_(0,2),
3755 t_(0,0), t_(0,1), t_(0,2));
3756 }
3757 else if constexpr (BSplineCore::parDim_ == 2 &&
3758 BSplineCore::geoDim_ == 3) {
3759 // Compute the cross product of tangent vectors
3760 auto jac_ = jac<memory_optimized>(xi, knot_indices, coeff_indices);
3761 return utils::BlockTensor<torch::Tensor, 1, 3>(jac_(1,0) * jac_(2,1) - jac_(2,0) * jac_(1,1),
3762 jac_(2,0) * jac_(0,1) - jac_(0,0) * jac_(2,1),
3763 jac_(0,0) * jac_(1,1) - jac_(1,0) * jac_(0,1));
3764 }
3765 else {
3766 throw std::runtime_error("Unsupported parametric/geometric dimension");
3768 }
3769 }
3770
3771 // clang-format off
3792 // clang-format off
3794 template <bool memory_optimized = false>
3795 inline auto curl(const torch::Tensor &xi) const {
3796 return curl<memory_optimized>(utils::TensorArray1({xi}));
3797 }
3798
3799 template <bool memory_optimized = false>
3800 inline auto curl(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
3801 return curl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
3802 }
3804
3805 // clang-format off
3828 // clang-format on
3829 template <bool memory_optimized = false>
3830 inline auto
3832 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
3833 return curl<memory_optimized>(
3834 xi, knot_indices,
3835 BSplineCore::template find_coeff_indices<memory_optimized>(
3836 knot_indices));
3837 }
3838
3865 template <bool memory_optimized = false>
3867 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
3868 const torch::Tensor &coeff_indices) const {
3869
3870 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
3871 "curl(.) requires that parametric and geometric dimension "
3872 "are the same");
3873
3874 // Check compatibility of arguments
3875 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3876 assert(xi[i].sizes() == knot_indices[i].sizes());
3877 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
3878 assert(xi[0].sizes() == xi[i].sizes());
3879
3880 if constexpr (BSplineCore::parDim_ == 2)
3881
3888 *BSplineCore::template eval<deriv::dx, memory_optimized>(
3889 xi, knot_indices, coeff_indices)[1] -
3890 *BSplineCore::template eval<deriv::dy, memory_optimized>(
3891 xi, knot_indices, coeff_indices)[0]);
3892
3893 else if constexpr (BSplineCore::parDim_ == 3)
3894
3899 *BSplineCore::template eval<deriv::dy, memory_optimized>(
3900 xi, knot_indices, coeff_indices)[2] -
3901 *BSplineCore::template eval<deriv::dz, memory_optimized>(
3902 xi, knot_indices, coeff_indices)[1],
3903 *BSplineCore::template eval<deriv::dz, memory_optimized>(
3904 xi, knot_indices, coeff_indices)[0] +
3905 *BSplineCore::template eval<deriv::dx, memory_optimized>(
3906 xi, knot_indices, coeff_indices)[2],
3907 *BSplineCore::template eval<deriv::dx, memory_optimized>(
3908 xi, knot_indices, coeff_indices)[1] +
3909 *BSplineCore::template eval<deriv::dy, memory_optimized>(
3910 xi, knot_indices, coeff_indices)[0]);
3911
3912 else {
3913 throw std::runtime_error("Unsupported parametric/geometric dimension");
3915 }
3916 }
3917
3939 template <bool memory_optimized = false, typename Geometry>
3940 inline auto icurl(const Geometry &G, const torch::Tensor &xi) const {
3941 if constexpr (BSplineCore::parDim_ == 0)
3943 torch::zeros_like(BSplineCore::coeffs_[0])};
3944 else
3945 return icurl<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
3946 }
3947
3948 template <bool memory_optimized = false, typename Geometry>
3949 inline auto icurl(const Geometry &G,
3951 if constexpr (BSplineCore::parDim_ == 0)
3953 torch::zeros_like(BSplineCore::coeffs_[0])};
3954 else
3955 return icurl<memory_optimized, Geometry>(
3956 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
3957 }
3959
3983 template <bool memory_optimized = false, typename Geometry>
3984 inline auto
3986 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
3987 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
3988 if constexpr (BSplineCore::parDim_ == 0)
3990 torch::zeros_like(BSplineCore::coeffs_[0])};
3991 else
3992 return icurl<memory_optimized, Geometry>(
3993 G, xi, knot_indices,
3994 BSplineCore::template find_coeff_indices<memory_optimized>(
3995 knot_indices),
3996 knot_indices_G,
3997 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
3998 }
3999
4030 template <bool memory_optimized = false, typename Geometry>
4031 inline auto
4033 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4034 const torch::Tensor &coeff_indices,
4035 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4036 const torch::Tensor &coeff_indices_G) const {
4037
4038 if constexpr (BSplineCore::parDim_ == 0)
4040 torch::zeros_like(BSplineCore::coeffs_[0])};
4041 else {
4043 det[0] = std::make_shared<torch::Tensor>(torch::reciprocal(
4044 G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G)
4045 .det()));
4046
4047 return det * (curl<memory_optimized>(xi, knot_indices, coeff_indices) *
4048 G.template jac<memory_optimized>(xi, knot_indices_G,
4049 coeff_indices_G));
4050 }
4051 }
4052
4074 template <bool memory_optimized = false>
4075 inline auto div(const torch::Tensor &xi) const {
4076 if constexpr (BSplineCore::parDim_ == 0)
4078 torch::zeros_like(BSplineCore::coeffs_[0])};
4079 return div<memory_optimized>(utils::TensorArray1({xi}));
4080 }
4081
4082 template <bool memory_optimized = false>
4083 inline auto div(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4084 if constexpr (BSplineCore::parDim_ == 0)
4086 torch::zeros_like(BSplineCore::coeffs_[0])};
4087 return div<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4088 }
4090
4113 template <bool memory_optimized = false>
4114 inline auto
4116 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4117 if constexpr (BSplineCore::parDim_ == 0)
4119 torch::zeros_like(BSplineCore::coeffs_[0])};
4120 return div<memory_optimized>(
4121 xi, knot_indices,
4122 BSplineCore::template find_coeff_indices<memory_optimized>(
4123 knot_indices));
4124 }
4125
4151 template <bool memory_optimized = false>
4153 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4154 const torch::Tensor &coeff_indices) const {
4155
4156 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
4157 "div(.) requires parDim == geoDim");
4158
4159 if constexpr (BSplineCore::parDim_ == 0)
4161 torch::zeros_like(BSplineCore::coeffs_[0])};
4162 // return torch::zeros_like(BSplineCore::coeffs_[0]);
4163
4164 else {
4165 // Check compatibility of arguments
4166 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4167 assert(xi[i].sizes() == knot_indices[i].sizes());
4168 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4169 assert(xi[0].sizes() == xi[i].sizes());
4170
4171 // Lambda expression to evaluate the divergence
4172 auto div_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4174 (*BSplineCore::template eval<
4175 (deriv)utils::integer_pow<10, Is>::value, memory_optimized>(
4176 xi, knot_indices, coeff_indices)[Is] +
4177 ...)};
4178 };
4179
4180 return div_(std::make_index_sequence<BSplineCore::parDim_>{});
4181 }
4182 }
4183
4206 template <bool memory_optimized = false, typename Geometry>
4207 inline auto idiv(const Geometry &G, const torch::Tensor &xi) {
4208 if constexpr (BSplineCore::parDim_ == 0)
4210 torch::zeros_like(BSplineCore::coeffs_[0])};
4211 else
4212 return idiv<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4213 }
4214
4215 template <bool memory_optimized = false, typename Geometry>
4216 inline auto idiv(const Geometry &G,
4218 if constexpr (BSplineCore::parDim_ == 0)
4220 torch::zeros_like(BSplineCore::coeffs_[0])};
4221 else
4222 return idiv<memory_optimized, Geometry>(
4223 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4224 }
4226
4252 template <bool memory_optimized = false, typename Geometry>
4253 inline auto
4255 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4256 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4257 if constexpr (BSplineCore::parDim_ == 0)
4259 torch::zeros_like(BSplineCore::coeffs_[0])};
4260 else
4261 return idiv<memory_optimized, Geometry>(
4262 G, xi, knot_indices,
4263 BSplineCore::template find_coeff_indices<memory_optimized>(
4264 knot_indices),
4265 knot_indices_G,
4266 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4267 }
4268
4300 template <bool memory_optimized = false, typename Geometry>
4301 inline auto idiv(const Geometry &G,
4303 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4304 const torch::Tensor &coeff_indices,
4305 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4306 const torch::Tensor &coeff_indices_G) const {
4307 if constexpr (BSplineCore::parDim_ == 0)
4309 torch::zeros_like(BSplineCore::coeffs_[0])};
4310 else
4311 return ijac<memory_optimized, Geometry>(G, xi, knot_indices,
4312 coeff_indices, knot_indices_G,
4313 coeff_indices_G)
4314 .trace();
4315 }
4316
4337 template <bool memory_optimized = false>
4338 inline auto grad(const torch::Tensor &xi) const {
4339
4340 static_assert(BSplineCore::geoDim_ == 1,
4341 "grad(.) requires 1D variable, use jac(.) instead");
4342
4343 if constexpr (BSplineCore::parDim_ == 0)
4345 torch::zeros_like(BSplineCore::coeffs_[0])};
4346 else
4347 return grad<memory_optimized>(utils::TensorArray1({xi}));
4348 }
4349
4350 template <bool memory_optimized = false>
4351 inline auto grad(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4352
4353 static_assert(BSplineCore::geoDim_ == 1,
4354 "grad(.) requires 1D variable, use jac(.) instead");
4355
4356 if constexpr (BSplineCore::parDim_ == 0)
4358 torch::zeros_like(BSplineCore::coeffs_[0])};
4359 else
4360 return grad<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4361 }
4363
4385 template <bool memory_optimized = false>
4386 inline auto
4388 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4389
4390 static_assert(BSplineCore::geoDim_ == 1,
4391 "grad(.) requires 1D variable, use jac(.) instead");
4392
4393 if constexpr (BSplineCore::parDim_ == 0)
4395 torch::zeros_like(BSplineCore::coeffs_[0])};
4396 else
4397 return grad<memory_optimized>(
4398 xi, knot_indices,
4399 BSplineCore::template find_coeff_indices<memory_optimized>(
4400 knot_indices));
4401 }
4402
4427 template <bool memory_optimized = false>
4429 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4430 const torch::Tensor &coeff_indices) const {
4431
4432 static_assert(BSplineCore::geoDim_ == 1,
4433 "grad(.) requires 1D variable, use jac(.) instead");
4434
4435 if constexpr (BSplineCore::parDim_ == 0)
4437 torch::zeros_like(BSplineCore::coeffs_[0])};
4438
4439 else {
4440 // Check compatibility of arguments
4441 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4442 assert(xi[i].sizes() == knot_indices[i].sizes());
4443 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4444 assert(xi[0].sizes() == xi[i].sizes());
4445
4446 // Lambda expression to evaluate the gradient
4447 auto grad_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4449 BSplineCore::template eval<(deriv)utils::integer_pow<10, Is>::value,
4450 memory_optimized>(xi, knot_indices,
4451 coeff_indices)...};
4452 };
4453
4454 return grad_(std::make_index_sequence<BSplineCore::parDim_>{});
4455 }
4456 }
4457
4479 template <bool memory_optimized = false, typename Geometry>
4480 inline auto igrad(const Geometry &G, const torch::Tensor &xi) const {
4481 if constexpr (BSplineCore::parDim_ == 0)
4483 torch::zeros_like(BSplineCore::coeffs_[0])};
4484 else
4485 return igrad<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4486 }
4487
4488 template <bool memory_optimized = false, typename Geometry>
4489 inline auto igrad(const Geometry &G,
4491 if constexpr (BSplineCore::parDim_ == 0)
4493 torch::zeros_like(BSplineCore::coeffs_[0])};
4494 else
4495 return igrad<memory_optimized, Geometry>(
4496 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4497 }
4499
4523 template <bool memory_optimized = false, typename Geometry>
4524 inline auto
4526 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4527 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4528 if constexpr (BSplineCore::parDim_ == 0)
4530 torch::zeros_like(BSplineCore::coeffs_[0])};
4531 else
4532 return igrad<memory_optimized, Geometry>(
4533 G, xi, knot_indices,
4534 BSplineCore::template find_coeff_indices<memory_optimized>(
4535 knot_indices),
4536 knot_indices_G,
4537 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4538 }
4539
4570 template <bool memory_optimized = false, typename Geometry>
4571 inline auto
4573 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4574 const torch::Tensor &coeff_indices,
4575 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4576 const torch::Tensor &coeff_indices_G) const {
4577 if constexpr (BSplineCore::parDim_ == 0)
4579 torch::zeros_like(BSplineCore::coeffs_[0])};
4580 else
4581 return grad<memory_optimized>(xi, knot_indices, coeff_indices) *
4582 G.template jac<memory_optimized>(xi, knot_indices_G,
4583 coeff_indices_G)
4584 .ginv();
4585 }
4586
4587 // clang-format off
4620 // clang-format on
4623 template <bool memory_optimized = false>
4624 inline auto hess(const torch::Tensor &xi) const {
4625 if constexpr (BSplineCore::parDim_ == 0)
4627 torch::zeros_like(BSplineCore::coeffs_[0])};
4628 else
4629 return hess<memory_optimized>(utils::TensorArray1({xi}));
4630 }
4631
4632 template <bool memory_optimized = false>
4633 inline auto hess(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4634 if constexpr (BSplineCore::parDim_ == 0)
4636 torch::zeros_like(BSplineCore::coeffs_[0])};
4637 else
4638 return hess<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4639 }
4641
4642 // clang-format off
4677 // clang-format on
4678 template <bool memory_optimized = false>
4679 inline auto
4681 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4682 if constexpr (BSplineCore::parDim_ == 0)
4684 torch::zeros_like(BSplineCore::coeffs_[0])};
4685 else
4686 return hess<memory_optimized>(
4687 xi, knot_indices,
4688 BSplineCore::template find_coeff_indices<memory_optimized>(
4689 knot_indices));
4690 }
4691
4692 // clang-format off
4729 // clang-format on
4730 template <bool memory_optimized = false>
4732 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4733 const torch::Tensor &coeff_indices) const {
4734
4735 if constexpr (BSplineCore::parDim_ == 0)
4737 torch::zeros_like(BSplineCore::coeffs_[0])};
4738
4739 else {
4740 // Check compatibility of arguments
4741 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4742 assert(xi[i].sizes() == knot_indices[i].sizes());
4743 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4744 assert(xi[0].sizes() == xi[i].sizes());
4745
4746 // Lambda expression to evaluate the hessian
4747 auto hess_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4748 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
4749 BSplineCore::geoDim_, BSplineCore::parDim_>{
4750 BSplineCore::template eval<
4752 Is / BSplineCore::parDim_>::value +
4754 Is % BSplineCore::parDim_>::value,
4755 memory_optimized>(xi, knot_indices, coeff_indices)...}
4756 .reorder_ikj();
4757 };
4758
4759 return hess_(std::make_index_sequence<BSplineCore::parDim_ *
4760 BSplineCore::parDim_>{});
4761 }
4762 }
4763
4791 template <bool memory_optimized = false, typename Geometry>
4792 inline auto ihess(const Geometry &G, const torch::Tensor &xi) const {
4793 if constexpr (BSplineCore::parDim_ == 0)
4795 torch::zeros_like(BSplineCore::coeffs_[0])};
4796 else
4797 return ihess<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4798 }
4799
4800 template <bool memory_optimized = false, typename Geometry>
4801 inline auto ihess(const Geometry &G,
4803 if constexpr (BSplineCore::parDim_ == 0)
4805 torch::zeros_like(BSplineCore::coeffs_[0])};
4806 else
4807 return ihess<memory_optimized, Geometry>(
4808 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4809 }
4811
4841 template <bool memory_optimized = false, typename Geometry>
4842 inline auto
4844 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4845 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4846 if constexpr (BSplineCore::parDim_ == 0)
4848 torch::zeros_like(BSplineCore::coeffs_[0])};
4849 else
4850 return ihess<memory_optimized, Geometry>(
4851 G, xi, knot_indices,
4852 BSplineCore::template find_coeff_indices<memory_optimized>(
4853 knot_indices),
4854 knot_indices_G,
4855 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4856 }
4857
4893 template <bool memory_optimized = false, typename Geometry>
4894 inline auto
4896 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4897 const torch::Tensor &coeff_indices,
4898 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4899 const torch::Tensor &coeff_indices_G) const {
4900
4901 if constexpr (BSplineCore::parDim_ == 0)
4903 torch::zeros_like(BSplineCore::coeffs_[0])};
4904 else {
4906
4907 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
4908 coeff_indices_G);
4909 auto ijacG = ijac<memory_optimized>(G, xi, knot_indices, coeff_indices,
4910 knot_indices_G, coeff_indices_G);
4911
4912 for (short_t component = 0; component < BSplineCore::geoDim_; ++component) {
4913 auto hess_component = hess<memory_optimized>(xi, knot_indices, coeff_indices).slice(component);
4914
4915 for (short_t k = 0; k < hessG.slices(); ++k) {
4916 hess_component -= ijacG(component, k) * hessG.slice(k);
4917 }
4918
4919 auto jacInv = G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G).ginv();
4920 auto hessu_component = jacInv.tr() * hess_component * jacInv;
4921
4922 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4923 for (short_t j = 0; j < BSplineCore::parDim_; ++j)
4924 hessu.set(i, j, component, hessu_component(i, j));
4925 }
4926
4927 return hessu;
4928
4929 }
4930 }
4931
4932 // clang-format off
4960 // clang-format on
4963 template <bool memory_optimized = false>
4964 inline auto jac(const torch::Tensor &xi) const {
4965 if constexpr (BSplineCore::parDim_ == 0)
4967 torch::zeros_like(BSplineCore::coeffs_[0])};
4968 else
4969 return jac<memory_optimized>(utils::TensorArray1({xi}));
4970 }
4971
4972 template <bool memory_optimized = false>
4973 inline auto jac(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4974 if constexpr (BSplineCore::parDim_ == 0)
4976 torch::zeros_like(BSplineCore::coeffs_[0])};
4977 else
4978 return jac<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4979 }
4981
4982 // clang-format off
5012 // clang-format on
5013 template <bool memory_optimized = false>
5014 inline auto
5016 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5017 if constexpr (BSplineCore::parDim_ == 0)
5019 torch::zeros_like(BSplineCore::coeffs_[0])};
5020 else
5021 return jac<memory_optimized>(
5022 xi, knot_indices,
5023 BSplineCore::template find_coeff_indices<memory_optimized>(
5024 knot_indices));
5025 }
5026
5027 // clang-format off
5065 // clang-format on
5066 template <bool memory_optimized = false>
5068 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5069 const torch::Tensor &coeff_indices) const {
5070
5071 if constexpr (BSplineCore::parDim_ == 0)
5073 torch::zeros_like(BSplineCore::coeffs_[0])};
5074
5075 else {
5076 // Check compatibility of arguments
5077 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5078 assert(xi[i].sizes() == knot_indices[i].sizes());
5079 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5080 assert(xi[0].sizes() == xi[i].sizes());
5081
5082 // Lambda expression to evaluate the jacobian
5083 auto jac_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5084 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5085 BSplineCore::geoDim_>{
5086 BSplineCore::template eval<(deriv)utils::integer_pow<10, Is>::value,
5087 memory_optimized>(xi, knot_indices,
5088 coeff_indices)...}
5089 .tr();
5090 };
5091
5092 return jac_(std::make_index_sequence<BSplineCore::parDim_>{});
5093 }
5094 }
5095
5117 template <bool memory_optimized = false, typename Geometry>
5118 inline auto ijac(const Geometry &G, const torch::Tensor &xi) const {
5119 if constexpr (BSplineCore::parDim_ == 0)
5121 torch::zeros_like(BSplineCore::coeffs_[0])};
5122 else
5123 return ijac<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5124 }
5125
5126 template <bool memory_optimized = false, typename Geometry>
5127 inline auto ijac(const Geometry &G,
5129 if constexpr (BSplineCore::parDim_ == 0)
5131 torch::zeros_like(BSplineCore::coeffs_[0])};
5132 else
5133 return ijac<memory_optimized, Geometry>(
5134 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5135 }
5137
5162 template <bool memory_optimized = false, typename Geometry>
5163 inline auto
5165 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5166 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5167 if constexpr (BSplineCore::parDim_ == 0)
5169 torch::zeros_like(BSplineCore::coeffs_[0])};
5170 else
5171 return ijac<memory_optimized, Geometry>(
5172 G, xi, knot_indices,
5173 BSplineCore::template find_coeff_indices<memory_optimized>(
5174 knot_indices),
5175 knot_indices_G,
5176 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5177 }
5178
5209 template <bool memory_optimized = false, typename Geometry>
5210 inline auto ijac(const Geometry &G,
5212 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5213 const torch::Tensor &coeff_indices,
5214 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5215 const torch::Tensor &coeff_indices_G) const {
5216 if constexpr (BSplineCore::parDim_ == 0)
5218 torch::zeros_like(BSplineCore::coeffs_[0])};
5219 else
5220 return jac<memory_optimized>(xi, knot_indices, coeff_indices) *
5221 G.template jac<memory_optimized>(xi, knot_indices_G,
5222 coeff_indices_G)
5223 .ginv();
5224 }
5225
5226 // clang-format off
5243 // clang-format on
5246 template <bool memory_optimized = false>
5247 inline auto lapl(const torch::Tensor &xi) const {
5248 if constexpr (BSplineCore::parDim_ == 0)
5250 torch::zeros_like(BSplineCore::coeffs_[0])};
5251 else
5252 return lapl<memory_optimized>(utils::TensorArray1({xi}));
5253 }
5254
5255 template <bool memory_optimized = false>
5256 inline auto lapl(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5257 if constexpr (BSplineCore::parDim_ == 0)
5259 torch::zeros_like(BSplineCore::coeffs_[0])};
5260 else
5261 return lapl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5262 }
5264
5265 // clang-format off
5285 // clang-format on
5286 template <bool memory_optimized = false>
5287 inline auto
5289 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5290 if constexpr (BSplineCore::parDim_ == 0)
5292 torch::zeros_like(BSplineCore::coeffs_[0])};
5293 else
5294 return lapl<memory_optimized>(
5295 xi, knot_indices,
5296 BSplineCore::template find_coeff_indices<memory_optimized>(
5297 knot_indices));
5298 }
5299
5300 // clang-format off
5323 // clang-format on
5324 template <bool memory_optimized = false>
5326 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5327 const torch::Tensor &coeff_indices) const {
5328
5329 if constexpr (BSplineCore::parDim_ == 0)
5331 torch::zeros_like(BSplineCore::coeffs_[0])};
5332
5333 else {
5334 // Check compatibility of arguments
5335 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5336 assert(xi[i].sizes() == knot_indices[i].sizes());
5337 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5338 assert(xi[0].sizes() == xi[i].sizes());
5339
5340 // Lambda expression to evaluate the laplacian
5341 auto lapl_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5343 (BSplineCore::template eval<
5345 memory_optimized>(xi, knot_indices, coeff_indices) +
5346 ...)}
5347 .reorder_ikj();
5348 };
5349
5350 return lapl_(std::make_index_sequence<BSplineCore::parDim_>{});
5351 }
5352 }
5353
5382 template <bool memory_optimized = false, typename Geometry>
5383 auto ilapl(const Geometry &G, const torch::Tensor &xi) const {
5384 if constexpr (BSplineCore::parDim_ == 0)
5386 torch::zeros_like(BSplineCore::coeffs_[0])};
5387 else
5388 return ilapl<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5389 }
5390
5391 template <bool memory_optimized = false, typename Geometry>
5392 inline auto ilapl(const Geometry &G,
5394 if constexpr (BSplineCore::parDim_ == 0)
5396 torch::zeros_like(BSplineCore::coeffs_[0])};
5397 else
5398 return ilapl<memory_optimized, Geometry>(
5399 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5400 }
5402
5433 template <bool memory_optimized = false, typename Geometry>
5434 inline auto
5436 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5437 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5438 if constexpr (BSplineCore::parDim_ == 0)
5440 torch::zeros_like(BSplineCore::coeffs_[0])};
5441 else
5442 return ilapl<memory_optimized, Geometry>(
5443 G, xi, knot_indices,
5444 BSplineCore::template find_coeff_indices<memory_optimized>(
5445 knot_indices),
5446 knot_indices_G,
5447 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5448 }
5449
5487 template <bool memory_optimized = false, typename Geometry>
5488 inline auto
5490 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5491 const torch::Tensor &coeff_indices,
5492 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5493 const torch::Tensor &coeff_indices_G) const {
5494
5495 if constexpr (BSplineCore::parDim_ == 0)
5497 torch::zeros_like(BSplineCore::coeffs_[0])};
5498
5499 else {
5500 auto hessu =
5501 hess<memory_optimized>(xi, knot_indices, coeff_indices).slice(0);
5502
5503 {
5504 auto igradG =
5505 igrad<memory_optimized>(G, xi, knot_indices, coeff_indices,
5506 knot_indices_G, coeff_indices_G);
5507 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
5508 coeff_indices_G);
5509 assert(igradG.cols() == hessG.slices());
5510 for (short_t k = 0; k < hessG.slices(); ++k)
5511 hessu -= igradG(0, k) * hessG.slice(k);
5512 }
5513
5514 auto jacInv =
5515 G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G)
5516 .ginv();
5517
5518 return (jacInv.tr() * hessu * jacInv).trace();
5519 }
5520 }
5521
5527#ifdef IGANET_WITH_MATPLOT
5528 template <typename Backend = matplot::backend::gnuplot>
5529#else
5530 template <typename Backend = void>
5531#endif
5532 inline auto plot(const nlohmann::json &json = {}) const {
5533 return plot<Backend>(*this, json);
5534 }
5535
5543#ifdef IGANET_WITH_MATPLOT
5544 template <typename Backend = matplot::backend::gnuplot>
5545#else
5546 template <typename Backend = void>
5547#endif
5549 const nlohmann::json &json = {}) const {
5550
5551 return plot<Backend>(*this, xi, json);
5552 }
5553
5561#ifdef IGANET_WITH_MATPLOT
5562 template <typename Backend = matplot::backend::gnuplot>
5563#else
5564 template <typename Backend = void>
5565#endif
5566 inline auto plot(
5567 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
5568 const nlohmann::json &json = {}) const {
5569
5570 return plot<Backend>(*this, xi, json);
5571 }
5572
5580#ifdef IGANET_WITH_MATPLOT
5581 template <typename Backend = matplot::backend::gnuplot,
5582 typename BSplineCoreColor>
5583#else
5584 template <typename Backend = void, typename BSplineCoreColor>
5585#endif
5586 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
5587 const nlohmann::json &json = {}) const {
5588#ifdef IGANET_WITH_MATPLOT
5589 static_assert(BSplineCore::parDim() == BSplineCoreColor::parDim(),
5590 "Parametric dimensions must match");
5591
5592 if ((void *)this != (void *)&color && BSplineCoreColor::geoDim() > 1)
5593 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5594
5595 if constexpr (BSplineCore::parDim() == 1 && BSplineCore::geoDim() == 1) {
5596
5597 //
5598 // mapping: [0,1] -> R^1
5599 //
5600
5601 int64_t res0 = BSplineCore::ncoeffs(0);
5602 if (json.contains("res0"))
5603 res0 = json["res0"].get<int64_t>();
5604
5605 // Create figure with specified backend
5606 auto f = matplot::figure<Backend>(true);
5607 auto ax = f->current_axes();
5608
5609 // Create line
5610 auto Coords =
5611 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5612#ifdef __clang__
5613 auto Coords_cpu =
5614 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5615 Coords(0), torch::kCPU);
5616 auto XAccessor = std::get<1>(Coords_cpu);
5617#else
5618 auto [Coords_cpu, XAccessor] =
5619 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5620 Coords(0), torch::kCPU);
5621#endif
5622
5623 matplot::vector_1d Xfine(res0, 0.0);
5624 matplot::vector_1d Yfine(res0, 0.0);
5625
5626#pragma omp parallel for simd
5627 for (int64_t i = 0; i < res0; ++i)
5628 Xfine[i] = XAccessor[i];
5629
5630 // Plot (colored) line
5631 if ((void *)this != (void *)&color) {
5632 if constexpr (BSplineCoreColor::geoDim_ == 1) {
5633
5634 // Create colors
5635 auto Color =
5636 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5637#ifdef __clang__
5638 auto Color_cpu =
5639 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5640 1>(Color(0), torch::kCPU);
5641 auto CAccessor = std::get<1>(Color_cpu);
5642#else
5643 auto [Color_cpu, CAccessor] =
5644 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5645 1>(Color(0), torch::kCPU);
5646#endif
5647
5648 matplot::vector_1d Cfine(res0, 0.0);
5649
5650#pragma omp parallel for simd
5651 for (int64_t i = 0; i < res0; ++i)
5652 Cfine[i] = CAccessor[i];
5653
5654 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
5655 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
5656
5657 auto Cmap = matplot::colormap();
5658
5659 auto a = Cmap.size() / (Cmax - Cmin);
5660 auto b = -a * Cmin;
5661
5662 // Plot colored line
5663 ax->hold(matplot::on);
5664 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
5665 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
5666 ->line_width(2)
5667 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
5668 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
5669 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
5670 ax->hold(matplot::off);
5671 matplot::colorbar(ax);
5672 } else
5673 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5674 } else {
5675 // Plot unicolor line
5676 ax->plot(Xfine, Yfine, "b-")->line_width(2);
5677 }
5678
5679 bool cnet = false;
5680 if (json.contains("cnet"))
5681 cnet = json["cnet"].get<bool>();
5682
5683 if (cnet) {
5684 // Create control net
5685#ifdef __clang__
5686 auto coeffs_cpu =
5687 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5688 BSplineCore::coeffs(0), torch::kCPU);
5689 auto xAccessor = std::get<1>(coeffs_cpu);
5690#else
5691 auto [coeffs_cpu, xAccessor] =
5692 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5693 BSplineCore::coeffs(0), torch::kCPU);
5694#endif
5695 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
5696 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
5697
5698#pragma omp parallel for simd
5699 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
5700 X[i] = xAccessor[i];
5701 }
5702
5703 // Plot control net
5704 ax->hold(matplot::on);
5705 ax->plot(X, Y, ".k-")->line_width(1);
5706 ax->hold(matplot::off);
5707 }
5708
5709 // Title
5710 if (json.contains("title"))
5711 ax->title(json["title"].get<std::string>());
5712 else
5713 ax->title("BSpline: [0,1] -> R");
5714
5715 // X-axis label
5716 if (json.contains("xlabel"))
5717 ax->xlabel(json["xlabel"].get<std::string>());
5718 else
5719 ax->xlabel("x");
5720
5721 // Y-axis label
5722 if (json.contains("ylabel"))
5723 ax->ylabel(json["ylabel"].get<std::string>());
5724 else
5725 ax->ylabel("y");
5726
5727 return f;
5728 }
5729
5730 else if constexpr (BSplineCore::parDim_ == 1 && BSplineCore::geoDim_ == 2) {
5731
5732 //
5733 // mapping: [0,1] -> R^2
5734 //
5735
5736 int64_t res0 = BSplineCore::ncoeffs(0);
5737 if (json.contains("res0"))
5738 res0 = json["res0"].get<int64_t>();
5739
5740 // Create figure with specified backend
5741 auto f = matplot::figure<Backend>(true);
5742 auto ax = f->current_axes();
5743
5744 // Create curve
5745 auto Coords =
5746 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5747#ifdef __clang__
5748 auto Coords_cpu =
5749 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5750 Coords, torch::kCPU);
5751 auto XAccessor = std::get<1>(Coords_cpu)[0];
5752 auto YAccessor = std::get<1>(Coords_cpu)[1];
5753#else
5754 auto [Coords0_cpu, XAccessor] =
5755 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5756 Coords(0), torch::kCPU);
5757 auto [Coords1_cpu, YAccessor] =
5758 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5759 Coords(1), torch::kCPU);
5760#endif
5761
5762 matplot::vector_1d Xfine(res0, 0.0);
5763 matplot::vector_1d Yfine(res0, 0.0);
5764
5765#pragma omp parallel for simd
5766 for (int64_t i = 0; i < res0; ++i) {
5767 Xfine[i] = XAccessor[i];
5768 Yfine[i] = YAccessor[i];
5769 }
5770
5771 // Plot (colored) curve
5772 if ((void *)this != (void *)&color) {
5773 if constexpr (BSplineCoreColor::geoDim() == 1) {
5774
5775 // Create colors
5776 auto Color =
5777 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5778#ifdef __clang__
5779 auto Color_cpu =
5780 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5781 1>(Color(0), torch::kCPU);
5782 auto CAccessor = std::get<1>(Color_cpu);
5783#else
5784 auto [Color_cpu, CAccessor] =
5785 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5786 1>(Color(0), torch::kCPU);
5787#endif
5788
5789 matplot::vector_1d Cfine(res0, 0.0);
5790
5791#pragma omp parallel for simd
5792 for (int64_t i = 0; i < res0; ++i) {
5793 Cfine[i] = CAccessor[i];
5794 }
5795
5796 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
5797 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
5798
5799 auto Cmap = matplot::colormap();
5800
5801 auto a = Cmap.size() / (Cmax - Cmin);
5802 auto b = -a * Cmin;
5803
5804 // Plot colored curve
5805 ax->hold(matplot::on);
5806 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
5807 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
5808 ->line_width(2)
5809 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
5810 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
5811 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
5812 ax->hold(matplot::off);
5813 matplot::colorbar(ax);
5814 } else
5815 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5816 } else {
5817 // Plot unicolor curve
5818 ax->plot(Xfine, Yfine, "b-")->line_width(2);
5819 }
5820
5821 bool cnet = false;
5822 if (json.contains("cnet"))
5823 cnet = json["cnet"].get<bool>();
5824
5825 if (cnet) {
5826 // Create control net
5827#ifdef __clang__
5828 auto coeffs_cpu =
5829 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5830 BSplineCore::coeffs(), torch::kCPU);
5831 auto xAccessor = std::get<1>(coeffs_cpu)[0];
5832 auto yAccessor = std::get<1>(coeffs_cpu)[1];
5833#else
5834 auto [coeffs0_cpu, xAccessor] =
5835 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5836 BSplineCore::coeffs(0), torch::kCPU);
5837 auto [coeffs1_cpu, yAccessor] =
5838 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5839 BSplineCore::coeffs(1), torch::kCPU);
5840#endif
5841
5842 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
5843 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
5844
5845#pragma omp parallel for simd
5846 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
5847 X[i] = xAccessor[i];
5848 Y[i] = yAccessor[i];
5849 }
5850
5851 // Plot control net
5852 ax->hold(matplot::on);
5853 ax->plot(X, Y, ".k-")->line_width(1);
5854 ax->hold(matplot::off);
5855 }
5856
5857 // Title
5858 if (json.contains("title"))
5859 ax->title(json["title"].get<std::string>());
5860 else
5861 ax->title("BSpline: [0,1] -> R^2");
5862
5863 // X-axis label
5864 if (json.contains("xlabel"))
5865 ax->xlabel(json["xlabel"].get<std::string>());
5866 else
5867 ax->xlabel("x");
5868
5869 // Y-axis label
5870 if (json.contains("ylabel"))
5871 ax->ylabel(json["ylabel"].get<std::string>());
5872 else
5873 ax->ylabel("y");
5874
5875 return f;
5876 }
5877
5878 else if constexpr (BSplineCore::parDim() == 1 &&
5879 BSplineCore::geoDim() == 3) {
5880
5881 //
5882 // mapping: [0,1] -> R^3
5883 //
5884
5885 int64_t res0 = BSplineCore::ncoeffs(0);
5886 if (json.contains("res0"))
5887 res0 = json["res0"].get<int64_t>();
5888
5889 // Create figure with specified backend
5890 auto f = matplot::figure<Backend>(true);
5891 auto ax = f->current_axes();
5892
5893 auto Coords =
5894 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5895#ifdef __clang__
5896 auto Coords_cpu =
5897 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5898 Coords, torch::kCPU);
5899 auto XAccessor = std::get<1>(Coords_cpu)[0];
5900 auto YAccessor = std::get<1>(Coords_cpu)[1];
5901 auto ZAccessor = std::get<1>(Coords_cpu)[2];
5902#else
5903 auto [Coords0_cpu, XAccessor] =
5904 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5905 Coords(0), torch::kCPU);
5906 auto [Coords1_cpu, YAccessor] =
5907 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5908 Coords(1), torch::kCPU);
5909 auto [Coords2_cpu, ZAccessor] =
5910 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5911 Coords(2), torch::kCPU);
5912#endif
5913
5914 // Create curve
5915 matplot::vector_1d Xfine(res0, 0.0);
5916 matplot::vector_1d Yfine(res0, 0.0);
5917 matplot::vector_1d Zfine(res0, 0.0);
5918
5919#pragma omp parallel for simd
5920 for (int64_t i = 0; i < res0; ++i) {
5921 Xfine[i] = XAccessor[i];
5922 Yfine[i] = YAccessor[i];
5923 Zfine[i] = ZAccessor[i];
5924 }
5925
5926 // Plot (colored) curve
5927 if ((void *)this != (void *)&color) {
5928 if constexpr (BSplineCoreColor::geoDim() == 1) {
5929
5930 auto Color =
5931 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
5932#ifdef __clang__
5933 auto Color_cpu =
5934 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5935 1>(Color(0), torch::kCPU);
5936 auto CAccessor = std::get<1>(Color_cpu);
5937#else
5938 auto [Color_cpu, CAccessor] =
5939 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
5940 1>(Color(0), torch::kCPU);
5941#endif
5942
5943 // Create colors
5944 matplot::vector_1d Cfine(matplot::vector_1d(res0, 0.0));
5945
5946#pragma omp parallel for simd
5947 for (int64_t i = 0; i < res0; ++i) {
5948 Cfine[i] = CAccessor[i];
5949 }
5950
5951 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
5952 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
5953
5954 auto Cmap = matplot::colormap();
5955
5956 auto a = Cmap.size() / (Cmax - Cmin);
5957 auto b = -a * Cmin;
5958
5959 // Plot colored line
5960 ax->hold(matplot::on);
5961 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
5962 ax->plot3({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]},
5963 {Zfine[i], Zfine[i + 1]})
5964 ->line_width(2)
5965 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
5966 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
5967 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
5968 ax->hold(matplot::off);
5969 matplot::colorbar(ax);
5970 } else
5971 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5972 } else {
5973 // Plot curve
5974 ax->plot3(Xfine, Yfine, Zfine, "b-")->line_width(2);
5975 }
5976
5977 bool cnet = false;
5978 if (json.contains("cnet"))
5979 cnet = json["cnet"].get<bool>();
5980
5981 if (cnet) {
5982 // Create control net
5983#ifdef __clang__
5984 auto coeffs_cpu =
5985 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5986 BSplineCore::coeffs(), torch::kCPU);
5987 auto xAccessor = std::get<1>(coeffs_cpu)[0];
5988 auto yAccessor = std::get<1>(coeffs_cpu)[1];
5989 auto zAccessor = std::get<1>(coeffs_cpu)[2];
5990#else
5991 auto [coeffs0_cpu, xAccessor] =
5992 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5993 BSplineCore::coeffs(0), torch::kCPU);
5994 auto [coeffs1_cpu, yAccessor] =
5995 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5996 BSplineCore::coeffs(1), torch::kCPU);
5997 auto [coeffs2_cpu, zAccessor] =
5998 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
5999 BSplineCore::coeffs(2), torch::kCPU);
6000#endif
6001
6002 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6003 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6004 matplot::vector_1d Z(BSplineCore::ncoeffs(0), 0.0);
6005
6006#pragma omp parallel for simd
6007 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6008 X[i] = xAccessor[i];
6009 Y[i] = yAccessor[i];
6010 Z[i] = zAccessor[i];
6011 }
6012
6013 // Plot control net
6014 ax->hold(matplot::on);
6015 ax->plot3(X, Y, Z, ".k-")->line_width(1);
6016 ax->hold(matplot::off);
6017 }
6018
6019 // Title
6020 if (json.contains("title"))
6021 ax->title(json["title"].get<std::string>());
6022 else
6023 ax->title("BSpline: [0,1] -> R^3");
6024
6025 // X-axis label
6026 if (json.contains("xlabel"))
6027 ax->xlabel(json["xlabel"].get<std::string>());
6028 else
6029 ax->xlabel("x");
6030
6031 // Y-axis label
6032 if (json.contains("ylabel"))
6033 ax->ylabel(json["ylabel"].get<std::string>());
6034 else
6035 ax->ylabel("y");
6036
6037 // Z-axis label
6038 if (json.contains("zlabel"))
6039 ax->zlabel(json["zlabel"].get<std::string>());
6040 else
6041 ax->zlabel("z");
6042
6043 return f;
6044 }
6045
6046 else if constexpr (BSplineCore::parDim() == 2 &&
6047 BSplineCore::geoDim() == 2) {
6048
6049 //
6050 // mapping: [0,1]^2 -> R^2
6051 //
6052
6053 int64_t res0 = BSplineCore::ncoeffs(0);
6054 int64_t res1 = BSplineCore::ncoeffs(1);
6055 if (json.contains("res0"))
6056 res0 = json["res0"].get<int64_t>();
6057 if (json.contains("res1"))
6058 res1 = json["res1"].get<int64_t>();
6059
6060 // Create figure with specified backend
6061 auto f = matplot::figure<Backend>(true);
6062 auto ax = f->current_axes();
6063
6064 // Create mesh
6065 utils::TensorArray<2> meshgrid = utils::to_array<2>(
6066 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
6067 torch::linspace(0, 1, res1, BSplineCore::options_)},
6068 "xy"));
6069 auto Coords = BSplineCore::eval(meshgrid);
6070#ifdef __clang__
6071 auto Coords_cpu =
6072 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6073 Coords, torch::kCPU);
6074 auto XAccessor = std::get<1>(Coords_cpu)[0];
6075 auto YAccessor = std::get<1>(Coords_cpu)[1];
6076#else
6077 auto [Coords0_cpu, XAccessor] =
6078 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6079 Coords(0), torch::kCPU);
6080 auto [Coords1_cpu, YAccessor] =
6081 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6082 Coords(1), torch::kCPU);
6083#endif
6084
6085 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
6086 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
6087 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
6088
6089#pragma omp parallel for simd collapse(2)
6090 for (int64_t i = 0; i < res0; ++i)
6091 for (int64_t j = 0; j < res1; ++j) {
6092 Xfine[j][i] = XAccessor[j][i];
6093 Yfine[j][i] = YAccessor[j][i];
6094 }
6095
6096 // Plot (colored) mesh
6097 if ((void *)this != (void *)&color) {
6098 if constexpr (BSplineCoreColor::geoDim() == 1) {
6099
6100 // Create colors
6101 auto Color = color.eval(meshgrid);
6102#ifdef __clang__
6103 auto Color_cpu =
6104 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6105 2>(Color, torch::kCPU);
6106 auto CAccessor = std::get<1>(Color_cpu)[0];
6107#else
6108 auto [Color0_cpu, CAccessor] =
6109 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6110 2>(Color(0), torch::kCPU);
6111#endif
6112
6113 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
6114
6115#pragma omp parallel for simd collapse(2)
6116 for (int64_t i = 0; i < res0; ++i)
6117 for (int64_t j = 0; j < res1; ++j)
6118 Cfine[j][i] = CAccessor[j][i];
6119
6120 // Plot colored mesh
6121 matplot::view(2);
6122 ax->mesh(Xfine, Yfine, Cfine)->hidden_3d(false);
6123 matplot::colorbar(ax);
6124 } else
6125 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6126 } else {
6127 // Plot unicolor mesh
6128 matplot::view(2);
6129 matplot::colormap(std::vector<std::vector<double>>{{ 0.0, 0.0, 1.0 }});
6130 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
6131 }
6132
6133 bool cnet = false;
6134 if (json.contains("cnet"))
6135 cnet = json["cnet"].get<bool>();
6136
6137 if (cnet) {
6138 // Create control net
6139#ifdef __clang__
6140 auto coeffs_cpu =
6141 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6142 BSplineCore::coeffs(), torch::kCPU);
6143 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6144 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6145#else
6146 auto [coeffs0_cpu, xAccessor] =
6147 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6148 BSplineCore::coeffs(0), torch::kCPU);
6149 auto [coeffs1_cpu, yAccessor] =
6150 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6151 BSplineCore::coeffs(1), torch::kCPU);
6152#endif
6153
6154 matplot::vector_2d X(BSplineCore::ncoeffs(1),
6155 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6156 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
6157 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6158 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
6159 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6160
6161#pragma omp parallel for simd collapse(2)
6162 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
6163 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
6164 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
6165 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
6166 }
6167
6168 // Plot control net
6169 ax->hold(matplot::on);
6170 ax->surf(X, Y, Z)
6171 ->palette_map_at_surface(true)
6172 .face_alpha(0)
6173 .line_width(1);
6174 for (std::size_t i = 0; i < X.size(); ++i)
6175 ax->scatter3(X[i], Y[i], Z[i], "k.");
6176 ax->hold(matplot::off);
6177 }
6178
6179 // Title
6180 if (json.contains("title"))
6181 ax->title(json["title"].get<std::string>());
6182 else
6183 ax->title("BSpline: [0,1]^2 -> R^2");
6184
6185 // X-axis label
6186 if (json.contains("xlabel"))
6187 ax->xlabel(json["xlabel"].get<std::string>());
6188 else
6189 ax->xlabel("x");
6190
6191 // Y-axis label
6192 if (json.contains("ylabel"))
6193 ax->ylabel(json["ylabel"].get<std::string>());
6194 else
6195 ax->ylabel("y");
6196
6197 // Z-axis label
6198 if (json.contains("zlabel"))
6199 ax->zlabel(json["zlabel"].get<std::string>());
6200 else
6201 ax->zlabel("z");
6202
6203 return f;
6204 }
6205
6206 else if constexpr (BSplineCore::parDim() == 2 &&
6207 BSplineCore::geoDim() == 3) {
6208
6210 // mapping: [0,1]^2 -> R^3
6212
6213 int64_t res0 = BSplineCore::ncoeffs(0);
6214 int64_t res1 = BSplineCore::ncoeffs(1);
6215 if (json.contains("res0"))
6216 res0 = json["res0"].get<int64_t>();
6217 if (json.contains("res1"))
6218 res1 = json["res1"].get<int64_t>();
6219
6220 // Create figure with specified backend
6221 auto f = matplot::figure<Backend>(true);
6222 auto ax = f->current_axes();
6223
6224 // Create surface
6225 utils::TensorArray<2> meshgrid = utils::to_array<2>(
6226 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
6227 torch::linspace(0, 1, res1, BSplineCore::options_)},
6228 "xy"));
6229 auto Coords = BSplineCore::eval(meshgrid);
6230#ifdef __clang__
6231 auto Coords_cpu =
6232 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6233 Coords, torch::kCPU);
6234 auto XAccessor = std::get<1>(Coords_cpu)[0];
6235 auto YAccessor = std::get<1>(Coords_cpu)[1];
6236 auto ZAccessor = std::get<1>(Coords_cpu)[2];
6237#else
6238 auto [Coords0_cpu, XAccessor] =
6239 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6240 Coords(0), torch::kCPU);
6241 auto [Coords1_cpu, YAccessor] =
6242 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6243 Coords(1), torch::kCPU);
6244 auto [Coords2_cpu, ZAccessor] =
6245 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6246 Coords(2), torch::kCPU);
6247#endif
6248
6249 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
6250 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
6251 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
6252
6253#pragma omp parallel for simd collapse(2)
6254 for (int64_t i = 0; i < res0; ++i)
6255 for (int64_t j = 0; j < res1; ++j) {
6256 Xfine[j][i] = XAccessor[j][i];
6257 Yfine[j][i] = YAccessor[j][i];
6258 Zfine[j][i] = ZAccessor[j][i];
6259 }
6260
6261 // Plot (colored) surface
6262 if ((void *)this != (void *)&color) {
6263 if constexpr (BSplineCoreColor::geoDim() == 1) {
6264
6265 // Create colors
6266 auto Color = color.eval(meshgrid);
6267#ifdef __clang__
6268 auto Color_cpu =
6269 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6270 2>(Color, torch::kCPU);
6271 auto CAccessor = std::get<1>(Color_cpu)[0];
6272#else
6273 auto [Color_cpu, CAccessor] =
6274 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6275 2>(Color(0), torch::kCPU);
6276#endif
6277
6278 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
6279
6280#pragma omp parallel for simd collapse(2)
6281 for (int64_t i = 0; i < res0; ++i)
6282 for (int64_t j = 0; j < res1; ++j) {
6283 Cfine[j][i] = CAccessor[j][i];
6284 }
6285
6286 // Plot colored surface
6287 ax->mesh(Xfine, Yfine, Zfine, Cfine)->hidden_3d(false);
6288 matplot::colorbar(ax);
6289 } else
6290 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6291 } else {
6292 // Plot unicolor surface
6293 matplot::colormap(std::vector<std::vector<double>>{{ 0.0, 0.0, 1.0 }});
6294 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
6295 }
6296
6297 bool cnet = false;
6298 if (json.contains("cnet"))
6299 cnet = json["cnet"].get<bool>();
6300
6301 if (cnet) {
6302 // Create control net
6303#ifdef __clang__
6304 auto coeffs_cpu =
6305 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6306 BSplineCore::coeffs(), torch::kCPU);
6307 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6308 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6309 auto zAccessor = std::get<1>(coeffs_cpu)[2];
6310#else
6311 auto [coeffs0_cpu, xAccessor] =
6312 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6313 BSplineCore::coeffs(0), torch::kCPU);
6314 auto [coeffs1_cpu, yAccessor] =
6315 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6316 BSplineCore::coeffs(1), torch::kCPU);
6317 auto [coeffs2_cpu, zAccessor] =
6318 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6319 BSplineCore::coeffs(2), torch::kCPU);
6320#endif
6321
6322 matplot::vector_2d X(BSplineCore::ncoeffs(1),
6323 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6324 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
6325 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6326 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
6327 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6328
6329#pragma omp parallel for simd collapse(2)
6330 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
6331 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
6332 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
6333 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
6334 Z[j][i] = zAccessor[j * BSplineCore::ncoeffs(0) + i];
6335 }
6336
6337 // Plot control net
6338 ax->hold(matplot::on);
6339 ax->surf(X, Y, Z)
6340 ->palette_map_at_surface(true)
6341 .face_alpha(0)
6342 .line_width(1);
6343 for (std::size_t i = 0; i < X.size(); ++i)
6344 ax->scatter3(X[i], Y[i], Z[i], "k.");
6345 ax->hold(matplot::off);
6346 }
6347
6348 // Title
6349 if (json.contains("title"))
6350 ax->title(json["title"].get<std::string>());
6351 else
6352 ax->title("BSpline: [0,1]^2 -> R^3");
6353
6354 // X-axis label
6355 if (json.contains("xlabel"))
6356 ax->xlabel(json["xlabel"].get<std::string>());
6357 else
6358 ax->xlabel("x");
6359
6360 // Y-axis label
6361 if (json.contains("ylabel"))
6362 ax->ylabel(json["ylabel"].get<std::string>());
6363 else
6364 ax->ylabel("y");
6365
6366 // Z-axis label
6367 if (json.contains("zlabel"))
6368 ax->zlabel(json["zlabel"].get<std::string>());
6369 else
6370 ax->zlabel("z");
6371
6372 return f;
6373 }
6374
6375 else
6376 throw std::runtime_error(
6377 "Unsupported combination of parametric/geometric dimensions");
6378#else
6379 throw std::runtime_error(
6380 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6381#endif
6382 }
6383
6394#ifdef IGANET_WITH_MATPLOT
6395 template <typename Backend = matplot::backend::gnuplot,
6396 typename BSplineCoreColor>
6397#else
6398 template <typename Backend = void, typename BSplineCoreColor>
6399#endif
6400 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
6402 const nlohmann::json &json = {}) const {
6403
6404#ifdef IGANET_WITH_MATPLOT
6405 auto f = plot<Backend>(color, json);
6406 auto ax = f->current_axes();
6407
6408#ifdef __clang__
6409 auto xi_cpu =
6410 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6411 xi, torch::kCPU);
6412 auto xiAccessor = std::get<1>(xi_cpu);
6413#else
6414 auto [xi_cpu, xiAccessor] =
6415 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6416 xi, torch::kCPU);
6417#endif
6418
6419 if constexpr (BSplineCore::parDim_ == 1) {
6420 matplot::vector_1d X(xi[0].size(0), 0.0);
6421 matplot::vector_1d Y(xi[0].size(0), 0.0);
6422
6423#pragma omp parallel for simd
6424 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6425 X[i] = xiAccessor[0][i];
6426 }
6427
6428 ax->hold(matplot::on);
6429 ax->scatter(X, Y, ".");
6430 ax->hold(matplot::off);
6431 } else if constexpr (BSplineCore::parDim_ == 2) {
6432 matplot::vector_1d X(xi[0].size(0), 0.0);
6433 matplot::vector_1d Y(xi[0].size(0), 0.0);
6434 matplot::vector_1d Z(xi[0].size(0), 0.0);
6435
6436#pragma omp parallel for simd
6437 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6438 X[i] = xiAccessor[0][i];
6439 Y[i] = xiAccessor[1][i];
6440 }
6441
6442 ax->hold(matplot::on);
6443 ax->scatter3(X, Y, Z, ".");
6444 ax->hold(matplot::off);
6445 } else if constexpr (BSplineCore::parDim_ == 3) {
6446 matplot::vector_1d X(xi[0].size(0), 0.0);
6447 matplot::vector_1d Y(xi[0].size(0), 0.0);
6448 matplot::vector_1d Z(xi[0].size(0), 0.0);
6449
6450#pragma omp parallel for simd
6451 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6452 X[i] = xiAccessor[0][i];
6453 Y[i] = xiAccessor[1][i];
6454 Z[i] = xiAccessor[2][i];
6455 }
6456
6457 ax->hold(matplot::on);
6458 ax->scatter3(X, Y, Z, ".");
6459 ax->hold(matplot::off);
6460 } else
6461 throw std::runtime_error("Invalid parametric dimension");
6462
6463 return f;
6464#else
6465 throw std::runtime_error(
6466 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6467#endif
6468 }
6469
6480#ifdef IGANET_WITH_MATPLOT
6481 template <typename Backend = matplot::backend::gnuplot,
6482 typename BSplineCoreColor>
6483#else
6484 template <typename Backend = void, typename BSplineCoreColor>
6485#endif
6486 inline auto plot(
6488 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
6489 const nlohmann::json &json = {}) const {
6490
6491#ifdef IGANET_WITH_MATPLOT
6492 auto f = plot<Backend>(color, json);
6493 auto ax = f->current_axes();
6494
6495 for (const auto &xi : xi) {
6496#ifdef __clang__
6497 auto xi_cpu =
6498 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6499 xi, torch::kCPU);
6500 auto xiAccessor = std::get<1>(xi_cpu);
6501#else
6502 auto [xi_cpu, xiAccessor] =
6503 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6504 xi, torch::kCPU);
6505#endif
6506
6507 if constexpr (BSplineCore::parDim_ == 1) {
6508 matplot::vector_1d X(xi[0].size(0), 0.0);
6509 matplot::vector_1d Y(xi[0].size(0), 0.0);
6510
6511#pragma omp parallel for simd
6512 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6513 X[i] = xiAccessor[0][i];
6514 }
6515
6516 ax->hold(matplot::on);
6517 ax->scatter(X, Y, ".");
6518 ax->hold(matplot::off);
6519 } else if constexpr (BSplineCore::parDim_ == 2) {
6520 matplot::vector_1d X(xi[0].size(0), 0.0);
6521 matplot::vector_1d Y(xi[0].size(0), 0.0);
6522 matplot::vector_1d Z(xi[0].size(0), 0.0);
6523
6524#pragma omp parallel for simd
6525 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6526 X[i] = xiAccessor[0][i];
6527 Y[i] = xiAccessor[1][i];
6528 }
6529
6530 ax->hold(matplot::on);
6531 ax->scatter3(X, Y, Z, ".");
6532 ax->hold(matplot::off);
6533 } else if constexpr (BSplineCore::parDim_ == 3) {
6534 matplot::vector_1d X(xi[0].size(0), 0.0);
6535 matplot::vector_1d Y(xi[0].size(0), 0.0);
6536 matplot::vector_1d Z(xi[0].size(0), 0.0);
6537
6538#pragma omp parallel for simd
6539 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6540 X[i] = xiAccessor[0][i];
6541 Y[i] = xiAccessor[1][i];
6542 Z[i] = xiAccessor[2][i];
6543 }
6544
6545 ax->hold(matplot::on);
6546 ax->scatter3(X, Y, Z, ".");
6547 ax->hold(matplot::off);
6548
6549 } else
6550 throw std::runtime_error("Invalid parametric dimension");
6551 }
6552 return f;
6553#else
6554 throw std::runtime_error(
6555 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6556#endif
6557 }
6558
6560 inline virtual void
6561 pretty_print(std::ostream &os = Log(log::info)) const noexcept override {
6562 os << name() << "(\nparDim = " << BSplineCore::parDim()
6563 << ", geoDim = " << BSplineCore::geoDim() << ", degrees = ";
6564
6565 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6566 os << BSplineCore::degree(i) << "x";
6567 if (BSplineCore::parDim() > 0)
6568 os << BSplineCore::degree(BSplineCore::parDim() - 1);
6569 else
6570 os << 0;
6571
6572 os << ", knots = ";
6573 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6574 os << BSplineCore::nknots(i) << "x";
6575 if (BSplineCore::parDim() > 0)
6576 os << BSplineCore::nknots(BSplineCore::parDim() - 1);
6577 else
6578 os << 0;
6579
6580 os << ", coeffs = ";
6581 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6582 os << BSplineCore::ncoeffs(i) << "x";
6583 if (BSplineCore::parDim() > 0)
6584 os << BSplineCore::ncoeffs(BSplineCore::parDim() - 1);
6585 else
6586 os << 1;
6587
6588 os << ", options = "
6589 << static_cast<torch::TensorOptions>(BSplineCore::options_);
6590
6591 if (is_verbose(os)) {
6592 os << "\nknots [ ";
6593 for (const torch::Tensor &knots : BSplineCore::knots()) {
6594 os << (knots.is_view() ? "view/" : "owns/");
6595 os << (knots.is_contiguous() ? "cont " : "non-cont ");
6596 }
6597 if (BSplineCore::parDim() > 0)
6598 os << "] = " << BSplineCore::knots();
6599 else
6600 os << "] = {}";
6601
6602 os << "\ncoeffs [ ";
6603 for (const torch::Tensor &coeffs : BSplineCore::coeffs()) {
6604 os << (coeffs.is_view() ? "view/" : "owns/");
6605 os << (coeffs.is_contiguous() ? "cont " : "non-cont ");
6606 }
6607 if (BSplineCore::ncumcoeffs() > 0)
6608 os << "] = " << BSplineCore::coeffs_view();
6609 else
6610 os << "] = {}";
6611 }
6612
6613 os << "\n)";
6614 }
6615
6624
6625 BSplineCommon result{*this};
6626
6627 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6628 result.coeffs(i) += other.coeffs(i);
6629
6630 return result;
6631 }
6632
6642
6643 BSplineCommon result{*this};
6644
6645 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6646 result.coeffs(i) -= other.coeffs(i);
6647
6648 return result;
6649 }
6650
6653 BSplineCommon operator*(typename BSplineCore::value_type s) const {
6654
6655 BSplineCommon result{*this};
6656
6657 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6658 result.coeffs(i) *= s;
6659
6660 return result;
6661 }
6662
6666 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
6667 const {
6668
6669 BSplineCommon result{*this};
6670
6671 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6672 result.coeffs(i) *= v[i];
6673
6674 return result;
6675 }
6676
6679 BSplineCommon operator/(typename BSplineCore::value_type s) const {
6680
6681 BSplineCommon result{*this};
6682
6683 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6684 result.coeffs(i) /= s;
6685
6686 return result;
6687 }
6688
6692 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
6693 const {
6694
6695 BSplineCommon result{*this};
6696
6697 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6698 result.coeffs(i) /= v[i];
6699
6700 return result;
6701 }
6702
6710
6711 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6712 BSplineCore::coeffs(i) += other.coeffs(i);
6713
6714 return *this;
6715 }
6716
6724
6725 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6726 BSplineCore::coeffs(i) -= other.coeffs(i);
6727
6728 return *this;
6729 }
6730
6732 BSplineCommon &operator*=(typename BSplineCore::value_type s) {
6733
6734 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6735 BSplineCore::coeffs(i) *= s;
6736
6737 return *this;
6738 }
6739
6742 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
6743
6744 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6745 BSplineCore::coeffs(i) *= v[i];
6746
6747 return *this;
6748 }
6749
6751 BSplineCommon &operator/=(typename BSplineCore::value_type s) {
6752
6753 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6754 BSplineCore::coeffs(i) /= s;
6755
6756 return *this;
6757 }
6758
6761 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
6762
6763 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
6764 BSplineCore::coeffs(i) /= v[i];
6765
6766 return *this;
6767 }
6768};
6769
6771template <typename real_t, short_t GeoDim, short_t... Degrees>
6773 BSplineCommon<UniformBSplineCore<real_t, GeoDim, Degrees...>>;
6774
6776template <typename real_t, short_t GeoDim, short_t... Degrees>
6777inline std::ostream &
6778operator<<(std::ostream &os,
6780 obj.pretty_print(os);
6781 return os;
6782}
6783
6785template <typename real_t, short_t GeoDim, short_t... Degrees>
6787 BSplineCommon<NonUniformBSplineCore<real_t, GeoDim, Degrees...>>;
6788
6790template <typename real_t, short_t GeoDim, short_t... Degrees>
6791inline std::ostream &
6792operator<<(std::ostream &os,
6794 obj.pretty_print(os);
6795 return os;
6796}
6797} // namespace iganet
Compile-time block tensor.
B-spline (common high-level functionality)
Definition bspline.hpp:3288
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:5164
auto scale(typename BSplineCore::value_type s, int dim=-1)
Scales the B-spline object by a scalar.
Definition bspline.hpp:3596
auto translate(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Translates the B-spline object by a vector.
Definition bspline.hpp:3614
BSplineCommon operator/(typename BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:6679
auto diff(const BSplineCommon &other, int dim=-1)
Computes the difference between two compatible B-spline objects.
Definition bspline.hpp:3543
auto scale(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the B-spline object by a vector.
Definition bspline.hpp:3607
BSplineCommon & operator*=(typename BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:6732
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:3985
auto ihess(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4801
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:3866
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:3795
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:4895
auto ilapl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5392
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:4792
BSplineCommon & operator*=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:6741
auto idiv(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the divergence of the B-spline object with respect to the physical variab...
Definition bspline.hpp:4216
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:4428
auto plot(const BSplineCommon< BSplineCoreColor > &color, const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:6486
auto div(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the divergence of the B-spline object with respect to the parametric vari...
Definition bspline.hpp:4083
auto rotate(std::array< typename BSplineCore::value_type, 3 > angle)
Rotates the B-spline object by three angles in 3d.
Definition bspline.hpp:3638
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:6665
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:3718
auto rotate(typename BSplineCore::value_type angle)
Rotates the B-spline object by an angle in 2d.
Definition bspline.hpp:3622
BSplineCommon & operator/=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:6760
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:5325
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:5118
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:4032
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:5210
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:5015
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:5067
auto hess(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Hessian of the B-spline object in the points xi with respect to the p...
Definition bspline.hpp:4633
std::unique_ptr< BSplineCommon > uPtr
Unique pointer for BSplineCommon.
Definition bspline.hpp:3327
BSplineCommon(const BSplineCommon &other, bool clone)
Copy/clone constructor.
Definition bspline.hpp:3333
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:4254
static Ptr make_unique(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3372
auto lapl(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Laplacian of the B-spline object in the points xi with respect to the...
Definition bspline.hpp:5256
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:5435
static Ptr make_shared(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3450
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:4338
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:4301
auto curl(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the curl of the B-spline object with respect to the parametric variables.
Definition bspline.hpp:3800
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:4624
auto igrad(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4489
static Ptr make_unique(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3389
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:3738
auto abs_diff(const BSplineCommon &other, int dim=-1)
Computes the absolute difference between two compatible B-spline objects.
Definition bspline.hpp:3571
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:4572
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:4152
std::shared_ptr< BSplineCommon > Ptr
Shared pointer for BSplineCommon.
Definition bspline.hpp:3324
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:4731
auto plot(const BSplineCommon< BSplineCoreColor > &color, const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:6400
static Ptr make_unique(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3380
static Ptr make_shared(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3442
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:4680
auto plot(const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:5548
static Ptr make_unique(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3406
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:3831
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:3366
BSplineCommon(BSplineCommon &&)=default
Move constructor.
auto plot(const nlohmann::json &json={}) const
Definition bspline.hpp:5532
BSplineCommon(BSplineCommon &&other, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs)
Move constructor with external coefficients.
Definition bspline.hpp:3356
BSplineCommon & operator+=(const BSplineCommon &other)
Adds the coefficients of another B-spline object.
Definition bspline.hpp:6709
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:5383
auto plot(const BSplineCommon< BSplineCoreColor > &color, const nlohmann::json &json={}) const
Definition bspline.hpp:5586
auto to() const
Returns a copy of the B-spline object with real_t type.
Definition bspline.hpp:3533
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:3698
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:6623
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:3940
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:4843
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:4207
auto ijac(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:5127
auto boundingBox() const
Computes the bounding box of the B-spline object.
Definition bspline.hpp:3672
static Ptr make_shared(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3425
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:4075
auto icurl(const Geometry &G, const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the curl of the B-spline object in the points xi with respect to the phys...
Definition bspline.hpp:3949
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:4387
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:4480
auto jac(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the Jacobian of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4973
BSplineCommon & operator/=(typename BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:6751
static Ptr make_shared(const std::array< int64_t, BSplineCore::parDim_ > &ncoeffs, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3433
static Ptr make_unique(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, enum init init=init::greville, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as unique pointer.
Definition bspline.hpp:3397
auto nv(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the outward pointing normal vector of the B-spline object.
Definition bspline.hpp:3703
BSplineCommon & operator-=(const BSplineCommon &other)
Substracts the coefficients of another B-spline object.
Definition bspline.hpp:6723
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:6641
BSplineCommon(const BSplineCommon &other, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false)
Copy constructor with external coefficients.
Definition bspline.hpp:3340
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:5489
auto grad(const utils::TensorArray< BSplineCore::parDim_ > &xi) const
Returns a block-tensor with the gradient of the B-spline object in the points xi with respect to the ...
Definition bspline.hpp:4351
auto to(torch::Device device) const
Returns a copy of the B-spline object with settings from device.
Definition bspline.hpp:3516
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:4525
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:5247
auto clone() const
Returns a clone of the B-spline object.
Definition bspline.hpp:3481
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:6691
virtual void pretty_print(std::ostream &os=Log(log::info)) const noexcept override
Returns a string representation of the BSplineCommon object.
Definition bspline.hpp:6561
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:3419
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:4964
static Ptr make_shared(const std::array< std::vector< typename BSplineCore::value_type >, BSplineCore::parDim_ > &kv, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false, Options< typename BSplineCore::value_type > options=Options< typename BSplineCore::value_type >{})
Creates a new B-spline object as shared pointer.
Definition bspline.hpp:3459
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:5288
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:4115
BSplineCommon & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:3475
auto plot(const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:5566
auto to(Options< real_t > options) const
Returns a copy of the B-spline object with settings from options.
Definition bspline.hpp:3498
BSplineCommon operator*(typename BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:6653
Abstract patch function base class.
Definition patch.hpp:25
Tensor-product non-uniform B-spline (core functionality)
Definition bspline.hpp:2743
typename Base::template derived_type< NonUniformBSplineCore, degree_elevate > self_type
Deduces the self-type possibly degrees (de-)elevated by the additive constant degree_elevate
Definition bspline.hpp:2765
static constexpr bool is_nonuniform()
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:2784
auto eval(const utils::TensorArray< Base::parDim_ > &xi, const utils::TensorArray< Base::parDim_ > &knot_indices) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2885
auto & from_gismo(BSpline &bspline, bool, bool)
Definition bspline.hpp:3245
auto eval(const torch::Tensor &xi) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2864
auto find_knot_indices(const torch::Tensor &xi) const
Returns the indices of knot spans containing xi
Definition bspline.hpp:2930
real_t value_type
Value type.
Definition bspline.hpp:2750
auto eval(const utils::TensorArray< Base::parDim_ > &xi) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2869
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:3068
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:2818
NonUniformBSplineCore & insert_knots(const utils::TensorArray< Base::parDim_ > &knots)
Returns the B-spline object with refined knot and coefficient vectors.
Definition bspline.hpp:3025
auto eval(const utils::TensorArray< Base::parDim_ > &xi, const utils::TensorArray< Base::parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:2900
UniformBSplineCore< real_t, GeoDim, Degrees... > Base
Base type.
Definition bspline.hpp:2746
auto find_knot_indices(const utils::TensorArray< Base::parDim_ > &xi) const
Returns the indices of knot spans containing xi
Definition bspline.hpp:2938
static constexpr bool is_uniform()
Returns true if the B-spline is uniform.
Definition bspline.hpp:2781
NonUniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:2959
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:2796
void init_knots(const std::array< std::vector< typename Base::value_type >, Base::parDim_ > &kv)
Initializes the B-spline knots.
Definition bspline.hpp:2840
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:2759
NonUniformSplineCore base class.
Definition bspline.hpp:114
The Options class handles the automated determination of dtype from the template argument and the sel...
Definition options.hpp:107
Spline base class.
Definition bspline.hpp:3255
SplineCore base class.
Definition bspline.hpp:108
Tensor-product uniform B-spline (core functionality)
Definition bspline.hpp:204
bool pinned_memory() const noexcept override
Returns the pinned_memory property.
Definition bspline.hpp:302
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:232
utils::TensorArray< parDim_ > find_knot_indices(const utils::TensorArray< parDim_ > &xi) const noexcept
Returns the indices of knot spans containing xi
Definition bspline.hpp:1050
utils::TensorArray< geoDim_ > coeffs_view() const noexcept
Returns an array of views to the coefficient vectors.
Definition bspline.hpp:591
real_t value_type
Value type.
Definition bspline.hpp:248
bool requires_grad() const noexcept override
Returns the requires_grad property.
Definition bspline.hpp:297
utils::TensorArray< parDim_ > knots_
Array storing the knot vectors .
Definition bspline.hpp:236
auto & from_gismo(BSpline &bspline, bool, bool)
Definition bspline.hpp:2710
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:911
utils::TensorArray< parDim_ > & knots() noexcept
Returns a non-constant reference to the array of knot vectors.
Definition bspline.hpp:520
torch::Tensor as_tensor_(std::index_sequence< Is... >) const noexcept
Returns all coefficients as a single tensor.
Definition bspline.hpp:651
void load(const std::string &filename, const std::string &key="bspline")
Loads the B-spline from file.
Definition bspline.hpp:1810
UniformBSplineCore(const UniformBSplineCore< other_t, GeoDim, Degrees... > &other, Options< real_t > options=Options< real_t >{})
Copy constructor.
Definition bspline.hpp:445
torch::Layout layout() const noexcept override
Returns the layout property.
Definition bspline.hpp:292
const Options< real_t > & options() const noexcept
Returns a constant reference to the B-spline object's options.
Definition bspline.hpp:343
static constexpr const short_t & degree(short_t i) noexcept
Returns a constant reference to the degree in the -th dimension.
Definition bspline.hpp:492
const std::array< int64_t, parDim_ > & nknots() const noexcept
Returns a constant reference to the array of knot vector dimensions.
Definition bspline.hpp:537
derived_type< UniformBSplineCore, degree_elevate > self_type
Deduces the self-type possibly degrees (de-)elevated by the additive constant degree_elevate
Definition bspline.hpp:262
torch::Tensor as_tensor() const noexcept override
Returns all coefficients as a single tensor.
Definition bspline.hpp:659
auto find_coeff_indices(const torch::Tensor &indices) const
Returns the indices of the coefficients corresponding to the knot indices indices
Definition bspline.hpp:1077
UniformBSplineCore & from_json(const nlohmann::json &json)
Updates the B-spline object from JSON object.
Definition bspline.hpp:1439
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:780
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:1150
const torch::Tensor & coeffs(short_t i) const noexcept
Returns a constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:566
utils::TensorArray< geoDim_ > coeffs_
Array storing the coefficients of the control net , .
Definition bspline.hpp:241
static constexpr short_t parDim() noexcept
Returns the parametric dimension.
Definition bspline.hpp:471
static constexpr bool is_uniform() noexcept
Returns true if the B-spline is uniform.
Definition bspline.hpp:312
int64_t ncoeffs(short_t i) const noexcept
Returns the total number of coefficients in the -th direction.
Definition bspline.hpp:641
auto find_coeff_indices(const utils::TensorArray< parDim_ > &indices) const
Returns the indices of the coefficients corresponding to the knot indices indices
Definition bspline.hpp:1087
auto eval(const utils::TensorArray< parDim_ > &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:890
const auto coeffs_view(short_t i) const noexcept
Returns a view to the coefficient vector in the -th dimension.
Definition bspline.hpp:604
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:2199
UniformBSplineCore & from_xml(const pugi::xml_document &doc, int id=0, std::string label="", int index=-1)
Updates the B-spline object from XML object.
Definition bspline.hpp:1596
torch::Dtype dtype() const noexcept override
Returns the dtype property.
Definition bspline.hpp:287
UniformBSplineCore & from_xml(const pugi::xml_node &root, int id=0, std::string label="", int index=-1)
Updates the B-spline object from XML node.
Definition bspline.hpp:1602
const utils::TensorArray< parDim_ > & knots() const noexcept
Returns a constant reference to the array of knot vectors.
Definition bspline.hpp:501
int64_t nknots(short_t i) const noexcept
Returns the dimension of the knot vector in the -th dimension.
Definition bspline.hpp:547
BSpline & to_gismo(BSpline &bspline, bool, bool) const
Definition bspline.hpp:2628
int64_t constexpr eval_prefactor() const
Computes the prefactor .
Definition bspline.hpp:2043
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:257
std::array< int64_t, parDim_ > nknots_
Array storing the sizes of the knot vectors .
Definition bspline.hpp:223
static constexpr const std::array< short_t, parDim_ > degrees_
Array storing the degrees .
Definition bspline.hpp:219
static constexpr const short_t parDim_
Dimension of the parametric space .
Definition bspline.hpp:211
utils::BlockTensor< torch::Tensor, 1, geoDim_ > eval_from_precomputed(const utils::TensorArray< parDim_ > &basfunc, const torch::Tensor &coeff_indices, int64_t numeval, torch::IntArrayRef sizes) const override
Returns the value of the B-spline object from precomputed basis function.
Definition bspline.hpp:796
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:362
Options< real_t > options_
Options.
Definition bspline.hpp:244
auto greville(bool interior=false) const
Returns the Greville abscissae.
Definition bspline.hpp:711
utils::TensorArray< geoDim_ > & coeffs() noexcept
Returns a non-constant reference to the array of coefficient vectors.
Definition bspline.hpp:575
pugi::xml_node & to_xml(pugi::xml_node &root, int id=0, std::string label="", int index=-1) const
Returns the B-spline object as XML node.
Definition bspline.hpp:1483
static constexpr const std::array< short_t, parDim_ > & degrees() noexcept
Returns a constant reference to the array of degrees.
Definition bspline.hpp:482
int32_t device_index() const noexcept override
Returns the device_index property.
Definition bspline.hpp:282
torch::Device device() const noexcept override
Returns the device property.
Definition bspline.hpp:277
UniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:1971
UniformBSplineCore & set_requires_grad(bool requires_grad) noexcept override
Sets the B-spline object's requires_grad property.
Definition bspline.hpp:325
UniformBSplineCore & transform(const std::function< std::array< real_t, geoDim_ >(const std::array< real_t, parDim_ > &)> transformation)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1236
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:2494
UniformBSplineCore & from_tensor(const torch::Tensor &tensor) noexcept override
Sets all coefficients from a single tensor.
Definition bspline.hpp:685
UniformBSplineCore(Options< real_t > options=Options< real_t >{})
Default constructor.
Definition bspline.hpp:348
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:1865
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:1819
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:2370
bool operator!=(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are different.
Definition bspline.hpp:1958
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:1894
bool operator==(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are the same.
Definition bspline.hpp:1926
nlohmann::json knots_to_json() const
Returns the B-spline object's knots as JSON object.
Definition bspline.hpp:1411
auto eval_basfunc(const utils::TensorArray< parDim_ > &xi) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1126
static constexpr const short_t geoDim_
Dimension of the geometric space .
Definition bspline.hpp:215
const utils::TensorArray< geoDim_ > & coeffs() const noexcept
Returns a constant reference to the array of coefficient vectors.
Definition bspline.hpp:556
int64_t ncumcoeffs() const noexcept
Returns the total number of coefficients.
Definition bspline.hpp:618
void save(const std::string &filename, const std::string &key="bspline") const
Saves the B-spline to file.
Definition bspline.hpp:1857
static constexpr bool is_nonuniform() noexcept
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:315
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:389
UniformBSplineCore & transform(const std::function< std::array< real_t, N >(const std::array< real_t, parDim_ > &)> transformation, std::array< short_t, N > dims)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1317
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:427
const torch::Tensor & knots(short_t i) const noexcept
Returns a constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:511
auto find_knot_indices(const torch::Tensor &xi) const noexcept
Returns the indices of knot spans containing xi
Definition bspline.hpp:1042
auto update_coeffs_univariate(const torch::Tensor &knots, const torch::Tensor &knot_indices) const
Returns the knot insertion matrix.
Definition bspline.hpp:2447
std::array< int64_t, parDim_ > ncoeffs_
Array storing the sizes of the coefficients of the control net .
Definition bspline.hpp:227
nlohmann::json to_json() const override
Returns the B-spline object as JSON object.
Definition bspline.hpp:1397
torch::Tensor & coeffs(short_t i) noexcept
Returns a non-constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:583
void init_coeffs(enum init init)
Initializes the B-spline coefficients.
Definition bspline.hpp:2079
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:936
UniformBSplineCore & from_tensor_(std::index_sequence< Is... >, const torch::Tensor &tensor) noexcept
Sets all coefficients from a single tensor.
Definition bspline.hpp:669
auto eval_basfunc(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1164
static constexpr short_t geoDim() noexcept
Returns the geometric dimension.
Definition bspline.hpp:476
const std::array< int64_t, parDim_ > & ncoeffs() const noexcept
Returns a constant reference to the array of coefficient vector dimensions.
Definition bspline.hpp:631
torch::Tensor & knots(short_t i) noexcept
Returns a non-constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:528
void init_knots()
Initializes the B-spline knots.
Definition bspline.hpp:2052
auto eval(const torch::Tensor &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:882
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:1115
bool is_sparse() const noexcept override
Returns true if the layout is sparse.
Definition bspline.hpp:307
int64_t as_tensor_size() const noexcept override
Returns the size of the single tensor representation of all coefficients.
Definition bspline.hpp:693
pugi::xml_document to_xml(int id=0, std::string label="", int index=-1) const
Returns the B-spline object as XML object.
Definition bspline.hpp:1473
nlohmann::json coeffs_to_json() const
Returns the B-spline object's coefficients as JSON object.
Definition bspline.hpp:1416
UniformSplineCore base class.
Definition bspline.hpp:111
Full qualified name descriptor.
Definition fqn.hpp:26
Concept to identify template parameters that are derived from iganet::NonUniformSplineCore_.
Definition bspline.hpp:126
Concept to identify template parameters that are derived from iganet::Spline_ and iganet::NonUniformS...
Definition bspline.hpp:3268
Concept to identify template parameters that are derived from iganet::SplineCore_.
Definition bspline.hpp:118
Concept to identify template parameters that are derived from iganet::Spline_.
Definition bspline.hpp:3259
Concept to identify template parameters that are derived from iganet::UniformSplineCore_.
Definition bspline.hpp:122
Concept to identify template parameters that are derived from iganet::Spline_ and iganet::UniformSpli...
Definition bspline.hpp:3263
Container utility functions.
Core components.
Full qualified name utility functions.
Integer sequence utility function.
Integer power utility function.
Linear algebra utility functions.
auto kron(T0 &&t0, T1 &&t1)
Computes the Kronecker-product between two or more tensors.
Definition linalg.hpp:210
std::array< torch::Tensor, N > TensorArray
Definition tensorarray.hpp:28
auto to_tensor(const std::array< T, N > &array, torch::IntArrayRef sizes=torch::IntArrayRef{-1}, const iganet::Options< T > &options=iganet::Options< T >{})
Converts an std::array to torch::Tensor.
Definition container.hpp:60
auto kronproduct(T0 &&t0, T1 &&t1)
Computes the directional Kronecker-product between two tensors along the given dimension.
Definition linalg.hpp:61
TensorArray< 1 > TensorArray1
Definition tensorarray.hpp:31
auto to_tensorAccessor(const torch::Tensor &tensor)
Converts a torch::Tensor object to a torch::TensorAccessor object.
Definition tensorarray.hpp:80
T prod(std::array< T, N > array, std::size_t start_index=0, std::size_t stop_index=N - 1)
Computes the (partial) product of all std::array entries.
Definition linalg.hpp:221
constexpr std::array< T, N - M > remove_from_back(std::array< T, N > array)
Derives an std::array object from a given std::array object dropping the last M entries.
Definition container.hpp:376
auto VSlice(torch::Tensor index, int64_t start_offset, int64_t stop_offset)
Vectorized version of torch::indexing::Slice (see https://pytorch.org/cppdocs/notes/tensor_indexing....
Definition vslice.hpp:47
auto dotproduct(T0 &&t0, T1 &&t1)
Computes the directional dot-product between two tensors with summation along the given dimension.
Definition linalg.hpp:37
auto to_ArrayRef(const std::array< T, N > &array)
Converts an std::array<int64_t, N> to a at::IntArrayRef object.
Definition container.hpp:192
Forward declaration of BlockTensor.
Definition blocktensor.hpp:46
Definition boundary.hpp:22
deriv
Enumerator for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:74
constexpr auto operator+(deriv lhs, deriv rhs)
Adds two enumerators for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:91
bool is_verbose(std::ostream &os)
Definition core.hpp:831
constexpr auto operator^(deriv lhs, short_t rhs)
Raises an enumerator for specifying the derivative of B-spline evaluation to a higher exponent.
Definition bspline.hpp:103
struct iganet::@0 Log
Logger.
init
Enumerator for specifying the initialization of B-spline coefficients.
Definition bspline.hpp:55
torch::serialize::InputArchive & operator>>(torch::serialize::InputArchive &archive, UniformBSplineCore< real_t, GeoDim, Degrees... > &obj)
De-serializes a B-spline object.
Definition bspline.hpp:2730
@ none
Definition boundary.hpp:38
short int short_t
Definition core.hpp:74
std::ostream & operator<<(std::ostream &os, const Boundary< Spline > &obj)
Print (as string) a Boundary object.
Definition boundary.hpp:1963
STL namespace.
Options.
Serialization utility functions.
Serialization prototype.
Definition serialize.hpp:31
Computes the power of integer E to the N at compile time.
Definition integer_pow.hpp:22
Reverse index sequence.
Definition index_sequence.hpp:37
TensorArray utility functions.
#define TENSORARRAY_FORALL(obj, func,...)
Definition tensorarray.hpp:159
VSlice utility functions.