Files
MapLibreParticleLab/test/particle/particle_gpu_math.test.cpp

89 lines
4.9 KiB
C++

#include <mln/particle/field/field_data_adapter.hpp>
#include <mln/particle/field/particle_gpu_math.hpp>
#include <gtest/gtest.h>
#include <cmath>
#include <limits>
using namespace mln;
TEST(ParticleGpuField, EightBitSignedZeroAndCancellation) {
constexpr float scale = 1.0f;
const auto zero = particle::encodeSignedVelocity8(0.0f, scale, true);
const auto east = particle::encodeSignedVelocity8(0.25f, scale, true);
const auto west = particle::encodeSignedVelocity8(-0.25f, scale, true);
EXPECT_EQ(zero, 128);
EXPECT_FLOAT_EQ(particle::decodeSignedVelocity8(zero, scale), 0.0f);
EXPECT_EQ(static_cast<int>(east) + static_cast<int>(west), 256);
EXPECT_FLOAT_EQ((particle::decodeSignedVelocity8(east, scale) +
particle::decodeSignedVelocity8(west, scale)) * 0.5f, 0.0f);
EXPECT_LE(std::abs(particle::decodeSignedVelocity8(east, scale) - 0.25f), scale / 254.0f);
EXPECT_EQ(particle::encodeSignedVelocity8(std::numeric_limits<float>::quiet_NaN(), scale, false), 128);
EXPECT_EQ(particle::encodeSignedVelocity8(1.0f, scale, false), 128);
// RGBA8 cannot preserve values below half a quantum; this explicit bound
// prevents a silent claim of arbitrary micro-wind accuracy.
EXPECT_EQ(particle::encodeSignedVelocity8(0.001f, scale, true), 128);
}
TEST(ParticleGpuField, EndpointIdentityCoversBothPlanesValidityGridEncodingAndRevision) {
particle::ParticleFieldSnapshot a;
a.fieldID = "wind"; a.fieldType = "qbin-v4"; a.displayDomain = "GLOBAL";
a.generation = 7; a.dataVersion = 3; a.validTime = 1000;
a.width = 1; a.height = 1; a.vector = {1.0f, 2.0f}; a.valid = {1};
a.vectorSemantics = particle::VectorSemantics::Velocity;
a.gpuEncodingIdentity = "float32-le";
auto b = a;
EXPECT_TRUE(particle::sameFieldGpuEndpoint(a, b));
b.vector[1] = 3.0f; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; b.valid[0] = 0; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; b.west = 1.0; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; b.gpuEncodingIdentity = "int16-scaled"; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; ++b.dataVersion; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; ++b.validTime; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; ++b.generation; EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
b = a; b.vectorSemantics = particle::VectorSemantics::Direction;
EXPECT_FALSE(particle::sameFieldGpuEndpoint(a, b));
}
TEST(ParticleGpuField, BoundaryReusesOldT1AndMixOnlyRequiresNoEndpointUpload) {
particle::ParticleFieldSnapshot oldT0;
oldT0.fieldID = "wind"; oldT0.generation = 1; oldT0.validTime = 1000;
oldT0.width = 1; oldT0.height = 1; oldT0.vector = {1.0f, 2.0f}; oldT0.valid = {1};
auto oldT1 = oldT0; oldT1.validTime = 2000;
auto newT1 = oldT0; newT1.validTime = 3000;
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, oldT0), particle::GpuEndpointSource::OldT0);
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, oldT1), particle::GpuEndpointSource::OldT1);
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, newT1), particle::GpuEndpointSource::Upload);
// Changing MIX alone leaves the two immutable endpoint decisions intact.
const float mixA = 0.25f, mixB = 0.75f;
EXPECT_NE(mixA, mixB);
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, oldT0), particle::GpuEndpointSource::OldT0);
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, oldT1), particle::GpuEndpointSource::OldT1);
auto onlyV = oldT1; onlyV.vector[1] = -2.0f;
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, onlyV), particle::GpuEndpointSource::Upload);
auto onlyValidity = oldT1; onlyValidity.valid[0] = 0;
EXPECT_EQ(particle::gpuEndpointSource(oldT0, oldT1, onlyValidity), particle::GpuEndpointSource::Upload);
}
TEST(ParticleGpuField, MercatorVelocityUsesSameLatitudeMetricForFourDirections) {
constexpr double circumference = 40075016.68557849;
for (const double latitude : {0.0, 45.0, 60.0}) {
const double expected = 10.0 / (circumference * std::cos(latitude * 3.14159265358979323846 / 180.0));
const auto east = particle::mercatorVelocityDelta(10, 0, latitude, 1);
const auto west = particle::mercatorVelocityDelta(-10, 0, latitude, 1);
const auto north = particle::mercatorVelocityDelta(0, 10, latitude, 1);
const auto south = particle::mercatorVelocityDelta(0, -10, latitude, 1);
EXPECT_NEAR(east[0], expected, 1e-15);
EXPECT_NEAR(west[0], -expected, 1e-15);
EXPECT_NEAR(north[1], -expected, 1e-15);
EXPECT_NEAR(south[1], expected, 1e-15);
EXPECT_DOUBLE_EQ(east[1], 0);
EXPECT_DOUBLE_EQ(north[0], 0);
}
const auto polar = particle::mercatorVelocityDelta(10, 10, 90, 2);
EXPECT_TRUE(std::isfinite(polar[0]));
EXPECT_TRUE(std::isfinite(polar[1]));
EXPECT_DOUBLE_EQ(polar[0], -polar[1]);
}