Files
mfem/tests/unit/general/test_reduction.cpp
T

399 lines
10 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include <algorithm>
#include <limits>
#include "mfem.hpp"
#include "unit_tests.hpp"
// must be included after mfem.hpp
#include "general/reducers.hpp"
using namespace mfem;
TEST_CASE("Reduce Sum", "[Reduction],[GPU]")
{
Array<int> workspace;
Array<int> a(1000);
a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i;
}
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
int res = 0;
mfem::reduce(
a.Size(), res, [=] MFEM_HOST_DEVICE(int i, int &r) { r += dptr[i]; },
SumReducer<int> {}, use_dev, workspace);
// correct for even-length summations
int expected = (AsConst(a)[0] + AsConst(a)[a.Size() - 1]) * a.Size() / 2;
CAPTURE(use_dev);
REQUIRE(res == expected);
}
}
TEST_CASE("Reduce Mult", "[Reduction],[GPU]")
{
Array<long long> workspace;
Array<long long> a(64);
a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i % 3 + 1;
}
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
SECTION("{ Zero Mult }")
{
auto dptr = a.Read(use_dev);
// 0 * anything == 0
long long res = 0;
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, long long &r) { r *= dptr[i]; },
MultReducer<long long> {}, use_dev, workspace);
long long expected = 0;
CAPTURE(use_dev);
REQUIRE(res == expected);
}
SECTION("{ One Mult }")
{
auto dptr = a.Read(use_dev);
long long res = 1;
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, long long &r) { r *= dptr[i]; },
MultReducer<long long> {}, use_dev, workspace);
long long expected = 21936950640377856;
CAPTURE(use_dev);
REQUIRE(res == expected);
}
}
}
TEST_CASE("Reduce BAnd", "[Reduction],[GPU]")
{
Array<unsigned> workspace;
Array<unsigned> a(10);
SECTION("{ Bit unset }")
{
// pick numbers which sets all bits except for one
a.HostReadWrite();
constexpr unsigned unset_bit = 17;
a[0] = ~(1u << unset_bit);
for (int i = 1; i < a.Size(); ++i)
{
a[i] = (~1u) & a[0];
}
a[0] = ~1u;
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
unsigned res = ~1u;
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, unsigned &r) { r &= dptr[i]; },
BAndReducer<unsigned> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res == ((~1u) & ~(1u << unset_bit)));
REQUIRE((res & (1u << unset_bit)) == 0);
}
}
SECTION("{ Bit set }")
{
// ensure one bit is set
a.HostReadWrite();
constexpr unsigned set_bit = 17;
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i | (1u << set_bit);
}
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
unsigned res = ~1u;
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, unsigned &r) { r &= dptr[i]; },
BAndReducer<unsigned> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res == (1u << set_bit));
}
}
}
TEST_CASE("Reduce BOr", "[Reduction],[GPU]")
{
Array<unsigned> workspace;
Array<unsigned> a(0x210);
a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i;
}
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
unsigned res = 0u;
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, unsigned &r) { r |= dptr[i]; },
BOrReducer<unsigned> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res == 0x3ffu);
}
}
TEST_CASE("Reduce Min", "[Reduction],[GPU]")
{
Array<int> workspace;
Array<int> a(1000);
auto hptr = a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i - 10;
}
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(hptr, hptr + a.Size(), gen);
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
int res = std::numeric_limits<int>::max();
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, int &r)
{
if (dptr[i] < r)
{
r = dptr[i];
}
},
MinReducer<int> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res == -10);
}
}
TEST_CASE("Reduce Max", "[Reduction],[GPU]")
{
Array<int> workspace;
Array<int> a(1000);
auto hptr = a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i - 10;
}
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(hptr, hptr + a.Size(), gen);
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
int res = std::numeric_limits<int>::min();
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, int &r)
{
if (dptr[i] > r)
{
r = dptr[i];
}
},
MaxReducer<int> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res == 999 - 10);
}
}
TEST_CASE("Reduce MinMax", "[Reduction],[GPU]")
{
Array<DevicePair<int, int>> workspace;
Array<int> a(1000);
auto hptr = a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i - 10;
}
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(hptr, hptr + a.Size(), gen);
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
DevicePair<int, int> res = {std::numeric_limits<int>::max(),
std::numeric_limits<int>::min()
};
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, DevicePair<int, int> &r)
{
if (dptr[i] < r.first)
{
r.first = dptr[i];
}
if (dptr[i] > r.second)
{
r.second = dptr[i];
}
},
MinMaxReducer<int> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res.first == -10);
REQUIRE(res.second == a.Size() - 11);
}
}
TEST_CASE("Reduce ArgMin", "[Reduction],[GPU]")
{
Array<DevicePair<double, int>> workspace;
Array<double> a(1000);
auto hptr = a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i - 10;
}
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(hptr, hptr + a.Size(), gen);
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
DevicePair<double, int> res = {std::numeric_limits<double>::infinity(), -1};
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, DevicePair<double, int> &r)
{
if (dptr[i] < r.first)
{
r.first = dptr[i];
r.second = i;
}
},
ArgMinReducer<double, int> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res.first == -10);
REQUIRE(res.second >= 0);
REQUIRE(res.second < a.Size());
REQUIRE(hptr[res.second] == res.first);
}
}
TEST_CASE("Reduce ArgMax", "[Reduction],[GPU]")
{
Array<DevicePair<double, int>> workspace;
Array<double> a(1000);
auto hptr = a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i - 10;
}
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(hptr, hptr + a.Size(), gen);
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
DevicePair<double, int> res = {-std::numeric_limits<double>::infinity(),
-1
};
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, DevicePair<double, int> &r)
{
if (dptr[i] > r.first)
{
r.first = dptr[i];
r.second = i;
}
},
ArgMaxReducer<double, int> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res.first == a.Size() - 11);
REQUIRE(res.second >= 0);
REQUIRE(res.second < a.Size());
REQUIRE(AsConst(a)[res.second] == res.first);
}
}
TEST_CASE("Reduce ArgMinMax", "[Reduction],[GPU]")
{
Array<MinMaxLocScalar<double, int>> workspace;
Array<double> a(1000);
auto hptr = a.HostReadWrite();
for (int i = 0; i < a.Size(); ++i)
{
a[i] = i - 10;
}
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(hptr, hptr + a.Size(), gen);
for (int use_dev = 0; use_dev < 2; ++use_dev)
{
auto dptr = a.Read(use_dev);
MinMaxLocScalar<double, int> res =
{
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(), -1, -1
};
mfem::reduce(
a.Size(), res,
[=] MFEM_HOST_DEVICE(int i, MinMaxLocScalar<double, int> &r)
{
if (dptr[i] < r.min_val)
{
r.min_val = dptr[i];
r.min_loc = i;
}
if (dptr[i] > r.max_val)
{
r.max_val = dptr[i];
r.max_loc = i;
}
},
ArgMinMaxReducer<double, int> {}, use_dev, workspace);
CAPTURE(use_dev);
REQUIRE(res.min_val == -10);
REQUIRE(res.min_loc >= 0);
REQUIRE(res.min_loc < a.Size());
REQUIRE(AsConst(a)[res.min_loc] == res.min_val);
REQUIRE(res.max_val == a.Size() - 11);
REQUIRE(res.max_loc >= 0);
REQUIRE(res.max_loc < a.Size());
REQUIRE(AsConst(a)[res.max_loc] == res.max_val);
}
}