Skip to content

Commit b1554b3

Browse files
committed
Use Field with serializer
Rather than pass data and layouts separately, we update the serializer to directly take the Field&. This does a better job of maintaining consistency with what data we are operating with. Also, it reduces the number of places we expose FieldData, which is really an internal implementation detail that we don't want readily exposed.
1 parent 6256e61 commit b1554b3

10 files changed

Lines changed: 254 additions & 208 deletions

‎src/pcms/coupler/field_communicator.hpp‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ class FieldCommunicator
5151
PCMS_FUNCTION_TIMER;
5252
PCMS_ALWAYS_ASSERT(layout_comm_.GetChannel().InSendCommunicationPhase());
5353
auto buffer = make_array_view(comm_buffer_);
54-
serializer_->Serialize(field_.GetData(), field_.GetLayout(), buffer,
55-
layout_comm_.GetPermutationArray());
54+
serializer_->Serialize(field_, buffer, layout_comm_.GetPermutationArray());
5655
comm_.Send(buffer.data_handle(), mode);
5756
}
5857

@@ -64,8 +63,7 @@ class FieldCommunicator
6463
// mode because we make an immediate call to deserialize after a call to
6564
// receive.
6665
auto data = comm_.Recv(redev::Mode::Synchronous);
67-
serializer_->Deserialize(field_.GetData(), field_.GetLayout(),
68-
make_const_array_view(data),
66+
serializer_->Deserialize(field_, make_const_array_view(data),
6967
layout_comm_.GetPermutationArray());
7068
}
7169

