Creating Analyses#
Analyses perform graph operations on a per-timestep basis, such as community detection or degree calculation. They operate on the igraph object after edges have been created and can add new node or edge attributes to the graph. See GRAPH.ANALYSIS for more information.
Analysis classes self-register with a factory. A CMake module (cmake/modules/AutoIncludeFunc.cmake) auto-generates an
analyses.h include file from all .hpp files in src/analyses/, so adding a new analysis only requires creating
the analysis file in that directory.
This guide walks through creating a new analysis using the modularity_optimization analysis
(src/analyses/modularity_optimization.hpp) as a reference.
Analysis Base Class#
All analyses inherit from BaseAnalysis (src/analyses/base_analysis.hpp), which defines two pure virtual methods:
virtual void parse(std::string value) = 0;
virtual void execute(igraph_t* igraph, std::shared_ptr<Data> data, std::string section_name, std::string params) = 0;
parse()receives the parameter string from the input file and parses it into member variables.execute()performs the graph operation for the current timestep.
Modularity Optimization Walkthrough#
Namespace and Subclass#
#pragma once
#include "base_analysis.hpp"
namespace ChemNetworks::Analyses {
class modularity_optimization : public BaseAnalysis {
Analyses live in the ChemNetworks::Analyses namespace and inherit from BaseAnalysis.
Self-Registering Boilerplate#
const static bool s_registered;
static std::unique_ptr<BaseAnalysis> CreateMethod() {
return std::make_unique<modularity_optimization>();
}
static std::string GetName() {
return "modularity_optimization";
}
s_registeredis a static bool that triggers registration at program startup.CreateMethodis the factory method that creates an instance of this analysis.GetNamereturns the string used in input files to refer to this analysis (e.g.,modularity_optimizationinANALYSIS = modularity_optimization fast).
The registration itself happens at file scope, outside the class:
const bool modularity_optimization::s_registered = AnalysisFactory::Register(GetName(), CreateMethod);
Parsing Analysis Parameters#
public:
bool fast_greedy = true;
void parse(const std::string params) override {
auto tk = IO::LineTokenizer(params);
if (tk.count() > 1) {
IO::Log::info("Analysis `{}` only takes one parameter. Ignoring all but the first.", GetName());
}
std::string method;
if (tk.count() > 0) {
tk.parse_next_into(method);
}
if (method.empty() || method == "fast") {
this->fast_greedy = true;
} else if (method == "full") {
this->fast_greedy = false;
} else {
IO::Log::error("Analysis {} : >>{}<< is an unrecognized parameter.", GetName(), method);
}
}
The parse method receives all text following the analysis name in the input file. For example,
ANALYSIS = modularity_optimization fast passes fast to the parse method. The string is split using a
LineTokenizer, the token is matched against the supported options, and the result is stored
in a member variable for use during execution. IO::Log::error() terminates on unrecognized parameters.
Executing the Analysis#
void execute(igraph_t* igraph, std::shared_ptr<Data>, std::string section_name, std::string params) override {
igraph_real_t modularity = 0;
igraph_vector_int_t mc;
igraph_vector_int_init(&mc, 0);
if (this->fast_greedy) {
if (igraph_is_directed(igraph)) {
IO::Log::error("Analysis {} : `fast` method requires an undirected graph.", GetName());
}
igraph_vector_t modularity_vec;
igraph_vector_init(&modularity_vec, 10);
igraph_community_fastgreedy(igraph, nullptr, nullptr, &modularity_vec, &mc);
modularity = VECTOR(modularity_vec)[igraph_vector_size(&modularity_vec)];
igraph_vector_destroy(&modularity_vec);
} else {
if (igraph_vcount(igraph) > 50) {
IO::Log::log("\n***WARNING*** : The `full` modularity_optimization algorithm is very slow "
"for >50 particles.\n");
}
igraph_community_optimal_modularity(igraph, nullptr, 1, &modularity, &mc);
}
for (int i = 0; i < igraph_vcount(igraph); i++) {
SETVAN(igraph, "modularity_optimization", i, VECTOR(mc)[i]);
}
igraph_vector_int_destroy(&mc);
IO::Log::info("Executed `{}` analysis. Modularity: {}.\n", GetName(), modularity);
}
};
The execute method receives the igraph object, a Data container, the graph block name, and the parameter string.
Using the options set during parse, it performs the selected modularity optimization, assigns the resulting community
IDs as a node attribute using SETVAN, and cleans up igraph data structures.