From 3e166e6775fdfcc8733d70ade51efbdc4e7c071a Mon Sep 17 00:00:00 2001 From: Robert Chisholm Date: Fri, 15 Dec 2023 11:13:39 +0000 Subject: [PATCH] =?UTF-8?q?BugFix:=20MsgSpatial=20interaction=20radius=20c?= =?UTF-8?q?orrect=20when=20not=20a=20factor=20of=20en=E2=80=A6=20(#1160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BugFix: MsgSpatial interaction radius correct when not a factor of environment width. Closes #1157 * Test that bug has been fixed. * Seatbelts now prevents wrapped spatial iterator if radius is not a factor of environment dims. --- .../MessageSpatial2DDevice.cuh | 11 +- .../MessageSpatial3DDevice.cuh | 15 +- .../runtime/messaging/test_spatial_2d.cu | 119 +++++++++++++++ .../runtime/messaging/test_spatial_3d.cu | 139 ++++++++++++++++++ 4 files changed, 279 insertions(+), 5 deletions(-) diff --git a/include/flamegpu/runtime/messaging/MessageSpatial2D/MessageSpatial2DDevice.cuh b/include/flamegpu/runtime/messaging/MessageSpatial2D/MessageSpatial2DDevice.cuh index f42fff6f6..c4eae6b07 100644 --- a/include/flamegpu/runtime/messaging/MessageSpatial2D/MessageSpatial2DDevice.cuh +++ b/include/flamegpu/runtime/messaging/MessageSpatial2D/MessageSpatial2DDevice.cuh @@ -500,6 +500,13 @@ class MessageSpatial2D::In { // Return iterator at min corner of env, this should be safe return WrapFilter(metadata, metadata->min[0], metadata->min[1]); } + if (fmodf(metadata->max[0] - metadata->min[0], metadata->radius) > 0.00001f || + fmodf(metadata->max[1] - metadata->min[1], metadata->radius) > 0.00001f) { + DTHROW("Spatial messaging radius (%g) is not a factor of environment dimensions (%g, %g)," + " this is unsupported for the wrapped iterator, MessageSpatial2D::In::wrap().\n", metadata->radius, + metadata->max[0] - metadata->min[0], + metadata->max[1] - metadata->min[1]); + } #endif return WrapFilter(metadata, x, y); } @@ -599,8 +606,8 @@ T MessageSpatial2D::In::WrapFilter::Message::getVariable(const char(&variable_na __device__ __forceinline__ MessageSpatial2D::GridPos2D getGridPosition2D(const MessageSpatial2D::MetaData *md, float x, float y) { // Clamp each grid coord to 0<=x(floorf(((x-md->min[0]) / md->environmentWidth[0])*md->gridDim[0])), - static_cast(floorf(((y-md->min[1]) / md->environmentWidth[1])*md->gridDim[1])) + static_cast(floorf((x-md->min[0]) / md->radius)), + static_cast(floorf((y-md->min[1]) / md->radius)) }; MessageSpatial2D::GridPos2D rtn = { gridPos[0] < 0 ? 0 : (gridPos[0] >= static_cast(md->gridDim[0]) ? static_cast(md->gridDim[0]) - 1 : gridPos[0]), diff --git a/include/flamegpu/runtime/messaging/MessageSpatial3D/MessageSpatial3DDevice.cuh b/include/flamegpu/runtime/messaging/MessageSpatial3D/MessageSpatial3DDevice.cuh index e8d35a9db..987f661ef 100644 --- a/include/flamegpu/runtime/messaging/MessageSpatial3D/MessageSpatial3DDevice.cuh +++ b/include/flamegpu/runtime/messaging/MessageSpatial3D/MessageSpatial3DDevice.cuh @@ -539,6 +539,15 @@ class MessageSpatial3D::In { // Return iterator at min corner of env, this should be safe return WrapFilter(metadata, metadata->min[0], metadata->min[1], metadata->min[2]); } + if (fmodf(metadata->max[0] - metadata->min[0], metadata->radius) > 0.00001f || + fmodf(metadata->max[1] - metadata->min[1], metadata->radius) > 0.00001f || + fmodf(metadata->max[2] - metadata->min[2], metadata->radius) > 0.00001f) { + DTHROW("Spatial messaging radius (%g) is not a factor of environment dimensions (%g, %g, %g)," + " this is unsupported for the wrapped iterator, MessageSpatial3D::In::wrap().\n", metadata->radius, + metadata->max[0] - metadata->min[0], + metadata->max[1] - metadata->min[1], + metadata->max[2] - metadata->min[2]); + } #endif return WrapFilter(metadata, x, y, z); } @@ -639,9 +648,9 @@ T MessageSpatial3D::In::WrapFilter::Message::getVariable(const char(&variable_na __device__ __forceinline__ MessageSpatial3D::GridPos3D getGridPosition3D(const MessageSpatial3D::MetaData *md, float x, float y, float z) { // Clamp each grid coord to 0<=x(floorf(((x-md->min[0]) / md->environmentWidth[0])*md->gridDim[0])), - static_cast(floorf(((y-md->min[1]) / md->environmentWidth[1])*md->gridDim[1])), - static_cast(floorf(((z-md->min[2]) / md->environmentWidth[2])*md->gridDim[2])) + static_cast(floorf((x-md->min[0]) / md->radius)), + static_cast(floorf((y-md->min[1]) / md->radius)), + static_cast(floorf((z-md->min[2]) / md->radius)) }; MessageSpatial3D::GridPos3D rtn = { gridPos[0] < 0 ? 0 : (gridPos[0] >= static_cast(md->gridDim[0]) ? static_cast(md->gridDim[0]) - 1 : gridPos[0]), diff --git a/tests/test_cases/runtime/messaging/test_spatial_2d.cu b/tests/test_cases/runtime/messaging/test_spatial_2d.cu index df1d7b81e..cf786184c 100644 --- a/tests/test_cases/runtime/messaging/test_spatial_2d.cu +++ b/tests/test_cases/runtime/messaging/test_spatial_2d.cu @@ -908,8 +908,52 @@ TEST(Spatial2DMessageTest, Wrapped7) { TEST(Spatial2DMessageTest, Wrapped_OutOfBounds) { EXPECT_THROW(wrapped_2d_test(141.0f, -540.0f, 200.0f), exception::DeviceError); } +FLAMEGPU_AGENT_FUNCTION(in_wrapped_EnvDimsNotFactor, MessageSpatial2D, MessageNone) { + const float x1 = FLAMEGPU->getVariable("x"); + const float y1 = FLAMEGPU->getVariable("y"); + for (auto& t : FLAMEGPU->message_in.wrap(x1, y1)) { + // Do nothing, it should throw a device exception + } + return ALIVE; +} +TEST(Spatial2DMessageTest, Wrapped_EnvDimsNotFactor) { + // This tests that bug #1157 is fixed + // When the interaction radius is not a factor of the width + // that agent's near the max env bound all have the full interaction radius + ModelDescription m("model"); + MessageSpatial2D::Description message = m.newMessage("location"); + message.setMin(0, 0); + message.setMax(50.1f, 50.1f); + message.setRadius(10); + message.newVariable("id"); // unused by current test + AgentDescription agent = m.newAgent("agent"); + agent.newVariable("x"); + agent.newVariable("y"); + AgentFunctionDescription fo = agent.newFunction("out", out_mandatory2D); + fo.setMessageOutput(message); + AgentFunctionDescription fi = agent.newFunction("in", in_wrapped_EnvDimsNotFactor); + fi.setMessageInput(message); + LayerDescription lo = m.newLayer(); + lo.addAgentFunction(fo); + LayerDescription li = m.newLayer(); + li.addAgentFunction(fi); + // Set pop in model + CUDASimulation c(m); + // Create an agent in the middle of each edge + AgentVector population(agent, 1); + // Initialise agents + // Vertical pair that can interact + // Top side + AgentVector::Agent i1 = population[0]; + i1.setVariable("x", 25.0f); + i1.setVariable("y", 25.0f); + c.setPopulationData(population); + c.SimulationConfig().steps = 1; + EXPECT_THROW(c.simulate(), exception::DeviceError); +} #else TEST(Spatial2DMessageTest, DISABLED_Wrapped_OutOfBounds) { } +TEST(Spatial2DMessageTest, DISABLED_Wrapped_EnvDimsNotFactor) { } #endif FLAMEGPU_AGENT_FUNCTION(out_mandatory2D_OddStep, MessageNone, MessageSpatial2D) { if (FLAMEGPU->getStepCounter() % 2 == 0) { @@ -964,5 +1008,80 @@ TEST(Spatial2DMessageTest, buffer_not_init) { EXPECT_NO_THROW(c.simulate()); } +FLAMEGPU_AGENT_FUNCTION(in_bounds_not_factor, MessageSpatial2D, MessageNone) { + const float x1 = FLAMEGPU->getVariable("x"); + const float y1 = FLAMEGPU->getVariable("y"); + unsigned int count = 0; + // Count how many messages we received (including our own) + for (const auto& message : FLAMEGPU->message_in(x1, y1)) { + ++count; + } + FLAMEGPU->setVariable("count", count); + return ALIVE; +} +TEST(Spatial2DMessageTest, bounds_not_factor_radius) { + // This tests that bug #1157 is fixed + // When the interaction radius is not a factor of the width + // that agent's near the max env bound all have the full interaction radius + ModelDescription m("model"); + MessageSpatial2D::Description message = m.newMessage("location"); + message.setMin(0, 0); + message.setMax(50.1f, 50.1f); + message.setRadius(10); + // Grid will be 6x6 + // 6th column/row should only be 0.1 wide of the environment + // Bug would incorrectly divide the whole environment by 6 + // So bin widths would instead become 8.35 (down from 10) + message.newVariable("id"); // unused by current test + AgentDescription agent = m.newAgent("agent"); + agent.newVariable("x"); + agent.newVariable("y"); + agent.newVariable("count", 0); + AgentFunctionDescription fo = agent.newFunction("out", out_mandatory2D); + fo.setMessageOutput(message); + AgentFunctionDescription fi = agent.newFunction("in", in_bounds_not_factor); + fi.setMessageInput(message); + LayerDescription lo = m.newLayer(); + lo.addAgentFunction(fo); + LayerDescription li = m.newLayer(); + li.addAgentFunction(fi); + // Set pop in model + CUDASimulation c(m); + // Create an agent in the middle of each edge + AgentVector population(agent, 4); + // Initialise agents + // Vertical pair that can interact + // Top side + AgentVector::Agent i1 = population[0]; + i1.setVariable("x", 10.0f); + i1.setVariable("y", 0.0f); + // Top side inner + AgentVector::Agent i2 = population[1]; + i2.setVariable("x", 10.0f); + i2.setVariable("y", 18.0f); + // Right side + AgentVector::Agent i3 = population[2]; + i3.setVariable("x", 50.1f); + i3.setVariable("y", 40.0f); + // Horizontal pair that can interact + // Right side inner + AgentVector::Agent i4 = population[3]; + i4.setVariable("x", 50.1f - 10.11f); + i4.setVariable("y", 40.0f); + c.setPopulationData(population); + c.SimulationConfig().steps = 1; + EXPECT_NO_THROW(c.simulate()); + // Recover the results and check they match what was expected + c.getPopulationData(population); + // Validate each agent has same result + for (AgentVector::Agent ai : population) { + if (ai.getID() < 3) { + EXPECT_EQ(2u, ai.getVariable("count")); + } else { + EXPECT_EQ(1u, ai.getVariable("count")); + } + } +} + } // namespace test_message_spatial2d } // namespace flamegpu diff --git a/tests/test_cases/runtime/messaging/test_spatial_3d.cu b/tests/test_cases/runtime/messaging/test_spatial_3d.cu index e6c73a1da..64253aedd 100644 --- a/tests/test_cases/runtime/messaging/test_spatial_3d.cu +++ b/tests/test_cases/runtime/messaging/test_spatial_3d.cu @@ -976,8 +976,55 @@ TEST(Spatial3DMessageTest, Wrapped3) { TEST(Spatial3DMessageTest, Wrapped_OutOfBounds) { EXPECT_THROW(wrapped_3d_test(141.0f, -540.0f, 0.0f, 200.0f), exception::DeviceError); } +FLAMEGPU_AGENT_FUNCTION(in_wrapped_EnvDimsNotFactor, MessageSpatial3D, MessageNone) { + const float x1 = FLAMEGPU->getVariable("x"); + const float y1 = FLAMEGPU->getVariable("y"); + const float z1 = FLAMEGPU->getVariable("z"); + for (auto &t : FLAMEGPU->message_in.wrap(x1, y1, z1)) { + // Do nothing, it should throw a device exception + } + return ALIVE; +} +TEST(Spatial3DMessageTest, Wrapped_EnvDimsNotFactor) { + // This tests that bug #1157 is fixed + // When the interaction radius is not a factor of the width + // that agent's near the max env bound all have the full interaction radius + ModelDescription m("model"); + MessageSpatial3D::Description message = m.newMessage("location"); + message.setMin(0, 0, 0); + message.setMax(50.1f, 50.1f, 50.1f); + message.setRadius(10); + message.newVariable("id"); // unused by current test + AgentDescription agent = m.newAgent("agent"); + agent.newVariable("x"); + agent.newVariable("y"); + agent.newVariable("z"); + AgentFunctionDescription fo = agent.newFunction("out", out_mandatory3D); + fo.setMessageOutput(message); + AgentFunctionDescription fi = agent.newFunction("in", in_wrapped_EnvDimsNotFactor); + fi.setMessageInput(message); + LayerDescription lo = m.newLayer(); + lo.addAgentFunction(fo); + LayerDescription li = m.newLayer(); + li.addAgentFunction(fi); + // Set pop in model + CUDASimulation c(m); + // Create an agent in the middle of each edge + AgentVector population(agent, 1); + // Initialise agents + // Vertical pair that can interact + // Top side + AgentVector::Agent i1 = population[0]; + i1.setVariable("x", 25.0f); + i1.setVariable("y", 25.0f); + i1.setVariable("z", 25.0f); + c.setPopulationData(population); + c.SimulationConfig().steps = 1; + EXPECT_THROW(c.simulate(), exception::DeviceError); +} #else TEST(Spatial3DMessageTest, DISABLED_Wrapped_OutOfBounds) { } +TEST(Spatial3DMessageTest, DISABLED_Wrapped_EnvDimsNotFactor) { } #endif FLAMEGPU_AGENT_FUNCTION(out_mandatory3D_OddStep, MessageNone, MessageSpatial3D) { if (FLAMEGPU->getStepCounter() % 2 == 0) { @@ -1034,5 +1081,97 @@ TEST(Spatial3DMessageTest, buffer_not_init) { c.SimulationConfig().steps = 4; EXPECT_NO_THROW(c.simulate()); } + +FLAMEGPU_AGENT_FUNCTION(in_bounds_not_factor, MessageSpatial3D, MessageNone) { + const float x1 = FLAMEGPU->getVariable("x"); + const float y1 = FLAMEGPU->getVariable("y"); + const float z1 = FLAMEGPU->getVariable("z"); + unsigned int count = 0; + // Count how many messages we received (including our own) + for (const auto& message : FLAMEGPU->message_in(x1, y1, z1)) { + ++count; + } + FLAMEGPU->setVariable("count", count); + return ALIVE; +} +TEST(Spatial3DMessageTest, bounds_not_factor_radius) { + // This tests that bug #1157 is fixed + // When the interaction radius is not a factor of the width + // that agent's near the max env bound all have the full interaction radius + ModelDescription m("model"); + MessageSpatial3D::Description message = m.newMessage("location"); + message.setMin(0, 0, 0); + message.setMax(50.1f, 50.1f, 50.1f); + message.setRadius(10); + // Grid will be 6x6 + // 6th column/row should only be 0.1 wide of the environment + // Bug would incorrectly divide the whole environment by 6 + // So bin widths would instead become 8.35 (down from 10) + message.newVariable("id"); // unused by current test + AgentDescription agent = m.newAgent("agent"); + agent.newVariable("x"); + agent.newVariable("y"); + agent.newVariable("z"); + agent.newVariable("count", 0); + AgentFunctionDescription fo = agent.newFunction("out", out_mandatory3D); + fo.setMessageOutput(message); + AgentFunctionDescription fi = agent.newFunction("in", in_bounds_not_factor); + fi.setMessageInput(message); + LayerDescription lo = m.newLayer(); + lo.addAgentFunction(fo); + LayerDescription li = m.newLayer(); + li.addAgentFunction(fi); + // Set pop in model + CUDASimulation c(m); + // Create an agent in the middle of each edge + AgentVector population(agent, 6); + // Initialise agents + // Vertical pair that can interact + // Top side + AgentVector::Agent i1 = population[0]; + i1.setVariable("x", 50.1f / 2); + i1.setVariable("y", 0.0f); + i1.setVariable("z", 50.1f / 2); + // Top side inner + AgentVector::Agent i2 = population[1]; + i2.setVariable("x", 50.1f / 2); + i2.setVariable("y", 18.0f); + i2.setVariable("z", 50.1f / 2); + // Right side + AgentVector::Agent i3 = population[2]; + i3.setVariable("x", 0.0f); + i3.setVariable("y", 50.1f / 2); + i3.setVariable("z", 1.0f / 2); + // Horizontal pair that can interact + // Right side inner + AgentVector::Agent i4 = population[3]; + i4.setVariable("x", 18.0f); + i4.setVariable("y", 50.1f / 2); + i4.setVariable("z", 1.0f / 2); + // Rear side + AgentVector::Agent i5 = population[4]; + i5.setVariable("x", 50.1f / 2); + i5.setVariable("y", 50.0f); + i5.setVariable("z", 50.1f); + // Horizontal pair that can interact + // Rear side inner + AgentVector::Agent i6 = population[5]; + i6.setVariable("x", 50.1f / 2); + i6.setVariable("y", 50.0f); + i6.setVariable("z", 50.1f - 10.11f); + c.setPopulationData(population); + c.SimulationConfig().steps = 1; + EXPECT_NO_THROW(c.simulate()); + // Recover the results and check they match what was expected + c.getPopulationData(population); + // Validate each agent has same result + for (AgentVector::Agent ai : population) { + if (ai.getID() < 5) { + EXPECT_EQ(2u, ai.getVariable("count")); + } else { + EXPECT_EQ(1u, ai.getVariable("count")); + } + } +} } // namespace test_message_spatial3d } // namespace flamegpu