Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,13 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/


#Temporary for this branch
GEMINI.md
executionsteps

# All log and image files
*.png
*.gif
*.csv
13 changes: 9 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ set(DOCUMENTATION_ONLY_BUILD OFF)

# Check compiler functionailty, as there are known issues in some cases, but version checks are not always sufficient.
include(./cmake/CheckCompilerFunctionality.cmake)
# If this returned a negative result, set the docs only build.
# If this returned a negative result, set the docs only build.
if(NOT FLAMEGPU_CheckCompilerFunctionality_RESULT)
set(DOCUMENTATION_ONLY_BUILD ON)
set(DOCUMENTATION_ONLY_BUILD ON)
message(STATUS "Documentation-only build: due to compiler compatability version. See prior warnings.")
endif()

Expand Down Expand Up @@ -60,7 +60,7 @@ endif()

# If CUDA is not available, or the minimum version is too low only build the docs.
if(DOCUMENTATION_ONLY_BUILD)
# Not able to build code, so just make docs
# Not able to build code, so just make docs
include(./cmake/dependencies/doxygen.cmake)
if(${FLAMEGPU_BUILD_API_DOCUMENTATION})
flamegpu_create_doxygen_target("${FLAMEGPU_ROOT}" "${CMAKE_CURRENT_BINARY_DIR}" "")
Expand All @@ -74,7 +74,7 @@ include(CMakeDependentOption)
# Option to enable building all examples, defaults to ON if FLAMEGPU is the top level cmake, else OFF
cmake_dependent_option(FLAMEGPU_BUILD_ALL_EXAMPLES "Enable building all FLAMEGPU examples" ON "FLAMEGPU_PROJECT_IS_TOP_LEVEL" OFF)

# Options to enable building individual examples, if FLAMEGPU_BUILD_ALL_EXAMPLES is off.
# Options to enable building individual examples, if FLAMEGPU_BUILD_ALL_EXAMPLES is off.
# Dependent options hide these from the CMake GUI if FLAMEGPU_BUILD_ALL_EXAMPLES is on, or if it is not the top level project
cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_BOIDS_BRUTEFORCE "Enable building examples/cpp/boids_bruteforce" OFF "FLAMEGPU_PROJECT_IS_TOP_LEVEL; NOT FLAMEGPU_BUILD_ALL_EXAMPLES" OFF)
cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_BOIDS_SPATIAL3D "Enable building examples/cpp/boids_spatial3D" OFF "FLAMEGPU_PROJECT_IS_TOP_LEVEL; NOT FLAMEGPU_BUILD_ALL_EXAMPLES" OFF)
Expand All @@ -87,6 +87,7 @@ cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_HOST_FUNCTIONS "Enable building ex
cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_ENSEMBLE "Enable building examples/cpp/ensemble" OFF "FLAMEGPU_PROJECT_IS_TOP_LEVEL; NOT FLAMEGPU_BUILD_ALL_EXAMPLES" OFF)
cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_SUGARSCAPE "Enable building examples/cpp/sugarscape" OFF "FLAMEGPU_PROJECT_IS_TOP_LEVEL; NOT FLAMEGPU_BUILD_ALL_EXAMPLES" OFF)
cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_DIFFUSION "Enable building examples/cpp/diffusion" OFF "FLAMEGPU_PROJECT_IS_TOP_LEVEL; NOT FLAMEGPU_BUILD_ALL_EXAMPLES" OFF)
cmake_dependent_option(FLAMEGPU_BUILD_EXAMPLE_BEESANDBEES "Enable building examples/cpp/beesandflowers" OFF "FLAMEGPU_PROJECT_IS_TOP_LEVEL; NOT FLAMEGPU_BUILD_ALL_EXAMPLES" OFF)

option(FLAMEGPU_BUILD_PYTHON "Enable python bindings via SWIG" OFF)

