From 006c6610810c4757175bfbac1044b887a2d2d2fe Mon Sep 17 00:00:00 2001 From: Peter Heywood Date: Mon, 14 Oct 2024 17:33:39 +0100 Subject: [PATCH] Add an initial FLAME GPU 2 model (Circle) with an empty test suite --- .gitignore | 512 +++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 46 ++++ cmake/GoogleTest.cmake | 41 ++++ cmake/flamegpu2.cmake | 75 ++++++ cpplint.cfg | 3 + src/CMakeLists.txt | 63 +++++ src/exatepp_abm.cu | 183 +++++++++++++++ src/exatepp_abm.h | 10 + src/main.cu | 9 + tests/CMakeLists.txt | 90 ++++++++ tests/src/main.cu | 26 +++ tests/src/temp.cu | 8 + 12 files changed, 1066 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/GoogleTest.cmake create mode 100644 cmake/flamegpu2.cmake create mode 100644 cpplint.cfg create mode 100644 src/CMakeLists.txt create mode 100644 src/exatepp_abm.cu create mode 100644 src/exatepp_abm.h create mode 100644 src/main.cu create mode 100644 tests/CMakeLists.txt create mode 100644 tests/src/main.cu create mode 100644 tests/src/temp.cu diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17a35ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,512 @@ +# Model output files +*.xml +*.json +*.bin + +# Visualisation screenshots +*.png + +# GoogleTest directories (handled by CMake) +googletest-build/ +googletest-download/ +googletest-src/ + +# Doxygen / CMake + Doxygen generated files +DoxyGen*/ +CMakeDoxyfile.in +CMakeDoxygenDefaults.cmake +Doxyfile.docs + +# Visual Studio (these are gen by CMake, don't want tracked) +*.vcxproj +*.vcxproj.filters +*.sln + +# Directories +docs/ +bin/ +obj/ +build*/ + +# Editor configuration files/directories +.vscode +*.cbp +*.layout +.cbp +.layout + +# CUDA +*.i +*.ii +*.gpu +*.ptx +*.cubin +*.fatbin +*.tlog +*.cache + +# Valgrind log files (for syntax-highlight enabled editors such as vscode with plugin) +*.valgrind + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db +*.opendb + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# CMake +# From: https://github.com/github/gitignore/blob/master/CMake.gitignore +# Commit 3784768 +# ========================= +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# ========================= +# Visual Studio +# From: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +# Commit 3784768 +# ========================= + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Python / post processing stuff +venv**/ +.venv**/ +*.png +*.csv + +# vscode configuration files (debugging etc) +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d21cc62 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +# Minimum CMake version 3.18 for CUDA --std=c++17 +cmake_minimum_required(VERSION 3.18...3.25 FATAL_ERROR) + +# Optionally set the version of flamegpu which should be used, ideally a tag (i.e. `v2.0.0-rc`) or branch name, or potentially a commit hash. +set(FLAMEGPU_VERSION "v2.0.0-rc.1" CACHE STRING "FLAMEGPU/FLAMEGPU2 git branch or tag to use") +# If the above version is a hash instead, also set FLAMEGPU_VERSION_ALLOW_HASH to ON +# set(FLAMEGPU_VERSION_ALLOW_HASH "ON") + +# Manually specify the FLAMEGPU_VISUALISATION option to provide it prior to original configuration and allow the default to be overridden in the downstream project +option(FLAMEGPU_VISUALISATION "Enable FLAMEGPU visualisation support" OFF) + +# Our core dependency is FLAMEGPU2 lib, first lets find it +include(${CMAKE_CURRENT_LIST_DIR}/cmake/flamegpu2.cmake) + +# Handle CMAKE_CUDA_ARCHITECTURES gracefully, passing the project name for code-injection +include(${FLAMEGPU_ROOT}/cmake/CUDAArchitectures.cmake) +flamegpu_init_cuda_architectures(PROJECT exatepp_abm) + +# Name the project and set languages, this must be done after flamegpu_init_cuda_architectures +project(exatepp_abm CUDA CXX) + +# Detect if we are the top level CMake file or not (for CMake < 3.21) +if(CMAKE_VERSION VERSION_LESS "3.21" AND CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + set(PROJECT_IS_TOP_LEVEL ON) +endif() + +# Option to enable building / enabling tests +option(BUILD_TESTING "Build the testing tree." OFF) +# Option to enable google test test discovery +cmake_dependent_option(ENABLE_GTEST_DISCOVER "Enable GTEST_DISCOVER for more detailed ctest output without -VV. This dramatically increases test suite runtime to CUDA context initialisation." OFF "BUILD_TESTING" OFF) + +# Include common rules from the FLAMEGPU/FLAMEGPU2 repositories CMake +include(${FLAMEGPU_ROOT}/cmake/common.cmake) + +# Define output location of binary files +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) + +# Add the child CMakeLists.txt which defines the static library and binary. +add_subdirectory(src "${PROJECT_BINARY_DIR}/exatepp_abm") + +# --- +# If testing is enabled and we are the top level cmake, add the test directory. +if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL) # OR FRE_BUILD_TESTING @todo? + enable_testing() + add_subdirectory(tests) +endif() \ No newline at end of file diff --git a/cmake/GoogleTest.cmake b/cmake/GoogleTest.cmake new file mode 100644 index 0000000..008d0b4 --- /dev/null +++ b/cmake/GoogleTest.cmake @@ -0,0 +1,41 @@ +############## +# GOOGLETEST # +############## + +include(FetchContent) +cmake_policy(SET CMP0079 NEW) + +# Googltest newer than 389cb68b87193358358ae87cc56d257fd0d80189 (included in release-1.11.0) or newer is required for CMake >= 3.19 +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.11.0 +) + +FetchContent_GetProperties(googletest) +if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + # Suppress installation target, as this makes a warning + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + mark_as_advanced(FORCE INSTALL_GTEST) + mark_as_advanced(FORCE BUILD_GMOCK) + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + # Set the folder to use + if (DEFINED flamegpu_set_target_folder) + flamegpu_set_target_folder(gtest "Tests/Dependencies") + endif() + # Disable compiler warnings + if(TARGET gtest) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(gtest PRIVATE "$<$:/W0>") + else() + target_compile_options(gtest PRIVATE "$<$:-w>") + endif() + # Always tell nvcc to disable warnings + target_compile_options(gtest PRIVATE "$<$:-w>") + endif() +endif() + diff --git a/cmake/flamegpu2.cmake b/cmake/flamegpu2.cmake new file mode 100644 index 0000000..cb62e9a --- /dev/null +++ b/cmake/flamegpu2.cmake @@ -0,0 +1,75 @@ +include(FetchContent) +cmake_policy(SET CMP0079 NEW) + +# If overridden by the user, attempt to use that +if (FLAMEGPU_ROOT) + # Look for the main flamegpu.h header to get the abs path, but only look relative to the hints/paths, no cmake defaults. Do not cache the result. + set(FLAMEGPU_INCLUDE_HEADER_FILE include/flamegpu/flamegpu.h) + find_path(FLAMEGPU_ROOT_ABS + NAMES + ${FLAMEGPU_INCLUDE_HEADER_FILE} + HINTS + ${FLAMEGPU_ROOT} + PATHS + ${FLAMEGPU_ROOT} + NO_DEFAULT_PATH + NO_CACHE + ) + # If found, use the local flamegpu, otherwise error. + if(FLAMEGPU_ROOT_ABS) + # If the correct flamegpu root was found, output a successful status message + message(STATUS "Found FLAMEGPU_ROOT: ${FLAMEGPU_ROOT_ABS} (${FLAMEGPU_ROOT})") + # update the value to the non abs version, in local and parent scope. + set(FLAMEGPU_ROOT "${FLAMEGPU_ROOT_ABS}") + # And set up the flamegpu build + add_subdirectory(${FLAMEGPU_ROOT_ABS} ${CMAKE_CURRENT_BINARY_DIR}/_deps/flamegpu2-build) + else() + # Send a fatal error if the flamegpu root passed is invalid. + message(FATAL_ERROR "Invalid FLAMEGPU_ROOT '${FLAMEGPU_ROOT}'.\nFLAMEGPU_ROOT must be a valid directory containing '${FLAMEGPU_INCLUDE_HEADER_FILE}'") + endif() +else() + # If a FLAMEGPU_VERSION has not been defined, set it to the default option. + if(NOT DEFINED FLAMEGPU_VERSION OR FLAMEGPU_VERSION STREQUAL "") + set(FLAMEGPU_VERSION "master" CACHE STRING "Git branch or tag to use") + endif() + + # If the FLAME GPU version is a hash not a branch or tag, + if(NOT DEFINED FLAMEGPU_VERSION_ALLOW_HASH OR FLAMEGPU_VERSION_ALLOW_HASH STREQUAL "") + set(FLAMEGPU_VERSION_ALLOW_HASH "OFF" CACHE BOOL "Boolean to enable use of a git hash in FLAMEGPU_VERSION. This will slow down CMake configuration.") + endif() + + # Allow users to switch to forks with relative ease. + if(NOT DEFINED FLAMEGPU_REPOSITORY OR FLAMEGPU_REPOSITORY STREQUAL "") + set(FLAMEGPU_REPOSITORY "https://github.com/FLAMEGPU/FLAMEGPU2.git" CACHE STRING "Remote Git Repository for FLAME GPU 2+") + endif() + + # CMake does not support simple negation, so map to a differnt non cache variable to invert to the correct truthyness for GIT_SHALLOW + set(USE_GIT_SHALLOW "ON") + if(FLAMEGPU_VERSION_ALLOW_HASH) + set(USE_GIT_SHALLOW OFF) + endif() + # Declare the fetch content target usign the above optional variables + FetchContent_Declare( + flamegpu2 + GIT_REPOSITORY ${FLAMEGPU_REPOSITORY} + GIT_TAG ${FLAMEGPU_VERSION} + GIT_SHALLOW ${USE_GIT_SHALLOW} + GIT_PROGRESS ON + # UPDATE_DISCONNECTED ON + ) + unset(USE_GIT_SHALLOW) + + # Fetch and populate the content if required. + FetchContent_GetProperties(flamegpu2) + if(NOT flamegpu2_POPULATED) + FetchContent_Populate(flamegpu2) + mark_as_advanced(FORCE BUILD_TESTS) + # Add the subdirectory + add_subdirectory(${flamegpu2_SOURCE_DIR} ${flamegpu2_BINARY_DIR}) + # Add flamegpu2' expected location to the prefix path. + set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${flamegpu2_SOURCE_DIR}/cmake") + endif() + + message(STATUS "Found FLAMEGPU2 ${flamegpu2_SOURCE_DIR}") + set(FLAMEGPU_ROOT ${flamegpu2_SOURCE_DIR}) +endif() \ No newline at end of file diff --git a/cpplint.cfg b/cpplint.cfg new file mode 100644 index 0000000..ea29464 --- /dev/null +++ b/cpplint.cfg @@ -0,0 +1,3 @@ +set noparent +linelength=320 +filter=-legal/copyright,-readability/todo,-runtime/references,-build/c++11 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..af9cd5f --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,63 @@ +# Minimum CMake version 3.18 for CUDA --std=c++17 +cmake_minimum_required(VERSION 3.18...3.25 FATAL_ERROR) + +# This CMakeLists should not be built on its own. +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR} cannot be the top CMakeLists.txt. Please use the parent directory") +endif() + +# We already have a project here, no need to reset one, but set variables for the target names +set(LIBRARY_NAME "${PROJECT_NAME}_lib") +set(BINARY_NAME "${PROJECT_NAME}") + +# -------------------------------------------- +# Define the static library target +# -------------------------------------------- +SET(LIBRARY_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/exatepp_abm.h + ${CMAKE_CURRENT_SOURCE_DIR}/exatepp_abm.cu +) + +flamegpu_add_library("${LIBRARY_NAME}" "${LIBRARY_SRC}" "${FLAMEGPU_ROOT}" "${PROJECT_BINARY_DIR}" FALSE) + + +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # Prevent windows.h definition of MIN and MAX macros on windows + target_compile_definitions(${LIBRARY_NAME} PRIVATE NOMINMAX) + # Allow use of M_PI anywhere + target_compile_definitions(${LIBRARY_NAME} PRIVATE _USE_MATH_DEFINES) +endif() + +# Add src directory to include path +target_include_directories("${LIBRARY_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") + +# -------------------------------------------- +# Define the binary target +# -------------------------------------------- +set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/main.cu) +flamegpu_add_executable("${BINARY_NAME}" "${SRC}" "${FLAMEGPU_ROOT}" "${PROJECT_BINARY_DIR}" FALSE) + + +# Link against the static library target. +target_link_libraries("${BINARY_NAME}" PRIVATE "${LIBRARY_NAME}") + + +# -------------------------------------------- +# Define shorthand lint target +# -------------------------------------------- + +# Create a target named lint which calls the generated lint_exatepp_abm and lint_exatepp_abm_lib targets +# Create an alias target `lint` for `lint_fujitsu_iow` out of laziness +if(TARGET lint_${LIBRARY_NAME}) + if(NOT TARGET lint) + add_custom_target(lint) + set_target_properties(lint PROPERTIES EXCLUDE_FROM_ALL TRUE) + flamegpu_set_target_folder(lint "Lint") + endif() + add_dependencies(lint lint_${LIBRARY_NAME}) +endif() + +# Make linting the binary also lint the static library, to keep prior behaviour +if(TARGET lint_${BINARY_NAME} AND TARGET lint_${LIBRARY_NAME}) + add_dependencies(lint_${BINARY_NAME} lint_${LIBRARY_NAME}) +endif() \ No newline at end of file diff --git a/src/exatepp_abm.cu b/src/exatepp_abm.cu new file mode 100644 index 0000000..29c428d --- /dev/null +++ b/src/exatepp_abm.cu @@ -0,0 +1,183 @@ +#include +#include "flamegpu/flamegpu.h" + +FLAMEGPU_AGENT_FUNCTION(outputMessage, flamegpu::MessageNone, flamegpu::MessageSpatial3D) { + FLAMEGPU->message_out.setVariable("id", FLAMEGPU->getID()); + FLAMEGPU->message_out.setLocation( + FLAMEGPU->getVariable("x"), + FLAMEGPU->getVariable("y"), + FLAMEGPU->getVariable("z")); + return flamegpu::ALIVE; +} +FLAMEGPU_AGENT_FUNCTION(move, flamegpu::MessageSpatial3D, flamegpu::MessageNone) { + const flamegpu::id_t ID = FLAMEGPU->getID(); + const float REPULSE_FACTOR = FLAMEGPU->environment.getProperty("repulse"); + const float RADIUS = FLAMEGPU->message_in.radius(); + float fx = 0.0; + float fy = 0.0; + float fz = 0.0; + const float x1 = FLAMEGPU->getVariable("x"); + const float y1 = FLAMEGPU->getVariable("y"); + const float z1 = FLAMEGPU->getVariable("z"); + int count = 0; + for (const auto &message : FLAMEGPU->message_in(x1, y1, z1)) { + if (message.getVariable("id") != ID) { + const float x2 = message.getVariable("x"); + const float y2 = message.getVariable("y"); + const float z2 = message.getVariable("z"); + float x21 = x2 - x1; + float y21 = y2 - y1; + float z21 = z2 - z1; + const float separation = sqrtf(x21*x21 + y21*y21 + z21*z21); + if (separation < RADIUS && separation > 0.0f) { + float k = sinf((separation / RADIUS)*3.141f*-2)*REPULSE_FACTOR; + // Normalise without recalculating separation + x21 /= separation; + y21 /= separation; + z21 /= separation; + fx += k * x21; + fy += k * y21; + fz += k * z21; + count++; + } + } + } + fx /= count > 0 ? count : 1; + fy /= count > 0 ? count : 1; + fz /= count > 0 ? count : 1; + FLAMEGPU->setVariable("x", x1 + fx); + FLAMEGPU->setVariable("y", y1 + fy); + FLAMEGPU->setVariable("z", z1 + fz); + FLAMEGPU->setVariable("drift", sqrtf(fx*fx + fy*fy + fz*fz)); + return flamegpu::ALIVE; +} +FLAMEGPU_STEP_FUNCTION(Validation) { + static float prevTotalDrift = FLT_MAX; + static unsigned int driftDropped = 0; + static unsigned int driftIncreased = 0; + // This value should decline? as the model moves towards a steady equlibrium state + // Once an equilibrium state is reached, it is likely to oscillate between 2-4? values + float totalDrift = FLAMEGPU->agent("Circle").sum("drift"); + if (totalDrift <= prevTotalDrift) + driftDropped++; + else + driftIncreased++; + prevTotalDrift = totalDrift; + // printf("Avg Drift: %g\n", totalDrift / FLAMEGPU->agent("Circle").count()); + printf("%.2f%% Drift correct\n", 100 * driftDropped / static_cast(driftDropped + driftIncreased)); +} +int entrypoint(int argc, const char ** argv) { + flamegpu::ModelDescription model("template"); + + const unsigned int AGENT_COUNT = 16384; + const float ENV_MAX = static_cast(floor(cbrt(AGENT_COUNT))); + const float RADIUS = 2.0f; + + // global environment variables + flamegpu::EnvironmentDescription env = model.Environment(); + env.newProperty("repulse", 0.05f); + + // Location message + flamegpu::MessageSpatial3D::Description message = model.newMessage("location"); + message.newVariable("id"); + message.setRadius(RADIUS); + message.setMin(0, 0, 0); + message.setMax(ENV_MAX, ENV_MAX, ENV_MAX); + + // Circle agent + flamegpu::AgentDescription agent = model.newAgent("Circle"); + agent.newVariable("x"); + agent.newVariable("y"); + agent.newVariable("z"); + agent.newVariable("drift"); // Store the distance moved here, for validation + + // Define each function. + flamegpu::AgentFunctionDescription outputMessageDescription = agent.newFunction("outputMessage", outputMessage); + outputMessageDescription.setMessageOutput("location"); + flamegpu::AgentFunctionDescription moveDescription = agent.newFunction("move", move); + moveDescription.setMessageInput("location"); + // Add a dependency that move requires outputMessage to have executed + moveDescription.dependsOn(outputMessageDescription); + + // Identify the root of execution + model.addExecutionRoot(outputMessageDescription); + + // Add a step function which in this case is used as a crude form of validation + model.addStepFunction(Validation); + + // Build the exeuction graph + model.generateLayers(); + + // Create the simulation + flamegpu::CUDASimulation simulation(model, argc, argv); + + // Create visualisation if enabled +#ifdef FLAMEGPU_VISUALISATION + flamegpu::visualiser::ModelVis visualiser = simulation.getVisualisation(); + { + const float INIT_CAM = ENV_MAX * 1.25F; + visualiser.setInitialCameraLocation(INIT_CAM, INIT_CAM, INIT_CAM); + visualiser.setCameraSpeed(0.01f); + auto cirlceAgentVisualiser = visualiser.addAgent("Circle"); + // Position vars are named x, y, z; so they are used by default + cirlceAgentVisualiser.setModel(flamegpu::visualiser::Stock::Models::ICOSPHERE); + cirlceAgentVisualiser.setModelScale(1/10.0f); + // Render the Subdivision of spatial messaging + { + const float ENV_MIN = 0; + const int DIM = static_cast(ceil((ENV_MAX - ENV_MIN) / RADIUS)); // Spatial partitioning scales up to fit none exact environments + const float DIM_MAX = DIM * RADIUS; + auto pen = visualiser.newLineSketch(1, 1, 1, 0.2f); // white + // X lines + for (int y = 0; y <= DIM; y++) { + for (int z = 0; z <= DIM; z++) { + pen.addVertex(ENV_MIN, y * RADIUS, z * RADIUS); + pen.addVertex(DIM_MAX, y * RADIUS, z * RADIUS); + } + } + // Y axis + for (int x = 0; x <= DIM; x++) { + for (int z = 0; z <= DIM; z++) { + pen.addVertex(x * RADIUS, ENV_MIN, z * RADIUS); + pen.addVertex(x * RADIUS, DIM_MAX, z * RADIUS); + } + } + // Z axis + for (int x = 0; x <= DIM; x++) { + for (int y = 0; y <= DIM; y++) { + pen.addVertex(x * RADIUS, y * RADIUS, ENV_MIN); + pen.addVertex(x * RADIUS, y * RADIUS, DIM_MAX); + } + } + } + } + visualiser.activate(); +#endif + + // initialise a population of agents if not provided on disk + if (simulation.getSimulationConfig().input_file.empty()) { + // Currently population has not been init, so generate an agent population on the fly + std::mt19937_64 rng; + std::uniform_real_distribution dist(0.0f, ENV_MAX); + flamegpu::AgentVector population(model.Agent("Circle"), AGENT_COUNT); + for (unsigned int i = 0; i < AGENT_COUNT; i++) { + flamegpu::AgentVector::Agent instance = population[i]; + instance.setVariable("x", dist(rng)); + instance.setVariable("y", dist(rng)); + instance.setVariable("z", dist(rng)); + } + simulation.setPopulationData(population); + } + + // Execute the simulation + simulation.simulate(); + +#ifdef FLAMEGPU_VISUALISATION + visualiser.join(); +#endif + + // Ensure profiling / memcheck work correctly + flamegpu::util::cleanup(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/exatepp_abm.h b/src/exatepp_abm.h new file mode 100644 index 0000000..d7932d4 --- /dev/null +++ b/src/exatepp_abm.h @@ -0,0 +1,10 @@ +#pragma once + +/** + * the model's "main" method, which is called by main.cu::main. This split allows testing of this method. + * + * @param argc argc forwarded from the main method + * @param argv argv forwarded from the main method + * @return exit code + */ +int entrypoint(int argc, const char **argv); \ No newline at end of file diff --git a/src/main.cu b/src/main.cu new file mode 100644 index 0000000..d6da536 --- /dev/null +++ b/src/main.cu @@ -0,0 +1,9 @@ + +#include "../src/exatepp_abm.h" + +/** + * Entry point when not running via the test suite. + */ +int main(int argc, const char **argv) { + return entrypoint(argc, argv); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a53dddb --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,90 @@ +# Minimum CMake version 3.18 for CUDA --std=c++17 +cmake_minimum_required(VERSION 3.18...3.25 FATAL_ERROR) + +# Enable CTest +enable_testing() + +# If we have the binary target, check the binary behaves as intended / produces the correct outputs for given inputs. +if(TARGET exatepp_abm) + # Test running exatepp_abm without any cli succeeds (returns 0 error code) + add_test( + NAME integration.defaultArgs + COMMAND exatepp_abm --console-mode + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + set_tests_properties(integration.defaultArgs PROPERTIES LABELS "integration") + set_tests_properties(integration.defaultArgs PROPERTIES WILL_FAIL FALSE) +endif() + +## ------ + +# Fetch GoogleTest and enable CMake integration +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GoogleTest.cmake) +include(GoogleTest) + +# Handle CMAKE_CUDA_ARCHITECTURES and inject code into the tests project() command +flamegpu_init_cuda_architectures(PROJECT tests) +# Name the project and set languages +project(tests CUDA CXX) +# Include common rules. +include(${FLAMEGPU_ROOT}/cmake/common.cmake) + +# List test source files +set(TESTS_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cu + ${CMAKE_CURRENT_SOURCE_DIR}/src/temp.cu +) + +# Define output location of binary files +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) +# Declare the new binary target and set required options. +flamegpu_add_executable("${PROJECT_NAME}" "${TESTS_SRC}" "${FLAMEGPU_ROOT}" "${PROJECT_BINARY_DIR}" FALSE) +# Add the current dir to the include path +target_include_directories("${PROJECT_NAME}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +# Link against GTest +target_link_libraries("${PROJECT_NAME}" PRIVATE GTest::gtest) +# Link against the static library +target_link_libraries("${PROJECT_NAME}" PRIVATE exatepp_abm_lib) +# Workaround for incremental rebuilds on MSVC, where device link was not being performed. +# https://github.com/FLAMEGPU/FLAMEGPU2/issues/483 +if(MSVC AND CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "11.1") + # Provide the absolute path to the lib file, rather than the relative version cmake provides. + target_link_libraries(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/$") +endif() + +# Prevent windows.h definition of MIN and MAX macros on windows +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX) +endif() + +# Put Within Tests filter +flamegpu_set_target_folder("${PROJECT_NAME}" "Tests") +# 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) debugger configure_file +set_target_properties("${PROJECT_NAME}" PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +VS_DEBUGGER_COMMAND_ARGUMENTS "$<$:--gtest_catch_exceptions=0> --gtest_filter=*") + +# Add the tests target as to ctest, optionally using the gtest_discover integration. +if(ENABLE_GTEST_DISCOVER) + # If GTEST_DISCOVER is enabled, add the unit test executable using it. This results in very long test exeuction times due to CUDA context initialisation per test + gtest_discover_tests( + "${PROJECT_NAME}" + WORKING_DIRECTORY ${PROJECT_DIR} + TEST_PREFIX "${PROJECT_NAME}." + PROPERTIES LABELS "unit" + ) +else() + # Otherwise add the whole test suite as a single test. Use ctest -VV to view per-test results in this case. + add_test( + NAME ${PROJECT_NAME} + COMMAND "${PROJECT_NAME}" + WORKING_DIRECTORY ${PROJECT_DIR} + ) + set_tests_properties(${PROJECT_NAME} PROPERTIES LABELS "unit") +endif() + +# Add to the exatepp_abm generic lint target. +if(TARGET lint_${PROJECT_NAME} AND TARGET lint) + add_dependencies(lint lint_${PROJECT_NAME}) +endif() diff --git a/tests/src/main.cu b/tests/src/main.cu new file mode 100644 index 0000000..032e539 --- /dev/null +++ b/tests/src/main.cu @@ -0,0 +1,26 @@ +#include +// Include google test +#include "gtest/gtest.h" +// Include flame gpu telemetry header to prevent every test from triggering a telemetry API call. +#include "flamegpu/io/Telemetry.h" + +GTEST_API_ int main(int argc, char **argv) { + // Disable flamegpu telemetry + flamegpu::io::Telemetry::disable(); + flamegpu::io::Telemetry::suppressNotice(); + // Run the main google test body + printf("Running main() from %s\n", __FILE__); + testing::InitGoogleTest(&argc, argv); + auto rtn = RUN_ALL_TESTS(); + // Reset all cuda devices for memcheck / profiling purposes. + cudaError_t status = cudaSuccess; + int devices = 0; + status = cudaGetDeviceCount(&devices); + if (status == cudaSuccess && devices > 0) { + for (int device = 0; device < devices; ++device) { + cudaSetDevice(device); + cudaDeviceReset(); + } + } + return rtn; +} diff --git a/tests/src/temp.cu b/tests/src/temp.cu new file mode 100644 index 0000000..9de569d --- /dev/null +++ b/tests/src/temp.cu @@ -0,0 +1,8 @@ +#include "gtest/gtest.h" + +// Temporary test case which does not actually test anything, just to make sure CMake is set up correctly. + +TEST(TestTemp, boolIsTrue) { + bool b = 1; + EXPECT_EQ(b, true); +} \ No newline at end of file