diff --git a/CMakeLists.txt b/CMakeLists.txt index 2451f2740..b13c9eb44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ if(NOT NO_EXAMPLES) option(BUILD_EXAMPLE_HOST_FUNCTIONS "Enable building examples/host_functions" OFF) option(BUILD_EXAMPLE_ENSEMBLE "Enable building examples/ensemble" OFF) option(BUILD_EXAMPLE_SUGARSCAPE "Enable building examples/sugarscape" OFF) + option(BUILD_EXAMPLE_DIFFUSION "Enable building examples/diffusion" OFF) endif() option(BUILD_SWIG_PYTHON "Enable python bindings via SWIG" OFF) @@ -172,6 +173,9 @@ endif() if(BUILD_ALL_EXAMPLES OR BUILD_EXAMPLE_SUGARSCAPE) add_subdirectory(examples/sugarscape) endif() +if(BUILD_ALL_EXAMPLES OR BUILD_EXAMPLE_DIFFUSION) + add_subdirectory(examples/diffusion) +endif() # Add the tests directory (if required) if(BUILD_TESTS OR BUILD_TESTS_DEV) # g++ 7 is required for c++ tests to build. diff --git a/examples/diffusion/CMakeLists.txt b/examples/diffusion/CMakeLists.txt new file mode 100644 index 000000000..bf8151611 --- /dev/null +++ b/examples/diffusion/CMakeLists.txt @@ -0,0 +1,39 @@ +# Set the minimum cmake version to that which supports cuda natively. +cmake_minimum_required(VERSION VERSION 3.12 FATAL_ERROR) + +# Name the project and set languages +project(diffusion CUDA CXX) + +# Set the location of the ROOT flame gpu project relative to this CMakeList.txt +get_filename_component(FLAMEGPU_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. REALPATH) + +# Include common rules. +include(${FLAMEGPU_ROOT}/cmake/common.cmake) + +# Define output location of binary files +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + # If top level project + SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) +else() + # If called via add_subdirectory() + SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../../bin/${CMAKE_BUILD_TYPE}/) +endif() + +# Prepare list of source files +# Can't do this automatically, as CMake wouldn't know when to regen (as CMakeLists.txt would be unchanged) +SET(ALL_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cu +) + +# Option to enable/disable building the static library +option(VISUALISATION "Enable visualisation support" OFF) + +# Add the executable and set required flags for the target +add_flamegpu_executable("${PROJECT_NAME}" "${ALL_SRC}" "${FLAMEGPU_ROOT}" "${PROJECT_BINARY_DIR}" TRUE) + +# Also set as startup project (if top level project) +set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT "${PROJECT_NAME}") + +# Set the default (visual studio) debug working directory and args +set_target_properties("${PROJECT_NAME}" PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VS_DEBUGGER_COMMAND_ARGUMENTS "-s 0") \ No newline at end of file diff --git a/examples/diffusion/src/main.cu b/examples/diffusion/src/main.cu new file mode 100644 index 000000000..3c2d291f0 --- /dev/null +++ b/examples/diffusion/src/main.cu @@ -0,0 +1,174 @@ +#include "flamegpu/flamegpu.h" + +/** + * This example reimplements the heat equation + * Based on: https://enccs.github.io/OpenACC-CUDA-beginners/2.02_cuda-heat-equation/ + */ + +FLAMEGPU_AGENT_FUNCTION(output, flamegpu::MessageNone, flamegpu::MessageArray2D) { + FLAMEGPU->message_out.setVariable("value", FLAMEGPU->getVariable("value")); + FLAMEGPU->message_out.setIndex(FLAMEGPU->getVariable("pos", 0), FLAMEGPU->getVariable("pos", 1)); + return flamegpu::ALIVE; +} +FLAMEGPU_AGENT_FUNCTION(update, flamegpu::MessageArray2D, flamegpu::MessageNone) { + const unsigned int i = FLAMEGPU->getVariable("pos", 0); + const unsigned int j = FLAMEGPU->getVariable("pos", 1); + + const float dx2 = FLAMEGPU->environment.getProperty("dx2"); + const float dy2 = FLAMEGPU->environment.getProperty("dy2"); + const float old_value = FLAMEGPU->getVariable("value"); + + const float left = FLAMEGPU->message_in.at(i == 0 ? FLAMEGPU->message_in.getDimX() - 1 : i - 1, j).getVariable("value"); + const float up = FLAMEGPU->message_in.at(i, j == 0 ? FLAMEGPU->message_in.getDimY() - 1 : j - 1).getVariable("value"); + const float right = FLAMEGPU->message_in.at(i + 1 >= FLAMEGPU->message_in.getDimX() ? 0 : i + 1, j).getVariable("value"); + const float down = FLAMEGPU->message_in.at(i, j + 1 >= FLAMEGPU->message_in.getDimY() ? 0 : j + 1).getVariable("value"); + + // Explicit scheme + float new_value = (left - 2.0 * old_value + right) / dx2 + (up - 2.0 * old_value + down) / dy2; + + const float a = FLAMEGPU->environment.getProperty("a"); + const float dt = FLAMEGPU->environment.getProperty("dt"); + + new_value *= a * dt; + new_value += old_value; + + FLAMEGPU->setVariable("value", new_value); + return flamegpu::ALIVE; +} +FLAMEGPU_EXIT_CONDITION(stable_temperature) { + // Exit when standard deviation of temperature across agents goes below 0.006 + // (At this point it looks kind of uniform to the eye) + const double sd = FLAMEGPU->agent("cell").meanStandardDeviation("value").second; + return sd < 0.006 ? flamegpu::EXIT : flamegpu::CONTINUE; +} +int main(int argc, const char ** argv) { + const unsigned int SQRT_AGENT_COUNT = 200; + const unsigned int AGENT_COUNT = SQRT_AGENT_COUNT * SQRT_AGENT_COUNT; + NVTX_RANGE("main"); + NVTX_PUSH("ModelDescription"); + flamegpu::ModelDescription model("Heat Equation"); + + { // Message + flamegpu::MessageArray2D::Description &message = model.newMessage("temperature"); + message.newVariable("value"); + message.setDimensions(SQRT_AGENT_COUNT, SQRT_AGENT_COUNT); + } + { // Cell agent + flamegpu::AgentDescription &agent = model.newAgent("cell"); + agent.newVariable("pos"); + agent.newVariable("value"); +#ifdef VISUALISATION + // Redundant separate floating point position vars for vis + agent.newVariable("x"); + agent.newVariable("y"); +#endif + agent.newFunction("output", output).setMessageOutput("temperature"); + agent.newFunction("update", update).setMessageInput("temperature"); + } + + /** + * GLOBALS + */ + { + flamegpu::EnvironmentDescription &env = model.Environment(); + // Diffusion constant + const float a = 0.5f; + env.newProperty("a", a); + // Grid spacing + const float dx = 0.01f; + env.newProperty("dx", dx); + const float dy = 0.01f; + env.newProperty("dy", dy); + // Grid spacing squared (pre-computed) + const float dx2 = powf(dx, 2); + env.newProperty("dx2", dx2); + const float dy2 = powf(dy, 2); + env.newProperty("dy2", dy2); + // Largest stable timestep + const float dt = dx2 * dy2 / (2.0f * a * (dx2 + dy2)); + env.newProperty("dt", dt); + } + + /** + * Control flow + */ + { // Layer #1 + flamegpu::LayerDescription &layer = model.newLayer(); + layer.addAgentFunction(output); + } + { // Layer #2 + flamegpu::LayerDescription &layer = model.newLayer(); + layer.addAgentFunction(update); + } + model.addExitCondition(stable_temperature); + NVTX_POP(); + + /** + * Create Model Runner + */ + NVTX_PUSH("CUDASimulation creation"); + flamegpu::CUDASimulation cudaSimulation(model, argc, argv); + NVTX_POP(); + + /** + * Initialisation + */ + if (cudaSimulation.getSimulationConfig().input_file.empty()) { + // Currently population has not been init, so generate an agent population on the fly + std::default_random_engine rng; + std::uniform_real_distribution dist(0.0f, 1.0f); + flamegpu::AgentVector init_pop(model.Agent("cell")); + init_pop.reserve(AGENT_COUNT); + for (unsigned int x = 0; x < SQRT_AGENT_COUNT; ++x) { + for (unsigned int y = 0; y < SQRT_AGENT_COUNT; ++y) { + init_pop.push_back(); + flamegpu::AgentVector::Agent instance = init_pop.back(); + instance.setVariable("pos", { x, y }); + instance.setVariable("value", dist(rng)); +#ifdef VISUALISATION +// Redundant separate floating point position vars for vis + instance.setVariable("x", static_cast(x)); + instance.setVariable("y", static_cast(y)); +#endif + } + } + cudaSimulation.setPopulationData(init_pop); + } + + /** + * Create visualisation + * @note FLAMEGPU2 doesn't currently have proper support for discrete/2d visualisations + */ +#ifdef VISUALISATION + flamegpu::visualiser::ModelVis & visualisation = cudaSimulation.getVisualisation(); + { + visualisation.setBeginPaused(true); + visualisation.setSimulationSpeed(5); + visualisation.setInitialCameraLocation(SQRT_AGENT_COUNT / 2.0f, SQRT_AGENT_COUNT / 2.0f, 450.0f); + visualisation.setInitialCameraTarget(SQRT_AGENT_COUNT / 2.0f, SQRT_AGENT_COUNT / 2.0f, 0.0f); + visualisation.setCameraSpeed(0.001f * SQRT_AGENT_COUNT); + visualisation.setViewClips(0.01f, 2500); + visualisation.setClearColor(0.0f, 0.0f, 0.0f); + auto& agt = visualisation.addAgent("cell"); + // Position vars are named x, y, z; so they are used by default + agt.setModel(flamegpu::visualiser::Stock::Models::CUBE); // 5 unwanted faces! + agt.setModelScale(1.0f); + // Assume that midpoint will be 0.5f, and any values outside this range will be lost in early steps + agt.setColor(flamegpu::visualiser::ViridisInterpolation("value", 0.35f, 0.65f)); + } + visualisation.activate(); +#endif + + /** + * Execution + */ + cudaSimulation.simulate(); + + /** + * Export Pop + */ +#ifdef VISUALISATION + visualisation.join(); +#endif + return 0; +}