Expand Down Expand Up @@ -144,10 +145,14 @@ if(FLAMEGPU_BUILD_ALL_EXAMPLES OR FLAMEGPU_BUILD_EXAMPLE_ENSEMBLE)
endif()
if(FLAMEGPU_BUILD_ALL_EXAMPLES OR FLAMEGPU_BUILD_EXAMPLE_SUGARSCAPE)
add_subdirectory(examples/cpp/sugarscape)
add_subdirectory(examples/cpp/sugarscapeSubModelAPI)
endif()
if(FLAMEGPU_BUILD_ALL_EXAMPLES OR FLAMEGPU_BUILD_EXAMPLE_DIFFUSION)
add_subdirectory(examples/cpp/diffusion)
endif()
if(FLAMEGPU_BUILD_ALL_EXAMPLES OR FLAMEGPU_BUILD_EXAMPLE_BEESANDBEES)
add_subdirectory(examples/cpp/beesandflowers)
endif()
# Add the tests directory (if required)
if(FLAMEGPU_BUILD_TESTS OR FLAMEGPU_BUILD_TESTS_DEV)
# Enable Ctest
Expand Down
34 changes: 34 additions & 0 deletions examples/cpp/beesandflowers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Minimum CMake version 3.25.2 for CUDA --std=c++20
cmake_minimum_required(VERSION 3.25.2...4.1.1 FATAL_ERROR)

# Set the location of the ROOT flame gpu project relative to this CMakeList.txt
get_filename_component(FLAMEGPU_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../.. REALPATH)

# Handle CMAKE_CUDA_ARCHITECTURES gracefully
include(${FLAMEGPU_ROOT}/cmake/CUDAArchitectures.cmake)
flamegpu_init_cuda_architectures(PROJECT beesandflowers)

# Name the project and enable required languages
project(beesandflowers CXX CUDA)

# Include common rules.
include(${FLAMEGPU_ROOT}/cmake/common.cmake)

# Define output location of binary files
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/)

# 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
)

# Add the executable and set required flags for the target
flamegpu_add_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 10")
275 changes: 275 additions & 0 deletions examples/cpp/beesandflowers/src/main.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <random>
#include "flamegpu/flamegpu.h"
#include "flamegpu/stockAgent/subModels/SingleAgentDiscreteMovement.h"

#define ENV_DIM 100
#define SIMULATION_STEPS 100

using flamegpu::ModelDescription;
using flamegpu::AgentDescription;
using flamegpu::AgentFunctionDescription;
using flamegpu::LayerDescription;
using flamegpu::CUDASimulation;
using flamegpu::MessageNone;
using flamegpu::ALIVE;
using flamegpu::EnvironmentDescription;

// Global log file
std::ofstream agents_log;

FLAMEGPU_AGENT_FUNCTION(calculate_priority, MessageNone, MessageNone) {
float current_nectar = FLAMEGPU->getVariable<float>("current_cell_score");
float hunger_level = FLAMEGPU->getVariable<float>("hunger_level");

// If at a flower and still hungry, stay put (priority 0)
if (current_nectar > 0.01f && hunger_level > 0.0f) {
FLAMEGPU->setVariable<float>("priority", 0.0f);
// Ensure submodel doesn't move us if we want to stay
FLAMEGPU->setVariable<float>("current_cell_score", 1000.0f);
return ALIVE;
}

int wait = FLAMEGPU->getVariable<int>("wait");
float wh = FLAMEGPU->environment.getProperty<float>("WH");
float ww = FLAMEGPU->environment.getProperty<float>("WW");

// Priority for movement (higher = more likely to win a cell)
float priority = hunger_level * wh + (float)wait * ww + FLAMEGPU->random.uniform<float>(0.0f, 1.0f);
FLAMEGPU->setVariable<float>("priority", priority);

// Force movement by setting current_cell_score to a low value.
// This ensures any neighbor with score >= 0.0 will be considered a valid move.
FLAMEGPU->setVariable<float>("current_cell_score", -1.0f);

return ALIVE;
}

