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/core.hpp>
24#include <core/options.hpp>
25#include <splines/patch.hpp>
26#include <utils/blocktensor.hpp>
27#include <utils/container.hpp>
28#include <utils/fqn.hpp>
30#include <utils/integer_pow.hpp>
31#include <utils/linalg.hpp>
32#include <utils/serialize.hpp>
33#include <utils/tensorarray.hpp>
34#include <utils/vslice.hpp>
35
40#define GENERATE_EXPR_SEQ (curl)(div)(grad)(hess)(jac)(lapl)
41
46#define GENERATE_IEXPR_SEQ (icurl)(idiv)(igrad)(ihess)(ijac)(ilapl)
47
48namespace iganet {
49
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 = 3,
60 random = 4,
61 greville = 5,
62 linspace = 6
64};
65
72enum class deriv : short_t {
73 func = 0,
74 dx = 1,
75 dy = 10,
76 dz = 100,
77 dt = 1000,
78};
79// clang-format on
80
89inline constexpr auto operator+(deriv lhs, deriv rhs) {
90 return static_cast<deriv>(static_cast<short_t>(lhs) +
91 static_cast<short_t>(rhs));
92}
93
102inline constexpr auto operator^(deriv lhs, short_t rhs) {
103 return static_cast<deriv>(static_cast<short_t>(lhs) *
104 static_cast<short_t>(rhs));
105}
106
107namespace detail {
108
109// @brief Concept to identify template parameters that have a
110// find_knot_indices function
111template <typename T>
112concept HasFindKnotIndices = requires(T t, typename T::eval_type x) {
113 { t.find_knot_indices(x) };
114};
115
116// @brief Concept to identify template parameters that have a
117// find_coeff_indices function
118template <typename T>
119concept HasFindCoeffIndices = requires(T t, typename T::eval_type x) {
120 { t.find_coeff_indices(x) };
121};
122
123} // namespace detail
124
126class SplineCore_ {};
127
130
133
136template <typename T>
137concept SplineCoreType = std::is_base_of_v<SplineCore_, T>;
138
141template <typename T>
142concept UniformSplineCoreType = std::is_base_of_v<UniformSplineCore_, T>;
143
146template <typename T>
147concept NonUniformSplineCoreType = std::is_base_of_v<NonUniformSplineCore_, T>;
148
221template <typename real_t, short_t GeoDim, short_t... Degrees>
223 : public UniformSplineCore_,
224 public utils::Serializable,
225 public BSplinePatch<real_t, GeoDim, sizeof...(Degrees)> {
227 template <typename BSplineCore>
229 friend class BSplineCommon;
230
231protected:
234 static constexpr const short_t parDim_ = sizeof...(Degrees);
235
238 static constexpr const short_t geoDim_ = GeoDim;
239
242 static constexpr const std::array<short_t, parDim_> degrees_ = {Degrees...};
243
246 std::array<int64_t, parDim_> nknots_;
247
250 std::array<int64_t, parDim_> ncoeffs_;
251
255 std::array<int64_t, parDim_> ncoeffs_reverse_;
256
260
265
268
269public:
271 using value_type = real_t;
272
278 template <template <typename, short_t, short_t...> class BSpline,
279 std::make_signed_t<short_t> degree_elevate = 0>
280 using derived_type = BSpline<real_t, GeoDim, (Degrees + degree_elevate)...>;
281
284 template <std::make_signed_t<short_t> degree_elevate = 0>
286
290 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
291 using derived_self_type = UniformBSplineCore<other_t, GeoDim_, Degrees_...>;
292
295 template <typename other_t>
297 UniformBSplineCore<other_t, GeoDim, Degrees...>;
298
300 [[nodiscard]] inline torch::Device device() const noexcept override {
301 return options_.device();
302 }
303
305 [[nodiscard]] inline int32_t device_index() const noexcept override {
306 return options_.device_index();
307 }
308
310 [[nodiscard]] inline torch::Dtype dtype() const noexcept override {
311 return options_.dtype();
312 }
313
315 [[nodiscard]] inline torch::Layout layout() const noexcept override {
316 return options_.layout();
317 }
318
320 [[nodiscard]] inline bool requires_grad() const noexcept override {
321 return options_.requires_grad();
322 }
323
325 [[nodiscard]] inline bool pinned_memory() const noexcept override {
326 return options_.pinned_memory();
327 }
328
330 [[nodiscard]] inline bool is_sparse() const noexcept override {
331 return options_.is_sparse();
332 }
333
335 inline static constexpr bool is_uniform() noexcept { return true; }
336
338 inline static constexpr bool is_nonuniform() noexcept { return false; }
339
347 inline UniformBSplineCore &
348 set_requires_grad(bool requires_grad) noexcept override {
349 if (options_.requires_grad() == requires_grad)
350 return *this;
351
352 for (short_t i = 0; i < parDim_; ++i)
354
355 for (short_t i = 0; i < geoDim_; ++i)
357
358 Options<real_t> tmp(options_.requires_grad(requires_grad));
359 options_.~Options<real_t>();
360 new (&options_) Options<real_t>(tmp);
361
362 return *this;
363 }
364
366 inline const Options<real_t> &options() const noexcept { return options_; }
367
372 : options_(options) {
373 nknots_.fill(0);
374 ncoeffs_.fill(0);
375 ncoeffs_reverse_.fill(0);
376 }
377
385 explicit UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
386 enum init init = init::greville,
389 // Reverse ncoeffs
390 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
391
392 // Initialize knot vectors
393 init_knots();
394
395 // Initialize coefficients
397 }
398
412 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
414 bool clone = false,
417 // Reverse ncoeffs
418 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
419
420 // Initialize knot vectors
421 init_knots();
422
423 // Check compatibility
424 for (short_t i = 0; i < geoDim_; ++i)
425 if (coeffs[i].numel() != ncumcoeffs())
426 throw std::runtime_error("Invalid number of coefficients");
427
428 // Copy/clone coefficients
429 if (clone)
430 for (short_t i = 0; i < geoDim_; ++i)
431 coeffs_[i] = coeffs[i]
432 .clone()
433 .to(options.requires_grad(false))
434 .requires_grad_(options.requires_grad());
435 else
436 for (short_t i = 0; i < geoDim_; ++i)
437 coeffs_[i] = coeffs[i];
438 }
439
450 UniformBSplineCore(const std::array<int64_t, parDim_> &ncoeffs,
454 coeffs_(std::move(coeffs)), options_(options) {
455 // Reverse ncoeffs
456 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
457
458 // Initialize knot vectors
459 init_knots();
460 }
461
467 template <typename other_t>
471 : nknots_(other.nknots()), ncoeffs_(other.ncoeffs()),
473 // Reverse ncoeffs
474 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
475
476 // Clone coefficients
477 for (short_t i = 0; i < geoDim_; ++i)
478 coeffs_[i] = other.coeffs(i)
479 .clone()
480 .to(options.requires_grad(false))
481 .requires_grad_(options.requires_grad());
482
483 // Clone knot vectors
484 for (short_t i = 0; i < parDim_; ++i)
485 knots_[i] = other.knots(i)
486 .clone()
487 .to(options.requires_grad(false))
488 .requires_grad_(options.requires_grad());
489 }
490
492 ~UniformBSplineCore() override = default;
493
497 inline static constexpr short_t parDim() noexcept { return parDim_; }
498
502 inline static constexpr short_t geoDim() noexcept { return geoDim_; }
503
507 inline static constexpr const std::array<short_t, parDim_> &
508 degrees() noexcept {
509 return degrees_;
510 }
511
518 inline static constexpr short_t degree(short_t i) noexcept {
519 assert(i >= 0 && i < parDim_);
520 return degrees_[i];
521 }
522
527 inline const utils::TensorArray<parDim_> &knots() const noexcept {
528 return knots_;
529 }
530
537 [[nodiscard]] inline const torch::Tensor &knots(short_t i) const noexcept {
538 assert(i >= 0 && i < parDim_);
539 return knots_[i];
540 }
541
546 inline utils::TensorArray<parDim_> &knots() noexcept { return knots_; }
547
554 inline torch::Tensor &knots(short_t i) noexcept {
555 assert(i >= 0 && i < parDim_);
556 return knots_[i];
557 }
558
563 inline const std::array<int64_t, parDim_> &nknots() const noexcept {
564 return nknots_;
565 }
566
573 [[nodiscard]] inline int64_t nknots(short_t i) const noexcept {
574 assert(i >= 0 && i < parDim_);
575 return nknots_[i];
576 }
577
582 inline const utils::TensorArray<geoDim_> &coeffs() const noexcept {
583 return coeffs_;
584 }
585
592 [[nodiscard]] inline const torch::Tensor &coeffs(short_t i) const noexcept {
593 assert(i >= 0 && i < geoDim_);
594 return coeffs_[i];
595 }
596
601 inline utils::TensorArray<geoDim_> &coeffs() noexcept { return coeffs_; }
602
609 inline torch::Tensor &coeffs(short_t i) noexcept {
610 assert(i >= 0 && i < geoDim_);
611 return coeffs_[i];
612 }
613
617 inline utils::TensorArray<geoDim_> coeffs_view() const noexcept {
619 for (short_t i = 0; i < geoDim_; ++i)
620 coeffs[i] = coeffs_view(i);
621 return coeffs;
622 }
623
630 inline const auto coeffs_view(short_t i) const noexcept {
631 assert(i >= 0 && i < geoDim_);
632 if constexpr (parDim_ > 1)
633 if (coeffs_[i].dim() > 1)
634 return coeffs_[i].view(utils::to_ArrayRef(ncoeffs_reverse_) + (-1_i64));
635 else
637 else
638 return coeffs_[i];
639 }
640
644 [[nodiscard]] inline int64_t ncumcoeffs() const noexcept {
645 int64_t s = 1;
646
647 for (short_t i = 0; i < parDim_; ++i)
648 s *= ncoeffs(i);
649
650 return s;
651 }
652
657 inline const std::array<int64_t, parDim_> &ncoeffs() const noexcept {
658 return ncoeffs_;
659 }
660
667 [[nodiscard]] inline int64_t ncoeffs(short_t i) const noexcept {
668 assert(i >= 0 && i < parDim_);
669 return ncoeffs_[i];
670 }
671
672private:
676 template <std::size_t... Is>
677 inline torch::Tensor as_tensor_(std::index_sequence<Is...>) const noexcept {
678 return torch::cat({coeffs_[Is]...});
679 }
680
681public:
685 [[nodiscard]] inline torch::Tensor as_tensor() const noexcept override {
686 return as_tensor_(std::make_index_sequence<geoDim_>{});
687 }
688
689private:
693 template <std::size_t... Is>
694 inline UniformBSplineCore &
695 from_tensor_(std::index_sequence<Is...>,
696 const torch::Tensor &tensor) noexcept {
697 ((coeffs_[Is] = tensor.index(
698 {torch::indexing::Slice(Is * ncumcoeffs(), (Is + 1) * ncumcoeffs()),
699 "..."})),
700 ...);
701 return *this;
702 }
703
704public:
710 inline UniformBSplineCore &
711 from_tensor(const torch::Tensor &tensor) noexcept override {
712 return from_tensor_(std::make_index_sequence<geoDim_>{}, tensor);
713 }
714
717 //
719 [[nodiscard]] inline int64_t as_tensor_size() const noexcept override {
720 return geoDim_ * ncumcoeffs();
721 }
722
737 inline auto greville(bool interior = false) const {
738 if constexpr (parDim_ == 0) {
739 return torch::zeros(1, options_);
740 } else {
742
743 // Fill coefficients with the tensor-product of Greville
744 // abscissae values per univariate dimension
745 for (short_t i = 0; i < parDim_; ++i) {
746 coeffs[i] = torch::ones(1, options_);
747
748 for (short_t j = 0; j < parDim_; ++j) {
749 if (i == j) {
750
751 int64_t offset = interior ? 1 : 0;
752 int64_t count = ncoeffs_[j] - (interior ? 2 : 0);
753
754 // idx_base: (count, 1)
755 auto idx_base =
756 torch::arange(
757 count,
758 options_.requires_grad(false).template dtype<int64_t>())
759 .unsqueeze(1);
760
761 // offsets: (1, degree)
762 auto offsets =
763 torch::arange(
764 1, degrees_[j] + 1,
765 options_.requires_grad(false).template dtype<int64_t>())
766 .unsqueeze(0);
767
768 // indices: (count, degree)
769 auto indices = idx_base + offset + offsets;
770
771 // Gather relevant knot values: shape (count, degree)
772 auto gathered = knots_[j]
773 .index_select(0, indices.flatten())
774 .view({count, degrees_[j]});
775
776 // Compute mean along degree dimension (dim=1)
777 auto greville_ = gathered.mean(1);
778
779 coeffs[i] = torch::kron(greville_, coeffs[i]);
780 } else
781 coeffs[i] = torch::kron(
782 torch::ones(ncoeffs_[j] - (interior ? 2 : 0), options_),
783 coeffs[i]);
784 }
785
786 // Enable gradient calculation for non-leaf tensor
787 if (options_.requires_grad())
788 coeffs[i].retain_grad();
789 }
790
791 return coeffs;
792 }
793 }
794
817 eval_from_precomputed(const torch::Tensor &basfunc,
818 const torch::Tensor &coeff_indices, int64_t numeval,
819 torch::IntArrayRef sizes) const override {
820
822
823 for (short_t i = 0; i < geoDim_; ++i)
824 result.set(
826 basfunc,
827 coeffs(i).index_select(0, coeff_indices).view({-1, numeval}))
828 .view(sizes));
829 return result;
830 }
831
834 const torch::Tensor &coeff_indices, int64_t numeval,
835 torch::IntArrayRef sizes) const override {
836
838
839 if constexpr (parDim_ == 0) {
840 for (short_t i = 0; i < geoDim_; ++i)
841 result.set(i, coeffs_[i]);
842 }
843
844 else {
845 // Lambda expression to evaluate the spline function
846 std::function<torch::Tensor(short_t, short_t)> eval_;
847
848 eval_ = [&, this](short_t i, short_t dim) {
849 if (dim == 0) {
850 return torch::matmul(coeffs(i)
851 .index_select(0, coeff_indices)
852 .view({numeval, -1, degrees_[0] + 1}),
853 basfunc[0].view({numeval, -1, 1}));
854 } else {
855 return torch::matmul(
856 (eval_(i, dim - 1)).view({numeval, -1, degrees_[dim] + 1}),
857 basfunc[dim].view({numeval, -1, 1}));
858 }
859 };
860
861 for (short_t i = 0; i < geoDim_; ++i)
862 result.set(i, (eval_(i, parDim_ - 1)).view(sizes));
863 }
864 return result;
865 }
867
918 template <deriv deriv = deriv::func, bool memory_optimized = false>
919 inline auto eval(const torch::Tensor &xi) const {
920 if constexpr (parDim_ == 1)
921 return eval<deriv, memory_optimized>(utils::TensorArray1({xi}));
922 else
923 throw std::runtime_error("Invalid parametric dimension");
924 }
925
926 template <deriv deriv = deriv::func, bool memory_optimized = false>
927 inline auto eval(const utils::TensorArray<parDim_> &xi) const {
928 return eval<deriv, memory_optimized>(xi, find_knot_indices(xi));
929 }
930
931 template <deriv deriv = deriv::func, bool memory_optimized = false>
932 inline auto eval_tr(const torch::Tensor &xi) const {
933 if constexpr (parDim_ == 1)
934 return eval_tr<deriv, memory_optimized>(utils::TensorArray1({xi}));
935 else
936 throw std::runtime_error("Invalid parametric dimension");
937 }
938
939 template <deriv deriv = deriv::func, bool memory_optimized = false>
940 inline auto eval_tr(const utils::TensorArray<parDim_> &xi) const {
941 return eval_tr<deriv, memory_optimized>(xi, find_knot_indices(xi));
942 }
944
960 template <deriv deriv = deriv::func, bool memory_optimized = false>
961 inline auto eval(const utils::TensorArray<parDim_> &xi,
962 const utils::TensorArray<parDim_> &knot_indices) const {
963 return eval<deriv, memory_optimized>(
964 xi, knot_indices, find_coeff_indices<memory_optimized>(knot_indices));
965 }
966
967 template <deriv deriv = deriv::func, bool memory_optimized = false>
969 const utils::TensorArray<parDim_> &knot_indices) const {
970 return eval_tr<deriv, memory_optimized>(
971 xi, knot_indices, find_coeff_indices<memory_optimized>(knot_indices));
972 }
973
992 template <deriv deriv = deriv::func, bool memory_optimized = false>
993 inline auto eval(const utils::TensorArray<parDim_> &xi,
994 const utils::TensorArray<parDim_> &knot_indices,
995 const torch::Tensor &coeff_indices) const {
996
998
999 if constexpr (parDim_ == 0) {
1000 for (short_t i = 0; i < geoDim_; ++i)
1001 if constexpr (deriv == deriv::func)
1002 result.set(i, coeffs_[i]);
1003 else
1004 result.set(i, torch::zeros_like(coeffs_[i]));
1005 return result;
1006 } // parDim == 0
1007
1008 else {
1009
1010 // Check compatibility of arguments
1011 for (short_t i = 0; i < parDim_; ++i)
1012 assert(xi[i].sizes() == knot_indices[i].sizes());
1013 for (short_t i = 1; i < parDim_; ++i)
1014 assert(xi[0].sizes() == xi[i].sizes());
1015
1016 if constexpr (memory_optimized) {
1017 // memory-optimized
1018
1019 if (coeffs(0).dim() > 1)
1020 throw std::runtime_error(
1021 "Memory-optimized evaluation requires single-valued coefficient");
1022
1023 else {
1024 auto basfunc =
1025 eval_basfunc<deriv, memory_optimized>(xi, knot_indices);
1026
1027 // Lambda expression to evaluate the spline function
1028 std::function<torch::Tensor(short_t, short_t)> eval_;
1029
1030 eval_ = [&, this](short_t i, short_t dim) {
1031 if (dim == 0) {
1032 return torch::matmul(
1033 coeffs(i)
1034 .index_select(0, coeff_indices)
1035 .view({xi[0].numel(), -1, degrees_[0] + 1}),
1036 basfunc[0].view({xi[0].numel(), -1, 1}));
1037 } else {
1038 return torch::matmul(
1039 (eval_(i, dim - 1))
1040 .view({xi[0].numel(), -1, degrees_[dim] + 1}),
1041 basfunc[dim].view({xi[0].numel(), -1, 1}));
1042 }
1043 };
1044
1045 for (short_t i = 0; i < geoDim_; ++i)
1046 result.set(i, (eval_(i, parDim_ - 1)).view(xi[0].sizes()));
1047
1048 return result;
1049 } // coeffs(0).dim() > 1
1050 }
1051
1052 else {
1053 // not memory-optimized
1054
1055 auto basfunc = eval_basfunc<deriv, memory_optimized>(xi, knot_indices);
1056
1057 if (coeffs(0).dim() > 1) {
1058 // coeffs has extra dimension
1059 auto sizes = xi[0].sizes() + (-1_i64);
1060 for (short_t i = 0; i < geoDim_; ++i)
1061 result.set(i, utils::dotproduct(basfunc.unsqueeze(-1),
1062 coeffs(i)
1063 .index_select(0, coeff_indices)
1064 .view({-1, xi[0].numel(),
1065 coeffs(i).size(-1)}))
1066 .view(sizes));
1067 } else {
1068 // coeffs does not have extra dimension
1069 for (short_t i = 0; i < geoDim_; ++i)
1070 result.set(i, utils::dotproduct(basfunc,
1071 coeffs(i)
1072 .index_select(0, coeff_indices)
1073 .view({-1, xi[0].numel()}))
1074 .view(xi[0].sizes()));
1075 }
1076 return result;
1077 }
1078 }
1079 }
1080
1081 template <deriv deriv = deriv::func, bool memory_optimized = false>
1083 const utils::TensorArray<parDim_> &knot_indices,
1084 const torch::Tensor &coeff_indices) const {
1085
1087
1088 if constexpr (parDim_ == 0) {
1089 for (short_t i = 0; i < geoDim_; ++i)
1090 if constexpr (deriv == deriv::func)
1091 result.set(i, coeffs_[i]);
1092 else
1093 result.set(i, torch::zeros_like(coeffs_[i]));
1094 return result;
1095 } // parDim == 0
1096
1097 else {
1098
1099 // Check compatibility of arguments
1100 for (short_t i = 0; i < parDim_; ++i)
1101 assert(xi[i].sizes() == knot_indices[i].sizes());
1102 for (short_t i = 1; i < parDim_; ++i)
1103 assert(xi[0].sizes() == xi[i].sizes());
1104
1105 if constexpr (memory_optimized) {
1106 // memory-optimized
1107
1108 if (coeffs(0).dim() > 1)
1109 throw std::runtime_error(
1110 "Memory-optimized evaluation requires single-valued coefficient");
1111
1112 else {
1113 auto basfunc =
1114 eval_basfunc_tr<deriv, memory_optimized>(xi, knot_indices);
1115
1116 // Lambda expression to evaluate the spline function
1117 std::function<torch::Tensor(short_t, short_t)> eval_;
1118
1119 eval_ = [&, this](short_t i, short_t dim) {
1120 if (dim == 0) {
1121 return torch::matmul(
1122 coeffs(i)
1123 .index_select(0, coeff_indices)
1124 .view({xi[0].numel(), -1, degrees_[0] + 1}),
1125 basfunc[0].view({xi[0].numel(), -1, 1}));
1126 } else {
1127 return torch::matmul(
1128 (eval_(i, dim - 1))
1129 .view({xi[0].numel(), -1, degrees_[dim] + 1}),
1130 basfunc[dim].view({xi[0].numel(), -1, 1}));
1131 }
1132 };
1133
1134 for (short_t i = 0; i < geoDim_; ++i)
1135 result.set(i, (eval_(i, parDim_ - 1)).view(xi[0].sizes()));
1136
1137 return result;
1138 } // coeffs(0).dim() > 1
1139 }
1140
1141 else {
1142 // not memory-optimized
1143
1144 auto basfunc = eval_basfunc_tr<deriv, memory_optimized>(xi, knot_indices);
1145
1146 if (coeffs(0).dim() > 1) {
1147 // coeffs has extra dimension
1148 auto sizes = xi[0].sizes() + (-1_i64);
1149 for (short_t i = 0; i < geoDim_; ++i)
1150 result.set(i, utils::dotproduct(basfunc.unsqueeze(-1),
1151 coeffs(i)
1152 .index_select(0, coeff_indices)
1153 .view({-1, xi[0].numel(),
1154 coeffs(i).size(-1)}))
1155 .view(sizes));
1156 } else {
1157 // coeffs does not have extra dimension
1158 for (short_t i = 0; i < geoDim_; ++i)
1159 result.set(i, utils::dotproduct(basfunc,
1160 coeffs(i)
1161 .index_select(0, coeff_indices)
1162 .view({-1, xi[0].numel()}))
1163 .view(xi[0].sizes()));
1164 }
1165 return result;
1166 }
1167 }
1168 }
1169
1188 inline auto find_knot_indices(const torch::Tensor &xi) const noexcept {
1189 if constexpr (parDim_ == 0)
1190 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1191 else
1193 }
1194
1197 if constexpr (parDim_ == 0)
1199 else {
1201
1202 for (short_t i = 0; i < parDim_; ++i)
1203 result[i] =
1204 torch::min(
1205 torch::full_like(xi[i], ncoeffs_[i] - 1, options_),
1206 torch::floor(xi[i] * (ncoeffs_[i] - degrees_[i]) + degrees_[i]))
1207 .to(torch::kInt64);
1208
1209 return result;
1210 }
1211 }
1213
1222 template <bool memory_optimized = false>
1223 inline auto find_coeff_indices(const torch::Tensor &indices) const {
1224 if constexpr (parDim_ == 0)
1225 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1226 else
1227 return find_coeff_indices<memory_optimized>(
1228 utils::TensorArray1({indices}));
1229 }
1230
1231 template <bool memory_optimized = false>
1232 inline auto
1234 using utils::operator-;
1235
1236 if constexpr (parDim_ == 0)
1237 return torch::zeros_like(coeffs_[0]).to(torch::kInt64);
1238 else if constexpr (parDim_ == 1)
1239 return utils::VSlice<memory_optimized>(indices[0].flatten(), -degrees_[0],
1240 1);
1241 else {
1242 return utils::VSlice<memory_optimized>(
1243 TENSORARRAY_FORALL(indices, flatten),
1244 utils::make_array<int64_t>(-degrees_),
1245 utils::make_array<int64_t, parDim_>(1),
1247 }
1248 }
1250
1260 template <deriv deriv = deriv::func, bool memory_optimized = false>
1261 inline auto eval_basfunc(const torch::Tensor &xi) const {
1262 if constexpr (parDim_ == 0) {
1263 if constexpr (deriv == deriv::func)
1264 return torch::ones_like(coeffs_[0]);
1265 else
1266 return torch::zeros_like(coeffs_[0]);
1267 } else
1268 return eval_basfunc<deriv, memory_optimized>(utils::TensorArray1({xi}));
1269 }
1270
1271 template <deriv deriv = deriv::func, bool memory_optimized = false>
1272 inline auto eval_basfunc(const utils::TensorArray<parDim_> &xi) const {
1273 if constexpr (parDim_ == 0) {
1274 if constexpr (deriv == deriv::func)
1275 return torch::ones_like(coeffs_[0]);
1276 else
1277 return torch::zeros_like(coeffs_[0]);
1278 } else
1279 return eval_basfunc<deriv, memory_optimized>(xi, find_knot_indices(xi));
1280 }
1281
1282 template <deriv deriv = deriv::func, bool memory_optimized = false>
1283 inline auto eval_basfunc_tr(const torch::Tensor &xi) const {
1284 if constexpr (parDim_ == 0) {
1285 if constexpr (deriv == deriv::func)
1286 return torch::ones_like(coeffs_[0]);
1287 else
1288 return torch::zeros_like(coeffs_[0]);
1289 } else
1290 return eval_basfunc_tr<deriv, memory_optimized>(utils::TensorArray1({xi}));
1291 }
1292
1293 template <deriv deriv = deriv::func, bool memory_optimized = false>
1294 inline auto eval_basfunc_tr(const utils::TensorArray<parDim_> &xi) const {
1295 if constexpr (parDim_ == 0) {
1296 if constexpr (deriv == deriv::func)
1297 return torch::ones_like(coeffs_[0]);
1298 else
1299 return torch::zeros_like(coeffs_[0]);
1300 } else
1301 return eval_basfunc_tr<deriv, memory_optimized>(xi, find_knot_indices(xi));
1302 }
1304
1317 template <deriv deriv = deriv::func, bool memory_optimized = false>
1318 inline auto eval_basfunc(const torch::Tensor &xi,
1319 const torch::Tensor &knot_indices) const {
1320 if constexpr (parDim_ == 0) {
1321 if constexpr (deriv == deriv::func)
1322 return torch::ones_like(coeffs_[0]);
1323 else
1324 return torch::zeros_like(coeffs_[0]);
1325 } else
1326 return eval_basfunc<deriv, memory_optimized>(
1327 utils::TensorArray1({xi}), utils::TensorArray1({knot_indices}));
1328 }
1329
1330 template <deriv deriv = deriv::func, bool memory_optimized = false>
1331 inline auto eval_basfunc_tr(const torch::Tensor &xi,
1332 const torch::Tensor &knot_indices) const {
1333 if constexpr (parDim_ == 0) {
1334 if constexpr (deriv == deriv::func)
1335 return torch::ones_like(coeffs_[0]);
1336 else
1337 return torch::zeros_like(coeffs_[0]);
1338 } else
1339 return eval_basfunc_tr<deriv, memory_optimized>(
1340 utils::TensorArray1({xi}), utils::TensorArray1({knot_indices}));
1341 }
1342
1343 template <deriv deriv = deriv::func, bool memory_optimized = false>
1344 inline auto
1346 const utils::TensorArray<parDim_> &knot_indices) const {
1347
1348 if constexpr (parDim_ == 0) {
1349 if constexpr (deriv == deriv::func)
1350 return torch::ones_like(coeffs_[0]);
1351 else
1352 return torch::zeros_like(coeffs_[0]);
1353 }
1354
1355 else {
1356 // Check compatibility of arguments
1357 for (short_t i = 0; i < parDim_; ++i)
1358 assert(xi[i].sizes() == knot_indices[i].sizes());
1359 for (short_t i = 1; i < parDim_; ++i)
1360 assert(xi[0].sizes() == xi[i].sizes());
1361
1362 if constexpr (memory_optimized) {
1363
1364 // Lambda expression to evaluate the vector of basis functions
1365 auto basfunc_ = [&,
1366 this]<std::size_t... Is>(std::index_sequence<Is...>) {
1369 static_cast<short_t>(deriv) /
1372 static_cast<short_t>(deriv) /
1374 10>(xi[Is].flatten(),
1375 knot_indices[Is].flatten())
1376 .transpose(0, 1))...};
1377 };
1378
1379 return basfunc_(std::make_index_sequence<parDim_>{});
1380
1381 }
1382
1383 else /* not memory optimize */ {
1384
1385 if constexpr (parDim_ == 1) {
1386 return eval_prefactor<degrees_[0],
1387 static_cast<short_t>(deriv) % 10>() *
1389 static_cast<short_t>(deriv) % 10>(
1390 xi[0].flatten(), knot_indices[0].flatten());
1391
1392 } else {
1393
1394 // Lambda expression to evaluate the cumulated basis function
1395 auto basfunc_ = [&, this]<std::size_t... Is>(
1396 std::index_sequence<Is...>) {
1397 return (1 * ... *
1399 static_cast<short_t>(deriv) /
1401 10>())) *
1404 degrees_[Is], Is,
1405 static_cast<short_t>(deriv) /
1407 xi[Is].flatten(), knot_indices[Is].flatten())...);
1408 };
1409
1410 // Note that the kronecker product must be called in reverse order
1412 }
1413 }
1414 }
1415 }
1416
1417 template <deriv deriv = deriv::func, bool memory_optimized = false>
1418 inline auto
1420 const utils::TensorArray<parDim_> &knot_indices) const {
1421
1422 if constexpr (parDim_ == 0) {
1423 if constexpr (deriv == deriv::func)
1424 return torch::ones_like(coeffs_[0]);
1425 else
1426 return torch::zeros_like(coeffs_[0]);
1427 }
1428
1429 else {
1430 // Check compatibility of arguments
1431 for (short_t i = 0; i < parDim_; ++i)
1432 assert(xi[i].sizes() == knot_indices[i].sizes());
1433 for (short_t i = 1; i < parDim_; ++i)
1434 assert(xi[0].sizes() == xi[i].sizes());
1435
1436 if constexpr (memory_optimized) {
1437
1438 // Lambda expression to evaluate the vector of basis functions
1439 auto basfunc_ = [&,
1440 this]<std::size_t... Is>(std::index_sequence<Is...>) {
1443 static_cast<short_t>(deriv) /
1446 static_cast<short_t>(deriv) /
1448 10>(xi[Is].flatten(),
1449 knot_indices[Is].flatten())
1450 .transpose(0, 1))...};
1451 };
1452
1453 return basfunc_(std::make_index_sequence<parDim_>{});
1454
1455 }
1456
1457 else /* not memory optimize */ {
1458
1459 if constexpr (parDim_ == 1) {
1460 return eval_prefactor<degrees_[0],
1461 static_cast<short_t>(deriv) % 10>() *
1463 static_cast<short_t>(deriv) % 10>(
1464 xi[0].flatten(), knot_indices[0].flatten());
1465
1466 } else {
1467
1468 // Lambda expression to evaluate the cumulated basis function
1469 auto basfunc_ = [&, this]<std::size_t... Is>(
1470 std::index_sequence<Is...>) {
1471 return (1 * ... *
1473 static_cast<short_t>(deriv) /
1475 10>())) *
1476 utils::kronproduct<-1>(
1478 degrees_[Is], Is,
1479 static_cast<short_t>(deriv) /
1481 xi[Is].flatten(), knot_indices[Is].flatten())...);
1482 };
1483
1484 // Note that the kronecker product must be called in reverse order
1486 }
1487 }
1488 }
1489 }
1491
1493 inline UniformBSplineCore &
1494 transform(const std::function<
1495 std::array<real_t, geoDim_>(const std::array<real_t, parDim_> &)>
1496 mapping) {
1497
1498 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1499
1500 // 0D
1501 if constexpr (parDim_ == 0) {
1502 auto c = mapping(std::array<real_t, parDim_>{});
1503 for (short_t d = 0; d < geoDim_; ++d)
1504 coeffs_[d].detach()[0] = c[d];
1505 }
1506
1507 // 1D
1508 else if constexpr (parDim_ == 1) {
1509#pragma omp parallel for
1510 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1511 auto c = mapping(
1512 std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1513 for (short_t d = 0; d < geoDim_; ++d)
1514 coeffs_[d].detach()[i] = c[d];
1515 }
1516 }
1517
1518 // 2D
1519 else if constexpr (parDim_ == 2) {
1520#pragma omp parallel for collapse(2)
1521 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1522 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1523 auto c = mapping(std::array<real_t, parDim_>{
1524 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1525 for (short_t d = 0; d < geoDim_; ++d)
1526 coeffs_[d].detach()[j * ncoeffs_[0] + i] = c[d];
1527 }
1528 }
1529 }
1530
1531 // 3D
1532 else if constexpr (parDim_ == 3) {
1533#pragma omp parallel for collapse(3)
1534 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1535 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1536 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1537 auto c = mapping(std::array<real_t, parDim_>{
1538 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1539 k / real_t(ncoeffs_[2] - 1)});
1540 for (short_t d = 0; d < geoDim_; ++d)
1541 coeffs_[d].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1542 j * ncoeffs_[0] + i] = c[d];
1543 }
1544 }
1545 }
1546 }
1547
1548 // 4D
1549 else if constexpr (parDim_ == 4) {
1550#pragma omp parallel for collapse(4)
1551 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1552 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1553 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1554 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1555 auto c = mapping(std::array<real_t, parDim_>{
1556 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1557 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1558 for (short_t d = 0; d < geoDim_; ++d)
1559 coeffs_[d]
1560 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1561 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1562 i] = c[d];
1563 }
1564 }
1565 }
1566 }
1567 } else
1568 throw std::runtime_error("Unsupported parametric dimension");
1569
1570 return *this;
1571 }
1572
1574 template <std::size_t N>
1575 inline UniformBSplineCore &
1576 transform(const std::function<
1577 std::array<real_t, N>(const std::array<real_t, parDim_> &)>
1578 mapping,
1579 std::array<short_t, N> dims) {
1580
1581 static_assert(parDim_ <= 4, "Unsupported parametric dimension");
1582
1583 // 0D
1584 if constexpr (parDim_ == 0) {
1585 auto c = mapping(std::array<real_t, parDim_>{});
1586 for (std::size_t d = 0; d < N; ++d)
1587 coeffs_[dims[d]].detach()[0] = c[d];
1588 }
1589
1590 // 1D
1591 else if constexpr (parDim_ == 1) {
1592#pragma omp parallel for
1593 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1594 auto c = mapping(
1595 std::array<real_t, parDim_>{i / real_t(ncoeffs_[0] - 1)});
1596 for (std::size_t d = 0; d < N; ++d)
1597 coeffs_[dims[d]].detach()[i] = c[d];
1598 }
1599 }
1600
1601 // 2D
1602 else if constexpr (parDim_ == 2) {
1603#pragma omp parallel for collapse(2)
1604 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1605 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1606 auto c = mapping(std::array<real_t, parDim_>{
1607 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1)});
1608 for (std::size_t d = 0; d < N; ++d)
1609 coeffs_[dims[d]].detach()[j * ncoeffs_[0] + i] = c[d];
1610 }
1611 }
1612 }
1613
1614 // 3D
1615 else if constexpr (parDim_ == 3) {
1616#pragma omp parallel for collapse(3)
1617 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1618 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1619 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1620 auto c = mapping(std::array<real_t, parDim_>{
1621 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1622 k / real_t(ncoeffs_[2] - 1)});
1623 for (std::size_t d = 0; d < N; ++d)
1624 coeffs_[dims[d]].detach()[k * ncoeffs_[0] * ncoeffs_[1] +
1625 j * ncoeffs_[0] + i] = c[d];
1626 }
1627 }
1628 }
1629 }
1630
1631 // 4D
1632 else if constexpr (parDim_ == 4) {
1633#pragma omp parallel for collapse(4)
1634 for (int64_t l = 0; l < ncoeffs_[3]; ++l) {
1635 for (int64_t k = 0; k < ncoeffs_[2]; ++k) {
1636 for (int64_t j = 0; j < ncoeffs_[1]; ++j) {
1637 for (int64_t i = 0; i < ncoeffs_[0]; ++i) {
1638 auto c = mapping(std::array<real_t, parDim_>{
1639 i / real_t(ncoeffs_[0] - 1), j / real_t(ncoeffs_[1] - 1),
1640 k / real_t(ncoeffs_[2] - 1), l / real_t(ncoeffs_[3] - 1)});
1641 for (std::size_t d = 0; d < N; ++d)
1642 coeffs_[dims[d]]
1643 .detach()[l * ncoeffs_[0] * ncoeffs_[1] * ncoeffs_[2] +
1644 k * ncoeffs_[0] * ncoeffs_[1] + j * ncoeffs_[0] +
1645 i] = c[d];
1646 }
1647 }
1648 }
1649 }
1650 } else
1651 throw std::runtime_error("Unsupported parametric dimension");
1652
1653 return *this;
1654 }
1655
1657 [[nodiscard]] inline nlohmann::json to_json() const override {
1658 nlohmann::json json;
1659 json["degrees"] = degrees_;
1660 json["geoDim"] = geoDim_;
1661 json["parDim"] = parDim_;
1662 json["ncoeffs"] = ncoeffs_;
1663 json["nknots"] = nknots_;
1664 json["knots"] = knots_to_json();
1665 json["coeffs"] = coeffs_to_json();
1666
1667 return json;
1668 }
1669
1671 [[nodiscard]] inline nlohmann::json knots_to_json() const {
1672 return ::iganet::utils::to_json<real_t, 1>(knots_);
1673 }
1674
1676 [[nodiscard]] inline nlohmann::json coeffs_to_json() const {
1677 auto coeffs_json = nlohmann::json::array();
1678 for (short_t g = 0; g < geoDim_; ++g) {
1679 auto [coeffs_cpu, coeffs_accessor] =
1680 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
1681
1682 auto json = nlohmann::json::array();
1683
1684 if constexpr (parDim_ == 0) {
1685 json.push_back(coeffs_accessor[0]);
1686 }
1687
1688 else {
1689 for (int64_t i = 0; i < ncumcoeffs(); ++i)
1690 json.push_back(coeffs_accessor[i]);
1691 }
1692
1693 coeffs_json.push_back(json);
1694 }
1695 return coeffs_json;
1696 }
1697
1699 inline UniformBSplineCore &from_json(const nlohmann::json &json) {
1700
1701 if (json["geoDim"].get<short_t>() != geoDim_)
1702 throw std::runtime_error(
1703 "JSON object provides incompatible geometric dimensions");
1704
1705 if (json["parDim"].get<short_t>() != parDim_)
1706 throw std::runtime_error(
1707 "JSON object provides incompatible parametric dimensions");
1708
1709 if (json["degrees"].get<std::array<short_t, parDim_>>() != degrees_)
1710 throw std::runtime_error("JSON object provides incompatible degrees");
1711
1712 nknots_ = json["nknots"].get<std::array<int64_t, parDim_>>();
1713 ncoeffs_ = json["ncoeffs"].get<std::array<int64_t, parDim_>>();
1714
1715 // Reverse ncoeffs
1717 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1718
1719 auto kv = json["knots"].get<std::array<std::vector<real_t>, parDim_>>();
1720
1721 for (short_t i = 0; i < parDim_; ++i)
1722 knots_[i] = utils::to_tensor(kv[i], options_);
1723
1724 auto c = json["coeffs"].get<std::array<std::vector<real_t>, geoDim_>>();
1725
1726 for (short_t i = 0; i < geoDim_; ++i)
1727 coeffs_[i] = utils::to_tensor(c[i], options_);
1728
1729 return *this;
1730 }
1731
1733 [[nodiscard]] inline pugi::xml_document
1734 to_xml(int id = 0, const std::string &label = "", int index = -1) const {
1735 pugi::xml_document doc;
1736 pugi::xml_node root = doc.append_child("xml");
1737 to_xml(root, id, label, index);
1738
1739 return doc;
1740 }
1741
1743 inline pugi::xml_node &to_xml(pugi::xml_node &root, int id = 0,
1744 const std::string &label = "",
1745 int index = -1) const {
1746 // add Geometry node
1747 pugi::xml_node geo = root.append_child("Geometry");
1748
1749 // 0D parametric dimension
1750 if constexpr (parDim_ == 0) {
1751 geo.append_attribute("type") = "Point";
1752
1753 if (id >= 0)
1754 geo.append_attribute("id") = id;
1755
1756 if (index >= 0)
1757 geo.append_attribute("index") = index;
1758
1759 if (!label.empty())
1760 geo.append_attribute("label") = label.c_str();
1761 }
1762
1763 // 1D parametric dimension
1764 else if constexpr (parDim_ == 1) {
1765 geo.append_attribute("type") = "BSpline";
1766
1767 if (id >= 0)
1768 geo.append_attribute("id") = id;
1769
1770 if (index >= 0)
1771 geo.append_attribute("index") = index;
1772
1773 if (!label.empty())
1774 geo.append_attribute("label") = label.c_str();
1775
1776 // add Basis node
1777 pugi::xml_node basis = geo.append_child("Basis");
1778 basis.append_attribute("type") = "BSplineBasis";
1779
1780 // add KnotVector node
1781 pugi::xml_node knots = basis.append_child("KnotVector");
1782 knots.append_attribute("degree") = degrees_[0];
1783
1784 std::stringstream ss;
1785 auto [knots_cpu, knots_accessor] =
1786 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
1787 for (int64_t i = 0; i < nknots_[0]; ++i)
1788 ss << std::to_string(knots_accessor[i])
1789 << (i < nknots_[0] - 1 ? " " : "");
1790 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1791 }
1792
1793 // >1D parametric dimension
1794 else {
1795 geo.append_attribute("type") =
1796 std::string("TensorBSpline").append(std::to_string(parDim_)).c_str();
1797
1798 if (id >= 0)
1799 geo.append_attribute("id") = id;
1800
1801 if (index >= 0)
1802 geo.append_attribute("index") = index;
1803
1804 if (!label.empty())
1805 geo.append_attribute("label") = label.c_str();
1806
1807 // add Basis node
1808 pugi::xml_node bases = geo.append_child("Basis");
1809 bases.append_attribute("type") = std::string("TensorBSplineBasis")
1810 .append(std::to_string(parDim_))
1811 .c_str();
1812
1813 for (short_t index = 0; index < parDim_; ++index) {
1814 pugi::xml_node basis = bases.append_child("Basis");
1815 basis.append_attribute("type") = "BSplineBasis";
1816 basis.append_attribute("index") = index;
1817
1818 // add KnotVector node
1819 pugi::xml_node knots = basis.append_child("KnotVector");
1820 knots.append_attribute("degree") = degrees_[index];
1821
1822 std::stringstream ss;
1823 auto [knots_cpu, knots_accessor] =
1824 utils::to_tensorAccessor<real_t, 1>(knots_[index], torch::kCPU);
1825 for (int64_t i = 0; i < nknots_[index]; ++i)
1826 ss << std::to_string(knots_accessor[i])
1827 << (i < nknots_[index] - 1 ? " " : "");
1828 knots.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1829 }
1830
1831 } // parametric dimension
1832
1833 // add Coefs node
1834 pugi::xml_node coefs = geo.append_child("coefs");
1835 coefs.append_attribute("geoDim") = geoDim_;
1836
1837 auto [coeffs_cpu, coeffs_accessors] =
1838 utils::to_tensorAccessor<real_t, 1>(coeffs_, torch::kCPU);
1839 std::stringstream ss;
1840
1841 if constexpr (parDim_ == 0) {
1842 for (short_t g = 0; g < geoDim_; ++g)
1843 ss << std::to_string(coeffs_accessors[g][0]) << " ";
1844
1845 } else {
1846 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
1847 for (short_t g = 0; g < geoDim_; ++g)
1848 ss << std::to_string(coeffs_accessors[g][i]) << " ";
1849 }
1850
1851 coefs.append_child(pugi::node_pcdata).set_value(ss.str().c_str());
1852
1853 return root;
1854 }
1855
1857 inline UniformBSplineCore &from_xml(const pugi::xml_document &doc, int id = 0,
1858 const std::string &label = "",
1859 int index = -1) {
1860 return from_xml(doc.child("xml"), id, label, index);
1861 }
1862
1864 inline UniformBSplineCore &from_xml(const pugi::xml_node &root, int id = 0,
1865 const std::string &label = "",
1866 int index = -1) {
1867
1868 std::array<bool, std::max(parDim_, short_t{1})> nknots_found{false},
1869 ncoeffs_found{false};
1870
1871 // Loop through all geometry nodes
1872 for (pugi::xml_node geo : root.children("Geometry")) {
1873
1874 // 0D parametric dimension
1875 if constexpr (parDim_ == 0) {
1876
1877 // Check for "Point" with given id, index, label
1878 if (geo.attribute("type").value() == std::string("Point") &&
1879 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1880 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1881 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1882
1883 nknots_found[0] = true;
1884 ncoeffs_found[0] = true;
1885 } // "Point"
1886 else
1887 continue; // try next "Geometry"
1888 }
1889
1890 // 1D parametric dimension
1891 else if constexpr (parDim_ == 1) {
1892
1893 // Check for "BSpline" with given id, index, label
1894 if (geo.attribute("type").value() == std::string("BSpline") &&
1895 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1896 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1897 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1898
1899 // Check for "BSplineBasis"
1900 if (pugi::xml_node basis = geo.child("Basis");
1901 basis.attribute("type").value() == std::string("BSplineBasis")) {
1902
1903 // Check for "KnotVector"
1904 if (pugi::xml_node knots = basis.child("KnotVector");
1905 knots.attribute("degree").as_int() == degrees_[0]) {
1906
1907 std::vector<real_t> kv;
1908 std::string values = std::regex_replace(
1909 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1910 for (auto value = strtok(&values[0], " "); value != nullptr;
1911 value = strtok(nullptr, " "))
1912 kv.push_back(static_cast<real_t>(std::stod(value)));
1913
1915 nknots_[0] = kv.size();
1916 ncoeffs_[0] = nknots_[0] - degrees_[0] - 1;
1917
1918 nknots_found[0] = true;
1919 ncoeffs_found[0] = true;
1920
1921 } // "KnotVector"
1922
1923 } // "BSplineBasis"
1924
1925 } // "Bspline"
1926 else
1927 continue; // try next "Geometry"
1928 }
1929
1930 // >1D parametric dimension
1931 else {
1932
1933 // Check for "TensorBSpline<parDim>" with given id, index, label
1934 if (geo.attribute("type").value() ==
1935 std::string("TensorBSpline").append(std::to_string(parDim_)) &&
1936 (id >= 0 ? geo.attribute("id").as_int() == id : true) &&
1937 (index >= 0 ? geo.attribute("index").as_int() == index : true) &&
1938 (!label.empty() ? geo.attribute("label").value() == label : true)) {
1939
1940 // Check for "TensorBSplineBasis<parDim>"
1941 if (pugi::xml_node bases = geo.child("Basis");
1942 bases.attribute("type").value() ==
1943 std::string("TensorBSplineBasis")
1944 .append(std::to_string(parDim_))) {
1945
1946 // Loop through all basis nodes
1947 for (pugi::xml_node basis : bases.children("Basis")) {
1948
1949 // Check for "BSplineBasis"
1950 if (basis.attribute("type").value() ==
1951 std::string("BSplineBasis")) {
1952
1953 int index = basis.attribute("index").as_int();
1954
1955 // Check for "KnotVector"
1956 if (pugi::xml_node knots = basis.child("KnotVector");
1957 knots.attribute("degree").as_int() == degrees_[index]) {
1958
1959 std::vector<real_t> kv;
1960 std::string values = std::regex_replace(
1961 knots.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
1962
1963 for (auto value = strtok(&values[0], " "); value != nullptr;
1964 value = strtok(nullptr, " "))
1965 kv.push_back(static_cast<real_t>(std::stod(value)));
1966
1967 knots_[index] = utils::to_tensor(kv, options_);
1968 nknots_[index] = kv.size();
1969 ncoeffs_[index] = nknots_[index] - degrees_[index] - 1;
1970
1971 nknots_found[index] = true;
1972 ncoeffs_found[index] = true;
1973
1974 } // "KnotVector"
1975
1976 } // "BSplineBasis"
1977
1978 } // "Basis"
1979
1980 } // "TensorBSplineBasis<parDim>"
1981
1982 } // "TensorBSpline<parDim>"
1983 else
1984 continue; // try next "Geometry"
1985
1986 } // parametric dimension
1987
1988 if (std::any_of(std::begin(nknots_found), std::end(nknots_found),
1989 [](bool i) { return !i; }))
1990 throw std::runtime_error(
1991 "XML object is not compatible with B-spline object");
1992
1993 // Reverse ncoeffs
1995 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
1996
1997 // Fill coefficients with zeros
1998 int64_t size = ncumcoeffs();
1999 for (short_t i = 0; i < geoDim_; ++i)
2000 coeffs_[i] = torch::zeros(size, options_.device(torch::kCPU));
2001
2002 // Check for "coefs"
2003 if (pugi::xml_node coefs = geo.child("coefs")) {
2004
2005 std::string values = std::regex_replace(
2006 coefs.text().get(), std::regex("[\t\r\n\a]+| +"), " ");
2007 auto coeffs_accessors = utils::to_tensorAccessor<real_t, 1>(coeffs_);
2008
2009 if constexpr (parDim_ == 0) {
2010 auto value = strtok(&values[0], " ");
2011
2012 for (short_t g = 0; g < geoDim_; ++g) {
2013 if (value == nullptr)
2014 throw std::runtime_error(
2015 "XML object does not provide enough coefficients");
2016
2017 coeffs_accessors[g][0] = static_cast<real_t>(std::stod(value));
2018 value = strtok(nullptr, " ");
2019 }
2020
2021 if (value != nullptr)
2022 throw std::runtime_error(
2023 "XML object provides too many coefficients");
2024
2025 } else {
2026 auto value = strtok(&values[0], " ");
2027
2028 for (int64_t i = 0; i < utils::prod(ncoeffs_); ++i)
2029 for (short_t g = 0; g < geoDim_; ++g) {
2030 if (value == nullptr)
2031 throw std::runtime_error(
2032 "XML object does not provide enough coefficients");
2033
2034 coeffs_accessors[g][i] = static_cast<real_t>(std::stod(value));
2035 value = strtok(nullptr, " ");
2036 }
2037
2038 if (value != nullptr)
2039 throw std::runtime_error(
2040 "XML object provides too many coefficients");
2041 }
2042
2043 // Copy coefficients to device (if needed)
2044 for (short_t i = 0; i < geoDim_; ++i)
2045 coeffs_[i] = coeffs_[i].to(options_.device());
2046
2047 if constexpr (parDim_ == 0) {
2048 if (nknots_found[0] && ncoeffs_found[0])
2049 return *this;
2050 } else if (std::all_of(std::begin(nknots_found), std::end(nknots_found),
2051 [](bool i) { return i; }) &&
2052 std::all_of(std::begin(ncoeffs_found),
2053 std::end(ncoeffs_found),
2054 [](bool i) { return i; }))
2055 return *this;
2056
2057 else
2058 throw std::runtime_error(
2059 "XML object is not compatible with B-spline object");
2060
2061 } // Coefs
2062 else
2063 throw std::runtime_error("XML object does not provide coefficients");
2064
2065 } // "Geometry"
2066
2067 throw std::runtime_error("XML object does not provide geometry with given "
2068 "id, index, and/or label");
2069 return *this;
2070 }
2071
2073 inline void load(const std::string &filename,
2074 const std::string &key = "bspline") {
2075 torch::serialize::InputArchive archive;
2076 archive.load_from(filename);
2077 read(archive, key);
2078 }
2079
2081 inline torch::serialize::InputArchive &
2082 read(torch::serialize::InputArchive &archive,
2083 const std::string &key = "bspline") {
2084 torch::Tensor tensor;
2085
2086 archive.read(key + ".parDim", tensor);
2087 if (tensor.item<int64_t>() != parDim_)
2088 throw std::runtime_error("parDim mismatch");
2089
2090 archive.read(key + ".geoDim", tensor);
2091 if (tensor.item<int64_t>() != geoDim_)
2092 throw std::runtime_error("geoDim mismatch");
2093
2094 for (short_t i = 0; i < parDim_; ++i) {
2095 archive.read(key + ".degree[" + std::to_string(i) + "]", tensor);
2096 if (tensor.item<int64_t>() != degrees_[i])
2097 throw std::runtime_error("degrees mismatch");
2098 }
2099
2100 for (short_t i = 0; i < parDim_; ++i) {
2101 archive.read(key + ".nknots[" + std::to_string(i) + "]", tensor);
2102 nknots_[i] = tensor.item<int64_t>();
2103 }
2104
2105 for (short_t i = 0; i < parDim_; ++i)
2106 archive.read(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
2107
2108 for (short_t i = 0; i < parDim_; ++i) {
2109 archive.read(key + ".ncoeffs[" + std::to_string(i) + "]", tensor);
2110 ncoeffs_[i] = tensor.item<int64_t>();
2111 }
2112
2114 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2115
2116 for (short_t i = 0; i < geoDim_; ++i)
2117 archive.read(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
2118
2119 return archive;
2120 }
2121
2123 inline void save(const std::string &filename,
2124 const std::string &key = "bspline") const {
2125 torch::serialize::OutputArchive archive;
2126 write(archive, key).save_to(filename);
2127 }
2128
2130 inline torch::serialize::OutputArchive &
2131 write(torch::serialize::OutputArchive &archive,
2132 const std::string &key = "bspline") const {
2133 archive.write(key + ".parDim", torch::full({1}, parDim_));
2134 archive.write(key + ".geoDim", torch::full({1}, geoDim_));
2135
2136 for (short_t i = 0; i < parDim_; ++i)
2137 archive.write(key + ".degree[" + std::to_string(i) + "]",
2138 torch::full({1}, degrees_[i]));
2139
2140 for (short_t i = 0; i < parDim_; ++i)
2141 archive.write(key + ".nknots[" + std::to_string(i) + "]",
2142 torch::full({1}, nknots_[i]));
2143
2144 for (short_t i = 0; i < parDim_; ++i)
2145 archive.write(key + ".knots[" + std::to_string(i) + "]", knots_[i]);
2146
2147 for (short_t i = 0; i < parDim_; ++i)
2148 archive.write(key + ".ncoeffs[" + std::to_string(i) + "]",
2149 torch::full({1}, ncoeffs_[i]));
2150
2151 for (short_t i = 0; i < geoDim_; ++i)
2152 archive.write(key + ".coeffs[" + std::to_string(i) + "]", coeffs_[i]);
2153
2154 return archive;
2155 }
2156
2159 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2161 real_t rtol = real_t{1e-5}, real_t atol = real_t{1e-8}) const {
2162 if constexpr (!std::is_same_v<real_t, other_t>)
2163 return false;
2164 bool result(true);
2165
2166 result *= (parDim_ == other.parDim());
2167 result *= (geoDim_ == other.geoDim());
2168
2169 for (short_t i = 0; i < parDim_; ++i)
2170 result *= (degree(i) == other.degree(i));
2171
2172 for (short_t i = 0; i < parDim_; ++i)
2173 result *= (nknots(i) == other.nknots(i));
2174
2175 for (short_t i = 0; i < parDim_; ++i)
2176 result *= (ncoeffs(i) == other.ncoeffs(i));
2177
2178 if (!result)
2179 return result;
2180
2181 for (short_t i = 0; i < parDim_; ++i)
2182 result *= torch::allclose(knots(i), other.knots(i), rtol, atol);
2183
2184 for (short_t i = 0; i < geoDim_; ++i)
2185 result *= torch::allclose(coeffs(i), other.coeffs(i), rtol, atol);
2186
2187 return result;
2188 }
2189
2191 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2194 if constexpr (!std::is_same_v<real_t, other_t>)
2195 return false;
2196 bool result(true);
2197
2198 result *= (parDim_ == other.parDim());
2199 result *= (geoDim_ == other.geoDim());
2200
2201 if (!result)
2202 return result;
2203
2204 for (short_t i = 0; i < parDim_; ++i)
2205 result *= (degree(i) == other.degree(i));
2206
2207 for (short_t i = 0; i < parDim_; ++i)
2208 result *= (nknots(i) == other.nknots(i));
2209
2210 for (short_t i = 0; i < parDim_; ++i)
2211 result *= (ncoeffs(i) == other.ncoeffs(i));
2212
2213 for (short_t i = 0; i < parDim_; ++i)
2214 result *= torch::equal(knots(i), other.knots(i));
2215
2216 for (short_t i = 0; i < geoDim_; ++i)
2217 result *= torch::equal(coeffs(i), other.coeffs(i));
2218
2219 return result;
2220 }
2221
2223 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
2226 return !(
2227 *this ==
2228 other); // Do not change this to (*this != other) is it does not work
2229 }
2230
2237 inline UniformBSplineCore &uniform_refine(int numRefine = 1, int dim = -1) {
2238 assert(numRefine > 0);
2239 assert(dim == -1 || (dim >= 0 && dim < parDim_));
2240
2241 // Update number of knots and coefficients
2242 std::array<int64_t, parDim_> nknots(nknots_);
2243 std::array<int64_t, parDim_> ncoeffs(ncoeffs_);
2244
2245 for (int refine = 0; refine < numRefine; ++refine) {
2246 if (dim == -1)
2247 for (short_t i = 0; i < parDim_; ++i) {
2248 ncoeffs[i] += nknots[i] - 2 * degrees_[i] - 1; // must be done first
2249 nknots[i] += nknots[i] - 2 * degrees_[i] - 1;
2250 }
2251 else {
2252 ncoeffs[dim] +=
2253 nknots[dim] - 2 * degrees_[dim] - 1; // must be done first
2254 nknots[dim] += nknots[dim] - 2 * degrees_[dim] - 1;
2255 }
2256 }
2257
2258 // Update knot vectors
2259 utils::TensorArray<parDim_> knots, knots_indices;
2260
2261 for (short_t i = 0; i < parDim_; ++i) {
2262 std::vector<real_t> kv;
2263 kv.reserve(nknots[i]);
2264
2265 for (int64_t j = 0; j < degrees_[i]; ++j)
2266 kv.push_back(static_cast<real_t>(0));
2267
2268 for (int64_t j = 0; j < ncoeffs[i] - degrees_[i] + 1; ++j)
2269 kv.push_back(static_cast<real_t>(j) /
2270 static_cast<real_t>(ncoeffs[i] - degrees_[i]));
2271
2272 for (int64_t j = 0; j < degrees_[i]; ++j)
2273 kv.push_back(static_cast<real_t>(1));
2274
2276 }
2277
2278 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
2279 // \f$m_d\f$ is the number of coefficients after the update. To
2280 // update the coefficients using the Oslo algorithm (Algorithm
2281 // 4.11 from \cite Lyche:2011) we need to neglect the last
2282 // \f$p_d+1\f$ knots in what follows
2283 for (short_t i = 0; i < parDim_; ++i)
2284 knots_indices[i] = knots[i].index(
2285 {torch::indexing::Slice(0, knots[i].numel() - degrees_[i] - 1)});
2286
2287 // Get indices of the first \f$m_d\f$ new knots relative to old
2288 // knot vectors
2289 auto new_knot_indices = find_knot_indices(knots_indices);
2290
2291 // Update coefficient vector
2292 update_coeffs(knots, new_knot_indices);
2293
2294 // Swap old and new data
2295 knots.swap(knots_);
2296 nknots.swap(nknots_);
2297 ncoeffs.swap(ncoeffs_);
2298
2300 std::reverse(ncoeffs_reverse_.begin(), ncoeffs_reverse_.end());
2301
2302 return *this;
2303 }
2304
2305private:
2308 template <int64_t degree, int64_t deriv, int64_t terminal = degree - deriv>
2309 [[nodiscard]] inline int64_t constexpr eval_prefactor() const {
2310 if constexpr (degree > terminal)
2311 return degree * eval_prefactor<degree - 1, deriv, terminal>();
2312 else
2313 return 1;
2314 }
2315
2316public:
2318 inline void init_knots() {
2319
2320 for (short_t i = 0; i < parDim_; ++i) {
2321
2322 // Check that open knot vector can be created
2323 if ((ncoeffs_[i] < degrees_[i] + 1) || (ncoeffs_[i] < 2))
2324 throw std::runtime_error(
2325 "Not enough coefficients to create open knot vector");
2326
2327 nknots_[i] = ncoeffs_[i] + degrees_[i] + 1;
2328 int64_t num_inner = ncoeffs_[i] - degrees_[i];
2329
2330 auto start = torch::zeros({degrees_[i]}, options_);
2331 auto end = torch::ones({degrees_[i]}, options_);
2332 auto inner = torch::empty({0}, options_);
2333
2334 if (num_inner > 0) {
2335 inner = torch::arange(0, num_inner + 1, options_);
2336 inner = inner / static_cast<real_t>(num_inner);
2337 }
2338
2339 knots_[i] = torch::cat({start, inner, end});
2340 }
2341 }
2342
2344 inline void init_coeffs(enum init init) {
2345 switch (init) {
2346
2347 case (init::none): {
2348 break;
2349 }
2350
2351 case (init::zeros): {
2352
2353 // Fill coefficients with zeros
2354 int64_t size = ncumcoeffs();
2355 for (short_t i = 0; i < geoDim_; ++i)
2356 coeffs_[i] = torch::zeros(size, options_);
2357
2358 break;
2359 }
2360
2361 case (init::ones): {
2362
2363 // Fill coefficients with ones
2364 int64_t size = ncumcoeffs();
2365 for (short_t i = 0; i < geoDim_; ++i)
2366 coeffs_[i] = torch::ones(size, options_);
2367
2368 break;
2369 }
2370
2371 case (init::random): {
2372
2373 // Fill coefficients with random values
2374 int64_t size = ncumcoeffs();
2375 for (short_t i = 0; i < geoDim_; ++i)
2376 coeffs_[i] = torch::rand(size, options_);
2377
2378 break;
2379 }
2380
2381 case (init::linear): {
2382
2383 // Fill coefficients with the tensor-product of linearly
2384 // increasing values between 0 and 1 per univariate dimension
2385 for (short_t i = 0; i < geoDim_; ++i) {
2386 coeffs_[i] = torch::ones(1, options_);
2387
2388 for (short_t j = 0; j < parDim_; ++j) {
2389 if (i == j)
2390 coeffs_[i] = torch::kron(torch::linspace(static_cast<real_t>(0),
2391 static_cast<real_t>(1),
2392 ncoeffs_[j], options_),
2393 coeffs_[i]);
2394 else
2395 coeffs_[i] =
2396 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2397 }
2398
2399 // Enable gradient calculation for non-leaf tensor
2400 if (options_.requires_grad())
2401 coeffs_[i].retain_grad();
2402 }
2403 break;
2404 }
2405
2406 case (init::greville): {
2407
2408 // Fill coefficients with the tensor-product of Greville
2409 // abscissae values per univariate dimension
2410 for (short_t i = 0; i < geoDim_; ++i) {
2411 coeffs_[i] = torch::ones(1, options_);
2412
2413 for (short_t j = 0; j < parDim_; ++j) {
2414 if (i == j) {
2415
2416 int64_t count = ncoeffs_[j];
2417
2418 // idx_base: (count, 1)
2419 auto idx_base =
2420 torch::arange(
2421 count,
2422 options_.requires_grad(false).template dtype<int64_t>())
2423 .unsqueeze(1);
2424
2425 // offsets: (1, degree)
2426 auto offsets =
2427 torch::arange(
2428 1, degrees_[j] + 1,
2429 options_.requires_grad(false).template dtype<int64_t>())
2430 .unsqueeze(0);
2431
2432 // indices: (count, degree)
2433 auto indices = idx_base + offsets;
2434
2435 // Gather relevant knot values: shape (count, degree)
2436 auto gathered = knots_[j]
2437 .index_select(0, indices.flatten())
2438 .view({count, degrees_[j]});
2439
2440 // Compute mean along degree dimension (dim=1)
2441 auto greville_ = gathered.mean(1);
2442
2443 coeffs_[i] = torch::kron(greville_, coeffs_[i]);
2444 } else
2445 coeffs_[i] =
2446 torch::kron(torch::ones(ncoeffs_[j], options_), coeffs_[i]);
2447 }
2448
2449 // Enable gradient calculation for non-leaf tensor
2450 if (options_.requires_grad())
2451 coeffs_[i].retain_grad();
2452 }
2453 break;
2454 }
2455
2456 case (init::linspace): {
2457
2458 // Fill coefficients with increasing values
2459 int64_t size = ncumcoeffs();
2460 for (short_t i = 0; i < geoDim_; ++i)
2461 coeffs_[i] = torch::linspace(
2462 std::pow(10, i) * 0, std::pow(10, i) * (size - 1), size, options_);
2463
2464 break;
2465 }
2466
2467 default:
2468 throw std::runtime_error("Unsupported init option");
2469 }
2470 }
2471
2472protected:
2475 const utils::TensorArray<parDim_> &knot_indices) {
2476
2477 // Check compatibility of arguments
2478 for (short_t i = 0; i < parDim_; ++i)
2479 assert(knots[i].numel() == knot_indices[i].numel() + degrees_[i] + 1);
2480
2481 if constexpr (parDim_ == 1) {
2482
2483 auto basfunc = update_coeffs_univariate<degrees_[0], 0>(
2484 knots[0].flatten(), knot_indices[0].flatten());
2485
2486 auto coeff_indices = find_coeff_indices(knot_indices);
2487
2488 for (short_t i = 0; i < geoDim_; ++i)
2489 coeffs(i) =
2490 utils::dotproduct(basfunc, coeffs(i)
2491 .index_select(0, coeff_indices)
2492 .view({-1, knot_indices[0].numel()}))
2493 .view(knot_indices[0].sizes());
2494
2495 } else {
2496
2497 // Lambda expressions to evaluate the basis functions
2498 auto basfunc_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
2499 if constexpr (sizeof...(Is) == 1)
2500 return (update_coeffs_univariate<degrees_[Is], Is>(
2501 knots[Is].flatten(), knot_indices[Is].flatten()),
2502 ...);
2503 else
2505 knots[Is].flatten(), knot_indices[Is].flatten())...);
2506 };
2507
2508 auto basfunc = basfunc_(utils::make_reverse_index_sequence<parDim_>{});
2509
2510 // Lambda expression to calculate the partial product of array
2511 // entry from start_index to stop_index (including the latter)
2512 auto prod_ = [](utils::TensorArray<parDim_> array, short_t start_index,
2513 short_t stop_index) {
2514 int64_t result{1};
2515 for (short_t i = start_index; i <= stop_index; ++i)
2516 result *= array[i].numel();
2517 return result;
2518 };
2519
2520 utils::TensorArray<parDim_> knot_indices_;
2521
2522 for (short_t i = 0; i < parDim_; ++i)
2523 knot_indices_[i] =
2524 knot_indices[i]
2525 .repeat_interleave(prod_(knot_indices, 0, i - 1), 0)
2526 .repeat(prod_(knot_indices, i + 1, parDim_ - 1));
2527
2528 auto coeff_indices = find_coeff_indices(knot_indices_);
2529
2530 for (short_t i = 0; i < geoDim_; ++i)
2531 coeffs(i) = utils::dotproduct(basfunc,
2532 coeffs(i)
2533 .index_select(0, coeff_indices)
2534 .view({-1, knot_indices_[0].numel()}))
2535 .view(knot_indices_[0].sizes());
2536 }
2537 }
2538
2539 // clang-format off
2643 // clang-format on
2644 template <short_t degree, short_t dim, short_t deriv>
2645 inline auto eval_basfunc_univariate(const torch::Tensor &xi,
2646 const torch::Tensor &knot_indices) const {
2647 assert(xi.sizes() == knot_indices.sizes());
2648
2649 if constexpr (deriv > degree) {
2650 return torch::zeros({degree + 1, xi.numel()}, options_);
2651 } else {
2652 // Algorithm 2.22 from \cite Lyche:2011
2653 torch::Tensor b = torch::ones({xi.numel()}, options_);
2654
2655 // Calculate R_k, k = 1, ..., p_d-r_d
2656 for (short_t k = 1; k <= degree - deriv; ++k) {
2657
2658 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2659 auto t1 =
2660 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2661 auto t21 =
2662 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2663 t1;
2664
2665 // We handle the special case 0/0:=0 by first creating a
2666 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2667 // we do not have to take the absolute value as t2 >= t1.
2668 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2669 .to(::iganet::dtype<real_t>());
2670
2671 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2672 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2673 // equals the original expression if the mask is 0, i.e.,
2674 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2675 auto w = torch::div(xi.repeat(k) - t1 - mask, t21 - mask);
2676
2677 // Calculate the vector of B-splines evaluated at xi
2678 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2679 torch::zeros_like(xi, options_)},
2680 0) +
2681 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2682 }
2683
2684 // Calculate DR_k, k = p_d-r_d+1, ..., p_d
2685 for (short_t k = degree - deriv + 1; k <= degree; ++k) {
2686
2687 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2688 auto t21 =
2689 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2690 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2691
2692 // We handle the special case 0/0:=0 by first creating a
2693 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2694 // we do not have to take the absolute value as t2 >= t1.
2695 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2696 .to(::iganet::dtype<real_t>());
2697
2698 // Instead of computing 1/(t2-t1) which is prone to yielding
2699 // 0/0 we compute (1-mask)/(t2-t1-mask) which equals the
2700 // original expression if the mask is 0, i.e., t2-t1 >= eps
2701 // and 1 otherwise since t1 <= xi < t2.
2702 auto w = torch::div(torch::ones_like(t21, options_) - mask, t21 - mask);
2703
2704 // Calculate the vector of B-splines evaluated at xi
2705 b = torch::cat({torch::mul(-w, b), torch::zeros_like(xi, options_)},
2706 0) +
2707 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2708 }
2709
2710 return b.view({degree + 1, xi.numel()});
2711 }
2712 }
2713
2714 template <short_t degree, short_t dim, short_t deriv>
2715 inline auto eval_basfunc_univariate_tr(const torch::Tensor &xi,
2716 const torch::Tensor &knot_indices) const {
2717 assert(xi.sizes() == knot_indices.sizes());
2718
2719 if constexpr (deriv > degree) {
2720 return torch::zeros({xi.numel(), degree + 1}, options_); // swapped shape
2721 } else {
2722 torch::Tensor b = torch::ones({xi.numel()}, options_);
2723
2724 // Calculate R_k, k = 1, ..., degree-deriv
2725 for (short_t k = 1; k <= degree - deriv; ++k) {
2726 auto t1 = knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2727 auto t21 = knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) - t1;
2728
2729 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2730 .to(::iganet::dtype<real_t>());
2731
2732 auto w = torch::div(xi.repeat(k) - t1 - mask, t21 - mask);
2733
2734 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2735 torch::zeros_like(xi, options_)},
2736 0) +
2737 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2738 }
2739
2740 // Calculate DR_k, k = degree-deriv+1, ..., degree
2741 for (short_t k = degree - deriv + 1; k <= degree; ++k) {
2742 auto t21 = knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2743 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2744
2745 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2746 .to(::iganet::dtype<real_t>());
2747
2748 auto w = torch::div(torch::ones_like(t21, options_) - mask, t21 - mask);
2749
2750 b = torch::cat({torch::mul(-w, b), torch::zeros_like(xi, options_)},
2751 0) +
2752 torch::cat({torch::zeros_like(xi, options_), torch::mul(w, b)}, 0);
2753 }
2754
2755 // Swap axes: shape [degree+1, xi.numel()] → [xi.numel(), degree+1]
2756 return b.view({degree + 1, xi.numel()}).transpose(0, 1);
2757 }
2758 }
2759
2766 template <short_t degree, short_t dim>
2767 inline auto
2768 update_coeffs_univariate(const torch::Tensor &knots,
2769 const torch::Tensor &knot_indices) const {
2770 // Algorithm 2.22 from \cite Lyche:2011 modified to implement
2771 // the Oslo algorithm (Algorithm 4.11 from \cite Lyche:2011)
2772 torch::Tensor b = torch::ones({knot_indices.numel()}, options_);
2773
2774 // Calculate R_k, k = 1, ..., p_d
2775 for (short_t k = 1; k <= degree; ++k) {
2776
2777 // Instead of calculating t1 and t2 we calculate t1 and t21=(t2-t1)
2778 auto t1 =
2779 knots_[dim].index_select(0, utils::VSlice(knot_indices, -k + 1, 1));
2780 auto t21 =
2781 knots_[dim].index_select(0, utils::VSlice(knot_indices, 1, k + 1)) -
2782 t1;
2783
2784 // We handle the special case 0/0:=0 by first creating a
2785 // mask that is 1 if t2-t1 < eps and 0 otherwise. Note that
2786 // we do not have to take the absolute value as t2 >= t1.
2787 auto mask = (t21 < std::numeric_limits<real_t>::epsilon())
2788 .to(::iganet::dtype<real_t>());
2789
2790 // Instead of computing (xi-t1)/(t2-t1) which is prone to
2791 // yielding 0/0 we compute (xi-t1-mask)/(t2-t1-mask) which
2792 // equals the original expression if the mask is 0, i.e.,
2793 // t2-t1 >= eps and 1 otherwise since t1 <= xi < t2.
2794 auto w = torch::div(
2795 knots.index({torch::indexing::Slice(k, knot_indices.numel() + k)})
2796 .repeat(k) -
2797 t1 - mask,
2798 t21 - mask);
2799
2800 // Calculate the vector of B-splines evaluated at xi
2801 b = torch::cat({torch::mul(torch::ones_like(w, options_) - w, b),
2802 torch::zeros_like(knot_indices, options_)},
2803 0) +
2804 torch::cat(
2805 {torch::zeros_like(knot_indices, options_), torch::mul(w, b)}, 0);
2806 }
2807
2808 return b.view({degree + 1, knot_indices.numel()});
2809 }
2810
2811public:
2815 auto to_gismo() const {
2816
2817#ifdef IGANET_WITH_GISMO
2818
2819 gismo::gsMatrix<real_t> coefs(ncumcoeffs(), geoDim_);
2820
2821 for (short_t g = 0; g < geoDim_; ++g) {
2822 auto [coeffs_cpu, coeffs_accessor] =
2823 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2824 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2825 coefs.col(g) =
2826 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2827 }
2828
2829 std::array<gismo::gsKnotVector<real_t>, parDim_> kv;
2830
2831 for (short_t i = 0; i < parDim_; ++i) {
2832 auto [knots_cpu, knots_accessor] =
2833 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
2834 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2835 kv[i] = gismo::gsKnotVector<real_t>(degrees_[i], knots_cpu_ptr,
2836 knots_cpu_ptr + knots_cpu.size(0));
2837 }
2838
2839 if constexpr (parDim_ == 1) {
2840
2841 return gismo::gsBSpline<real_t>(gismo::give(kv[0]), gismo::give(coefs));
2842
2843 } else if constexpr (parDim_ == 2) {
2844
2845 return gismo::gsTensorBSpline<parDim_, real_t>(
2846 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(coefs));
2847
2848 } else if constexpr (parDim_ == 3) {
2849
2850 return gismo::gsTensorBSpline<parDim_, real_t>(
2851 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
2852 gismo::give(coefs));
2853
2854 } else if constexpr (parDim_ == 4) {
2855
2856 return gismo::gsTensorBSpline<parDim_, real_t>(
2857 gismo::give(kv[0]), gismo::give(kv[1]), gismo::give(kv[2]),
2858 gismo::give(kv[3]), gismo::give(coefs));
2859
2860 } else
2861 throw std::runtime_error("Invalid parametric dimension");
2862
2863#else
2864 throw std::runtime_error(
2865 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2866#endif
2867 }
2868
2869#ifdef IGANET_WITH_GISMO
2870
2871 // @brief Updates a given gsBSpline object from the B-spline object
2872 gismo::gsBSpline<real_t> &to_gismo(gismo::gsBSpline<real_t> &bspline,
2873 bool updateKnotVector = true,
2874 bool updateCoeffs = true) const {
2875
2876 if (updateKnotVector) {
2877
2878 if constexpr (parDim_ == 1) {
2879
2880 if (bspline.degree(0) != degrees_[0])
2881 throw std::runtime_error("Degrees mismatch");
2882
2883 auto [knots_cpu, knots_accessor] =
2884 utils::to_tensorAccessor<real_t, 1>(knots_[0], torch::kCPU);
2885 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2886
2887 gismo::gsKnotVector<real_t> kv(degrees_[0], knots_cpu_ptr,
2888 knots_cpu_ptr + knots_cpu.size(0));
2889
2890 bspline.knots(0).swap(kv);
2891
2892 } else
2893 throw std::runtime_error("Invalid parametric dimension");
2894 }
2895
2896 if (updateCoeffs) {
2897
2898 for (short_t g = 0; g < geoDim_; ++g) {
2899 auto [coeffs_cpu, coeffs_accessor] =
2900 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2901 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2902 bspline.coefs().col(g) =
2903 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2904 }
2905 }
2906
2907 return bspline;
2908 }
2909
2910 // @brief Updates a given gsTensorBSpline object from the B-spline object
2911 gismo::gsTensorBSpline<parDim_, real_t> &
2912 to_gismo(gismo::gsTensorBSpline<parDim_, real_t> &bspline,
2913 bool updateKnotVector = true, bool updateCoeffs = true) const {
2914
2915 if (updateKnotVector) {
2916
2917 // Check compatibility of arguments
2918 for (short_t i = 0; i < parDim_; ++i)
2919 assert(bspline.degree(i) == degrees_[i]);
2920
2921 for (short_t i = 0; i < parDim_; ++i) {
2922 auto [knots_cpu, knots_accessor] =
2923 utils::to_tensorAccessor<real_t, 1>(knots_[i], torch::kCPU);
2924 auto knots_cpu_ptr = knots_cpu.template data_ptr<real_t>();
2925
2926 gismo::gsKnotVector<real_t> kv(degrees_[i], knots_cpu_ptr,
2927 knots_cpu_ptr + knots_cpu.size(0));
2928 bspline.knots(i).swap(kv);
2929 }
2930 }
2931
2932 if (updateCoeffs) {
2933
2934 for (short_t g = 0; g < geoDim_; ++g) {
2935 auto [coeffs_cpu, coeffs_accessor] =
2936 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2937 auto coeffs_cpu_ptr = coeffs_cpu.template data_ptr<real_t>();
2938 bspline.coefs().col(g) =
2939 gismo::gsAsConstVector<real_t>(coeffs_cpu_ptr, coeffs_cpu.size(0));
2940 }
2941 }
2942
2943 return bspline;
2944 }
2945
2946#else // IGANET_WITH_GISMO
2947
2948 template <typename BSpline>
2949 BSpline &to_gismo(BSpline &bspline, bool, bool) const {
2950 throw std::runtime_error(
2951 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
2952 return bspline;
2953 }
2954
2955#endif // IGANET_WITH_GISMO
2956
2957#ifdef IGANET_WITH_GISMO
2958
2959 // @brief Updates the B-spline object from a given gsBSpline object
2960 auto &from_gismo(const gismo::gsBSpline<real_t> &bspline,
2961 bool updateCoeffs = true, bool updateKnotVector = false) {
2962
2963 if (updateKnotVector) {
2964
2965 throw std::runtime_error(
2966 "Knot vectors can only be updated for Non-uniform B-splines");
2967 }
2968
2969 if (updateCoeffs) {
2970
2971 if (bspline.coefs().cols() != geoDim_)
2972 throw std::runtime_error("Geometric dimensions mismatch");
2973
2974 if (bspline.coefs().rows() != ncumcoeffs())
2975 throw std::runtime_error("Coefficient vector dimensions mismatch");
2976
2977 for (short_t g = 0; g < geoDim_; ++g) {
2978
2979 auto [coeffs_cpu, coeffs_accessor] =
2980 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
2981
2982 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
2983
2984 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
2985 coeffs_accessor[i] = coeffs_ptr[i];
2986
2987 coeffs_[g] = coeffs_[g].to(options_.device());
2988 }
2989 }
2990
2991 return *this;
2992 }
2993
2994 // @brief Updates the B-spline object from a given gsTensorBSpline object
2995 auto &from_gismo(const gismo::gsTensorBSpline<parDim_, real_t> &bspline,
2996 bool updateCoeffs = true, bool updateKnotVector = false) {
2997
2998 if (updateKnotVector) {
2999
3000 throw std::runtime_error(
3001 "Knot vectors can only be updated for Non-uniform B-splines");
3002 }
3003
3004 if (updateCoeffs) {
3005
3006 if (bspline.coefs().cols() != geoDim_)
3007 throw std::runtime_error("Geometric dimensions mismatch");
3008
3009 if (bspline.coefs().rows() != ncumcoeffs())
3010 throw std::runtime_error("Coefficient vector dimensions mismatch");
3011
3012 for (short_t g = 0; g < geoDim_; ++g) {
3013
3014 auto [coeffs_cpu, coeffs_accessor] =
3015 utils::to_tensorAccessor<real_t, 1>(coeffs_[g], torch::kCPU);
3016
3017 const real_t *coeffs_ptr = bspline.coefs().col(g).data();
3018
3019 for (int64_t i = 0; i < ncoeffs_[g]; ++i)
3020 coeffs_accessor[i] = coeffs_ptr[i];
3021
3022 coeffs_[g] = coeffs_[g].to(options_.device());
3023 }
3024 }
3025
3026 return *this;
3027 }
3028
3029#else // IGANET_WITH_GISMO
3030
3031 template <typename BSpline> auto &from_gismo(BSpline &bspline, bool, bool) {
3032 throw std::runtime_error(
3033 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3034 return *this;
3035 }
3036
3037#endif // IGANET_WITH_GISMO
3038};
3039
3041template <typename real_t, short_t GeoDim, short_t... Degrees>
3042inline torch::serialize::OutputArchive &
3043operator<<(torch::serialize::OutputArchive &archive,
3045 return obj.write(archive);
3046}
3047
3049template <typename real_t, short_t GeoDim, short_t... Degrees>
3050inline torch::serialize::InputArchive &
3051operator>>(torch::serialize::InputArchive &archive,
3053 return obj.read(archive);
3054}
3055
3061template <typename real_t, short_t GeoDim, short_t... Degrees>
3063 : public NonUniformSplineCore_,
3064 public UniformBSplineCore<real_t, GeoDim, Degrees...> {
3065private:
3067 using Base = UniformBSplineCore<real_t, GeoDim, Degrees...>;
3068
3069public:
3071 using value_type = real_t;
3072
3078 template <template <typename, short_t, short_t...> class BSpline,
3079 std::make_signed_t<short_t> degree_elevate = 0>
3080 using derived_type = BSpline<real_t, GeoDim, (Degrees + degree_elevate)...>;
3081
3084 template <std::make_signed_t<short_t> degree_elevate = 0>
3087
3091 template <typename other_t, short_t GeoDim_, short_t... Degrees_>
3093 NonUniformBSplineCore<other_t, GeoDim_, Degrees_...>;
3094
3097 template <typename other_t>
3099 NonUniformBSplineCore<other_t, GeoDim, Degrees...>;
3100
3102 inline static constexpr bool is_uniform() { return false; }
3103
3105 inline static constexpr bool is_nonuniform() { return true; }
3106
3108 using UniformBSplineCore<real_t, GeoDim, Degrees...>::UniformBSplineCore;
3109
3118 const std::array<std::vector<typename Base::value_type>, Base::parDim_>
3119 &kv,
3120 enum init init = init::greville,
3122 : Base(options) {
3123 init_knots(kv);
3125 }
3126
3140 NonUniformBSplineCore(const std::array<std::vector<typename Base::value_type>,
3141 Base::parDim_> &kv,
3143 bool clone = false,
3145 : Base(options) {
3146 init_knots(kv);
3147
3148 // Copy/clone coefficients
3149 if (clone)
3150 for (short_t i = 0; i < Base::geoDim_; ++i)
3151 Base::coeffs_[i] = coeffs[i]
3152 .clone()
3153 .to(options.requires_grad(false))
3154 .requires_grad_(Base::options.requires_grad());
3155 else
3156 for (short_t i = 0; i < Base::geoDim_; ++i)
3157 Base::coeffs_[i] = coeffs[i];
3158 }
3159
3160private:
3162 inline void init_knots(
3163 const std::array<std::vector<typename Base::value_type>, Base::parDim_>
3164 &kv) {
3165 for (short_t i = 0; i < Base::parDim_; ++i) {
3166
3167 // Check that knot vector has enough (n+p+1) entries
3168 if (2 * Base::degrees_[i] > kv[i].size() - 2)
3169 throw std::runtime_error("Knot vector is too short for an open knot "
3170 "vector (n+p+1 > 2*(p+1))");
3171
3173 Base::nknots_[i] = Base::knots_[i].size(0);
3176 }
3177 // Reverse ncoeffs
3178 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3179 }
3180
3181public:
3185 template <deriv deriv = deriv::func, bool memory_optimized = false>
3186 inline auto eval(const torch::Tensor &xi) const {
3187 return eval<deriv, memory_optimized>(utils::TensorArray1({xi}));
3188 }
3189
3190 template <deriv deriv = deriv::func, bool memory_optimized = false>
3191 inline auto eval(const utils::TensorArray<Base::parDim_> &xi) const {
3192 if constexpr (Base::parDim_ == 0) {
3194 for (short_t i = 0; i < Base::geoDim_; ++i)
3195 if constexpr (deriv == deriv::func)
3196 result.set(i, Base::coeffs_[i]);
3197 else
3198 result.set(i, torch::zeros_like(Base::coeffs_[i]));
3199 return result;
3200 } else
3201 return Base::template eval<deriv, memory_optimized>(
3202 xi, find_knot_indices(xi));
3203 }
3204
3205 template <deriv deriv = deriv::func, bool memory_optimized = false>
3206 inline auto
3208 const utils::TensorArray<Base::parDim_> &knot_indices) const {
3209 if constexpr (Base::parDim_ == 0) {
3211 for (short_t i = 0; i < Base::geoDim_; ++i)
3212 if constexpr (deriv == deriv::func)
3213 result.set(i, Base::coeffs_[i]);
3214 else
3215 result.set(i, torch::zeros_like(Base::coeffs_[i]));
3216 return result;
3217 } else
3218 return Base::template eval<deriv, memory_optimized>(xi, knot_indices);
3219 }
3220
3221 template <deriv deriv = deriv::func, bool memory_optimized = false>
3223 const utils::TensorArray<Base::parDim_> &knot_indices,
3224 const torch::Tensor &coeff_indices) const {
3225 if constexpr (Base::parDim_ == 0) {
3227 for (short_t i = 0; i < Base::geoDim_; ++i)
3228 if constexpr (deriv == deriv::func)
3229 result.set(i, Base::coeffs_[i]);
3230 else
3231 result.set(i, torch::zeros_like(Base::coeffs_[i]));
3232 return result;
3233 } else
3234 return Base::template eval<deriv, memory_optimized>(xi, knot_indices,
3235 coeff_indices);
3236 }
3238
3252 inline auto find_knot_indices(const torch::Tensor &xi) const {
3253 if constexpr (Base::parDim_ == 0)
3254 return torch::zeros_like(Base::coeffs_[0]).to(torch::kInt64);
3255 else
3257 }
3258
3259 inline auto
3261
3263 for (short_t i = 0; i < Base::parDim_; ++i) {
3264 auto nnz = Base::knots_[i].repeat({xi[i].numel(), 1}) >
3265 xi[i].flatten().view({-1, 1});
3266 indices[i] =
3267 torch::remainder(std::get<1>(((nnz.cumsum(1) == 1) & nnz).max(1)) - 1,
3268 Base::nknots_[i] - Base::degrees_[i] - 1)
3269 .view(xi[i].sizes());
3270 }
3271 return indices;
3272 }
3274
3281 inline NonUniformBSplineCore &uniform_refine(int numRefine = 1,
3282 int dim = -1) {
3283 assert(numRefine > 0);
3284 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
3285
3286 // Update knot vectors, number of knots and coefficients
3287 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
3289
3290 for (short_t i = 0; i < Base::parDim_; ++i) {
3291 auto [kv_cpu, kv_accessor] =
3292 utils::to_tensorAccessor<typename Base::value_type, 1>(
3293 Base::knots_[i], torch::kCPU);
3294
3295 std::vector<typename Base::value_type> kv;
3296 kv.reserve(Base::nknots_[i]);
3297 kv.push_back(kv_accessor[0]);
3298
3299 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
3300
3301 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]))
3302 for (int refine = 1; refine < (2 << (numRefine - 1)); ++refine)
3303 kv.push_back(
3304 kv_accessor[j - 1] +
3305 static_cast<Base::value_type>(refine) /
3306 static_cast<Base::value_type>(2 << (numRefine - 1)) *
3307 (kv_accessor[j] - kv_accessor[j - 1]));
3308
3309 kv.push_back(kv_accessor[j]);
3310 }
3311
3313 nknots[i] = kv.size();
3314 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
3315 }
3316
3317 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3318 // \f$m_d\f$ is the number of coefficients after the update. To
3319 // update the coefficients using the Oslo algorithm (Algorithm
3320 // 4.11 from \cite Lyche:2011) we need to neglect the last
3321 // \f$p_d+1\f$ knots in what follows
3322 for (short_t i = 0; i < Base::parDim_; ++i)
3323 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3324 0, knots[i].numel() - Base::degrees_[i] - 1)});
3325
3326 // Get indices of the first \f$m_d\f$ new knots relative to old
3327 // knot vectors
3328 auto new_knot_indices = find_knot_indices(knots_indices);
3329
3330 // Update coefficient vector
3331 Base::update_coeffs(knots, new_knot_indices);
3332
3333 // Swap old and new data
3334 knots.swap(Base::knots_);
3335 nknots.swap(Base::nknots_);
3336 ncoeffs.swap(Base::ncoeffs_);
3337
3339 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3340
3341 return *this;
3342 }
3343
3346 inline NonUniformBSplineCore &
3348 std::array<int64_t, Base::parDim_> nknots(Base::nknots_);
3349 std::array<int64_t, Base::parDim_> ncoeffs(Base::ncoeffs_);
3351
3352 // Update number of knots and coefficients and generate new knot
3353 // vectors
3354 for (short_t i = 0; i < Base::parDim_; ++i) {
3355 nknots[i] += knots[i].numel();
3356 ncoeffs[i] += knots[i].numel();
3357 knots_[i] =
3358 std::get<0>(torch::sort(torch::cat({Base::knots_[i], knots[i]})));
3359 }
3360
3361 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3362 // \f$m_d\f$ is the number of coefficients after the update. To
3363 // update the coefficients using the Oslo algorithm (Algorithm
3364 // 4.11 from \cite Lyche:2011) we need to neglect the last
3365 // \f$p_d+1\f$ knots in what follows
3366 for (short_t i = 0; i < Base::parDim_; ++i)
3367 knots_indices[i] = knots_[i].index({torch::indexing::Slice(
3368 0, knots_[i].numel() - Base::degrees_[i] - 1)});
3369
3370 // Get indices of the first \f$m_d\f$ new knots relative to old
3371 // knot vectors
3372 auto new_knot_indices = find_knot_indices(knots_indices);
3373
3374 // Update coefficient vector
3375 Base::update_coeffs(knots_, new_knot_indices);
3376
3377 // Swap old and new data
3378 knots_.swap(Base::knots_);
3379 nknots.swap(Base::nknots_);
3380 ncoeffs.swap(Base::ncoeffs_);
3381
3383 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3384
3385 return *this;
3386 }
3387
3390 inline NonUniformBSplineCore &reduce_continuity(int numReduce = 1,
3391 int dim = -1) {
3392 assert(numReduce > 0);
3393 assert(dim == -1 || (dim >= 0 && dim < Base::parDim_));
3394
3395 // Update knot vectors, number of knots and coefficients
3396 std::array<int64_t, Base::parDim_> nknots, ncoeffs;
3398
3399 for (short_t i = 0; i < Base::parDim_; ++i) {
3400 auto [kv_cpu, kv_accessor] =
3401 utils::to_tensorAccessor<typename Base::value_type, 1>(
3402 Base::knots_[i], torch::kCPU);
3403
3404 std::vector<typename Base::value_type> kv;
3405 kv.reserve(Base::nknots_[i]);
3406 kv.push_back(kv_accessor[0]);
3407
3408 for (int64_t j = 1; j < kv_accessor.size(0); ++j) {
3409
3410 if ((dim == -1 || dim == i) && (kv_accessor[j - 1] < kv_accessor[j]) &&
3411 (kv_accessor[j] < kv_accessor[kv_accessor.size(0) - 1]))
3412 for (int reduce = 0; reduce < numReduce; ++reduce)
3413 kv.push_back(kv_accessor[j]);
3414
3415 kv.push_back(kv_accessor[j]);
3416 }
3417
3419 nknots[i] = kv.size();
3420 ncoeffs[i] = nknots[i] - Base::degrees_[i] - 1;
3421 }
3422
3423 // The updated knot vectors have lengths \f$m_d+p_d+1\f$, where
3424 // \f$m_d\f$ is the number of coefficients after the update. To
3425 // update the coefficients using the Oslo algorithm (Algorithm
3426 // 4.11 from \cite Lyche:2011) we need to neglect the last
3427 // \f$p_d+1\f$ knots in what follows
3428 for (short_t i = 0; i < Base::parDim_; ++i)
3429 knots_indices[i] = knots[i].index({torch::indexing::Slice(
3430 0, knots[i].numel() - Base::degrees_[i] - 1)});
3431
3432 // Get indices of the first \f$m_d\f$ new knots relative to old
3433 // knot vectors
3434 auto new_knot_indices = find_knot_indices(knots_indices);
3435
3436 // Update coefficient vector
3437 Base::update_coeffs(knots, new_knot_indices);
3438
3439 // Swap old and new data
3440 knots.swap(Base::knots_);
3441 nknots.swap(Base::nknots_);
3442 ncoeffs.swap(Base::ncoeffs_);
3443
3445 std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end());
3446
3447 return *this;
3448 }
3449
3450#ifdef IGANET_WITH_GISMO
3451
3452 // @brief Updates the B-spline object from a given gsBSpline object
3453 auto &from_gismo(const gismo::gsBSpline<typename Base::value_type> &bspline,
3454 bool updateCoeffs = true, bool updateKnotVector = false) {
3455
3456 if (updateKnotVector) {
3457
3458 if constexpr (Base::parDim_ == 1) {
3459
3460 if (bspline.degree(0) != Base::degrees_[0])
3461 throw std::runtime_error("Degrees mismatch");
3462
3463 if (bspline.knots(0).size() != Base::nknots_[0])
3464 throw std::runtime_error("Knot vector dimensions mismatch");
3465
3466 auto [knots0_cpu, knots0_accessor] =
3467 utils::to_tensorAccessor<typename Base::value_type, 1>(
3468 Base::knots_[0], torch::kCPU);
3469
3470 const typename Base::value_type *knots0_ptr =
3471 bspline.knots(0).asMatrix().data();
3472
3473 for (int64_t i = 0; i < Base::nknots_[0]; ++i)
3474 knots0_accessor[i] = knots0_ptr[i];
3475
3477
3478 } else
3479 throw std::runtime_error("Invalid parametric dimension");
3480 }
3481
3482 if (updateCoeffs) {
3483
3484 if (bspline.coefs().rows() != Base::geoDim_)
3485 throw std::runtime_error("Geometric dimensions mismatch");
3486
3487 if (bspline.coefs().cols() != Base::ncumcoeffs())
3488 throw std::runtime_error("Coefficient vector dimensions mismatch");
3489
3490 for (short_t g = 0; g < Base::geoDim_; ++g) {
3491
3492 auto [coeffs_cpu, coeffs_accessor] =
3493 utils::to_tensorAccessor<typename Base::value_type, 1>(
3494 Base::coeffs_[g], torch::kCPU);
3495
3496 const typename Base::value_type *coeffs_ptr =
3497 bspline.coefs().row(g).data();
3498
3499 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3500 coeffs_accessor[i] = coeffs_ptr[i];
3501
3503 }
3504 }
3505
3506 return *this;
3507 }
3508
3509 // @brief Updates the B-spline object from a given gsTensorBSpline object
3510 auto &from_gismo(
3511 const gismo::gsTensorBSpline<Base::parDim_, typename Base::value_type>
3512 &bspline,
3513 bool updateCoeffs = true, bool updateKnotVector = false) {
3514
3515 if (updateKnotVector) {
3516
3517 for (short_t i = 0; i < Base::parDim_; ++i) {
3518 if (bspline.degree(i) != Base::degrees_[i])
3519 throw std::runtime_error("Degrees mismatch");
3520
3521 if (bspline.knots(i).size() != Base::nknots_[i])
3522 throw std::runtime_error("Knot vector dimensions mismatch");
3523
3524 auto [knots_cpu, knots_accessor] =
3525 utils::to_tensorAccessor<typename Base::value_type, 1>(
3526 Base::knots_[i], torch::kCPU);
3527
3528 const typename Base::value_type *knots_ptr =
3529 bspline.knots(i).asMatrix().data();
3530
3531 for (int64_t i = 0; i < Base::nknots_[i]; ++i)
3532 knots_accessor[i] = knots_ptr[i];
3533
3535 }
3536 }
3537
3538 if (updateCoeffs) {
3539
3540 if (bspline.coefs().rows() != Base::geoDim_)
3541 throw std::runtime_error("Geometric dimensions mismatch");
3542
3543 if (bspline.coefs().cols() != Base::ncumcoeffs())
3544 throw std::runtime_error("Coefficient vector dimensions mismatch");
3545
3546 for (short_t g = 0; g < Base::geoDim_; ++g) {
3547
3548 auto [coeffs_cpu, coeffs_accessor] =
3549 utils::to_tensorAccessor<typename Base::value_type, 1>(
3550 Base::coeffs_[g], torch::kCPU);
3551
3552 const typename Base::value_type *coeffs_ptr =
3553 bspline.coefs().row(g).data();
3554
3555 for (int64_t i = 0; i < Base::ncoeffs_[g]; ++i)
3556 coeffs_accessor[i] = coeffs_ptr[i];
3557
3559 }
3560 }
3561
3562 return *this;
3563 }
3564
3565#else // IGANET_WITH_GISMO
3566
3567 template <typename BSpline> auto &from_gismo(BSpline &bspline, bool, bool) {
3568 throw std::runtime_error(
3569 "This functions must be compiled with -DIGANET_WITH_GISMO turned on");
3570 return *this;
3571 }
3572
3573#endif // IGANET_WITH_GISMO
3574};
3575
3577class Spline_ {};
3578
3581template <typename T>
3582concept SplineType = std::is_base_of_v<Spline_, T>;
3583
3586template <typename T>
3588 std::is_base_of_v<Spline_, T> && std::is_base_of_v<UniformSplineCore_, T> &&
3589 !std::is_base_of_v<NonUniformSplineCore_, T>;
3590
3593template <typename T>
3594concept NonUniformSplineType = std::is_base_of_v<Spline_, T> &&
3595 std::is_base_of_v<NonUniformSplineCore_, T>;
3596
3610template <typename BSplineCore>
3612class BSplineCommon : public Spline_,
3613 public BSplineCore,
3614 protected utils::FullQualifiedName {
3615public:
3617 using BSplineCore::BSplineCore;
3618
3624 template <template <typename, short_t, short_t...> class T,
3625 std::make_signed_t<short_t> degree_elevate = 0>
3627 typename BSplineCore::template derived_type<T, degree_elevate>>;
3628
3631 template <std::make_signed_t<short_t> degree_elevate = 0>
3634
3638 template <typename real_t, short_t GeoDim, short_t... Degrees>
3640 BSplineCommon<typename BSplineCore::template derived_self_type<
3641 real_t, GeoDim, Degrees...>>;
3642
3645 template <typename other_t>
3647 typename BSplineCore::template real_derived_self_type<other_t>>;
3648
3650 using Ptr = std::shared_ptr<BSplineCommon>;
3651
3653 using uPtr = std::unique_ptr<BSplineCommon>;
3654
3656 BSplineCommon(const BSplineCommon &) = default;
3657
3659 BSplineCommon(const BSplineCommon &other, bool clone) : BSplineCommon(other) {
3660 if (clone)
3661 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3662 BSplineCore::coeffs_[i] = other.coeffs(i).clone();
3663 }
3664
3668 bool clone = false)
3669 : BSplineCommon(other) {
3670 if (clone)
3671 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3672 BSplineCore::coeffs_[i] = coeffs[i].clone();
3673 else
3674 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3675 BSplineCore::coeffs_[i] = coeffs[i];
3676 }
3677
3680
3684 : BSplineCommon(std::move(other)) {
3685 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3686 BSplineCore::coeffs_[i] = std::move(coeffs[i]);
3687 }
3688
3691 inline static Ptr
3696
3697 inline static Ptr
3698 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3699 enum init init = init::greville,
3702 return uPtr(new BSplineCommon(ncoeffs, init, options));
3703 }
3704
3705 inline static Ptr
3706 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3708 bool clone = false,
3711 return uPtr(new BSplineCommon(ncoeffs, coeffs, clone, options));
3712 }
3713
3714 inline static Ptr
3715 make_unique(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3719 return uPtr(new BSplineCommon(ncoeffs, coeffs, options));
3720 }
3721
3722 inline static Ptr
3723 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
3724 BSplineCore::parDim_> &kv,
3725 enum init init = init::greville,
3728 return uPtr(new BSplineCommon(kv, init, options));
3729 }
3730
3731 inline static Ptr
3732 make_unique(const std::array<std::vector<typename BSplineCore::value_type>,
3733 BSplineCore::parDim_> &kv,
3735 bool clone = false,
3738 return uPtr(new BSplineCommon(kv, coeffs, clone, options));
3739 }
3741
3744 inline static Ptr
3747 return std::make_shared<BSplineCommon>(options);
3748 }
3749
3750 inline static Ptr
3751 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3752 enum init init = init::greville,
3755 return std::make_shared<BSplineCommon>(ncoeffs, init, options);
3756 }
3757
3758 inline static Ptr
3759 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3761 bool clone = false,
3764 return std::make_shared<BSplineCommon>(ncoeffs, coeffs, clone, options);
3765 }
3766
3767 inline static Ptr
3768 make_shared(const std::array<int64_t, BSplineCore::parDim_> &ncoeffs,
3772 return std::make_shared<BSplineCommon>(ncoeffs, coeffs, options);
3773 }
3774
3775 inline static Ptr
3776 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
3777 BSplineCore::parDim_> &kv,
3778 enum init init = init::greville,
3781 return std::make_shared<BSplineCommon>(kv, init, options);
3782 }
3783
3784 inline static Ptr
3785 make_shared(const std::array<std::vector<typename BSplineCore::value_type>,
3786 BSplineCore::parDim_> &kv,
3788 bool clone = false,
3791 return std::make_shared<BSplineCommon>(kv, coeffs, clone, options);
3792 }
3794
3801 inline BSplineCommon &uniform_refine(int numRefine = 1, int dim = -1) {
3802 BSplineCore::uniform_refine(numRefine, dim);
3803 return *this;
3804 }
3805
3807 inline auto clone() const {
3808 BSplineCommon result;
3809
3810 result.nknots_ = BSplineCore::nknots_;
3811 result.ncoeffs_ = BSplineCore::ncoeffs_;
3812 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3813
3814 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3815 result.knots_[i] = BSplineCore::knots_[i].clone();
3816
3817 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3818 result.coeffs_[i] = BSplineCore::coeffs_[i].clone();
3819
3820 return result;
3821 }
3822
3824 template <typename real_t> inline auto to(Options<real_t> options) const {
3826 result(options);
3827
3828 result.nknots_ = BSplineCore::nknots_;
3829 result.ncoeffs_ = BSplineCore::ncoeffs_;
3830 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3831
3832 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3833 result.knots_[i] = BSplineCore::knots_[i].to(options);
3834
3835 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3836 result.coeffs_[i] = BSplineCore::coeffs_[i].to(options);
3837
3838 return result;
3839 }
3840
3842 inline auto to(torch::Device device) const {
3843 BSplineCommon result(BSplineCore::options_.device(device));
3844
3845 result.nknots_ = BSplineCore::nknots_;
3846 result.ncoeffs_ = BSplineCore::ncoeffs_;
3847 result.ncoeffs_reverse_ = BSplineCore::ncoeffs_reverse_;
3848
3849 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3850 result.knots_[i] = BSplineCore::knots_[i].to(device);
3851
3852 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3853 result.coeffs_[i] = BSplineCore::coeffs_[i].to(device);
3854
3855 return result;
3856 }
3857
3859 template <typename real_t> inline auto to() const {
3860 return to(BSplineCore::options_.template dtype<real_t>());
3861 }
3862
3869 inline auto diff(const BSplineCommon &other, int dim = -1) const {
3870 return this->clone().diff_(other, dim);
3871 }
3872
3879 inline auto diff_(const BSplineCommon &other, int dim = -1) {
3880
3881 bool compatible(true);
3882
3883 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3884 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
3885
3886 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3887 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
3888
3889 if (!compatible)
3890 throw std::runtime_error("B-splines are not compatible");
3891
3892 if (dim == -1) {
3893 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3894 BSplineCore::coeffs(i) -= other.coeffs(i);
3895 } else
3896 BSplineCore::coeffs(dim) -= other.coeffs(dim);
3897
3898 return *this;
3899 }
3900
3907 inline auto abs_diff(const BSplineCommon &other, int dim = -1) const {
3908 return this->clone().abs_diff_(other, dim);
3909 }
3910
3917 inline auto abs_diff_(const BSplineCommon &other, int dim = -1) {
3918
3919 bool compatible(true);
3920
3921 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3922 compatible *= (BSplineCore::nknots(i) == other.nknots(i));
3923
3924 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
3925 compatible *= (BSplineCore::ncoeffs(i) == other.ncoeffs(i));
3926
3927 if (!compatible)
3928 throw std::runtime_error("B-splines are not compatible");
3929
3930 if (dim == -1) {
3931 for (short_t i = 0; i < BSplineCore::geoDim_; ++i)
3932 BSplineCore::coeffs(i) =
3933 torch::abs(BSplineCore::coeffs(i) - other.coeffs(i));
3934 } else
3935 BSplineCore::coeffs(dim) =
3936 torch::abs(BSplineCore::coeffs(dim) - other.coeffs(dim));
3937
3938 return *this;
3939 }
3940
3944 inline auto norm() const {
3945 return torch::mean(torch::pow(BSplineCore::eval(BSplineCore::greville())(0), 2));
3946 }
3947
3949 inline auto scale(BSplineCore::value_type s, int dim = -1) const {
3950 return this->clone().scale_(s, dim);
3951 }
3952
3954 inline auto scale_(BSplineCore::value_type s, int dim = -1) {
3955 if (dim == -1)
3956 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3957 BSplineCore::coeffs(i) *= s;
3958 else
3959 BSplineCore::coeffs(dim) *= s;
3960 return *this;
3961 }
3962
3964 inline auto
3965 scale(std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) const {
3966 return this->clone().scale_(v);
3967 }
3968
3970 inline auto
3971 scale_(std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
3972 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3973 BSplineCore::coeffs(i) *= v[i];
3974 return *this;
3975 }
3976
3978 inline auto translate(
3979 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) const {
3980 return this->clone().translate_(v);
3981 }
3982
3984 inline auto translate_(
3985 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
3986 for (int i = 0; i < BSplineCore::geoDim(); ++i)
3987 BSplineCore::coeffs(i) += v[i];
3988 return *this;
3989 }
3990
3992 inline auto rotate(BSplineCore::value_type angle) const {
3993 return this->clone().rotate_(angle);
3994 }
3995
3997 inline auto rotate_(BSplineCore::value_type angle) {
3998
3999 static_assert(BSplineCore::geoDim() == 2,
4000 "Rotation about one angle is only available in 2D");
4001
4002 utils::TensorArray<2> coeffs;
4003 coeffs[0] = std::cos(angle) * BSplineCore::coeffs(0) -
4004 std::sin(angle) * BSplineCore::coeffs(1);
4005 coeffs[1] = std::sin(angle) * BSplineCore::coeffs(0) +
4006 std::cos(angle) * BSplineCore::coeffs(1);
4007
4008 BSplineCore::coeffs().swap(coeffs);
4009 return *this;
4010 }
4011
4013 inline auto rotate(std::array<typename BSplineCore::value_type, 3> angle) const {
4014 return this->clone().rotate_(angle);
4015 }
4016
4018 inline auto rotate_(std::array<typename BSplineCore::value_type, 3> angle) {
4019
4020 static_assert(BSplineCore::geoDim() == 3,
4021 "Rotation about two angles is only available in 3D");
4022
4023 utils::TensorArray<3> coeffs;
4024 coeffs[0] =
4025 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(0) +
4026 (std::sin(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) -
4027 std::cos(angle[0]) * std::sin(angle[2])) *
4028 BSplineCore::coeffs(1) +
4029 (std::cos(angle[0]) * std::sin(angle[1]) * std::cos(angle[2]) +
4030 std::sin(angle[0]) * std::sin(angle[2])) *
4031 BSplineCore::coeffs(2);
4032
4033 coeffs[1] =
4034 std::cos(angle[1]) * std::sin(angle[2]) * BSplineCore::coeffs(0) +
4035 (std::sin(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) +
4036 std::cos(angle[0]) * std::cos(angle[2])) *
4037 BSplineCore::coeffs(1) +
4038 (std::cos(angle[0]) * std::sin(angle[1]) * std::sin(angle[2]) -
4039 std::sin(angle[0]) * std::cos(angle[2])) *
4040 BSplineCore::coeffs(2);
4041
4042 coeffs[2] =
4043 -std::sin(angle[1]) * BSplineCore::coeffs(0) +
4044 std::sin(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(1) +
4045 std::cos(angle[0]) * std::cos(angle[1]) * BSplineCore::coeffs(2);
4046
4047 BSplineCore::coeffs().swap(coeffs);
4048 return *this;
4049 }
4050
4052 inline auto boundingBox() const {
4053
4054 // Lambda expression to compute the minimum value of all dimensions
4055 auto min_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4056 return torch::stack({BSplineCore::coeffs(Is).min()...});
4057 };
4058
4059 // Lambda expression to compute the maximum value of all dimensions
4060 auto max_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4061 return torch::stack({BSplineCore::coeffs(Is).max()...});
4062 };
4063
4064 std::pair<torch::Tensor, torch::Tensor> bbox;
4065 bbox.first = min_(std::make_index_sequence<BSplineCore::geoDim_>{});
4066 bbox.second = max_(std::make_index_sequence<BSplineCore::geoDim_>{});
4067 return bbox;
4068 }
4069
4077 template <bool memory_optimized = false>
4078 inline auto nv(const torch::Tensor &xi) const {
4079 return nv<memory_optimized>(utils::TensorArray1({xi}));
4080 }
4081
4082 template <bool memory_optimized = false>
4083 inline auto nv(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4084 return nv<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4085 }
4087
4096 template <bool memory_optimized = false>
4097 inline auto
4099 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4100 return nv<memory_optimized>(
4101 xi, knot_indices,
4102 BSplineCore::template find_coeff_indices<memory_optimized>(
4103 knot_indices));
4104 }
4105
4117 template <bool memory_optimized = false>
4119 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4120 const torch::Tensor &coeff_indices) const {
4121
4122 if constexpr (BSplineCore::parDim_ == 1 && BSplineCore::geoDim_ == 2) {
4123 // Compute the perpendicular vector
4124 auto eval_ = BSplineCore::template eval<deriv::dx, memory_optimized>(
4125 xi, knot_indices, coeff_indices);
4126 return utils::BlockTensor<torch::Tensor, 1, 2>(*eval_[1], -*eval_[0]);
4127 } else if constexpr (BSplineCore::parDim_ == 1 &&
4128 BSplineCore::geoDim_ == 3) {
4129 // Compute the Frenet normal vector
4130 auto t_ = BSplineCore::template eval<deriv::dx, memory_optimized>(
4131 xi, knot_indices, coeff_indices)
4132 .normalize();
4133 auto a_ = BSplineCore::template eval<deriv::dx ^ 2, memory_optimized>(
4134 xi, knot_indices, coeff_indices);
4135 auto n_ = a_ - a_.dot(t_) * t_;
4137 n_(0, 0), n_(0, 1), n_(0, 2), t_(0, 0), t_(0, 1), t_(0, 2));
4138 } else if constexpr (BSplineCore::parDim_ == 2 &&
4139 BSplineCore::geoDim_ == 3) {
4140 // Compute the cross product of tangent vectors
4141 auto jac_ = jac<memory_optimized>(xi, knot_indices, coeff_indices);
4143 jac_(1, 0) * jac_(2, 1) - jac_(2, 0) * jac_(1, 1),
4144 jac_(2, 0) * jac_(0, 1) - jac_(0, 0) * jac_(2, 1),
4145 jac_(0, 0) * jac_(1, 1) - jac_(1, 0) * jac_(0, 1));
4146 } else {
4147 throw std::runtime_error("Unsupported parametric/geometric dimension");
4149 }
4150 }
4151
4152 // clang-format off
4173 // clang-format off
4175 template <bool memory_optimized = false>
4176 inline auto curl(const torch::Tensor &xi) const {
4177 return curl<memory_optimized>(utils::TensorArray1({xi}));
4178 }
4179
4180 template <bool memory_optimized = false>
4181 inline auto curl(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4182 return curl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4183 }
4185
4186 // clang-format off
4209 // clang-format on
4210 template <bool memory_optimized = false>
4211 inline auto
4213 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4214 return curl<memory_optimized>(
4215 xi, knot_indices,
4216 BSplineCore::template find_coeff_indices<memory_optimized>(
4217 knot_indices));
4218 }
4219
4246 template <bool memory_optimized = false>
4248 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4249 const torch::Tensor &coeff_indices) const {
4250
4251 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
4252 "curl(.) requires that parametric and geometric dimension "
4253 "are the same");
4254
4255 // Check compatibility of arguments
4256 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4257 assert(xi[i].sizes() == knot_indices[i].sizes());
4258 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4259 assert(xi[0].sizes() == xi[i].sizes());
4260
4261 if constexpr (BSplineCore::parDim_ == 2)
4262
4269 *BSplineCore::template eval<deriv::dx, memory_optimized>(
4270 xi, knot_indices, coeff_indices)[1] -
4271 *BSplineCore::template eval<deriv::dy, memory_optimized>(
4272 xi, knot_indices, coeff_indices)[0]);
4273
4274 else if constexpr (BSplineCore::parDim_ == 3)
4275
4280 *BSplineCore::template eval<deriv::dy, memory_optimized>(
4281 xi, knot_indices, coeff_indices)[2] -
4282 *BSplineCore::template eval<deriv::dz, memory_optimized>(
4283 xi, knot_indices, coeff_indices)[1],
4284 *BSplineCore::template eval<deriv::dz, memory_optimized>(
4285 xi, knot_indices, coeff_indices)[0] +
4286 *BSplineCore::template eval<deriv::dx, memory_optimized>(
4287 xi, knot_indices, coeff_indices)[2],
4288 *BSplineCore::template eval<deriv::dx, memory_optimized>(
4289 xi, knot_indices, coeff_indices)[1] +
4290 *BSplineCore::template eval<deriv::dy, memory_optimized>(
4291 xi, knot_indices, coeff_indices)[0]);
4292
4293 else {
4294 throw std::runtime_error("Unsupported parametric/geometric dimension");
4296 }
4297 }
4298
4320 template <bool memory_optimized = false, typename Geometry>
4321 inline auto icurl(const Geometry &G, const torch::Tensor &xi) const {
4322 if constexpr (BSplineCore::parDim_ == 0)
4324 torch::zeros_like(BSplineCore::coeffs_[0])};
4325 else
4326 return icurl<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4327 }
4328
4329 template <bool memory_optimized = false, typename Geometry>
4330 inline auto icurl(const Geometry &G,
4332 if constexpr (BSplineCore::parDim_ == 0)
4334 torch::zeros_like(BSplineCore::coeffs_[0])};
4335 else
4336 return icurl<memory_optimized, Geometry>(
4337 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4338 }
4340
4364 template <bool memory_optimized = false, typename Geometry>
4365 inline auto
4367 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4368 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4369 if constexpr (BSplineCore::parDim_ == 0)
4371 torch::zeros_like(BSplineCore::coeffs_[0])};
4372 else
4373 return icurl<memory_optimized, Geometry>(
4374 G, xi, knot_indices,
4375 BSplineCore::template find_coeff_indices<memory_optimized>(
4376 knot_indices),
4377 knot_indices_G,
4378 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4379 }
4380
4411 template <bool memory_optimized = false, typename Geometry>
4412 inline auto
4414 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4415 const torch::Tensor &coeff_indices,
4416 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4417 const torch::Tensor &coeff_indices_G) const {
4418
4419 if constexpr (BSplineCore::parDim_ == 0)
4421 torch::zeros_like(BSplineCore::coeffs_[0])};
4422 else {
4424 det[0] = std::make_shared<torch::Tensor>(torch::reciprocal(
4425 G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G)
4426 .det()));
4427
4428 return det * (curl<memory_optimized>(xi, knot_indices, coeff_indices) *
4429 G.template jac<memory_optimized>(xi, knot_indices_G,
4430 coeff_indices_G));
4431 }
4432 }
4433
4455 template <bool memory_optimized = false>
4456 inline auto div(const torch::Tensor &xi) const {
4457 if constexpr (BSplineCore::parDim_ == 0)
4459 torch::zeros_like(BSplineCore::coeffs_[0])};
4460 return div<memory_optimized>(utils::TensorArray1({xi}));
4461 }
4462
4463 template <bool memory_optimized = false>
4464 inline auto div(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4465 if constexpr (BSplineCore::parDim_ == 0)
4467 torch::zeros_like(BSplineCore::coeffs_[0])};
4468 return div<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4469 }
4471
4494 template <bool memory_optimized = false>
4495 inline auto
4497 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4498 if constexpr (BSplineCore::parDim_ == 0)
4500 torch::zeros_like(BSplineCore::coeffs_[0])};
4501 return div<memory_optimized>(
4502 xi, knot_indices,
4503 BSplineCore::template find_coeff_indices<memory_optimized>(
4504 knot_indices));
4505 }
4506
4532 template <bool memory_optimized = false>
4534 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4535 const torch::Tensor &coeff_indices) const {
4536
4537 static_assert(BSplineCore::parDim_ == BSplineCore::geoDim_,
4538 "div(.) requires parDim == geoDim");
4539
4540 if constexpr (BSplineCore::parDim_ == 0)
4542 torch::zeros_like(BSplineCore::coeffs_[0])};
4543 // return torch::zeros_like(BSplineCore::coeffs_[0]);
4544
4545 else {
4546 // Check compatibility of arguments
4547 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4548 assert(xi[i].sizes() == knot_indices[i].sizes());
4549 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4550 assert(xi[0].sizes() == xi[i].sizes());
4551
4552 // Lambda expression to evaluate the divergence
4553 auto div_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4555 (*BSplineCore::template eval<
4557 memory_optimized>(xi, knot_indices, coeff_indices)[Is] +
4558 ...)};
4559 };
4560
4561 return div_(std::make_index_sequence<BSplineCore::parDim_>{});
4562 }
4563 }
4564
4587 template <bool memory_optimized = false, typename Geometry>
4588 inline auto idiv(const Geometry &G, const torch::Tensor &xi) {
4589 if constexpr (BSplineCore::parDim_ == 0)
4591 torch::zeros_like(BSplineCore::coeffs_[0])};
4592 else
4593 return idiv<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4594 }
4595
4596 template <bool memory_optimized = false, typename Geometry>
4597 inline auto idiv(const Geometry &G,
4599 if constexpr (BSplineCore::parDim_ == 0)
4601 torch::zeros_like(BSplineCore::coeffs_[0])};
4602 else
4603 return idiv<memory_optimized, Geometry>(
4604 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4605 }
4607
4633 template <bool memory_optimized = false, typename Geometry>
4634 inline auto
4636 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4637 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4638 if constexpr (BSplineCore::parDim_ == 0)
4640 torch::zeros_like(BSplineCore::coeffs_[0])};
4641 else
4642 return idiv<memory_optimized, Geometry>(
4643 G, xi, knot_indices,
4644 BSplineCore::template find_coeff_indices<memory_optimized>(
4645 knot_indices),
4646 knot_indices_G,
4647 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4648 }
4649
4681 template <bool memory_optimized = false, typename Geometry>
4682 inline auto idiv(const Geometry &G,
4684 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4685 const torch::Tensor &coeff_indices,
4686 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4687 const torch::Tensor &coeff_indices_G) const {
4688 if constexpr (BSplineCore::parDim_ == 0)
4690 torch::zeros_like(BSplineCore::coeffs_[0])};
4691 else
4692 return ijac<memory_optimized, Geometry>(G, xi, knot_indices,
4693 coeff_indices, knot_indices_G,
4694 coeff_indices_G)
4695 .trace();
4696 }
4697
4718 template <bool memory_optimized = false>
4719 inline auto grad(const torch::Tensor &xi) const {
4720
4721 static_assert(BSplineCore::geoDim_ == 1,
4722 "grad(.) requires 1D variable, use jac(.) instead");
4723
4724 if constexpr (BSplineCore::parDim_ == 0)
4726 torch::zeros_like(BSplineCore::coeffs_[0])};
4727 else
4728 return grad<memory_optimized>(utils::TensorArray1({xi}));
4729 }
4730
4731 template <bool memory_optimized = false>
4732 inline auto grad(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
4733
4734 static_assert(BSplineCore::geoDim_ == 1,
4735 "grad(.) requires 1D variable, use jac(.) instead");
4736
4737 if constexpr (BSplineCore::parDim_ == 0)
4739 torch::zeros_like(BSplineCore::coeffs_[0])};
4740 else
4741 return grad<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
4742 }
4744
4766 template <bool memory_optimized = false>
4767 inline auto
4769 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
4770
4771 static_assert(BSplineCore::geoDim_ == 1,
4772 "grad(.) requires 1D variable, use jac(.) instead");
4773
4774 if constexpr (BSplineCore::parDim_ == 0)
4776 torch::zeros_like(BSplineCore::coeffs_[0])};
4777 else
4778 return grad<memory_optimized>(
4779 xi, knot_indices,
4780 BSplineCore::template find_coeff_indices<memory_optimized>(
4781 knot_indices));
4782 }
4783
4808 template <bool memory_optimized = false>
4810 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4811 const torch::Tensor &coeff_indices) const {
4812
4813 static_assert(BSplineCore::geoDim_ == 1,
4814 "grad(.) requires 1D variable, use jac(.) instead");
4815
4816 if constexpr (BSplineCore::parDim_ == 0)
4818 torch::zeros_like(BSplineCore::coeffs_[0])};
4819
4820 else {
4821 // Check compatibility of arguments
4822 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
4823 assert(xi[i].sizes() == knot_indices[i].sizes());
4824 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
4825 assert(xi[0].sizes() == xi[i].sizes());
4826
4827 // Lambda expression to evaluate the gradient
4828 auto grad_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
4830 BSplineCore::template eval<
4832 memory_optimized>(xi, knot_indices, coeff_indices)...};
4833 };
4834
4835 return grad_(std::make_index_sequence<BSplineCore::parDim_>{});
4836 }
4837 }
4838
4860 template <bool memory_optimized = false, typename Geometry>
4861 inline auto igrad(const Geometry &G, const torch::Tensor &xi) const {
4862 if constexpr (BSplineCore::parDim_ == 0)
4864 torch::zeros_like(BSplineCore::coeffs_[0])};
4865 else
4866 return igrad<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
4867 }
4868
4869 template <bool memory_optimized = false, typename Geometry>
4870 inline auto igrad(const Geometry &G,
4872 if constexpr (BSplineCore::parDim_ == 0)
4874 torch::zeros_like(BSplineCore::coeffs_[0])};
4875 else
4876 return igrad<memory_optimized, Geometry>(
4877 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
4878 }
4880
4904 template <bool memory_optimized = false, typename Geometry>
4905 inline auto
4907 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4908 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
4909 if constexpr (BSplineCore::parDim_ == 0)
4911 torch::zeros_like(BSplineCore::coeffs_[0])};
4912 else
4913 return igrad<memory_optimized, Geometry>(
4914 G, xi, knot_indices,
4915 BSplineCore::template find_coeff_indices<memory_optimized>(
4916 knot_indices),
4917 knot_indices_G,
4918 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
4919 }
4920
4951 template <bool memory_optimized = false, typename Geometry>
4952 inline auto
4954 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
4955 const torch::Tensor &coeff_indices,
4956 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
4957 const torch::Tensor &coeff_indices_G) const {
4958 if constexpr (BSplineCore::parDim_ == 0)
4960 torch::zeros_like(BSplineCore::coeffs_[0])};
4961 else
4962 return grad<memory_optimized>(xi, knot_indices, coeff_indices) *
4963 G.template jac<memory_optimized>(xi, knot_indices_G,
4964 coeff_indices_G)
4965 .ginv();
4966 }
4967
4968 // clang-format off
5001 // clang-format on
5004 template <bool memory_optimized = false>
5005 inline auto hess(const torch::Tensor &xi) const {
5006 if constexpr (BSplineCore::parDim_ == 0)
5008 torch::zeros_like(BSplineCore::coeffs_[0])};
5009 else
5010 return hess<memory_optimized>(utils::TensorArray1({xi}));
5011 }
5012
5013 template <bool memory_optimized = false>
5014 inline auto hess(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5015 if constexpr (BSplineCore::parDim_ == 0)
5017 torch::zeros_like(BSplineCore::coeffs_[0])};
5018 else
5019 return hess<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5020 }
5022
5023 // clang-format off
5058 // clang-format on
5059 template <bool memory_optimized = false>
5060 inline auto
5062 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5063 if constexpr (BSplineCore::parDim_ == 0)
5065 torch::zeros_like(BSplineCore::coeffs_[0])};
5066 else
5067 return hess<memory_optimized>(
5068 xi, knot_indices,
5069 BSplineCore::template find_coeff_indices<memory_optimized>(
5070 knot_indices));
5071 }
5072
5073 // clang-format off
5110 // clang-format on
5111 template <bool memory_optimized = false>
5113 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5114 const torch::Tensor &coeff_indices) const {
5115
5116 if constexpr (BSplineCore::parDim_ == 0)
5118 torch::zeros_like(BSplineCore::coeffs_[0])};
5119
5120 else {
5121 // Check compatibility of arguments
5122 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5123 assert(xi[i].sizes() == knot_indices[i].sizes());
5124 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5125 assert(xi[0].sizes() == xi[i].sizes());
5126
5127 // Lambda expression to evaluate the hessian
5128 auto hess_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5129 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5130 BSplineCore::geoDim_, BSplineCore::parDim_>{
5131 BSplineCore::template eval<
5132 static_cast<deriv>(
5133 utils::integer_pow<10, Is / BSplineCore::parDim_>::value) +
5134 static_cast<deriv>(utils::integer_pow<
5135 10, Is % BSplineCore::parDim_>::value),
5136 memory_optimized>(xi, knot_indices, coeff_indices)...}
5137 .reorder_ikj();
5138 };
5139
5140 return hess_(std::make_index_sequence<BSplineCore::parDim_ *
5141 BSplineCore::parDim_>{});
5142 }
5143 }
5144
5172 template <bool memory_optimized = false, typename Geometry>
5173 inline auto ihess(const Geometry &G, const torch::Tensor &xi) const {
5174 if constexpr (BSplineCore::parDim_ == 0)
5176 torch::zeros_like(BSplineCore::coeffs_[0])};
5177 else
5178 return ihess<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5179 }
5180
5181 template <bool memory_optimized = false, typename Geometry>
5182 inline auto ihess(const Geometry &G,
5184 if constexpr (BSplineCore::parDim_ == 0)
5186 torch::zeros_like(BSplineCore::coeffs_[0])};
5187 else
5188 return ihess<memory_optimized, Geometry>(
5189 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5190 }
5192
5222 template <bool memory_optimized = false, typename Geometry>
5223 inline auto
5225 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5226 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5227 if constexpr (BSplineCore::parDim_ == 0)
5229 torch::zeros_like(BSplineCore::coeffs_[0])};
5230 else
5231 return ihess<memory_optimized, Geometry>(
5232 G, xi, knot_indices,
5233 BSplineCore::template find_coeff_indices<memory_optimized>(
5234 knot_indices),
5235 knot_indices_G,
5236 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5237 }
5238
5274 template <bool memory_optimized = false, typename Geometry>
5275 inline auto
5277 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5278 const torch::Tensor &coeff_indices,
5279 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5280 const torch::Tensor &coeff_indices_G) const {
5281
5282 if constexpr (BSplineCore::parDim_ == 0)
5284 torch::zeros_like(BSplineCore::coeffs_[0])};
5285 else {
5286 utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5287 BSplineCore::parDim_, BSplineCore::geoDim_>
5288 hessu;
5289
5290 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
5291 coeff_indices_G);
5292 auto ijacG = ijac<memory_optimized>(G, xi, knot_indices, coeff_indices,
5293 knot_indices_G, coeff_indices_G);
5294
5295 for (short_t component = 0; component < BSplineCore::geoDim_;
5296 ++component) {
5297 auto hess_component =
5298 hess<memory_optimized>(xi, knot_indices, coeff_indices)
5299 .slice(component);
5300
5301 for (short_t k = 0; k < hessG.slices(); ++k) {
5302 hess_component -= ijacG(component, k) * hessG.slice(k);
5303 }
5304
5305 auto jacInv = G.template jac<memory_optimized>(xi, knot_indices_G,
5306 coeff_indices_G)
5307 .ginv();
5308 auto hessu_component = jacInv.tr() * hess_component * jacInv;
5309
5310 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5311 for (short_t j = 0; j < BSplineCore::parDim_; ++j)
5312 hessu.set(i, j, component, hessu_component(i, j));
5313 }
5314
5315 return hessu;
5316 }
5317 }
5318
5319 // clang-format off
5347 // clang-format on
5350 template <bool memory_optimized = false>
5351 inline auto jac(const torch::Tensor &xi) const {
5352 if constexpr (BSplineCore::parDim_ == 0)
5354 torch::zeros_like(BSplineCore::coeffs_[0])};
5355 else
5356 return jac<memory_optimized>(utils::TensorArray1({xi}));
5357 }
5358
5359 template <bool memory_optimized = false>
5360 inline auto jac(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5361 if constexpr (BSplineCore::parDim_ == 0)
5363 torch::zeros_like(BSplineCore::coeffs_[0])};
5364 else
5365 return jac<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5366 }
5368
5369 // clang-format off
5399 // clang-format on
5400 template <bool memory_optimized = false>
5401 inline auto
5403 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5404 if constexpr (BSplineCore::parDim_ == 0)
5406 torch::zeros_like(BSplineCore::coeffs_[0])};
5407 else
5408 return jac<memory_optimized>(
5409 xi, knot_indices,
5410 BSplineCore::template find_coeff_indices<memory_optimized>(
5411 knot_indices));
5412 }
5413
5414 // clang-format off
5452 // clang-format on
5453 template <bool memory_optimized = false>
5455 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5456 const torch::Tensor &coeff_indices) const {
5457
5458 if constexpr (BSplineCore::parDim_ == 0)
5460 torch::zeros_like(BSplineCore::coeffs_[0])};
5461
5462 else {
5463 // Check compatibility of arguments
5464 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5465 assert(xi[i].sizes() == knot_indices[i].sizes());
5466 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5467 assert(xi[0].sizes() == xi[i].sizes());
5468
5469 // Lambda expression to evaluate the jacobian
5470 auto jac_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5471 return utils::BlockTensor<torch::Tensor, BSplineCore::parDim_,
5472 BSplineCore::geoDim_>{
5473 BSplineCore::template eval<
5475 memory_optimized>(xi, knot_indices, coeff_indices)...}
5476 .tr();
5477 };
5478
5479 return jac_(std::make_index_sequence<BSplineCore::parDim_>{});
5480 }
5481 }
5482
5504 template <bool memory_optimized = false, typename Geometry>
5505 inline auto ijac(const Geometry &G, const torch::Tensor &xi) const {
5506 if constexpr (BSplineCore::parDim_ == 0)
5508 torch::zeros_like(BSplineCore::coeffs_[0])};
5509 else
5510 return ijac<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5511 }
5512
5513 template <bool memory_optimized = false, typename Geometry>
5514 inline auto ijac(const Geometry &G,
5516 if constexpr (BSplineCore::parDim_ == 0)
5518 torch::zeros_like(BSplineCore::coeffs_[0])};
5519 else
5520 return ijac<memory_optimized, Geometry>(
5521 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5522 }
5524
5549 template <bool memory_optimized = false, typename Geometry>
5550 inline auto
5552 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5553 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5554 if constexpr (BSplineCore::parDim_ == 0)
5556 torch::zeros_like(BSplineCore::coeffs_[0])};
5557 else
5558 return ijac<memory_optimized, Geometry>(
5559 G, xi, knot_indices,
5560 BSplineCore::template find_coeff_indices<memory_optimized>(
5561 knot_indices),
5562 knot_indices_G,
5563 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5564 }
5565
5596 template <bool memory_optimized = false, typename Geometry>
5597 inline auto ijac(const Geometry &G,
5599 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5600 const torch::Tensor &coeff_indices,
5601 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5602 const torch::Tensor &coeff_indices_G) const {
5603 if constexpr (BSplineCore::parDim_ == 0)
5605 torch::zeros_like(BSplineCore::coeffs_[0])};
5606 else
5607 return jac<memory_optimized>(xi, knot_indices, coeff_indices) *
5608 G.template jac<memory_optimized>(xi, knot_indices_G,
5609 coeff_indices_G)
5610 .ginv();
5611 }
5612
5613 // clang-format off
5630 // clang-format on
5633 template <bool memory_optimized = false>
5634 inline auto lapl(const torch::Tensor &xi) const {
5635 if constexpr (BSplineCore::parDim_ == 0)
5637 torch::zeros_like(BSplineCore::coeffs_[0])};
5638 else
5639 return lapl<memory_optimized>(utils::TensorArray1({xi}));
5640 }
5641
5642 template <bool memory_optimized = false>
5643 inline auto lapl(const utils::TensorArray<BSplineCore::parDim_> &xi) const {
5644 if constexpr (BSplineCore::parDim_ == 0)
5646 torch::zeros_like(BSplineCore::coeffs_[0])};
5647 else
5648 return lapl<memory_optimized>(xi, BSplineCore::find_knot_indices(xi));
5649 }
5651
5652 // clang-format off
5672 // clang-format on
5673 template <bool memory_optimized = false>
5674 inline auto
5676 const utils::TensorArray<BSplineCore::parDim_> &knot_indices) const {
5677 if constexpr (BSplineCore::parDim_ == 0)
5679 torch::zeros_like(BSplineCore::coeffs_[0])};
5680 else
5681 return lapl<memory_optimized>(
5682 xi, knot_indices,
5683 BSplineCore::template find_coeff_indices<memory_optimized>(
5684 knot_indices));
5685 }
5686
5687 // clang-format off
5710 // clang-format on
5711 template <bool memory_optimized = false>
5713 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5714 const torch::Tensor &coeff_indices) const {
5715
5716 if constexpr (BSplineCore::parDim_ == 0)
5718 torch::zeros_like(BSplineCore::coeffs_[0])};
5719
5720 else {
5721 // Check compatibility of arguments
5722 for (short_t i = 0; i < BSplineCore::parDim_; ++i)
5723 assert(xi[i].sizes() == knot_indices[i].sizes());
5724 for (short_t i = 1; i < BSplineCore::parDim_; ++i)
5725 assert(xi[0].sizes() == xi[i].sizes());
5726
5727 // Lambda expression to evaluate the laplacian
5728 auto lapl_ = [&, this]<std::size_t... Is>(std::index_sequence<Is...>) {
5730 (BSplineCore::template eval<
5731 static_cast<deriv>(utils::integer_pow<10, Is>::value) ^ 2,
5732 memory_optimized>(xi, knot_indices, coeff_indices) +
5733 ...)}
5734 .reorder_ikj();
5735 };
5736
5737 return lapl_(std::make_index_sequence<BSplineCore::parDim_>{});
5738 }
5739 }
5740
5769 template <bool memory_optimized = false, typename Geometry>
5770 auto ilapl(const Geometry &G, const torch::Tensor &xi) const {
5771 if constexpr (BSplineCore::parDim_ == 0)
5773 torch::zeros_like(BSplineCore::coeffs_[0])};
5774 else
5775 return ilapl<memory_optimized, Geometry>(G, utils::TensorArray1({xi}));
5776 }
5777
5778 template <bool memory_optimized = false, typename Geometry>
5779 inline auto ilapl(const Geometry &G,
5781 if constexpr (BSplineCore::parDim_ == 0)
5783 torch::zeros_like(BSplineCore::coeffs_[0])};
5784 else
5785 return ilapl<memory_optimized, Geometry>(
5786 G, xi, BSplineCore::find_knot_indices(xi), G.find_knot_indices(xi));
5787 }
5789
5820 template <bool memory_optimized = false, typename Geometry>
5821 inline auto
5823 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5824 const utils::TensorArray<Geometry::parDim()> &knot_indices_G) const {
5825 if constexpr (BSplineCore::parDim_ == 0)
5827 torch::zeros_like(BSplineCore::coeffs_[0])};
5828 else
5829 return ilapl<memory_optimized, Geometry>(
5830 G, xi, knot_indices,
5831 BSplineCore::template find_coeff_indices<memory_optimized>(
5832 knot_indices),
5833 knot_indices_G,
5834 G.template find_coeff_indices<memory_optimized>(knot_indices_G));
5835 }
5836
5874 template <bool memory_optimized = false, typename Geometry>
5875 inline auto
5877 const utils::TensorArray<BSplineCore::parDim_> &knot_indices,
5878 const torch::Tensor &coeff_indices,
5879 const utils::TensorArray<Geometry::parDim()> &knot_indices_G,
5880 const torch::Tensor &coeff_indices_G) const {
5881
5882 if constexpr (BSplineCore::parDim_ == 0)
5884 torch::zeros_like(BSplineCore::coeffs_[0])};
5885
5886 else {
5887 auto hessu =
5888 hess<memory_optimized>(xi, knot_indices, coeff_indices).slice(0);
5889
5890 {
5891 auto igradG =
5892 igrad<memory_optimized>(G, xi, knot_indices, coeff_indices,
5893 knot_indices_G, coeff_indices_G);
5894 auto hessG = G.template hess<memory_optimized>(xi, knot_indices_G,
5895 coeff_indices_G);
5896 assert(igradG.cols() == hessG.slices());
5897 for (short_t k = 0; k < hessG.slices(); ++k)
5898 hessu -= igradG(0, k) * hessG.slice(k);
5899 }
5900
5901 auto jacInv =
5902 G.template jac<memory_optimized>(xi, knot_indices_G, coeff_indices_G)
5903 .ginv();
5904
5905 return (jacInv.tr() * hessu * jacInv).trace();
5906 }
5907 }
5908
5914#ifdef IGANET_WITH_MATPLOT
5915 template <typename Backend = matplot::backend::gnuplot>
5916#else
5917 template <typename Backend = void>
5918#endif
5919 inline auto plot(const nlohmann::json &json = {}) const {
5920 return plot<Backend>(*this, json);
5921 }
5922
5930#ifdef IGANET_WITH_MATPLOT
5931 template <typename Backend = matplot::backend::gnuplot>
5932#else
5933 template <typename Backend = void>
5934#endif
5936 const nlohmann::json &json = {}) const {
5937
5938 return plot<Backend>(*this, xi, json);
5939 }
5940
5948#ifdef IGANET_WITH_MATPLOT
5949 template <typename Backend = matplot::backend::gnuplot>
5950#else
5951 template <typename Backend = void>
5952#endif
5953 inline auto plot(
5954 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
5955 const nlohmann::json &json = {}) const {
5956
5957 return plot<Backend>(*this, xi, json);
5958 }
5959
5967#ifdef IGANET_WITH_MATPLOT
5968 template <typename Backend = matplot::backend::gnuplot,
5969 typename BSplineCoreColor>
5970#else
5971 template <typename Backend = void, typename BSplineCoreColor>
5972#endif
5973 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
5974 const nlohmann::json &json = {}) const {
5975#ifdef IGANET_WITH_MATPLOT
5976 static_assert(BSplineCore::parDim() == BSplineCoreColor::parDim(),
5977 "Parametric dimensions must match");
5978
5979 if ((void *)this != (void *)&color && BSplineCoreColor::geoDim() > 1)
5980 throw std::runtime_error("BSpline for coloring must have geoDim=1");
5981
5982 if constexpr (BSplineCore::parDim() == 1 && BSplineCore::geoDim() == 1) {
5983
5984 //
5985 // mapping: [0,1] -> R^1
5986 //
5987
5988 int64_t res0 = BSplineCore::ncoeffs(0);
5989 if (json.contains("res0"))
5990 res0 = json["res0"].get<int64_t>();
5991
5992 // Create figure with specified backend
5993 auto f = matplot::figure<Backend>(false);
5994 f->backend()->run_command("unset warnings");
5995 f->ioff();
5996 auto ax = f->current_axes();
5997
5998 // Create line
5999 auto Coords =
6000 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6001#ifdef __clang__
6002 auto Coords_cpu =
6003 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6004 Coords(0), torch::kCPU);
6005 auto XAccessor = std::get<1>(Coords_cpu);
6006#else
6007 auto [Coords_cpu, XAccessor] =
6008 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6009 Coords(0), torch::kCPU);
6010#endif
6011
6012 matplot::vector_1d Xfine(res0, 0.0);
6013 matplot::vector_1d Yfine(res0, 0.0);
6014
6015#pragma omp parallel for simd
6016 for (int64_t i = 0; i < res0; ++i)
6017 Xfine[i] = XAccessor[i];
6018
6019 // Plot (colored) line
6020 if ((void *)this != (void *)&color) {
6021 if constexpr (BSplineCoreColor::geoDim_ == 1) {
6022
6023 // Create colors
6024 auto Color =
6025 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6026#ifdef __clang__
6027 auto Color_cpu =
6028 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6029 1>(Color(0), torch::kCPU);
6030 auto CAccessor = std::get<1>(Color_cpu);
6031#else
6032 auto [Color_cpu, CAccessor] =
6033 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6034 1>(Color(0), torch::kCPU);
6035#endif
6036
6037 matplot::vector_1d Cfine(res0, 0.0);
6038
6039#pragma omp parallel for simd
6040 for (int64_t i = 0; i < res0; ++i)
6041 Cfine[i] = CAccessor[i];
6042
6043 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
6044 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
6045
6046 auto Cmap = matplot::colormap();
6047
6048 auto a = Cmap.size() / (Cmax - Cmin);
6049 auto b = -a * Cmin;
6050
6051 // Plot colored line
6052 ax->hold(matplot::on);
6053 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
6054 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
6055 ->line_width(2)
6056 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
6057 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
6058 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
6059 ax->hold(matplot::off);
6060 matplot::colorbar(ax);
6061 } else
6062 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6063 } else {
6064 // Plot unicolor line
6065 ax->plot(Xfine, Yfine, "b-")->line_width(2);
6066 }
6067
6068 bool cnet = false;
6069 if (json.contains("cnet"))
6070 cnet = json["cnet"].get<bool>();
6071
6072 if (cnet) {
6073 // Create control net
6074#ifdef __clang__
6075 auto coeffs_cpu =
6076 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6077 BSplineCore::coeffs(0), torch::kCPU);
6078 auto xAccessor = std::get<1>(coeffs_cpu);
6079#else
6080 auto [coeffs_cpu, xAccessor] =
6081 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6082 BSplineCore::coeffs(0), torch::kCPU);
6083#endif
6084 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6085 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6086
6087#pragma omp parallel for simd
6088 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6089 X[i] = xAccessor[i];
6090 }
6091
6092 // Plot control net
6093 ax->hold(matplot::on);
6094 ax->plot(X, Y, ".k-")->line_width(1);
6095 ax->hold(matplot::off);
6096 }
6097
6098 // Title
6099 if (json.contains("title"))
6100 ax->title(json["title"].get<std::string>());
6101 else
6102 ax->title("BSpline: [0,1] -> R");
6103
6104 // X-axis label
6105 if (json.contains("xlabel"))
6106 ax->xlabel(json["xlabel"].get<std::string>());
6107 else
6108 ax->xlabel("x");
6109
6110 // Y-axis label
6111 if (json.contains("ylabel"))
6112 ax->ylabel(json["ylabel"].get<std::string>());
6113 else
6114 ax->ylabel("y");
6115
6116 return f;
6117 }
6118
6119 else if constexpr (BSplineCore::parDim_ == 1 && BSplineCore::geoDim_ == 2) {
6120
6121 //
6122 // mapping: [0,1] -> R^2
6123 //
6124
6125 int64_t res0 = BSplineCore::ncoeffs(0);
6126 if (json.contains("res0"))
6127 res0 = json["res0"].get<int64_t>();
6128
6129 // Create figure with specified backend
6130 auto f = matplot::figure<Backend>(false);
6131 f->backend()->run_command("unset warnings");
6132 f->ioff();
6133 auto ax = f->current_axes();
6134
6135 // Create curve
6136 auto Coords =
6137 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6138#ifdef __clang__
6139 auto Coords_cpu =
6140 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6141 Coords, torch::kCPU);
6142 auto XAccessor = std::get<1>(Coords_cpu)[0];
6143 auto YAccessor = std::get<1>(Coords_cpu)[1];
6144#else
6145 auto [Coords0_cpu, XAccessor] =
6146 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6147 Coords(0), torch::kCPU);
6148 auto [Coords1_cpu, YAccessor] =
6149 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6150 Coords(1), torch::kCPU);
6151#endif
6152
6153 matplot::vector_1d Xfine(res0, 0.0);
6154 matplot::vector_1d Yfine(res0, 0.0);
6155
6156#pragma omp parallel for simd
6157 for (int64_t i = 0; i < res0; ++i) {
6158 Xfine[i] = XAccessor[i];
6159 Yfine[i] = YAccessor[i];
6160 }
6161
6162 // Plot (colored) curve
6163 if ((void *)this != (void *)&color) {
6164 if constexpr (BSplineCoreColor::geoDim() == 1) {
6165
6166 // Create colors
6167 auto Color =
6168 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6169#ifdef __clang__
6170 auto Color_cpu =
6171 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6172 1>(Color(0), torch::kCPU);
6173 auto CAccessor = std::get<1>(Color_cpu);
6174#else
6175 auto [Color_cpu, CAccessor] =
6176 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6177 1>(Color(0), torch::kCPU);
6178#endif
6179
6180 matplot::vector_1d Cfine(res0, 0.0);
6181
6182#pragma omp parallel for simd
6183 for (int64_t i = 0; i < res0; ++i) {
6184 Cfine[i] = CAccessor[i];
6185 }
6186
6187 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
6188 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
6189
6190 auto Cmap = matplot::colormap();
6191
6192 auto a = Cmap.size() / (Cmax - Cmin);
6193 auto b = -a * Cmin;
6194
6195 // Plot colored curve
6196 ax->hold(matplot::on);
6197 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
6198 ax->plot({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]})
6199 ->line_width(2)
6200 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
6201 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
6202 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
6203 ax->hold(matplot::off);
6204 matplot::colorbar(ax);
6205 } else
6206 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6207 } else {
6208 // Plot unicolor curve
6209 ax->plot(Xfine, Yfine, "b-")->line_width(2);
6210 }
6211
6212 bool cnet = false;
6213 if (json.contains("cnet"))
6214 cnet = json["cnet"].get<bool>();
6215
6216 if (cnet) {
6217 // Create control net
6218#ifdef __clang__
6219 auto coeffs_cpu =
6220 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6221 BSplineCore::coeffs(), torch::kCPU);
6222 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6223 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6224#else
6225 auto [coeffs0_cpu, xAccessor] =
6226 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6227 BSplineCore::coeffs(0), torch::kCPU);
6228 auto [coeffs1_cpu, yAccessor] =
6229 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6230 BSplineCore::coeffs(1), torch::kCPU);
6231#endif
6232
6233 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6234 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6235
6236#pragma omp parallel for simd
6237 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6238 X[i] = xAccessor[i];
6239 Y[i] = yAccessor[i];
6240 }
6241
6242 // Plot control net
6243 ax->hold(matplot::on);
6244 ax->plot(X, Y, ".k-")->line_width(1);
6245 ax->hold(matplot::off);
6246 }
6247
6248 // Title
6249 if (json.contains("title"))
6250 ax->title(json["title"].get<std::string>());
6251 else
6252 ax->title("BSpline: [0,1] -> R^2");
6253
6254 // X-axis label
6255 if (json.contains("xlabel"))
6256 ax->xlabel(json["xlabel"].get<std::string>());
6257 else
6258 ax->xlabel("x");
6259
6260 // Y-axis label
6261 if (json.contains("ylabel"))
6262 ax->ylabel(json["ylabel"].get<std::string>());
6263 else
6264 ax->ylabel("y");
6265
6266 return f;
6267 }
6268
6269 else if constexpr (BSplineCore::parDim() == 1 &&
6270 BSplineCore::geoDim() == 3) {
6271
6272 //
6273 // mapping: [0,1] -> R^3
6274 //
6275
6276 int64_t res0 = BSplineCore::ncoeffs(0);
6277 if (json.contains("res0"))
6278 res0 = json["res0"].get<int64_t>();
6279
6280 // Create figure with specified backend
6281 auto f = matplot::figure<Backend>(false);
6282 f->backend()->run_command("unset warnings");
6283 f->ioff();
6284 auto ax = f->current_axes();
6285
6286 auto Coords =
6287 BSplineCore::eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6288#ifdef __clang__
6289 auto Coords_cpu =
6290 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6291 Coords, torch::kCPU);
6292 auto XAccessor = std::get<1>(Coords_cpu)[0];
6293 auto YAccessor = std::get<1>(Coords_cpu)[1];
6294 auto ZAccessor = std::get<1>(Coords_cpu)[2];
6295#else
6296 auto [Coords0_cpu, XAccessor] =
6297 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6298 Coords(0), torch::kCPU);
6299 auto [Coords1_cpu, YAccessor] =
6300 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6301 Coords(1), torch::kCPU);
6302 auto [Coords2_cpu, ZAccessor] =
6303 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6304 Coords(2), torch::kCPU);
6305#endif
6306
6307 // Create curve
6308 matplot::vector_1d Xfine(res0, 0.0);
6309 matplot::vector_1d Yfine(res0, 0.0);
6310 matplot::vector_1d Zfine(res0, 0.0);
6311
6312#pragma omp parallel for simd
6313 for (int64_t i = 0; i < res0; ++i) {
6314 Xfine[i] = XAccessor[i];
6315 Yfine[i] = YAccessor[i];
6316 Zfine[i] = ZAccessor[i];
6317 }
6318
6319 // Plot (colored) curve
6320 if ((void *)this != (void *)&color) {
6321 if constexpr (BSplineCoreColor::geoDim() == 1) {
6322
6323 auto Color =
6324 color.eval(torch::linspace(0, 1, res0, BSplineCore::options_));
6325#ifdef __clang__
6326 auto Color_cpu =
6327 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6328 1>(Color(0), torch::kCPU);
6329 auto CAccessor = std::get<1>(Color_cpu);
6330#else
6331 auto [Color_cpu, CAccessor] =
6332 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6333 1>(Color(0), torch::kCPU);
6334#endif
6335
6336 // Create colors
6337 matplot::vector_1d Cfine(matplot::vector_1d(res0, 0.0));
6338
6339#pragma omp parallel for simd
6340 for (int64_t i = 0; i < res0; ++i) {
6341 Cfine[i] = CAccessor[i];
6342 }
6343
6344 auto Cmin = *std::min_element(Cfine.begin(), Cfine.end());
6345 auto Cmax = *std::max_element(Cfine.begin(), Cfine.end());
6346
6347 auto Cmap = matplot::colormap();
6348
6349 auto a = Cmap.size() / (Cmax - Cmin);
6350 auto b = -a * Cmin;
6351
6352 // Plot colored line
6353 ax->hold(matplot::on);
6354 for (std::size_t i = 0; i < Xfine.size() - 1; ++i)
6355 ax->plot3({Xfine[i], Xfine[i + 1]}, {Yfine[i], Yfine[i + 1]},
6356 {Zfine[i], Zfine[i + 1]})
6357 ->line_width(2)
6358 .color({Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][0],
6359 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][1],
6360 Cmap[a * (Cfine[i] + Cfine[i + 1]) / 2.0 - b][2]});
6361 ax->hold(matplot::off);
6362 matplot::colorbar(ax);
6363 } else
6364 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6365 } else {
6366 // Plot curve
6367 ax->plot3(Xfine, Yfine, Zfine, "b-")->line_width(2);
6368 }
6369
6370 bool cnet = false;
6371 if (json.contains("cnet"))
6372 cnet = json["cnet"].get<bool>();
6373
6374 if (cnet) {
6375 // Create control net
6376#ifdef __clang__
6377 auto coeffs_cpu =
6378 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6379 BSplineCore::coeffs(), torch::kCPU);
6380 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6381 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6382 auto zAccessor = std::get<1>(coeffs_cpu)[2];
6383#else
6384 auto [coeffs0_cpu, xAccessor] =
6385 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6386 BSplineCore::coeffs(0), torch::kCPU);
6387 auto [coeffs1_cpu, yAccessor] =
6388 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6389 BSplineCore::coeffs(1), torch::kCPU);
6390 auto [coeffs2_cpu, zAccessor] =
6391 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6392 BSplineCore::coeffs(2), torch::kCPU);
6393#endif
6394
6395 matplot::vector_1d X(BSplineCore::ncoeffs(0), 0.0);
6396 matplot::vector_1d Y(BSplineCore::ncoeffs(0), 0.0);
6397 matplot::vector_1d Z(BSplineCore::ncoeffs(0), 0.0);
6398
6399#pragma omp parallel for simd
6400 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i) {
6401 X[i] = xAccessor[i];
6402 Y[i] = yAccessor[i];
6403 Z[i] = zAccessor[i];
6404 }
6405
6406 // Plot control net
6407 ax->hold(matplot::on);
6408 ax->plot3(X, Y, Z, ".k-")->line_width(1);
6409 ax->hold(matplot::off);
6410 }
6411
6412 // Title
6413 if (json.contains("title"))
6414 ax->title(json["title"].get<std::string>());
6415 else
6416 ax->title("BSpline: [0,1] -> R^3");
6417
6418 // X-axis label
6419 if (json.contains("xlabel"))
6420 ax->xlabel(json["xlabel"].get<std::string>());
6421 else
6422 ax->xlabel("x");
6423
6424 // Y-axis label
6425 if (json.contains("ylabel"))
6426 ax->ylabel(json["ylabel"].get<std::string>());
6427 else
6428 ax->ylabel("y");
6429
6430 // Z-axis label
6431 if (json.contains("zlabel"))
6432 ax->zlabel(json["zlabel"].get<std::string>());
6433 else
6434 ax->zlabel("z");
6435
6436 return f;
6437 }
6438
6439 else if constexpr (BSplineCore::parDim() == 2 &&
6440 BSplineCore::geoDim() == 2) {
6441
6442 //
6443 // mapping: [0,1]^2 -> R^2
6444 //
6445
6446 int64_t res0 = BSplineCore::ncoeffs(0);
6447 int64_t res1 = BSplineCore::ncoeffs(1);
6448 if (json.contains("res0"))
6449 res0 = json["res0"].get<int64_t>();
6450 if (json.contains("res1"))
6451 res1 = json["res1"].get<int64_t>();
6452
6453 // Create figure with specified backend
6454 auto f = matplot::figure<Backend>(false);
6455 f->backend()->run_command("unset warnings");
6456 f->ioff();
6457 auto ax = f->current_axes();
6458
6459 // Create mesh
6460 utils::TensorArray<2> meshgrid = utils::to_array<2>(
6461 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
6462 torch::linspace(0, 1, res1, BSplineCore::options_)},
6463 "xy"));
6464 auto Coords = BSplineCore::eval(meshgrid);
6465#ifdef __clang__
6466 auto Coords_cpu =
6467 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6468 Coords, torch::kCPU);
6469 auto XAccessor = std::get<1>(Coords_cpu)[0];
6470 auto YAccessor = std::get<1>(Coords_cpu)[1];
6471#else
6472 auto [Coords0_cpu, XAccessor] =
6473 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6474 Coords(0), torch::kCPU);
6475 auto [Coords1_cpu, YAccessor] =
6476 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6477 Coords(1), torch::kCPU);
6478#endif
6479
6480 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
6481 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
6482 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
6483
6484#pragma omp parallel for simd collapse(2)
6485 for (int64_t i = 0; i < res0; ++i)
6486 for (int64_t j = 0; j < res1; ++j) {
6487 Xfine[j][i] = XAccessor[j][i];
6488 Yfine[j][i] = YAccessor[j][i];
6489 }
6490
6491 // Plot (colored) mesh
6492 if ((void *)this != (void *)&color) {
6493 if constexpr (BSplineCoreColor::geoDim() == 1) {
6494
6495 // Create colors
6496 auto Color = color.eval(meshgrid);
6497#ifdef __clang__
6498 auto Color_cpu =
6499 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6500 2>(Color, torch::kCPU);
6501 auto CAccessor = std::get<1>(Color_cpu)[0];
6502#else
6503 auto [Color0_cpu, CAccessor] =
6504 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6505 2>(Color(0), torch::kCPU);
6506#endif
6507
6508 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
6509
6510#pragma omp parallel for simd collapse(2)
6511 for (int64_t i = 0; i < res0; ++i)
6512 for (int64_t j = 0; j < res1; ++j)
6513 Cfine[j][i] = CAccessor[j][i];
6514
6515 // Plot colored mesh
6516 matplot::view(2);
6517 ax->mesh(Xfine, Yfine, Cfine)->hidden_3d(false);
6518 matplot::colorbar(ax);
6519 } else
6520 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6521 } else {
6522 // Plot unicolor mesh
6523 matplot::view(2);
6524 matplot::colormap(std::vector<std::vector<double>>{{ 0.0, 0.0, 1.0 }});
6525 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
6526 }
6527
6528 bool cnet = false;
6529 if (json.contains("cnet"))
6530 cnet = json["cnet"].get<bool>();
6531
6532 if (cnet) {
6533 // Create control net
6534#ifdef __clang__
6535 auto coeffs_cpu =
6536 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6537 BSplineCore::coeffs(), torch::kCPU);
6538 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6539 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6540#else
6541 auto [coeffs0_cpu, xAccessor] =
6542 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6543 BSplineCore::coeffs(0), torch::kCPU);
6544 auto [coeffs1_cpu, yAccessor] =
6545 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6546 BSplineCore::coeffs(1), torch::kCPU);
6547#endif
6548
6549 matplot::vector_2d X(BSplineCore::ncoeffs(1),
6550 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6551 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
6552 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6553 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
6554 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6555
6556#pragma omp parallel for simd collapse(2)
6557 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
6558 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
6559 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
6560 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
6561 }
6562
6563 // Plot control net
6564 ax->hold(matplot::on);
6565 ax->surf(X, Y, Z)
6566 ->palette_map_at_surface(true)
6567 .face_alpha(0)
6568 .line_width(1);
6569 for (std::size_t i = 0; i < X.size(); ++i)
6570 ax->scatter3(X[i], Y[i], Z[i], "k.");
6571 ax->hold(matplot::off);
6572 }
6573
6574 // Title
6575 if (json.contains("title"))
6576 ax->title(json["title"].get<std::string>());
6577 else
6578 ax->title("BSpline: [0,1]^2 -> R^2");
6579
6580 // X-axis label
6581 if (json.contains("xlabel"))
6582 ax->xlabel(json["xlabel"].get<std::string>());
6583 else
6584 ax->xlabel("x");
6585
6586 // Y-axis label
6587 if (json.contains("ylabel"))
6588 ax->ylabel(json["ylabel"].get<std::string>());
6589 else
6590 ax->ylabel("y");
6591
6592 // Z-axis label
6593 if (json.contains("zlabel"))
6594 ax->zlabel(json["zlabel"].get<std::string>());
6595 else
6596 ax->zlabel("z");
6597
6598 return f;
6599 }
6600
6601 else if constexpr (BSplineCore::parDim() == 2 &&
6602 BSplineCore::geoDim() == 3) {
6603
6605 // mapping: [0,1]^2 -> R^3
6607
6608 int64_t res0 = BSplineCore::ncoeffs(0);
6609 int64_t res1 = BSplineCore::ncoeffs(1);
6610 if (json.contains("res0"))
6611 res0 = json["res0"].get<int64_t>();
6612 if (json.contains("res1"))
6613 res1 = json["res1"].get<int64_t>();
6614
6615 // Create figure with specified backend
6616 auto f = matplot::figure<Backend>(false);
6617 f->backend()->run_command("unset warnings");
6618 f->ioff();
6619 auto ax = f->current_axes();
6620
6621 // Create surface
6622 utils::TensorArray<2> meshgrid = utils::to_array<2>(
6623 torch::meshgrid({torch::linspace(0, 1, res0, BSplineCore::options_),
6624 torch::linspace(0, 1, res1, BSplineCore::options_)},
6625 "xy"));
6626 auto Coords = BSplineCore::eval(meshgrid);
6627#ifdef __clang__
6628 auto Coords_cpu =
6629 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6630 Coords, torch::kCPU);
6631 auto XAccessor = std::get<1>(Coords_cpu)[0];
6632 auto YAccessor = std::get<1>(Coords_cpu)[1];
6633 auto ZAccessor = std::get<1>(Coords_cpu)[2];
6634#else
6635 auto [Coords0_cpu, XAccessor] =
6636 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6637 Coords(0), torch::kCPU);
6638 auto [Coords1_cpu, YAccessor] =
6639 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6640 Coords(1), torch::kCPU);
6641 auto [Coords2_cpu, ZAccessor] =
6642 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 2>(
6643 Coords(2), torch::kCPU);
6644#endif
6645
6646 matplot::vector_2d Xfine(res1, matplot::vector_1d(res0, 0.0));
6647 matplot::vector_2d Yfine(res1, matplot::vector_1d(res0, 0.0));
6648 matplot::vector_2d Zfine(res1, matplot::vector_1d(res0, 0.0));
6649
6650#pragma omp parallel for simd collapse(2)
6651 for (int64_t i = 0; i < res0; ++i)
6652 for (int64_t j = 0; j < res1; ++j) {
6653 Xfine[j][i] = XAccessor[j][i];
6654 Yfine[j][i] = YAccessor[j][i];
6655 Zfine[j][i] = ZAccessor[j][i];
6656 }
6657
6658 // Plot (colored) surface
6659 if ((void *)this != (void *)&color) {
6660 if constexpr (BSplineCoreColor::geoDim() == 1) {
6661
6662 // Create colors
6663 auto Color = color.eval(meshgrid);
6664#ifdef __clang__
6665 auto Color_cpu =
6666 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6667 2>(Color, torch::kCPU);
6668 auto CAccessor = std::get<1>(Color_cpu)[0];
6669#else
6670 auto [Color_cpu, CAccessor] =
6671 utils::to_tensorAccessor<typename BSplineCoreColor::value_type,
6672 2>(Color(0), torch::kCPU);
6673#endif
6674
6675 matplot::vector_2d Cfine(res1, matplot::vector_1d(res0, 0.0));
6676
6677#pragma omp parallel for simd collapse(2)
6678 for (int64_t i = 0; i < res0; ++i)
6679 for (int64_t j = 0; j < res1; ++j) {
6680 Cfine[j][i] = CAccessor[j][i];
6681 }
6682
6683 // Plot colored surface
6684 ax->mesh(Xfine, Yfine, Zfine, Cfine)->hidden_3d(false);
6685 matplot::colorbar(ax);
6686 } else
6687 throw std::runtime_error("BSpline for coloring must have geoDim=1");
6688 } else {
6689 // Plot unicolor surface
6690 matplot::colormap(std::vector<std::vector<double>>{{ 0.0, 0.0, 1.0 }});
6691 ax->mesh(Xfine, Yfine, Zfine)->hidden_3d(false).line_width(2);
6692 }
6693
6694 bool cnet = false;
6695 if (json.contains("cnet"))
6696 cnet = json["cnet"].get<bool>();
6697
6698 if (cnet) {
6699 // Create control net
6700#ifdef __clang__
6701 auto coeffs_cpu =
6702 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6703 BSplineCore::coeffs(), torch::kCPU);
6704 auto xAccessor = std::get<1>(coeffs_cpu)[0];
6705 auto yAccessor = std::get<1>(coeffs_cpu)[1];
6706 auto zAccessor = std::get<1>(coeffs_cpu)[2];
6707#else
6708 auto [coeffs0_cpu, xAccessor] =
6709 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6710 BSplineCore::coeffs(0), torch::kCPU);
6711 auto [coeffs1_cpu, yAccessor] =
6712 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6713 BSplineCore::coeffs(1), torch::kCPU);
6714 auto [coeffs2_cpu, zAccessor] =
6715 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6716 BSplineCore::coeffs(2), torch::kCPU);
6717#endif
6718
6719 matplot::vector_2d X(BSplineCore::ncoeffs(1),
6720 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6721 matplot::vector_2d Y(BSplineCore::ncoeffs(1),
6722 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6723 matplot::vector_2d Z(BSplineCore::ncoeffs(1),
6724 matplot::vector_1d(BSplineCore::ncoeffs(0), 0.0));
6725
6726#pragma omp parallel for simd collapse(2)
6727 for (int64_t i = 0; i < BSplineCore::ncoeffs(0); ++i)
6728 for (int64_t j = 0; j < BSplineCore::ncoeffs(1); ++j) {
6729 X[j][i] = xAccessor[j * BSplineCore::ncoeffs(0) + i];
6730 Y[j][i] = yAccessor[j * BSplineCore::ncoeffs(0) + i];
6731 Z[j][i] = zAccessor[j * BSplineCore::ncoeffs(0) + i];
6732 }
6733
6734 // Plot control net
6735 ax->hold(matplot::on);
6736 ax->surf(X, Y, Z)
6737 ->palette_map_at_surface(true)
6738 .face_alpha(0)
6739 .line_width(1);
6740 for (std::size_t i = 0; i < X.size(); ++i)
6741 ax->scatter3(X[i], Y[i], Z[i], "k.");
6742 ax->hold(matplot::off);
6743 }
6744
6745 // Title
6746 if (json.contains("title"))
6747 ax->title(json["title"].get<std::string>());
6748 else
6749 ax->title("BSpline: [0,1]^2 -> R^3");
6750
6751 // X-axis label
6752 if (json.contains("xlabel"))
6753 ax->xlabel(json["xlabel"].get<std::string>());
6754 else
6755 ax->xlabel("x");
6756
6757 // Y-axis label
6758 if (json.contains("ylabel"))
6759 ax->ylabel(json["ylabel"].get<std::string>());
6760 else
6761 ax->ylabel("y");
6762
6763 // Z-axis label
6764 if (json.contains("zlabel"))
6765 ax->zlabel(json["zlabel"].get<std::string>());
6766 else
6767 ax->zlabel("z");
6768
6769 return f;
6770 }
6771
6772 else
6773 throw std::runtime_error(
6774 "Unsupported combination of parametric/geometric dimensions");
6775#else
6776 throw std::runtime_error(
6777 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6778#endif
6779 }
6780
6791#ifdef IGANET_WITH_MATPLOT
6792 template <typename Backend = matplot::backend::gnuplot,
6793 typename BSplineCoreColor>
6794#else
6795 template <typename Backend = void, typename BSplineCoreColor>
6796#endif
6797 inline auto plot(const BSplineCommon<BSplineCoreColor> &color,
6799 const nlohmann::json &json = {}) const {
6800
6801#ifdef IGANET_WITH_MATPLOT
6802 auto f = plot<Backend>(color, json);
6803 auto ax = f->current_axes();
6804
6805#ifdef __clang__
6806 auto xi_cpu =
6807 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6808 xi, torch::kCPU);
6809 auto xiAccessor = std::get<1>(xi_cpu);
6810#else
6811 auto [xi_cpu, xiAccessor] =
6812 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6813 xi, torch::kCPU);
6814#endif
6815
6816 if constexpr (BSplineCore::parDim_ == 1) {
6817 matplot::vector_1d X(xi[0].size(0), 0.0);
6818 matplot::vector_1d Y(xi[0].size(0), 0.0);
6819
6820#pragma omp parallel for simd
6821 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6822 X[i] = xiAccessor[0][i];
6823 }
6824
6825 ax->hold(matplot::on);
6826 ax->scatter(X, Y, ".");
6827 ax->hold(matplot::off);
6828 } else if constexpr (BSplineCore::parDim_ == 2) {
6829 matplot::vector_1d X(xi[0].size(0), 0.0);
6830 matplot::vector_1d Y(xi[0].size(0), 0.0);
6831 matplot::vector_1d Z(xi[0].size(0), 0.0);
6832
6833#pragma omp parallel for simd
6834 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6835 X[i] = xiAccessor[0][i];
6836 Y[i] = xiAccessor[1][i];
6837 }
6838
6839 ax->hold(matplot::on);
6840 ax->scatter3(X, Y, Z, ".");
6841 ax->hold(matplot::off);
6842 } else if constexpr (BSplineCore::parDim_ == 3) {
6843 matplot::vector_1d X(xi[0].size(0), 0.0);
6844 matplot::vector_1d Y(xi[0].size(0), 0.0);
6845 matplot::vector_1d Z(xi[0].size(0), 0.0);
6846
6847#pragma omp parallel for simd
6848 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6849 X[i] = xiAccessor[0][i];
6850 Y[i] = xiAccessor[1][i];
6851 Z[i] = xiAccessor[2][i];
6852 }
6853
6854 ax->hold(matplot::on);
6855 ax->scatter3(X, Y, Z, ".");
6856 ax->hold(matplot::off);
6857 } else
6858 throw std::runtime_error("Invalid parametric dimension");
6859
6860 return f;
6861#else
6862 throw std::runtime_error(
6863 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6864#endif
6865 }
6866
6877#ifdef IGANET_WITH_MATPLOT
6878 template <typename Backend = matplot::backend::gnuplot,
6879 typename BSplineCoreColor>
6880#else
6881 template <typename Backend = void, typename BSplineCoreColor>
6882#endif
6883 inline auto plot(
6885 const std::initializer_list<utils::TensorArray<BSplineCore::parDim_>> &xi,
6886 const nlohmann::json &json = {}) const {
6887
6888#ifdef IGANET_WITH_MATPLOT
6889 auto f = plot<Backend>(color, json);
6890 auto ax = f->current_axes();
6891
6892 for (const auto &xi : xi) {
6893#ifdef __clang__
6894 auto xi_cpu =
6895 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6896 xi, torch::kCPU);
6897 auto xiAccessor = std::get<1>(xi_cpu);
6898#else
6899 auto [xi_cpu, xiAccessor] =
6900 utils::to_tensorAccessor<typename BSplineCoreColor::value_type, 1>(
6901 xi, torch::kCPU);
6902#endif
6903
6904 if constexpr (BSplineCore::parDim_ == 1) {
6905 matplot::vector_1d X(xi[0].size(0), 0.0);
6906 matplot::vector_1d Y(xi[0].size(0), 0.0);
6907
6908#pragma omp parallel for simd
6909 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6910 X[i] = xiAccessor[0][i];
6911 }
6912
6913 ax->hold(matplot::on);
6914 ax->scatter(X, Y, ".");
6915 ax->hold(matplot::off);
6916 } else if constexpr (BSplineCore::parDim_ == 2) {
6917 matplot::vector_1d X(xi[0].size(0), 0.0);
6918 matplot::vector_1d Y(xi[0].size(0), 0.0);
6919 matplot::vector_1d Z(xi[0].size(0), 0.0);
6920
6921#pragma omp parallel for simd
6922 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6923 X[i] = xiAccessor[0][i];
6924 Y[i] = xiAccessor[1][i];
6925 }
6926
6927 ax->hold(matplot::on);
6928 ax->scatter3(X, Y, Z, ".");
6929 ax->hold(matplot::off);
6930 } else if constexpr (BSplineCore::parDim_ == 3) {
6931 matplot::vector_1d X(xi[0].size(0), 0.0);
6932 matplot::vector_1d Y(xi[0].size(0), 0.0);
6933 matplot::vector_1d Z(xi[0].size(0), 0.0);
6934
6935#pragma omp parallel for simd
6936 for (int64_t i = 0; i < xi[0].size(0); ++i) {
6937 X[i] = xiAccessor[0][i];
6938 Y[i] = xiAccessor[1][i];
6939 Z[i] = xiAccessor[2][i];
6940 }
6941
6942 ax->hold(matplot::on);
6943 ax->scatter3(X, Y, Z, ".");
6944 ax->hold(matplot::off);
6945
6946 } else
6947 throw std::runtime_error("Invalid parametric dimension");
6948 }
6949 return f;
6950#else
6951 throw std::runtime_error(
6952 "This functions must be compiled with -DIGANET_WITH_MATPLOT turned on");
6953#endif
6954 }
6955
6957 inline void pretty_print(std::ostream &os) const noexcept override {
6958 os << name() << "(\nparDim = " << BSplineCore::parDim()
6959 << ", geoDim = " << BSplineCore::geoDim() << ", degrees = ";
6960
6961 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6962 os << BSplineCore::degree(i) << "x";
6963 if (BSplineCore::parDim() > 0)
6964 os << BSplineCore::degree(BSplineCore::parDim() - 1);
6965 else
6966 os << 0;
6967
6968 os << ", knots = ";
6969 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6970 os << BSplineCore::nknots(i) << "x";
6971 if (BSplineCore::parDim() > 0)
6972 os << BSplineCore::nknots(BSplineCore::parDim() - 1);
6973 else
6974 os << 0;
6975
6976 os << ", coeffs = ";
6977 for (short_t i = 0; i < BSplineCore::parDim() - 1; ++i)
6978 os << BSplineCore::ncoeffs(i) << "x";
6979 if (BSplineCore::parDim() > 0)
6980 os << BSplineCore::ncoeffs(BSplineCore::parDim() - 1);
6981 else
6982 os << 1;
6983
6984 os << ", options = "
6985 << static_cast<torch::TensorOptions>(BSplineCore::options_);
6986
6987 if (is_verbose(os)) {
6988 os << "\nknots [ ";
6989 for (const torch::Tensor &knots : BSplineCore::knots()) {
6990 os << (knots.is_view() ? "view/" : "owns/");
6991 os << (knots.is_contiguous() ? "cont " : "non-cont ");
6992 }
6993 if (BSplineCore::parDim() > 0)
6994 os << "] = " << BSplineCore::knots();
6995 else
6996 os << "] = {}";
6997
6998 os << "\ncoeffs [ ";
6999 for (const torch::Tensor &coeffs : BSplineCore::coeffs()) {
7000 os << (coeffs.is_view() ? "view/" : "owns/");
7001 os << (coeffs.is_contiguous() ? "cont " : "non-cont ");
7002 }
7003 if (BSplineCore::ncumcoeffs() > 0)
7004 os << "] = " << BSplineCore::coeffs_view();
7005 else
7006 os << "] = {}";
7007 }
7008
7009 os << "\n)";
7010 }
7011
7020
7021 BSplineCommon result{*this};
7022
7023 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7024 result.coeffs(i) += other.coeffs(i);
7025
7026 return result;
7027 }
7028
7038
7039 BSplineCommon result{*this};
7040
7041 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7042 result.coeffs(i) -= other.coeffs(i);
7043
7044 return result;
7045 }
7046
7049 BSplineCommon operator*(BSplineCore::value_type s) const {
7050
7051 BSplineCommon result{*this};
7052
7053 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7054 result.coeffs(i) *= s;
7055
7056 return result;
7057 }
7058
7062 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
7063 const {
7064
7065 BSplineCommon result{*this};
7066
7067 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7068 result.coeffs(i) *= v[i];
7069
7070 return result;
7071 }
7072
7075 BSplineCommon operator/(BSplineCore::value_type s) const {
7076
7077 BSplineCommon result{*this};
7078
7079 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7080 result.coeffs(i) /= s;
7081
7082 return result;
7083 }
7084
7088 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v)
7089 const {
7090
7091 BSplineCommon result{*this};
7092
7093 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7094 result.coeffs(i) /= v[i];
7095
7096 return result;
7097 }
7098
7106
7107 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7108 BSplineCore::coeffs(i) += other.coeffs(i);
7109
7110 return *this;
7111 }
7112
7120
7121 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7122 BSplineCore::coeffs(i) -= other.coeffs(i);
7123
7124 return *this;
7125 }
7126
7128 BSplineCommon &operator*=(BSplineCore::value_type s) {
7129
7130 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7131 BSplineCore::coeffs(i) *= s;
7132
7133 return *this;
7134 }
7135
7138 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
7139
7140 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7141 BSplineCore::coeffs(i) *= v[i];
7142
7143 return *this;
7144 }
7145
7147 BSplineCommon &operator/=(BSplineCore::value_type s) {
7148
7149 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7150 BSplineCore::coeffs(i) /= s;
7151
7152 return *this;
7153 }
7154
7157 std::array<typename BSplineCore::value_type, BSplineCore::geoDim()> v) {
7158
7159 for (short_t i = 0; i < BSplineCore::geoDim(); ++i)
7160 BSplineCore::coeffs(i) /= v[i];
7161
7162 return *this;
7163 }
7164};
7165
7167template <typename real_t, short_t GeoDim, short_t... Degrees>
7169 BSplineCommon<UniformBSplineCore<real_t, GeoDim, Degrees...>>;
7170
7172template <typename real_t, short_t GeoDim, short_t... Degrees>
7173inline std::ostream &
7174operator<<(std::ostream &os,
7176 obj.pretty_print(os);
7177 return os;
7178}
7179
7181template <typename real_t, short_t GeoDim, short_t... Degrees>
7183 BSplineCommon<NonUniformBSplineCore<real_t, GeoDim, Degrees...>>;
7184
7186template <typename real_t, short_t GeoDim, short_t... Degrees>
7187inline std::ostream &
7188operator<<(std::ostream &os,
7190 obj.pretty_print(os);
7191 return os;
7192}
7193} // namespace iganet
Compile-time block tensor.
B-spline (common high-level functionality)
Definition bspline.hpp:3614
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:5551
auto rotate(std::array< typename BSplineCore::value_type, 3 > angle) const
Rotates the B-spline object by three angles in 3d.
Definition bspline.hpp:4013
BSplineCommon & operator*=(BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:7128
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:4366
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:5182
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:4247
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:4176
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:5276
auto diff_(const BSplineCommon &other, int dim=-1)
Computes the difference between two compatible B-spline objects in-place.
Definition bspline.hpp:3879
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:5779
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:5173
auto rotate_(std::array< typename BSplineCore::value_type, 3 > angle)
Rotates the B-spline object by three angles in 3d in-place.
Definition bspline.hpp:4018
BSplineCommon & operator*=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:7137
auto rotate_(BSplineCore::value_type angle)
Rotates the B-spline object by an angle in 2d in-place.
Definition bspline.hpp:3997
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:4597
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:4809
auto plot(const BSplineCommon< BSplineCoreColor > &color, const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:6883
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:4464
void pretty_print(std::ostream &os) const noexcept override
Returns a string representation of the BSplineCommon object.
Definition bspline.hpp:6957
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:7061
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:4098
BSplineCommon & operator/=(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the coefficients by a vector.
Definition bspline.hpp:7156
auto translate_(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Translates the B-spline object by a vector in-place.
Definition bspline.hpp:3984
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:5712
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:5505
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:4413
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:5597
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:5402
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:5454
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:5014
std::unique_ptr< BSplineCommon > uPtr
Unique pointer for BSplineCommon.
Definition bspline.hpp:3653
BSplineCommon(const BSplineCommon &other, bool clone)
Copy/clone constructor.
Definition bspline.hpp:3659
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:4635
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:3698
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:5643
auto abs_diff_(const BSplineCommon &other, int dim=-1)
Computes the absolute difference between two compatible B-spline objects in-place.
Definition bspline.hpp:3917
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:5822
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:3776
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:4719
auto diff(const BSplineCommon &other, int dim=-1) const
Computes the difference between two compatible B-spline objects.
Definition bspline.hpp:3869
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:4682
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:4181
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:5005
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:4870
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:3715
BSplineCommon operator*(BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:7049
auto scale(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v) const
Scales the B-spline object by a vector.
Definition bspline.hpp:3965
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:4118
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:4953
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:4533
std::shared_ptr< BSplineCommon > Ptr
Shared pointer for BSplineCommon.
Definition bspline.hpp:3650
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:5112
auto plot(const BSplineCommon< BSplineCoreColor > &color, const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:6797
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:3706
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:3768
auto scale(BSplineCore::value_type s, int dim=-1) const
Scales the B-spline object by a scalar.
Definition bspline.hpp:3949
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:5061
auto plot(const utils::TensorArray< BSplineCore::parDim_ > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:5935
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:3732
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:4212
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:3692
BSplineCommon(BSplineCommon &&)=default
Move constructor.
BSplineCommon operator/(BSplineCore::value_type s) const
Returns a new B-spline object whose coefficients are scaled by a scalar.
Definition bspline.hpp:7075
auto norm() const
Computes the norm of the B-spline object by computing the mean-squared sum of the function values eva...
Definition bspline.hpp:3944
auto plot(const nlohmann::json &json={}) const
Definition bspline.hpp:5919
BSplineCommon(BSplineCommon &&other, utils::TensorArray< BSplineCore::geoDim_ > &&coeffs)
Move constructor with external coefficients.
Definition bspline.hpp:3682
BSplineCommon & operator+=(const BSplineCommon &other)
Adds the coefficients of another B-spline object.
Definition bspline.hpp:7105
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:5770
auto plot(const BSplineCommon< BSplineCoreColor > &color, const nlohmann::json &json={}) const
Definition bspline.hpp:5973
auto to() const
Returns a copy of the B-spline object with real_t type.
Definition bspline.hpp:3859
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:4078
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:7019
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:4321
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:5224
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:4588
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:5514
auto translate(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v) const
Translates the B-spline object by a vector.
Definition bspline.hpp:3978
auto boundingBox() const
Computes the bounding box of the B-spline object.
Definition bspline.hpp:4052
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:3751
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:4456
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:4330
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:4768
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:4861
auto scale_(std::array< typename BSplineCore::value_type, BSplineCore::geoDim()> v)
Scales the B-spline object by a vector in-place.
Definition bspline.hpp:3971
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:5360
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:3759
BSplineCommon & operator/=(BSplineCore::value_type s)
Scales the coefficients by a scalar.
Definition bspline.hpp:7147
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:3723
auto rotate(BSplineCore::value_type angle) const
Rotates the B-spline object by an angle in 2d.
Definition bspline.hpp:3992
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:4083
BSplineCommon & operator-=(const BSplineCommon &other)
Substracts the coefficients of another B-spline object.
Definition bspline.hpp:7119
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:7037
BSplineCommon(const BSplineCommon &other, const utils::TensorArray< BSplineCore::geoDim_ > &coeffs, bool clone=false)
Copy constructor with external coefficients.
Definition bspline.hpp:3666
auto scale_(BSplineCore::value_type s, int dim=-1)
Scales the B-spline object by a scalar in-place.
Definition bspline.hpp:3954
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:5876
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:4732
auto to(torch::Device device) const
Returns a copy of the B-spline object with settings from device.
Definition bspline.hpp:3842
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:4906
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:5634
auto clone() const
Returns a clone of the B-spline object.
Definition bspline.hpp:3807
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:7087
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:3745
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:5351
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:3785
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:5675
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:4496
BSplineCommon & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:3801
auto plot(const std::initializer_list< utils::TensorArray< BSplineCore::parDim_ > > &xi, const nlohmann::json &json={}) const
Definition bspline.hpp:5953
auto abs_diff(const BSplineCommon &other, int dim=-1) const
Computes the absolute difference between two compatible B-spline objects.
Definition bspline.hpp:3907
auto to(Options< real_t > options) const
Returns a copy of the B-spline object with settings from options.
Definition bspline.hpp:3824
Abstract patch function base class.
Definition patch.hpp:50
Tensor-product non-uniform B-spline (core functionality)
Definition bspline.hpp:3064
static constexpr bool is_nonuniform()
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:3105
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:3207
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:3086
auto & from_gismo(BSpline &bspline, bool, bool)
Definition bspline.hpp:3567
auto eval(const torch::Tensor &xi) const
Returns the value of the multivariate B-spline object in the point xi
Definition bspline.hpp:3186
auto find_knot_indices(const torch::Tensor &xi) const
Returns the indices of knot spans containing xi
Definition bspline.hpp:3252
real_t value_type
Value type.
Definition bspline.hpp:3071
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:3191
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:3390
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:3140
NonUniformBSplineCore & insert_knots(const utils::TensorArray< Base::parDim_ > &knots)
Returns the B-spline object with refined knot and coefficient vectors.
Definition bspline.hpp:3347
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:3222
UniformBSplineCore< real_t, GeoDim, Degrees... > Base
Base type.
Definition bspline.hpp:3067
auto find_knot_indices(const utils::TensorArray< Base::parDim_ > &xi) const
Returns the indices of knot spans containing xi
Definition bspline.hpp:3260
static constexpr bool is_uniform()
Returns true if the B-spline is uniform.
Definition bspline.hpp:3102
NonUniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:3281
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:3117
void init_knots(const std::array< std::vector< typename Base::value_type >, Base::parDim_ > &kv)
Initializes the B-spline knots.
Definition bspline.hpp:3162
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:3080
NonUniformSplineCore base class.
Definition bspline.hpp:132
The Options class handles the automated determination of dtype from the template argument and the sel...
Definition options.hpp:104
Spline base class.
Definition bspline.hpp:3577
SplineCore base class.
Definition bspline.hpp:126
Tensor-product uniform B-spline (core functionality)
Definition bspline.hpp:225
bool pinned_memory() const noexcept override
Returns the pinned_memory property.
Definition bspline.hpp:325
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:255
utils::TensorArray< parDim_ > find_knot_indices(const utils::TensorArray< parDim_ > &xi) const noexcept
Returns the indices of knot spans containing xi
Definition bspline.hpp:1196
utils::TensorArray< geoDim_ > coeffs_view() const noexcept
Returns an array of views to the coefficient vectors.
Definition bspline.hpp:617
real_t value_type
Value type.
Definition bspline.hpp:271
bool requires_grad() const noexcept override
Returns the requires_grad property.
Definition bspline.hpp:320
utils::TensorArray< parDim_ > knots_
Array storing the knot vectors .
Definition bspline.hpp:259
UniformBSplineCore & transform(const std::function< std::array< real_t, geoDim_ >(const std::array< real_t, parDim_ > &)> mapping)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1494
auto & from_gismo(BSpline &bspline, bool, bool)
Definition bspline.hpp:3031
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:961
utils::TensorArray< parDim_ > & knots() noexcept
Returns a non-constant reference to the array of knot vectors.
Definition bspline.hpp:546
torch::Tensor as_tensor_(std::index_sequence< Is... >) const noexcept
Returns all coefficients as a single tensor.
Definition bspline.hpp:677
void load(const std::string &filename, const std::string &key="bspline")
Loads the B-spline from file.
Definition bspline.hpp:2073
UniformBSplineCore & from_xml(const pugi::xml_document &doc, int id=0, const std::string &label="", int index=-1)
Updates the B-spline object from XML object.
Definition bspline.hpp:1857
UniformBSplineCore(const UniformBSplineCore< other_t, GeoDim, Degrees... > &other, Options< real_t > options=Options< real_t >{})
Copy constructor.
Definition bspline.hpp:468
torch::Layout layout() const noexcept override
Returns the layout property.
Definition bspline.hpp:315
auto eval_basfunc_tr(const torch::Tensor &xi) const
Returns the vector of multivariate B-spline basis functions (or their derivatives) evaluated in the p...
Definition bspline.hpp:1283
const Options< real_t > & options() const noexcept
Returns a constant reference to the B-spline object's options.
Definition bspline.hpp:366
const std::array< int64_t, parDim_ > & nknots() const noexcept
Returns a constant reference to the array of knot vector dimensions.
Definition bspline.hpp:563
UniformBSplineCore & transform(const std::function< std::array< real_t, N >(const std::array< real_t, parDim_ > &)> mapping, std::array< short_t, N > dims)
Transforms the coefficients based on the given mapping.
Definition bspline.hpp:1576
auto eval_basfunc_tr(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:1331
auto eval_basfunc_univariate_tr(const torch::Tensor &xi, const torch::Tensor &knot_indices) const
Definition bspline.hpp:2715
derived_type< UniformBSplineCore, degree_elevate > self_type
Deduces the self-type possibly degrees (de-)elevated by the additive constant degree_elevate
Definition bspline.hpp:285
torch::Tensor as_tensor() const noexcept override
Returns all coefficients as a single tensor.
Definition bspline.hpp:685
auto eval_tr(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices) const
Definition bspline.hpp:968
auto find_coeff_indices(const torch::Tensor &indices) const
Returns the indices of the coefficients corresponding to the knot indices indices
Definition bspline.hpp:1223
UniformBSplineCore & from_json(const nlohmann::json &json)
Updates the B-spline object from JSON object.
Definition bspline.hpp:1699
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:817
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:1318
const torch::Tensor & coeffs(short_t i) const noexcept
Returns a constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:592
utils::TensorArray< geoDim_ > coeffs_
Array storing the coefficients of the control net , .
Definition bspline.hpp:264
static constexpr short_t parDim() noexcept
Returns the parametric dimension.
Definition bspline.hpp:497
static constexpr bool is_uniform() noexcept
Returns true if the B-spline is uniform.
Definition bspline.hpp:335
int64_t ncoeffs(short_t i) const noexcept
Returns the total number of coefficients in the -th direction.
Definition bspline.hpp:667
auto eval_tr(const torch::Tensor &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:932
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:1233
auto eval(const utils::TensorArray< parDim_ > &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:927
const auto coeffs_view(short_t i) const noexcept
Returns a view to the coefficient vector in the -th dimension.
Definition bspline.hpp:630
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:2474
pugi::xml_document to_xml(int id=0, const std::string &label="", int index=-1) const
Returns the B-spline object as XML object.
Definition bspline.hpp:1734
static constexpr short_t degree(short_t i) noexcept
Returns a constant reference to the degree in the -th dimension.
Definition bspline.hpp:518
torch::Dtype dtype() const noexcept override
Returns the dtype property.
Definition bspline.hpp:310
const utils::TensorArray< parDim_ > & knots() const noexcept
Returns a constant reference to the array of knot vectors.
Definition bspline.hpp:527
int64_t nknots(short_t i) const noexcept
Returns the dimension of the knot vector in the -th dimension.
Definition bspline.hpp:573
pugi::xml_node & to_xml(pugi::xml_node &root, int id=0, const std::string &label="", int index=-1) const
Returns the B-spline object as XML node.
Definition bspline.hpp:1743
BSpline & to_gismo(BSpline &bspline, bool, bool) const
Definition bspline.hpp:2949
int64_t constexpr eval_prefactor() const
Computes the prefactor .
Definition bspline.hpp:2309
UniformBSplineCore & from_xml(const pugi::xml_node &root, int id=0, const std::string &label="", int index=-1)
Updates the B-spline object from XML node.
Definition bspline.hpp:1864
BSpline< real_t, GeoDim,(Degrees+degree_elevate)... > derived_type
Deduces the type of the template parameter BSpline when exposed to the class template parameters real...
Definition bspline.hpp:280
std::array< int64_t, parDim_ > nknots_
Array storing the sizes of the knot vectors .
Definition bspline.hpp:246
static constexpr const std::array< short_t, parDim_ > degrees_
Array storing the degrees .
Definition bspline.hpp:242
static constexpr const short_t parDim_
Dimension of the parametric space .
Definition bspline.hpp:234
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:833
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:385
Options< real_t > options_
Options.
Definition bspline.hpp:267
auto greville(bool interior=false) const
Returns the Greville abscissae.
Definition bspline.hpp:737
utils::TensorArray< geoDim_ > & coeffs() noexcept
Returns a non-constant reference to the array of coefficient vectors.
Definition bspline.hpp:601
static constexpr const std::array< short_t, parDim_ > & degrees() noexcept
Returns a constant reference to the array of degrees.
Definition bspline.hpp:508
int32_t device_index() const noexcept override
Returns the device_index property.
Definition bspline.hpp:305
torch::Device device() const noexcept override
Returns the device property.
Definition bspline.hpp:300
UniformBSplineCore & uniform_refine(int numRefine=1, int dim=-1)
Returns the B-spline object with uniformly refined knot and coefficient vectors.
Definition bspline.hpp:2237
UniformBSplineCore & set_requires_grad(bool requires_grad) noexcept override
Sets the B-spline object's requires_grad property.
Definition bspline.hpp:348
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:2815
UniformBSplineCore & from_tensor(const torch::Tensor &tensor) noexcept override
Sets all coefficients from a single tensor.
Definition bspline.hpp:711
UniformBSplineCore(Options< real_t > options=Options< real_t >{})
Default constructor.
Definition bspline.hpp:371
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:2131
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:2082
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:2645
bool operator!=(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are different.
Definition bspline.hpp:2224
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:2160
~UniformBSplineCore() override=default
Destructor.
bool operator==(const UniformBSplineCore< other_t, GeoDim_, Degrees_... > &other) const
Returns true if both B-spline objects are the same.
Definition bspline.hpp:2192
nlohmann::json knots_to_json() const
Returns the B-spline object's knots as JSON object.
Definition bspline.hpp:1671
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:1272
static constexpr const short_t geoDim_
Dimension of the geometric space .
Definition bspline.hpp:238
auto eval_tr(const utils::TensorArray< parDim_ > &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:940
const utils::TensorArray< geoDim_ > & coeffs() const noexcept
Returns a constant reference to the array of coefficient vectors.
Definition bspline.hpp:582
auto eval_basfunc_tr(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:1294
int64_t ncumcoeffs() const noexcept
Returns the total number of coefficients.
Definition bspline.hpp:644
void save(const std::string &filename, const std::string &key="bspline") const
Saves the B-spline to file.
Definition bspline.hpp:2123
static constexpr bool is_nonuniform() noexcept
Returns true if the B-spline is non-uniform.
Definition bspline.hpp:338
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:412
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:450
const torch::Tensor & knots(short_t i) const noexcept
Returns a constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:537
auto find_knot_indices(const torch::Tensor &xi) const noexcept
Returns the indices of knot spans containing xi
Definition bspline.hpp:1188
auto update_coeffs_univariate(const torch::Tensor &knots, const torch::Tensor &knot_indices) const
Returns the knot insertion matrix.
Definition bspline.hpp:2768
std::array< int64_t, parDim_ > ncoeffs_
Array storing the sizes of the coefficients of the control net .
Definition bspline.hpp:250
nlohmann::json to_json() const override
Returns the B-spline object as JSON object.
Definition bspline.hpp:1657
torch::Tensor & coeffs(short_t i) noexcept
Returns a non-constant reference to the coefficient vector in the -th dimension.
Definition bspline.hpp:609
void init_coeffs(enum init init)
Initializes the B-spline coefficients.
Definition bspline.hpp:2344
auto eval_tr(const utils::TensorArray< parDim_ > &xi, const utils::TensorArray< parDim_ > &knot_indices, const torch::Tensor &coeff_indices) const
Definition bspline.hpp:1082
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:993
UniformBSplineCore & from_tensor_(std::index_sequence< Is... >, const torch::Tensor &tensor) noexcept
Sets all coefficients from a single tensor.
Definition bspline.hpp:695
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:1345
static constexpr short_t geoDim() noexcept
Returns the geometric dimension.
Definition bspline.hpp:502
const std::array< int64_t, parDim_ > & ncoeffs() const noexcept
Returns a constant reference to the array of coefficient vector dimensions.
Definition bspline.hpp:657
torch::Tensor & knots(short_t i) noexcept
Returns a non-constant reference to the knot vector in the -th dimension.
Definition bspline.hpp:554
auto eval_basfunc_tr(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:1419
void init_knots()
Initializes the B-spline knots.
Definition bspline.hpp:2318
auto eval(const torch::Tensor &xi) const
Returns the value of the B-spline object in the point xi
Definition bspline.hpp:919
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:1261
bool is_sparse() const noexcept override
Returns true if the layout is sparse.
Definition bspline.hpp:330
int64_t as_tensor_size() const noexcept override
Returns the size of the single tensor representation of all coefficients.
Definition bspline.hpp:719
nlohmann::json coeffs_to_json() const
Returns the B-spline object's coefficients as JSON object.
Definition bspline.hpp:1676
UniformSplineCore base class.
Definition bspline.hpp:129
Full qualified name descriptor.
Definition fqn.hpp:22
Concept to identify template parameters that are derived from iganet::NonUniformSplineCore_.
Definition bspline.hpp:147
Concept to identify template parameters that are derived from iganet::Spline_ and iganet::NonUniformS...
Definition bspline.hpp:3594
Concept to identify template parameters that are derived from iganet::SplineCore_.
Definition bspline.hpp:137
Concept to identify template parameters that are derived from iganet::Spline_.
Definition bspline.hpp:3582
Concept to identify template parameters that are derived from iganet::UniformSplineCore_.
Definition bspline.hpp:142
Concept to identify template parameters that are derived from iganet::Spline_ and iganet::UniformSpli...
Definition bspline.hpp:3587
Definition bspline.hpp:119
Definition bspline.hpp:112
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:209
std::array< torch::Tensor, N > TensorArray
Definition tensorarray.hpp:26
auto to_tensor(const std::array< T, N > &array, torch::IntArrayRef sizes=torch::IntArrayRef{-1}, const iganet::Options< T > &options=iganet::Options< T >{})
Converts a std::array to torch::Tensor.
Definition container.hpp:56
auto kronproduct(T0 &&t0, T1 &&t1)
Computes the directional Kronecker-product between two tensors along the given dimension.
Definition linalg.hpp:60
TensorArray< 1 > TensorArray1
Definition tensorarray.hpp:29
auto to_tensorAccessor(const torch::Tensor &tensor)
Converts a torch::Tensor object to a torch::TensorAccessor object.
Definition tensorarray.hpp:78
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:220
constexpr std::array< T, N - M > remove_from_back(std::array< T, N > array)
Derives a std::array object from a given std::array object dropping the last M entries.
Definition container.hpp:372
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:45
auto dotproduct(T0 &&t0, T1 &&t1)
Computes the directional dot-product between two tensors with summation along the given dimension.
Definition linalg.hpp:36
auto to_ArrayRef(const std::array< T, N > &array)
Converts a std::array<int64_t, N> to an at::IntArrayRef object.
Definition container.hpp:188
Forward declaration of BlockTensor.
Definition blocktensor.hpp:43
Definition core.hpp:72
deriv
Enumerator for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:72
constexpr auto operator+(deriv lhs, deriv rhs)
Adds two enumerators for specifying the derivative of B-spline evaluation.
Definition bspline.hpp:89
bool is_verbose(std::ostream &os)
Definition core.hpp:831
std::ostream & operator<<(std::ostream &os, const MemoryDebugger< id > &obj)
Print (as string) a memory debugger object.
Definition memory.hpp:125
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:102
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:3051
@ none
Definition boundary.hpp:38
short int short_t
Definition core.hpp:74
STL namespace.
Options.
Serialization utility functions.
Serialization prototype.
Definition serialize.hpp:29
Computes the power of integer E to the N at compile time.
Definition integer_pow.hpp:21
Reverse index sequence.
Definition index_sequence.hpp:36
TensorArray utility functions.
#define TENSORARRAY_FORALL(obj, func,...)
Definition tensorarray.hpp:156
VSlice utility functions.