diff --git a/CMakeLists.txt b/CMakeLists.txt index 28d2d5f..20952ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,8 @@ option(KAGEN_USE_SPARSEHASH "Build with Google Sparsehash. If turned off, fall b option(KAGEN_USE_FAST_MATH "Use -ffast-math." OFF) option(KAGEN_USE_MKL "Build with Intel MKL random generator." OFF) option(KAGEN_USE_XXHASH "Build with xxHash. If turned off, path permutation will not be available." ON) +option(KAGEN_ENABLE_BRAIN_GRAPH "Enable support for the RELeARN brain graph generator. Requires an additional dependency." OFF) + option(KAGEN_WARNINGS_ARE_ERRORS "Make compiler warnings compiler errors." OFF) @@ -162,6 +164,25 @@ if (KAGEN_USE_SPARSEHASH) list(APPEND KAGEN_LINK_LIBRARIES Sparsehash::Sparsehash) add_definitions(-DKAGEN_SPARSEHASH_FOUND) endif () + +############################################################################### +# RELeARN brain graph generator +############################################################################### + +if (KAGEN_ENABLE_BRAIN_GRAPH) + set(CMAKE_CXX_STANDARD 20) + FetchContent_Declare( + relearn + GIT_REPOSITORY https://github.com/KarlsruheGraphGeneration/RELeARN.git + GIT_TAG 60380e9 + SOURCE_SUBDIR relearn + SYSTEM + ) + FetchContent_MakeAvailable(relearn) + list(APPEND KAGEN_LINK_LIBRARIES relearn_lib) + add_definitions(-DKAGEN_ENABLE_BRAIN_GRAPH) + +endif() ############################################################################### # CGAL @@ -193,7 +214,8 @@ if (KAGEN_USE_MKL) else () message(STATUS "MKL requested but not found, building without MKL") endif () -endif () + endif () + ################################################################################ diff --git a/app/KaGen.cpp b/app/KaGen.cpp index ad36db4..c493285 100644 --- a/app/KaGen.cpp +++ b/app/KaGen.cpp @@ -6,6 +6,7 @@ * * All rights reserved. Published under the BSD-2 license in the LICENSE file. ******************************************************************************/ +#include "kagen.h" #include "kagen/context.h" #include "kagen/definitions.h" #include "kagen/external_memory_facade.h" @@ -428,6 +429,18 @@ This is mostly useful for experimental graph generators or when using KaGen to l cmd->add_option("--cols-per-pe", config.image_mesh.cols_per_pe, "Number of columns assigned to the same PE"); cmd->add_option("--rows-per-pe", config.image_mesh.rows_per_pe, "Number of rows assigned to the same PE"); } + { // BRAIN + auto* cmd = app.add_subcommand("brain", "R-MAT Graph"); + cmd->callback([&] { config.generator = GeneratorType::BRAIN; }); + add_option_n(cmd); + cmd->add_option("--synapse-creation-model", config.brain.algorithm) + ->transform(CLI::CheckedTransformer(GetBrainSynapseCreationModelMap())); + cmd->add_option("--gaussian-mu", config.brain.gaussian_mu); + cmd->add_option("--gaussian-sigma", config.brain.gaussian_sigma); + cmd->add_option("--simulation-steps", config.brain.simulation_steps); + cmd->add_option("--synapses-ub", config.brain.synapses_ub); + cmd->add_option("--synapses-lb", config.brain.synapses_lb); + } { // Graph from file auto* cmd = diff --git a/kagen/CMakeLists.txt b/kagen/CMakeLists.txt index dd90962..6b6c0e2 100644 --- a/kagen/CMakeLists.txt +++ b/kagen/CMakeLists.txt @@ -21,6 +21,10 @@ if (NOT SAMPLING_HAVE_MKL) list(FILTER KAGEN_SOURCE_FILES EXCLUDE REGEX "mkl.*") endif () +if (NOT KAGEN_ENABLE_BRAIN_GRAPH) + list(FILTER KAGEN_SOURCE_FILES EXCLUDE REGEX "brain.*") +endif () + add_library(kagen OBJECT ${KAGEN_SOURCE_FILES}) target_compile_features(kagen PRIVATE cxx_std_17) target_link_libraries(kagen PUBLIC ${KAGEN_LINK_LIBRARIES}) diff --git a/kagen/context.cpp b/kagen/context.cpp index 4c66ba1..18dd9ae 100644 --- a/kagen/context.cpp +++ b/kagen/context.cpp @@ -1,6 +1,7 @@ #include "kagen/context.h" #include "kagen/definitions.h" +#include "kagen/kagen.h" #include @@ -166,6 +167,16 @@ std::ostream& operator<<(std::ostream& out, const PGeneratorConfig& config) { << (config.image_mesh.rows_per_pe == 0 ? "auto" : std::to_string(config.image_mesh.rows_per_pe)) << "\n"; break; + case kagen::GeneratorType::BRAIN: + out << " Number of vertices: " << config.n << "\n"; + out << " Number of neurons per rank: " << config.brain.num_neurons_per_rank << "\n"; + out << " Synapses lower bound: " << config.brain.synapses_lb << "\n"; + out << " Synapses upper bound: " << config.brain.synapses_ub << "\n"; + out << " Simulation steps: " << config.brain.simulation_steps << "\n"; + out << " Algorithm: " << config.brain.algorithm << "\n"; + out << " Gaussian sigma: " << config.brain.gaussian_sigma << "\n"; + out << " Gaussian mu: " << config.brain.gaussian_mu << "\n"; + break; case GeneratorType::FILE: out << " Input file: " << config.input_graph.filename << "\n"; @@ -375,6 +386,21 @@ PGeneratorConfig CreateConfigFromString(const std::string& options_str, PGenerat throw std::runtime_error("invalid weight model name"); } config.image_mesh.weight_model = weight_model_it->second; + + } else if (config.generator == GeneratorType::BRAIN) { + const auto synapse_creation_models = GetBrainSynapseCreationModelMap(); + const std::string synapse_creation_model_name = + get_string_or_default("synapse_creation_model", StringifyEnum(config.brain.algorithm)); + const auto it = synapse_creation_models.find(synapse_creation_model_name); + if (it == synapse_creation_models.end()) { + throw std::runtime_error("invalid synapse creation model name"); + } + config.brain.algorithm = it->second; + config.brain.gaussian_mu = get_hpfloat_or_default("gaussian_mu", config.brain.gaussian_mu); + config.brain.gaussian_sigma = get_hpfloat_or_default("gaussian_sigma", config.brain.gaussian_sigma); + config.brain.simulation_steps = get_sint_or_default("simulation_steps", config.brain.simulation_steps); + config.brain.synapses_lb = get_hpfloat_or_default("synapses_lb", config.brain.synapses_lb); + config.brain.synapses_ub = get_hpfloat_or_default("synapses_ub", config.brain.synapses_ub); } else if (config.generator == GeneratorType::FILE) { const std::string filename = get_string_or_default("filename"); if (filename.empty()) { diff --git a/kagen/context.h b/kagen/context.h index 04ecf63..7f41a71 100644 --- a/kagen/context.h +++ b/kagen/context.h @@ -39,6 +39,16 @@ struct ImageMeshConfig { SInt rows_per_pe = 0; }; +struct BrainConfig { + std::uint64_t num_neurons_per_rank; + double synapses_ub = 5.0; + double synapses_lb = 5.0; + std::uint32_t simulation_steps = 1001; + BrainSynapseCreationModel algorithm = BrainSynapseCreationModel::BARNES_HUT; + double gaussian_sigma = 2.0; + double gaussian_mu = 0.0; +}; + struct InputGraphConfig { std::string filename = ""; FileFormat format = FileFormat::EXTENSION; @@ -144,6 +154,9 @@ struct PGeneratorConfig { // Image mesh generator settings ImageMeshConfig image_mesh{}; + // Brain generator settings + BrainConfig brain{}; + // Settings for the static graph pseudo-generator InputGraphConfig input_graph{}; diff --git a/kagen/factories.cpp b/kagen/factories.cpp index 4b4e871..78104a6 100644 --- a/kagen/factories.cpp +++ b/kagen/factories.cpp @@ -15,10 +15,16 @@ #include "kagen/generators/path/path_directed.h" #include "kagen/generators/rmat/rmat.h" +#include + #ifdef KAGEN_CGAL_FOUND #include "kagen/generators/geometric/delaunay.h" #endif // KAGEN_CGAL_FOUND +#ifdef KAGEN_ENABLE_BRAIN_GRAPH + #include "kagen/generators/brain/brain.h" +#endif + namespace kagen { std::unique_ptr CreateGeneratorFactory(const GeneratorType type) { switch (type) { @@ -79,6 +85,14 @@ std::unique_ptr CreateGeneratorFactory(const GeneratorType typ case GeneratorType::FILE: return std::make_unique(); + + case GeneratorType::BRAIN: +#ifdef KAGEN_ENABLE_BRAIN_GRAPH + return std::make_unique(); +#else + // throw exception after switch + break; +#endif } throw std::runtime_error("invalid graph generator type"); diff --git a/kagen/generators/brain/brain.cpp b/kagen/generators/brain/brain.cpp new file mode 100644 index 0000000..827a3b1 --- /dev/null +++ b/kagen/generators/brain/brain.cpp @@ -0,0 +1,249 @@ +#include "kagen/generators/brain/brain.h" + +#include "kagen/context.h" +#include "kagen/kagen.h" + +#include +#include + +#include "algorithm/BarnesHutInternal/BarnesHutCell.h" +#include "algorithm/BarnesHutInternal/BarnesHutInvertedCell.h" +#include "algorithm/Kernel/Kernel.h" +#include "algorithm/NaiveInternal/NaiveCell.h" +#include "neurons/CalciumCalculator.h" +#include "neurons/helper/SynapseDeletionFinder.h" +#include "neurons/input/BackgroundActivityCalculators.h" +#include "neurons/input/FiredStatusCommunicationMap.h" +#include "neurons/input/SynapticInputCalculator.h" +#include "neurons/input/SynapticInputCalculators.h" +#include "neurons/models/NeuronModels.h" +#include "neurons/models/SynapticElements.h" +#include "sim/Essentials.h" +#include "sim/Simulation.h" +#include "sim/random/SubdomainFromNeuronPerRank.h" +#include "structure/Partition.h" +#include +namespace kagen { +PGeneratorConfig +BrainFactory::NormalizeParameters(PGeneratorConfig config, PEID, const PEID size, const bool /*output*/) const { + config.brain.num_neurons_per_rank = (config.n + size - 1) / size; + return config; +} +std::unique_ptr +BrainFactory::Create(const PGeneratorConfig& config, const PEID rank, const PEID size) const { + return std::make_unique(config, rank, size); +} + +BrainGenerator::BrainGenerator(const PGeneratorConfig& config, const PEID rank, const PEID size) + : _sim(std::make_unique(), std::shared_ptr(nullptr)), + _config(config.brain), + _rank(rank) { + MPIWrapper::init(1, nullptr); + AlgorithmEnum algorithm; + switch (_config.algorithm) { + case BrainSynapseCreationModel::NAIVE: + algorithm = AlgorithmEnum::Naive; + break; + case BrainSynapseCreationModel::BARNES_HUT: + algorithm = AlgorithmEnum::BarnesHut; + break; + case BrainSynapseCreationModel::BARNES_HUT_INVERTED: + algorithm = AlgorithmEnum::BarnesHutInverted; + break; + } + SetVertexRange(rank * _config.num_neurons_per_rank, (rank + 1) * _config.num_neurons_per_rank); + // AlgorithmEnum chosen_algorithm = AlgorithmEnum::BarnesHut; + + // RelearnTypes::step_type simulation_steps{1001}; + + // unsigned int random_seed{0}; + // app.add_option("-r,--random-seed", random_seed, "Random seed. Default: 0."); + + // RelearnTypes::number_neurons_type number_neurons_per_rank{1000}; + + double fraction_excitatory_neurons{1.0}; + + double um_per_neuron{1.0}; + + // double gaussian_sigma{ GaussianDistributionKernel::default_sigma }; + // double gaussian_sigma{2}; + + // double gaussian_mu{ GaussianDistributionKernel::default_mu }; + // double gaussian_mu{ 0 }; + + // double synapses = 5.0; + double synaptic_elements_init_lb = _config.synapses_lb; + + double synaptic_elements_init_ub = _config.synapses_ub; + + RelearnException::check( + synaptic_elements_init_lb >= 0.0, "The minimum number of vacant synaptic elements must not be negative"); + RelearnException::check( + synaptic_elements_init_ub >= synaptic_elements_init_lb, + "The minimum number of vacant synaptic elements must not be larger than the maximum number"); + + omp_set_num_threads(1); + + std::size_t current_seed = 0; + boost::hash_combine(current_seed, rank); + boost::hash_combine(current_seed, config.seed); + + RandomHolder::seed_all(current_seed); + LogFiles::disable = true; + + auto essentials = std::make_unique(); + + MPIWrapper::barrier(); + + // Timers::start(TimerRegion::INITIALIZATION); + + auto prepare_algorithm = [&]() -> void { + // Set the correct kernel and initialize the MPIWrapper to return the correct type + if (algorithm == AlgorithmEnum::BarnesHut) { + MPIWrapper::init_buffer_octree(); + } else if (algorithm == AlgorithmEnum::BarnesHutInverted) { + MPIWrapper::init_buffer_octree(); + } else { + RelearnException::check( + algorithm == AlgorithmEnum::Naive, "An algorithm was chosen that is not supported"); + MPIWrapper::init_buffer_octree(); + } + + GaussianDistributionKernel::set_sigma(_config.gaussian_sigma); + GaussianDistributionKernel::set_mu(_config.gaussian_mu); + }; + prepare_algorithm(); + + auto partition = std::make_shared(size, MPIRank{rank}); + + auto subdomain = std::make_unique( + _config.num_neurons_per_rank, fraction_excitatory_neurons, um_per_neuron, partition); + + auto background_activity_calculator = std::make_unique(); + + auto stimulus_calculator = std::make_unique(); + + auto construct_input = [&]() -> std::unique_ptr { + auto fired_status_communicator = std::make_unique(MPIWrapper::get_num_ranks()); + return std::make_unique( + SynapticInputCalculator::default_conductance, std::move(fired_status_communicator)); + }; + auto input_calculator = construct_input(); + + auto neuron_model = std::make_unique( + NeuronModel::default_h, std::move(input_calculator), std::move(background_activity_calculator), + std::move(stimulus_calculator), models::PoissonModel::default_x_0, models::PoissonModel::default_tau_x, + models::PoissonModel::default_refractory_period); + + auto construct_calcium_calculator = [&]() -> std::unique_ptr { + auto calcium_calculator = std::make_unique(); + + auto initial_calcium_calculator = [](MPIRank /*mpi_rank*/, NeuronID::value_type /*neuron_id*/) { + return 0.0; + }; + calcium_calculator->set_initial_calcium_calculator(std::move(initial_calcium_calculator)); + + auto target_calcium_calculator = [](MPIRank /*mpi_rank*/, NeuronID::value_type /*neuron_id*/) { + return CalciumCalculator::default_C_target; + }; + calcium_calculator->set_target_calcium_calculator(std::move(target_calcium_calculator)); + + return calcium_calculator; + }; + auto calcium_calculator = construct_calcium_calculator(); + + auto axons_model = std::make_shared( + ElementType::Axon, SynapticElements::default_eta_Axons, SynapticElements::default_nu, + SynapticElements::default_vacant_retract_ratio, synaptic_elements_init_lb, synaptic_elements_init_ub); + + auto excitatory_dendrites_model = std::make_shared( + ElementType::Dendrite, SynapticElements::default_eta_Dendrites_exc, SynapticElements::default_nu, + SynapticElements::default_vacant_retract_ratio, synaptic_elements_init_lb, synaptic_elements_init_ub); + + auto inhibitory_dendrites_model = std::make_shared( + ElementType::Dendrite, SynapticElements::default_eta_Dendrites_inh, SynapticElements::default_nu, + SynapticElements::default_vacant_retract_ratio, synaptic_elements_init_lb, synaptic_elements_init_ub); + + auto synapse_deletion_finder = std::make_unique(); + + _sim = Simulation(std::move(essentials), partition); + _sim.set_neuron_model(std::move(neuron_model)); + _sim.set_calcium_calculator(std::move(calcium_calculator)); + _sim.set_synapse_deletion_finder(std::move(synapse_deletion_finder)); + _sim.set_axons(std::move(axons_model)); + _sim.set_dendrites_ex(std::move(excitatory_dendrites_model)); + _sim.set_dendrites_in(std::move(inhibitory_dendrites_model)); + + if (is_barnes_hut(algorithm)) { + _sim.set_acceptance_criterion_for_barnes_hut(Constants::bh_default_theta); + } + + _sim.set_algorithm(algorithm); + _sim.set_subdomain_assignment(std::move(subdomain)); + + /**********************************************************************************/ + + // The barrier ensures that every rank finished its local stores. + // Otherwise, a "fast" rank might try to read from the RMA window of another + // rank which has not finished (or even begun) its local stores + MPIWrapper::barrier(); // TODO(future) Really needed? + + // Lock local RMA memory for local stores + MPIWrapper::lock_window(MPIWindow::Window::Octree, MPIRank{rank}, MPI_Locktype::Exclusive); + + _sim.initialize(); + + // Unlock local RMA memory and make local stores visible in public window copy + MPIWrapper::unlock_window(MPIWindow::Window::Octree, MPIRank{rank}); +} + +void BrainGenerator::GenerateEdgeList() { + _sim.simulate(_config.simulation_steps); + + MPIWrapper::barrier(); + + _sim.finalize(); + auto graph = _sim.get_network_graph(); + auto to_global_id = [&](auto neuron_id) { + PEID rank; + NeuronID::value_type local_id; + if constexpr (std::is_same_v, NeuronID>) { + rank = this->_rank; + local_id = neuron_id.get_neuron_id(); + } else if constexpr (std::is_same_v, RankNeuronId>) { + rank = neuron_id.get_rank().get_rank(); + local_id = neuron_id.get_neuron_id().get_neuron_id(); + } else { + static_assert(std::is_same_v, RankNeuronId>, "Invalid Neuron type"); + } + + auto local_offset = rank * _config.num_neurons_per_rank; + return local_offset + local_id; + }; + + for (auto const& local_neuron_id: NeuronID::range(graph->get_number_neurons())) { + std::unordered_set neighbors; + auto push_edges = [&](auto const& edge_list) { + auto const [plastic_edges, static_edges] = edge_list; + for (auto const& [head, _weight]: plastic_edges) { + auto head_global = to_global_id(head); + if (neighbors.insert(head_global).second) { + PushEdge(to_global_id(local_neuron_id), head_global); + } + } + for (auto const& [head, _weight]: static_edges) { + auto head_global = to_global_id(head); + if (neighbors.insert(head_global).second) { + PushEdge(to_global_id(local_neuron_id), head_global); + } + } + }; + push_edges(graph->get_local_out_edges(local_neuron_id)); + push_edges(graph->get_distant_out_edges(local_neuron_id)); + push_edges(graph->get_local_in_edges(local_neuron_id)); + push_edges(graph->get_distant_in_edges(local_neuron_id)); + } + MPIWrapper::finalize(false); +} + +} // namespace kagen diff --git a/kagen/generators/brain/brain.h b/kagen/generators/brain/brain.h new file mode 100644 index 0000000..f98504d --- /dev/null +++ b/kagen/generators/brain/brain.h @@ -0,0 +1,32 @@ +#pragma once + +#include "kagen/context.h" +#include "kagen/generators/generator.h" +#include "sim/Simulation.h" + +#include +#include + +namespace kagen { +class BrainFactory : public GeneratorFactory { +public: + PGeneratorConfig NormalizeParameters(PGeneratorConfig config, PEID rank, PEID size, bool output) const final; + + std::unique_ptr Create(const PGeneratorConfig& config, PEID rank, PEID size) const final; +}; + + class BrainGenerator : public virtual Generator, private EdgeListOnlyGenerator { +public: + BrainGenerator(const PGeneratorConfig& config, const PEID rank, const PEID size); + + void GenerateEdgeList() final; + + private: + + Simulation _sim; + BrainConfig _config; + PEID _rank; + +}; + +} // namespace kagen diff --git a/kagen/kagen.cpp b/kagen/kagen.cpp index a145009..22d2271 100644 --- a/kagen/kagen.cpp +++ b/kagen/kagen.cpp @@ -162,6 +162,7 @@ std::unordered_map GetGeneratorTypeMap() { {"image", GeneratorType::IMAGE_MESH}, {"imagemesh", GeneratorType::IMAGE_MESH}, {"image-mesh", GeneratorType::IMAGE_MESH}, + {"brain", GeneratorType::BRAIN}, {"file", GeneratorType::FILE}, {"static", GeneratorType::FILE}, // @deprecated }; @@ -217,6 +218,9 @@ std::ostream& operator<<(std::ostream& out, GeneratorType generator_type) { case GeneratorType::IMAGE_MESH: return out << "image-mesh"; + case GeneratorType::BRAIN: + return out << "brain"; + case GeneratorType::FILE: return out << "file"; } @@ -276,6 +280,26 @@ std::ostream& operator<<(std::ostream& out, ImageMeshWeightModel weight_model) { return out << ""; } +std::unordered_map GetBrainSynapseCreationModelMap() { + return { + {"naive", BrainSynapseCreationModel::NAIVE}, + {"barnes-hut", BrainSynapseCreationModel::BARNES_HUT}, + {"barnes-hut-inverted", BrainSynapseCreationModel::BARNES_HUT_INVERTED}, + }; +} + +std::ostream& operator<<(std::ostream& out, BrainSynapseCreationModel brain_model) { + switch (brain_model) { + case BrainSynapseCreationModel::NAIVE: + return out << "naive"; + case BrainSynapseCreationModel::BARNES_HUT: + return out << "barnes-hut"; + case BrainSynapseCreationModel::BARNES_HUT_INVERTED: + return out << "barnes-hut-inverted"; + } + return out << ""; +} + std::unordered_map GetGraphDistributionMap() { return { {"root", GraphDistribution::ROOT}, diff --git a/kagen/kagen.h b/kagen/kagen.h index 71f1e22..8ab5baf 100644 --- a/kagen/kagen.h +++ b/kagen/kagen.h @@ -115,6 +115,7 @@ enum class GeneratorType { RMAT, IMAGE_MESH, FILE, + BRAIN }; std::unordered_map GetGeneratorTypeMap(); @@ -145,6 +146,16 @@ std::unordered_map GetImageMeshWeightModelMap std::ostream& operator<<(std::ostream& out, ImageMeshWeightModel weight_model); +enum class BrainSynapseCreationModel : std::uint8_t { + NAIVE = 0, + BARNES_HUT = 1, + BARNES_HUT_INVERTED = 2, +}; + +std::unordered_map GetBrainSynapseCreationModelMap(); + +std::ostream& operator<<(std::ostream& out, BrainSynapseCreationModel brain_model); + enum class GraphRepresentation { EDGE_LIST, CSR,