FLAMEGPU_AGENT_FUNCTION(update_hunger_wait, MessageNone, MessageNone) {
float current_nectar = FLAMEGPU->getVariable<float>("current_cell_score");
float hunger_level = FLAMEGPU->getVariable<float>("hunger_level");
int wait = FLAMEGPU->getVariable<int>("wait");

if (current_nectar > 0.01f && hunger_level > 0.0f) {
// Feed: decrease hunger_level
hunger_level -= 5.0f;
if (hunger_level <= 0.0f) {
hunger_level = 0.0f;
}
wait = 0;
} else {
// Hunger increases over time
hunger_level += 2.0f;
wait += 1;
}

FLAMEGPU->setVariable<float>("hunger_level", hunger_level);
FLAMEGPU->setVariable<int>("wait", wait);

return ALIVE;
}

FLAMEGPU_INIT_FUNCTION(createAgent) {
const int GRID_DIM = 100;
const int FLOWER_SPACING = 5;

// Create bees at random unique positions first
const int NUM_BEES = 100;
auto bee_api = FLAMEGPU->agent("bee");

std::vector<int> available_indices(GRID_DIM * GRID_DIM);
std::iota(available_indices.begin(), available_indices.end(), 0);

std::mt19937 g(std::random_device {}());
std::shuffle(available_indices.begin(), available_indices.end(), g);

std::vector<bool> is_bee_at(GRID_DIM * GRID_DIM, false);

for (int i = 0; i < NUM_BEES; ++i) {
int index = available_indices[i];
int x = index / GRID_DIM;
int y = index % GRID_DIM;
is_bee_at[index] = true;

auto bee = bee_api.newAgent();
bee.setVariable<int>("x", x);
bee.setVariable<int>("y", y);
bee.setVariable<int>("last_x", -1);
bee.setVariable<int>("last_y", -1);
bee.setVariable<int>("last_resources_x", -1);
bee.setVariable<int>("last_resources_y", -1);
bee.setVariable<float>("hunger_level", FLAMEGPU->random.uniform<float>(0.0f, 100.0f));
bee.setVariable<int>("wait", 0);
bee.setVariable<float>("priority", 0.0f);
bee.setVariable<float>("current_cell_score", 0.0f);
}

// Create a 100x100 grid of cells and set occupancy
auto cell_api = FLAMEGPU->agent("flower_cell");
for (int i = 0; i < GRID_DIM; ++i) {
for (int j = 0; j < GRID_DIM; ++j) {
int index = i * GRID_DIM + j;
auto cell = cell_api.newAgent();
cell.setVariable<int>("x", i);
cell.setVariable<int>("y", j);
cell.setVariable<int>("is_occupied", is_bee_at[index] ? 1 : 0);

float nectar = 0.0f;
if (i % FLOWER_SPACING == 0 && j % FLOWER_SPACING == 0) {
nectar = FLAMEGPU->random.uniform<float>(10.0f, 50.0f);
}
cell.setVariable<float>("nectar", nectar);
}
}
}


FLAMEGPU_INIT_FUNCTION(initLog) {
agents_log.open("bees_log.csv");
agents_log << "step,id,x,y,hunger_level,wait" << std::endl;
}