‎src/pcms/coupler/field_serializer.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ template <typename T>
1414
class FieldSerializer
1515
{
1616
public:
17-
virtual int Serialize(const FieldData<T>& field, const FieldLayout& layout,
17+
virtual int Serialize(const Field<T>& field,
1818
Rank1View<T, HostMemorySpace> buffer,
1919
Rank1View<const LO, HostMemorySpace> permutation) const
2020
{
2121
auto data = field.GetDOFHolderDataHost();
22-
auto owned = layout.GetOwnedHost();
22+
auto owned = field.GetLayout().GetOwnedHost();
2323
// The exchange plan is per DOF holder: owned[i] and permutation[i] are
2424
// indexed by holder. All num_components components of a holder share its
2525
// location, so they occupy one contiguous block permutation[i]*num_comp in
@@ -41,10 +41,10 @@ class FieldSerializer
4141
}
4242

4343
virtual void Deserialize(
44-
FieldData<T>& field, const FieldLayout& layout,
45-
Rank1View<const T, HostMemorySpace> buffer,
44+
Field<T>& field, Rank1View<const T, HostMemorySpace> buffer,
4645
Rank1View<const LO, HostMemorySpace> permutation) const
4746
{
47+
const auto& layout = field.GetLayout();
4848
const LO num_dof = layout.GetNumOwnedDofHolder();
4949
const LO num_comp = layout.GetNumComponents();
5050
Kokkos::View<T*, HostMemorySpace> sorted("sorted", layout.OwnedSize());

‎src/pcms/coupler/serializer/xgc.h‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,21 @@ class XGCFieldSerializer : public FieldSerializer<T>
2020
{
2121
}
2222

23-
int Serialize(const FieldData<T>& field, const FieldLayout& layout,
24-
Rank1View<T, HostMemorySpace> buffer,
23+
int Serialize(const Field<T>& field, Rank1View<T, HostMemorySpace> buffer,
2524
Rank1View<const LO, HostMemorySpace> permutation) const override
2625
{
2726
if (!rank_participates_) {
2827
return 0;
2928
}
3029

31-
auto const* xgc_field = dynamic_cast<const XGCFieldData<T>*>(&field);
30+
auto const* xgc_field =
31+
dynamic_cast<const XGCFieldData<T>*>(&field.GetData());
3232
if (!xgc_field) {
3333
throw pcms_error("XGCFieldSerializer::Serialize: incompatible FieldData");
3434
}
3535

3636
auto data = xgc_field->GetDOFHolderDataHost();
37+
const auto& layout = field.GetLayout();
3738
auto owned = layout.GetOwnedHost();
3839
// Per-holder plan: owned[i]/permutation[i] index holders; a holder's
3940
// num_components values form one contiguous block in the wire buffer.
@@ -54,17 +55,17 @@ class XGCFieldSerializer : public FieldSerializer<T>
5455
}
5556

5657
void Deserialize(
57-
FieldData<T>& field, const FieldLayout& layout,
58-
Rank1View<const T, HostMemorySpace> buffer,
58+
Field<T>& field, Rank1View<const T, HostMemorySpace> buffer,
5959
Rank1View<const LO, HostMemorySpace> permutation) const override
6060
{
61-
auto* xgc_field = dynamic_cast<XGCFieldData<T>*>(&field);
61+
auto* xgc_field = dynamic_cast<XGCFieldData<T>*>(&field.GetData());
6262
if (!xgc_field) {
6363
throw pcms_error(
6464
"XGCFieldSerializer::Deserialize: incompatible FieldData");
6565
}
6666

6767
auto current = xgc_field->GetDOFHolderDataHost();
68+
const auto& layout = field.GetLayout();
6869
const LO num_dof = static_cast<LO>(current.extent(0));
6970
const LO num_comp = static_cast<LO>(current.extent(1));
7071
std::vector<T> full_data(current.size());

‎test/CMakeLists.txt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,8 @@ if(Catch2_FOUND)
400400
message(
401401
STATUS "Found Catch2: ${Catch2_DIR} (found version ${Catch2_VERSION})")
402402
set(PCMS_UNIT_TEST_SOURCES unit_test_main.cpp test_coordinate_transform.cpp
403-
test_coordinate.cpp test_bounding_box.cpp)
403+
test_coordinate.cpp test_bounding_box.cpp
404+
test_field_serializer.cpp)
404405
if(PCMS_ENABLE_XGC)
405406
list(APPEND PCMS_UNIT_TEST_SOURCES test_xgc_reverse_classification.cpp
406407
test_xgc_field_data.cpp)

‎test/field_test_utils.h‎

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include "pcms/field/evaluation_request.h"
1111
#include "pcms/field/point_evaluator.h"
1212
#include "pcms/field/out_of_bounds_policy.h"
13-
#include "pcms/coupler/field_serializer.h"
1413
#include "pcms/field/coordinate_system.h"
1514
#include "pcms/utility/arrays.h"
1615
#include "pcms/utility/memory_spaces.h"
@@ -282,39 +281,6 @@ inline void SetField(FieldData<Real>& field, const FieldLayout& layout,
282281
SetField<ExecutionSpace>(layout, field, func);
283282
}
284283

285-
// Check that serialize followed by deserialize round-trips the data.
286-
// Uses an identity permutation so permutation[i] = i.
287-
inline void CheckSerializeDeserialize(const FieldLayout& layout,
288-
FieldData<Real>& field)
289-
{
290-
auto data_before = FlattenToRank1View(field.GetDOFHolderDataHost());
291-
int n = static_cast<int>(data_before.size());
292-
293-
std::vector<Real> buffer(n);
294-
std::vector<LO> perm(n);
295-
for (int i = 0; i < n; ++i)
296-
perm[i] = i;
297-
298-
Rank1View<Real, HostMemorySpace> buf_view(buffer.data(), n);
299-
Rank1View<const LO, HostMemorySpace> perm_view(perm.data(), n);
300-
301-
FieldSerializer<Real> serializer;
302-
serializer.Serialize(field, layout, buf_view, perm_view);
303-
serializer.Deserialize(
304-
field, layout, Rank1View<const Real, HostMemorySpace>(buf_view), perm_view);
305-
306-
auto data_after = FlattenToRank1View(field.GetDOFHolderDataHost());
307-
REQUIRE(data_after.size() == data_before.size());
308-
for (int i = 0; i < n; ++i) {
309-
REQUIRE(data_after[i] == Catch::Approx(data_before[i]));
310-
}
311-
}
312-
313-
inline void CheckSerializeDeserialize(Field<Real>& field)
314-
{
315-
CheckSerializeDeserialize(field.GetLayout(), field.GetData());
316-
}
317-
318284
// Helper structure to hold device coordinates with proper lifetime management
319285
struct DeviceCoordinates
320286
{

‎test/test_field_serializer.cpp‎

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
#include <catch2/catch_approx.hpp>
2+
#include <catch2/catch_test_macros.hpp>
3+
4+
#include "pcms/configuration.h"
5+
#include "pcms/coupler/field_serializer.h"
6+
#include "pcms/utility/arrays.h"
7+
8+
#include <memory>
9+
#include <numeric>
10+
#include <vector>
11+
12+
#ifdef PCMS_ENABLE_OMEGA_H
13+
#include "field_test_utils.h"
14+
#include "pcms/field/function_space/lagrange.h"
15+
#include "pcms/field/function_space/polynomial_reconstruction.hpp"
16+
#include "pcms/utility/uniform_grid.h"
17+
#include <Omega_h_library.hpp>
18+
#endif
19+
20+
#ifdef PCMS_ENABLE_XGC
21+
#include "pcms/coupler/serializer/xgc.h"
22+
#include "pcms/field/data/xgc.h"
23+
#include "pcms/field/function_space/xgc.h"
24+
#endif
25+
26+
namespace
27+
{
28+
29+
#ifdef PCMS_ENABLE_OMEGA_H
30+
void CheckSerializeDeserialize(pcms::Field<pcms::Real>& field)
31+
{
32+
auto data_before = field.GetDOFHolderDataHost();
33+
const auto num_dof = static_cast<pcms::LO>(data_before.extent(0));
34+
const auto num_components = static_cast<pcms::LO>(data_before.extent(1));
35+
const auto num_values = static_cast<size_t>(data_before.size());
36+
37+
std::vector<pcms::Real> expected(num_values);
38+
for (pcms::LO i = 0; i < num_dof; ++i) {
39+
for (pcms::LO c = 0; c < num_components; ++c) {
40+
expected[static_cast<size_t>(i) * num_components + c] = data_before(i, c);
41+
}
42+
}
43+
44+
std::vector<pcms::Real> buffer(num_values);
45+
std::vector<pcms::LO> permutation(num_dof);
46+
std::iota(permutation.begin(), permutation.end(), pcms::LO{0});
47+
48+
pcms::FieldSerializer<pcms::Real> serializer;
49+
REQUIRE(serializer.Serialize(field, pcms::make_array_view(buffer),
50+
pcms::make_const_array_view(permutation)) ==
51+
static_cast<int>(num_values));
52+
53+
std::vector<pcms::Real> cleared(num_values, -1.0);
54+
field.SetDOFHolderDataHost(
55+
pcms::Rank2View<const pcms::Real, pcms::HostMemorySpace>(
56+
cleared.data(), num_dof, num_components));
57+
serializer.Deserialize(field, pcms::make_const_array_view(buffer),
58+
pcms::make_const_array_view(permutation));
59+
60+
auto data_after = field.GetDOFHolderDataHost();
61+
REQUIRE(data_after.size() == expected.size());
62+
for (pcms::LO i = 0; i < num_dof; ++i) {
63+
for (pcms::LO c = 0; c < num_components; ++c) {
64+
REQUIRE(
65+
data_after(i, c) ==
66+
Catch::Approx(expected[static_cast<size_t>(i) * num_components + c]));
67+
}
68+
}
69+
}
70+
#endif
71+
72+
#ifdef PCMS_ENABLE_XGC
73+
pcms::ReverseClassificationVertex CreateDummyReverseClassification(int size)
74+
{
75+
pcms::ReverseClassificationVertex rc;
76+
for (int i = 0; i < size; ++i) {
77+
rc.Insert(i % 4 == 0 ? pcms::DimID{0, 0} : pcms::DimID{0, 1}, i);
78+
}
79+
return rc;
80+
}
81+
82+
bool InXGCOverlap(int, int id)
83+
{
84+
return id == 0;
85+
}
86+
#endif
87+
88+
} // namespace
89+
90+
#ifdef PCMS_ENABLE_XGC
91+
TEST_CASE("XGCFieldSerializer preserves inactive field entries")
92+
{
93+
static constexpr int data_size = 16;
94+
auto rc = CreateDummyReverseClassification(data_size);
95+
pcms::XGCFieldFactory factory(rc, InXGCOverlap, data_size);
96+
97+
std::vector<pcms::Real> data(data_size);
98+
std::iota(data.begin(), data.end(), 0.0);
99+
const auto original = data;
100+
auto field = factory.CreateField<pcms::Real>(
101+
"", std::make_unique<pcms::XGCFieldData<pcms::Real>>(
102+
factory.GetXGCLayout(), pcms::FieldMetadata{},
103+
pcms::make_array_view(data)));
104+
pcms::XGCFieldSerializer<pcms::Real> serializer(MPI_COMM_SELF);
105+
106+
auto owned = field.GetLayout().GetOwnedHost();
107+
std::vector<pcms::LO> permutation(data_size, -1);
108+
int entry = 0;
109+
for (int i = 0; i < data_size; ++i) {
110+
if (owned[i]) {
111+
permutation[i] = entry++;
112+
}
113+
}
114+
const int num_owned = entry;
115+
REQUIRE(num_owned == 4);
116+
117+
std::vector<pcms::Real> buffer(num_owned, -1.0);
118+
REQUIRE(serializer.Serialize(field, pcms::make_array_view(buffer),
119+
pcms::make_const_array_view(permutation)) ==
120+
num_owned);
121+
122+
for (int i = 0; i < data_size; ++i) {
123+
if (owned[i]) {
124+
REQUIRE(buffer[permutation[i]] == data[i]);
125+
buffer[permutation[i]] += 100.0;
126+
} else {
127+
REQUIRE(permutation[i] == -1);
128+
}
129+
}
130+
131+
serializer.Deserialize(field, pcms::make_const_array_view(buffer),
132+
pcms::make_const_array_view(permutation));
133+
134+
auto after = pcms::FlattenToRank1View(field.GetDOFHolderDataHost());
135+
for (int i = 0; i < data_size; ++i) {
136+
if (owned[i]) {
137+
REQUIRE(after[i] == Catch::Approx(original[i] + 100.0));
138+
} else {
139+
REQUIRE(after[i] == Catch::Approx(original[i]));
140+
}
141+
}
142+
}
143+
#endif
144+
145+
#ifdef PCMS_ENABLE_OMEGA_H
146+
TEST_CASE("FieldSerializer round-trips a polynomial-reconstruction field")
147+
{
148+
std::vector<pcms::Real> coords{0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0};
149+
pcms::Rank2View<pcms::Real, pcms::HostMemorySpace> coords_view(coords.data(),
150+
4, 2);
151+
auto space = pcms::PolynomialReconstructionFunctionSpace::Create(
152+
coords_view, pcms::CoordinateSystem::Cartesian);
153+
auto field = space->CreateFunction<pcms::Real>();
154+
155+
std::vector<pcms::Real> data{5.0, 6.0, 7.0, 8.0};
156+
field.SetDOFHolderDataHost(
157+
pcms::Rank2View<const pcms::Real, pcms::HostMemorySpace>(data.data(), 4,
158+
1));
159+
160+
CheckSerializeDeserialize(field);
161+
}
162+
163+
TEST_CASE("FieldSerializer round-trips a uniform-grid field")
164+
{
165+
pcms::UniformGrid<2> grid;
166+
grid.bot_left = {0.0, 0.0};
167+
grid.edge_length = {10.0, 10.0};
168+
grid.divisions = {3, 3};
169+
auto space = pcms::LagrangeFunctionSpace::FromUniformGrid(
170+
grid, 1, pcms::CoordinateSystem::Cartesian);
171+
auto field = space->CreateFunction<pcms::Real>();
172+
173+
std::vector<pcms::Real> data(16);
174+
for (size_t i = 0; i < data.size(); ++i) {
175+
data[i] = static_cast<pcms::Real>(i * 10);
176+
}
177+
field.SetDOFHolderDataHost(
178+
pcms::Rank2View<const pcms::Real, pcms::HostMemorySpace>(data.data(), 16,
179+
1));
180+
181+
CheckSerializeDeserialize(field);
182+
}
183+
184+
TEST_CASE("FieldSerializer round-trips an order-1 Omega_h field")
185+
{
186+
auto lib = Omega_h::Library{};
187+
auto mesh = pcms::test::BuildUnitSquare(lib, 0);
188+
auto space = pcms::test::MakeP1Space(mesh);
189+
auto field = space->CreateFunction<pcms::Real>();
190+
191+
pcms::test::SetField(
192+
field, OMEGA_H_LAMBDA(pcms::Real x, pcms::Real y) {
193+
return pcms::test::linear_f(x, y);
194+
});
195+
196+
CheckSerializeDeserialize(field);
197+
}
198+
199+
TEST_CASE("FieldSerializer round-trips a multi-component Omega_h field")
200+
{
201+
auto lib = Omega_h::Library{};
202+
auto mesh = pcms::test::BuildUnitSquare(lib, 0);
203+
constexpr int num_components = 3;
204+
auto space = pcms::LagrangeFunctionSpace::FromMesh(
205+
mesh, 1, num_components, pcms::CoordinateSystem::Cartesian, "global",
206+
pcms::LagrangeFunctionSpace::Backend::OmegaH);
207+
auto field = space->CreateFunction<pcms::Real>();
208+
209+
const int num_dof = field.GetLayout().GetNumOwnedDofHolder();
210+
std::vector<pcms::Real> data(static_cast<size_t>(num_dof) * num_components);
211+
for (int i = 0; i < num_dof; ++i) {
212+
for (int c = 0; c < num_components; ++c) {
213+
data[static_cast<size_t>(i) * num_components + c] = i + 0.25 * c;
214+
}
215+
}
216+
field.SetDOFHolderDataHost(
217+
pcms::Rank2View<const pcms::Real, pcms::HostMemorySpace>(
218+
data.data(), num_dof, num_components));
219+
220+
CheckSerializeDeserialize(field);
221+
}
222+
223+
TEST_CASE("FieldSerializer round-trips an order-0 Omega_h field")
224+
{
225+
auto lib = Omega_h::Library{};
226+
auto mesh = pcms::test::BuildUnitSquare(lib, 0);
227+
auto space = pcms::test::MakeP0Space(mesh);
228+
auto field = space->CreateFunction<pcms::Real>();
229+
230+
const int num_dof = mesh.nelems();
231+
std::vector<pcms::Real> data(num_dof);
232+
std::iota(data.begin(), data.end(), pcms::Real{0});
233+
field.SetDOFHolderDataHost(
234+
pcms::Rank2View<const pcms::Real, pcms::HostMemorySpace>(data.data(),
235+
num_dof, 1));
236+
237+
CheckSerializeDeserialize(field);
238+
}
239+
#endif

0 commit comments

Comments
 (0)