From 4cca1171ac3bcd80aa4f6808930d7c445c344b80 Mon Sep 17 00:00:00 2001 From: mahf708 Date: Wed, 30 Oct 2024 21:38:00 -0700 Subject: [PATCH] dot_along_rank1_dim --- .../eamxx/src/share/field/field_utils.hpp | 7 + .../src/share/field/field_utils_impl_dot.hpp | 387 ++++++++++++++++++ .../eamxx/src/share/tests/field_utils.cpp | 137 +++++++ 3 files changed, 531 insertions(+) create mode 100644 components/eamxx/src/share/field/field_utils_impl_dot.hpp diff --git a/components/eamxx/src/share/field/field_utils.hpp b/components/eamxx/src/share/field/field_utils.hpp index 8f977a7caa1..908f21642b6 100644 --- a/components/eamxx/src/share/field/field_utils.hpp +++ b/components/eamxx/src/share/field/field_utils.hpp @@ -2,6 +2,7 @@ #define SCREAM_FIELD_UTILS_HPP #include "share/field/field_utils_impl.hpp" +#include "share/field/field_utils_impl_dot.hpp" namespace scream { @@ -111,6 +112,12 @@ void perturb (const Field& f, impl::perturb(f, engine, pdf, base_seed, level_mask, dof_gids); } +template +Field dot_along_rank1_dim(const int &pd, const Field &f1, const Field &f2, + const ekat::Comm *co = nullptr) { + return do_dot_along_rank1_dim(pd, f1, f2, co); +} + template ST frobenius_norm(const Field& f, const ekat::Comm* comm = nullptr) { diff --git a/components/eamxx/src/share/field/field_utils_impl_dot.hpp b/components/eamxx/src/share/field/field_utils_impl_dot.hpp new file mode 100644 index 00000000000..b8ff7971940 --- /dev/null +++ b/components/eamxx/src/share/field/field_utils_impl_dot.hpp @@ -0,0 +1,387 @@ +#ifndef SCREAM_FIELD_UTILS_IMPL_DOT_HPP +#define SCREAM_FIELD_UTILS_IMPL_DOT_HPP + +#include "ekat/kokkos/ekat_kokkos_utils.hpp" +#include "ekat/mpi/ekat_comm.hpp" +#include "share/field/field.hpp" + +namespace scream { + +// dot product of rank 1 field with a rank N field, return a rank N-1 field +template +Field do_dot_along_rank1_dim(const int &pd, const Field &f1, const Field &f2, + const ekat::Comm *co) { + using KT = ekat::KokkosTypes; + using RangePolicy = Kokkos::RangePolicy; + using TeamPolicy = Kokkos::TeamPolicy; + using TeamMember = typename TeamPolicy::member_type; + using ESU = ekat::ExeSpaceUtils; + + const auto &l1 = f1.get_header().get_identifier().get_layout(); + + const auto &n2 = f2.get_header().get_identifier().name(); + const auto &l2 = f2.get_header().get_identifier().get_layout(); + const auto &u2 = f2.get_header().get_identifier().get_units(); + const auto &g2 = f2.get_header().get_identifier().get_grid_name(); + + EKAT_REQUIRE_MSG(pd <= 5 && pd >= 0, + "Error! First argument pd must be between 0 and 5.\n" + "The input pd is " + << pd << ", which is not accepted.\n"); + EKAT_REQUIRE_MSG(l1.rank() == 1, + "Error! Second argument f1 must be rank-1.\n" + "The input f1 rank is " + << l1.rank() << ", which is not accepted.\n"); + EKAT_REQUIRE_MSG(l2.rank() <= 6, + "Error! Third argument f2 must be at most rank-6.\n" + "The input f2 rank is " + << l2.rank() << ", which is not accepted.\n"); + EKAT_REQUIRE_MSG( + l1.dim(0) == l2.dim(pd), + "Error! The two input fields must have the same dimension along " + "which we are taking the dot product.\n" + "The first field f1 has dimension " + << l1.dim(0) + << " while " + "the second field f2 has dimension " + << l2.dim(pd) + << " \n" + "along the provided dimension pd " + << pd << " .\n"); + + auto v1 = f1.get_view(); + + FieldIdentifier fo_id(n2, l2.clone().strip_dim(pd), u2, g2); + Field fo(fo_id); + fo.allocate_view(); + fo.deep_copy(0); + + const int d0 = l2.dim(0); + + switch(l2.rank()) { + case 1: { + auto v2 = f2.get_view(); + auto vo = fo.get_view(); + Kokkos::parallel_reduce( + fo.name(), Kokkos::RangePolicy<>(0, d0), + KOKKOS_LAMBDA(const int i, Real &ls) { ls += v1(i) * v2(i); }, vo); + } break; + case 2: { + auto v2 = f2.get_view(); + auto vo = fo.get_view(); + const int d1 = l2.dim(1); + if(pd == 0) { + auto p = ESU::get_default_team_policy(d1, d0); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int j = tm.league_rank(); + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d0), + [&](int i, ST &ac) { ac += v1(i) * v2(i, j); }, vo(j)); + }); + } else if(pd == 1) { + auto p = ESU::get_default_team_policy(d0, d1); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int i = tm.league_rank(); + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d1), + [&](int j, ST &ac) { ac += v1(j) * v2(i, j); }, vo(i)); + }); + } + } break; + case 3: { + auto v2 = f2.get_view(); + auto vo = fo.get_view(); + const int d1 = l2.dim(1); + const int d2 = l2.dim(2); + if(pd == 0) { + auto p = ESU::get_default_team_policy(d1 * d2, d0); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int j = idx / d2; + const int k = idx % d2; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d0), + [&](int i, ST &ac) { ac += v1(i) * v2(i, j, k); }, vo(j, k)); + }); + } else if(pd == 1) { + auto p = ESU::get_default_team_policy(d0 * d2, d1); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = idx / d2; + const int k = idx % d2; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d1), + [&](int j, ST &ac) { ac += v1(j) * v2(i, j, k); }, vo(i, k)); + }); + } else if(pd == 2) { + auto p = ESU::get_default_team_policy(d0 * d1, d2); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = idx / d1; + const int j = idx % d1; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d2), + [&](int k, ST &ac) { ac += v1(k) * v2(i, j, k); }, vo(i, j)); + }); + } + } break; + case 4: { + auto v2 = f2.get_view(); + auto vo = fo.get_view(); + const int d1 = l2.dim(1); + const int d2 = l2.dim(2); + const int d3 = l2.dim(3); + if(pd == 0) { + auto p = ESU::get_default_team_policy(d1 * d2 * d3, d0); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int j = (idx / d3) / d2; + const int k = (idx / d3) % d2; + const int l = idx % d3; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d0), + [&](int i, ST &ac) { ac += v1(i) * v2(i, j, k, l); }, + vo(j, k, l)); + }); + } else if(pd == 1) { + auto p = ESU::get_default_team_policy(d0 * d2 * d3, d1); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (idx / d3) / d2; + const int k = (idx / d3) % d2; + const int l = idx % d3; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d1), + [&](int j, ST &ac) { ac += v1(j) * v2(i, j, k, l); }, + vo(i, k, l)); + }); + } else if(pd == 2) { + auto p = ESU::get_default_team_policy(d0 * d1 * d3, d2); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (idx / d3) / d1; + const int j = (idx / d3) % d1; + const int l = idx % d3; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d2), + [&](int k, ST &ac) { ac += v1(k) * v2(i, j, k, l); }, + vo(i, j, l)); + }); + } else if(pd == 3) { + auto p = ESU::get_default_team_policy(d0 * d1 * d2, d3); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (idx / d2) / d1; + const int j = (idx / d2) % d1; + const int k = idx % d2; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d3), + [&](int l, ST &ac) { ac += v1(l) * v2(i, j, k, l); }, + vo(i, j, k)); + }); + } + } break; + case 5: { + auto v2 = f2.get_view(); + auto vo = fo.get_view(); + const int d1 = l2.dim(1); + const int d2 = l2.dim(2); + const int d3 = l2.dim(3); + const int d4 = l2.dim(4); + if(pd == 0) { + auto p = ESU::get_default_team_policy(d1 * d2 * d3 * d4, d0); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int j = ((idx / d4) / d3) / d2; + const int k = ((idx / d4) / d3) % d2; + const int l = (idx / d4) % d3; + const int n = idx % d4; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d0), + [&](int i, ST &ac) { ac += v1(i) * v2(i, j, k, l, n); }, + vo(j, k, l, n)); + }); + } else if(pd == 1) { + auto p = ESU::get_default_team_policy(d0 * d2 * d3 * d4, d1); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = ((idx / d4) / d3) / d2; + const int k = ((idx / d4) / d3) % d2; + const int l = (idx / d4) % d3; + const int n = idx % d4; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d1), + [&](int j, ST &ac) { ac += v1(j) * v2(i, j, k, l, n); }, + vo(i, k, l, n)); + }); + } else if(pd == 2) { + auto p = ESU::get_default_team_policy(d0 * d1 * d3 * d4, d2); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = ((idx / d4) / d3) / d1; + const int j = ((idx / d4) / d3) % d1; + const int l = (idx / d4) % d3; + const int n = idx % d4; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d2), + [&](int k, ST &ac) { ac += v1(k) * v2(i, j, k, l, n); }, + vo(i, j, l, n)); + }); + } else if(pd == 3) { + auto p = ESU::get_default_team_policy(d0 * d1 * d2 * d4, d3); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = ((idx / d4) / d2) / d1; + const int j = ((idx / d4) / d2) % d1; + const int k = (idx / d4) % d2; + const int n = idx % d4; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d3), + [&](int l, ST &ac) { ac += v1(l) * v2(i, j, k, l, n); }, + vo(i, j, k, n)); + }); + } else if(pd == 4) { + auto p = ESU::get_default_team_policy(d0 * d1 * d2 * d3, d4); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = ((idx / d3) / d2) / d1; + const int j = ((idx / d3) / d2) % d1; + const int k = (idx / d3) % d2; + const int l = idx % d3; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d4), + [&](int n, ST &ac) { ac += v1(n) * v2(i, j, k, l, n); }, + vo(i, j, k, l)); + }); + } + } break; + case 6: { + auto v2 = f2.get_view(); + auto vo = fo.get_view(); + const int d1 = l2.dim(1); + const int d2 = l2.dim(2); + const int d3 = l2.dim(3); + const int d4 = l2.dim(4); + const int d5 = l2.dim(5); + if(pd == 0) { + auto p = ESU::get_default_team_policy(d1 * d2 * d3 * d4 * d5, d0); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int j = (((idx / d5) / d4) / d3) / d2; + const int k = (((idx / d5) / d4) / d3) % d2; + const int l = ((idx / d5) / d4) % d3; + const int n = (idx / d5) % d4; + const int m = idx % d5; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d0), + [&](int i, ST &ac) { ac += v1(i) * v2(i, j, k, l, n, m); }, + vo(j, k, l, n, m)); + }); + } else if(pd == 1) { + auto p = ESU::get_default_team_policy(d0 * d2 * d3 * d4 * d5, d1); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (((idx / d5) / d4) / d3) / d2; + const int k = (((idx / d5) / d4) / d3) % d2; + const int l = ((idx / d5) / d4) % d3; + const int n = (idx / d5) % d4; + const int m = idx % d5; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d1), + [&](int j, ST &ac) { ac += v1(j) * v2(i, j, k, l, n, m); }, + vo(i, k, l, n, m)); + }); + } else if(pd == 2) { + auto p = ESU::get_default_team_policy(d0 * d1 * d3 * d4 * d5, d2); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (((idx / d5) / d4) / d3) / d1; + const int j = (((idx / d5) / d4) / d3) % d1; + const int l = ((idx / d5) / d4) % d3; + const int n = (idx / d5) % d4; + const int m = idx % d5; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d2), + [&](int k, ST &ac) { ac += v1(k) * v2(i, j, k, l, n, m); }, + vo(i, j, l, n, m)); + }); + } else if(pd == 3) { + auto p = ESU::get_default_team_policy(d0 * d1 * d2 * d4 * d5, d3); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (((idx / d5) / d4) / d2) / d1; + const int j = (((idx / d5) / d4) / d2) % d1; + const int k = ((idx / d5) / d4) % d2; + const int n = (idx / d5) % d4; + const int m = idx % d5; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d3), + [&](int l, ST &ac) { ac += v1(l) * v2(i, j, k, l, n, m); }, + vo(i, j, k, n, m)); + }); + } else if(pd == 4) { + auto p = ESU::get_default_team_policy(d0 * d1 * d2 * d3 * d5, d4); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (((idx / d5) / d3) / d2) / d1; + const int j = (((idx / d5) / d3) / d2) % d1; + const int k = ((idx / d5) / d3) % d2; + const int l = (idx / d5) % d3; + const int m = idx % d5; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d4), + [&](int n, ST &ac) { ac += v1(n) * v2(i, j, k, l, n, m); }, + vo(i, j, k, l, m)); + }); + } else if(pd == 5) { + auto p = ESU::get_default_team_policy(d0 * d1 * d2 * d3 * d4, d5); + Kokkos::parallel_for( + fo.name(), p, KOKKOS_LAMBDA(const TeamMember &tm) { + const int idx = tm.league_rank(); + const int i = (((idx / d4) / d3) / d2) / d1; + const int j = (((idx / d4) / d3) / d2) % d1; + const int k = ((idx / d4) / d3) % d2; + const int l = (idx / d4) % d3; + const int n = idx % d4; + Kokkos::parallel_reduce( + Kokkos::TeamVectorRange(tm, d5), + [&](int m, ST &ac) { ac += v1(m) * v2(i, j, k, l, n, m); }, + vo(i, j, k, l, n)); + }); + } + } break; + default: + EKAT_ERROR_MSG("Error! Unsupported field rank.\n"); + } + Kokkos::fence(); + if(co) { + fo.sync_to_host(); + co->all_reduce(fo.template get_internal_view_data(), + l2.size() / l2.dim(pd), MPI_SUM); + fo.sync_to_dev(); + } + return fo; +} + +} // namespace scream + +#endif // SCREAM_FIELD_UTILS_IMPL_HPP diff --git a/components/eamxx/src/share/tests/field_utils.cpp b/components/eamxx/src/share/tests/field_utils.cpp index f444ec75d52..4fc7e7ca328 100644 --- a/components/eamxx/src/share/tests/field_utils.cpp +++ b/components/eamxx/src/share/tests/field_utils.cpp @@ -126,6 +126,143 @@ TEST_CASE("utils") { REQUIRE(field_sum(f1,&comm)==gsum); } + SECTION("dot_along_rank1_dim") { + using RPDF = std::uniform_real_distribution; + auto engine = setup_random_test(); + RPDF pdf(0, 1); + + int dim0 = 3; + int dim1 = 9; + int dim2 = 2; + FieldIdentifier fid00("field_00", {{COL}, {dim0}}, m / s, "sg"); + FieldIdentifier fid01("field_01", {{CMP}, {dim1}}, m / s, "sg"); + FieldIdentifier fid02("field_02", {{LEV}, {dim2}}, m / s, "sg"); + Field field00(fid00); + Field field01(fid01); + Field field02(fid02); + field00.allocate_view(); + field01.allocate_view(); + field02.allocate_view(); + field00.sync_to_host(); + field01.sync_to_host(); + field02.sync_to_host(); + auto v00 = field00.get_strided_view(); + auto v01 = field01.get_strided_view(); + auto v02 = field02.get_strided_view(); + for(int i = 0; i < dim0; ++i) { + v00(i) = (i + 1) / sp(6); + } + for(int i = 0; i < dim1; ++i) { + v01(i) = (i + 1) / sp(45); + } + for(int i = 0; i < dim2; ++i) { + v02(i) = (i + 1) / sp(3); + } + field00.sync_to_dev(); + field01.sync_to_dev(); + field02.sync_to_dev(); + + FieldIdentifier fid1("field_1", {{COL, CMP}, {dim0, dim1}}, m / s, "sg"); + FieldIdentifier fid2("field_2", {{COL, CMP, LEV}, {dim0, dim1, dim2}}, + m / s, "sg"); + FieldIdentifier fid3("field_3", + {{COL, CMP, CMP, LEV}, {dim0, dim1, dim1, dim2}}, + m / s, "sg"); + FieldIdentifier fid4( + "field_4", {{COL, CMP, CMP, CMP, LEV}, {dim0, dim1, dim1, dim1, dim2}}, + m / s, "sg"); + FieldIdentifier fid5( + "field_5", + {{COL, CMP, CMP, CMP, CMP, LEV}, {dim0, dim1, dim1, dim1, dim1, dim2}}, + m / s, "sg"); + Field field1(fid1); + Field field2(fid2); + Field field3(fid3); + Field field4(fid4); + Field field5(fid5); + field1.allocate_view(); + field2.allocate_view(); + field3.allocate_view(); + field4.allocate_view(); + field5.allocate_view(); + randomize(field1, engine, pdf); + randomize(field2, engine, pdf); + randomize(field3, engine, pdf); + randomize(field4, engine, pdf); + randomize(field5, engine, pdf); + + Field result; + + result = dot_along_rank1_dim(0, field00, field2); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({CMP, LEV})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(0) == dim1); + REQUIRE(result.get_header().get_identifier().get_layout().dim(1) == dim2); + + result = dot_along_rank1_dim(1, field01, field2); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({COL, LEV})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(0) == dim0); + REQUIRE(result.get_header().get_identifier().get_layout().dim(1) == dim2); + + result = dot_along_rank1_dim(2, field02, field2); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({COL, CMP})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(0) == dim0); + REQUIRE(result.get_header().get_identifier().get_layout().dim(1) == dim1); + + field2.sync_to_host(); + auto manual_result = result.clone(); + manual_result.deep_copy(0); + manual_result.sync_to_host(); + auto v2 = field2.get_strided_view(); + auto mr = manual_result.get_strided_view(); + for(int i = 0; i < dim0; ++i) { + for(int j = 0; j < dim1; ++j) { + for(int k = 0; k < dim2; ++k) { + mr(i, j) += v02(k) * v2(i, j, k); + } + } + } + field3.sync_to_dev(); + manual_result.sync_to_dev(); + REQUIRE(views_are_equal(result, manual_result)); + + result = dot_along_rank1_dim(1, field01, field1); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({COL})); + result = dot_along_rank1_dim(2, field02, field2); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({COL, CMP})); + result = dot_along_rank1_dim(0, field00, field3); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({CMP, CMP, LEV})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(2) == dim2); + result = dot_along_rank1_dim(0, field00, field4); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({CMP, CMP, CMP, LEV})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(3) == dim2); + result = dot_along_rank1_dim(1, field01, field4); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({COL, CMP, CMP, LEV})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(0) == dim0); + REQUIRE(result.get_header().get_identifier().get_layout().dim(3) == dim2); + result = dot_along_rank1_dim(4, field02, field4); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({COL, CMP, CMP, CMP})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(0) == dim0); + REQUIRE(result.get_header().get_identifier().get_layout().dim(2) == dim1); + result = dot_along_rank1_dim(0, field00, field5); + REQUIRE(result.get_header().get_identifier().get_layout().tags() == + std::vector({CMP, CMP, CMP, CMP, LEV})); + REQUIRE(result.get_header().get_identifier().get_layout().dim(4) == dim2); + + result = dot_along_rank1_dim(0, field00, field00); + result.sync_to_host(); + auto v = result.get_view(); + REQUIRE(v() == (1 / sp(36) + 4 / sp(36) + 9 / sp(36))); + } + SECTION ("frobenius") { auto v1 = f1.get_strided_view();