FLAMEGPU_STEP_FUNCTION(stepLogger) {
auto bees = FLAMEGPU->agent("bee");
auto& bee_pop = bees.getPopulationData();
unsigned int step = FLAMEGPU->getStepCounter();

int count = 0;
for (const auto& bee : bee_pop) {
if (count < 5) {
std::cout << "Bee " << bee.getID() << " at (" << bee.getVariable<int>("x") << ", " << bee.getVariable<int>("y") << ")" << std::endl;
count++;
}
agents_log << step << ","
<< bee.getID() << ","
<< bee.getVariable<int>("x") << ","
<< bee.getVariable<int>("y") << ","
<< bee.getVariable<float>("hunger_level") << ","
<< bee.getVariable<int>("wait") << "\n";
}

// Log cells with nectar once at the start
if (step == 0) {
std::ofstream flower_log("flowers_log.csv");
flower_log << "id,x,y,nectar" << std::endl;
auto cells = FLAMEGPU->agent("flower_cell");
auto& cell_pop = cells.getPopulationData();
for (const auto& cell : cell_pop) {
float nectar = cell.getVariable<float>("nectar");
if (nectar > 0.0f) {
flower_log << cell.getID() << ","
<< cell.getVariable<int>("x") << ","
<< cell.getVariable<int>("y") << ","
<< nectar << "\n";
}
}
flower_log.close();
}

float avg_hunger = bees.sum<float>("hunger_level") / (float)bees.count();
std::cout << "Step: " << step
<< " | Bee count: " << bees.count()
<< " | Avg Hunger: " << avg_hunger << std::endl;
}

FLAMEGPU_EXIT_FUNCTION(exitLog) {
if (agents_log.is_open()) {
agents_log.close();
}
}

void define_model(ModelDescription &model) {
// Environment variables
EnvironmentDescription env = model.Environment();
env.newProperty<float>("WH", 0.6f);
env.newProperty<float>("WW", 0.4f);

// Cell Agent
AgentDescription cell = model.newAgent("flower_cell");
cell.newVariable<int>("x");
cell.newVariable<int>("y");
cell.newVariable<int>("is_occupied", 0);
cell.newVariable<float>("nectar", 0.0f);

// Bee Agent
AgentDescription bee = model.newAgent("bee");
bee.newVariable<int>("x");
bee.newVariable<int>("y");
bee.newVariable<int>("last_x", -1);
bee.newVariable<int>("last_y", -1);
bee.newVariable<int>("last_resources_x", -1);
bee.newVariable<int>("last_resources_y", -1);
bee.newVariable<float>("hunger_level");
bee.newVariable<int>("wait", 0);
bee.newVariable<float>("priority", 0.0f);
bee.newVariable<float>("current_cell_score", 0.0f);


// Declared on the stack - uses empty constructor
flamegpu::stockAgent::submodels::SingleAgentDiscreteMovement move_sub_logic;

// Initialize the submodel
move_sub_logic.addSingleAgentDiscreteMovementSubmodel(model, ENV_DIM, ENV_DIM);

// Bind parent agents to submodel
move_sub_logic.setMovingAgent("bee",
{
{"x", "x"},
{"y", "y"},
{"last_x", "last_x"},
{"last_y", "last_y"},
{"last_resources_x", "last_resources_x"},
{"last_resources_y", "last_resources_y"},
{"priority", "priority"},
{"current_cell_score", "current_cell_score"}
},
{
{"active", flamegpu::ModelData::DEFAULT_STATE}
});

move_sub_logic.setEnvironmentAgent("flower_cell",
{
{"x", "x"},
{"y", "y"},
{"is_occupied", "is_occupied"},
{"cell_score", "nectar"}
},
{
{"active", flamegpu::ModelData::DEFAULT_STATE}
});


bee.newFunction("calculate_priority", calculate_priority);
bee.newFunction("update_hunger_wait", update_hunger_wait);

LayerDescription l0 = model.newLayer();
l0.addAgentFunction(calculate_priority);

LayerDescription l1 = model.newLayer();
l1.addSubModel(move_sub_logic.getSubModelDescription());

LayerDescription l2 = model.newLayer();
l2.addAgentFunction(update_hunger_wait);

model.addInitFunction(createAgent);
model.addInitFunction(initLog);
model.addStepFunction(stepLogger);
model.addExitFunction(exitLog);
}

int main(int argc, const char ** argv) {
ModelDescription model("OneAgentMovingModel");

define_model(model);

CUDASimulation simulation(model);
simulation.SimulationConfig().steps = SIMULATION_STEPS;

simulation.simulate();

return EXIT_SUCCESS;
}
Loading
Loading