Add graph references

This commit is contained in:
Abdelrahman Said
2026-06-28 13:49:01 +01:00
parent 0a9807e448
commit a11edf0c53
2578 changed files with 868045 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_H_
#define INFOMAP_H_
#include "core/InfomapBase.h"
#include "io/Config.h"
#include <string>
#include <utility>
#include <map>
namespace infomap {
// Wrapper class for the Python API
struct InfomapWrapper : public InfomapBase {
public:
InfomapWrapper() : InfomapBase() { }
InfomapWrapper(const std::string& flags) : InfomapBase(flags) { }
InfomapWrapper(const Config& conf) : InfomapBase(conf) { }
virtual ~InfomapWrapper() = default;
// ===================================================
// Wrapper methods
// ===================================================
void readInputData(std::string filename = "", bool accumulate = true) { m_network.readInputData(std::move(filename), accumulate); }
void addNode(unsigned int id) { m_network.addNode(id); }
void addNode(unsigned int id, std::string name) { m_network.addNode(id, std::move(name)); }
void addNode(unsigned int id, double weight) { m_network.addNode(id, weight); }
void addNode(unsigned int id, std::string name, double weight) { m_network.addNode(id, std::move(name), weight); }
void addName(unsigned int id, const std::string& name) { m_network.addName(id, name); }
std::string getName(unsigned int id) const
{
auto& names = m_network.names();
auto it = names.find(id);
return it != names.end() ? it->second : "";
}
const std::map<unsigned int, std::string>& getNames() const { return m_network.names(); }
void addPhysicalNode(unsigned int id, const std::string& name = "") { m_network.addPhysicalNode(id, name); }
void addStateNode(unsigned int id, unsigned int physId) { m_network.addStateNode(id, physId); }
void addLink(unsigned int sourceId, unsigned int targetId, double weight = 1.0) { m_network.addLink(sourceId, targetId, weight); }
void addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight) { m_network.addLink(sourceId, targetId, weight); }
void addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight = 1.0) { m_network.addMultilayerLink(layer1, n1, layer2, n2, weight); }
void addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight) { m_network.addMultilayerIntraLink(layer, n1, n2, weight); }
void addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight) { m_network.addMultilayerInterLink(layer1, n, layer2, interWeight); }
void setBipartiteStartId(unsigned int startId) { m_network.setBipartiteStartId(startId); }
std::map<std::pair<unsigned int, unsigned int>, double> getLinks(bool flow) const
{
std::map<std::pair<unsigned int, unsigned int>, double> links;
for (const auto& node : m_network.nodeLinkMap()) {
const auto sourceId = node.first.id;
for (const auto& link : node.second) {
const auto targetId = link.first.id;
links[{ sourceId, targetId }] = flow ? link.second.flow : link.second.weight;
}
}
return links;
}
std::map<unsigned int, unsigned int> getModules(int level = 1, bool states = false)
{
if (haveMemory() && !states) {
throw std::runtime_error("Cannot get modules on higher-order network without states.");
}
std::map<unsigned int, unsigned int> modules;
for (auto it = iterTree(level); !it.isEnd(); ++it) {
auto& node = *it;
if (node.isLeaf()) {
modules[states ? node.stateId : node.physicalId] = it.moduleId();
}
}
return modules;
}
using InfomapBase::codelength;
using InfomapBase::getEntropyRate;
using InfomapBase::getMultilevelModules;
using InfomapBase::iterLeafNodes;
using InfomapBase::iterTree;
using InfomapBase::run;
};
} // namespace infomap
#endif // INFOMAP_H_
@@ -0,0 +1,240 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "BiasedMapEquation.h"
#include "FlowData.h"
#include "InfoNode.h"
#include <vector>
#include <utility>
#include <cstdlib>
#include "StateNetwork.h"
namespace infomap {
double BiasedMapEquation::s_totalDegree = 1;
unsigned int BiasedMapEquation::s_numNodes = 0;
void BiasedMapEquation::setNetworkProperties(const StateNetwork& network)
{
s_totalDegree = network.sumWeightedDegree();
// Negative entropy bias is based on discrete counts, if average weight is below 1, use unweighted total degree
if (s_totalDegree < network.sumDegree()) {
s_totalDegree = network.sumDegree();
}
s_numNodes = network.numNodes();
}
double BiasedMapEquation::getIndexCodelength() const
{
return indexCodelength + indexEntropyBiasCorrection;
}
double BiasedMapEquation::getModuleCodelength() const
{
return moduleCodelength + biasedCost + moduleEntropyBiasCorrection;
}
double BiasedMapEquation::getCodelength() const
{
return codelength + biasedCost + getEntropyBiasCorrection();
}
double BiasedMapEquation::getEntropyBiasCorrection() const
{
return indexEntropyBiasCorrection + moduleEntropyBiasCorrection;
}
// ===================================================
// IO
// ===================================================
std::ostream& BiasedMapEquation::print(std::ostream& out) const
{
out << indexCodelength << " + " << moduleCodelength;
if (preferredNumModules != 0) {
out << " + " << biasedCost;
}
if (useEntropyBiasCorrection) {
out << " + " << getEntropyBiasCorrection();
}
out << " = " << io::toPrecision(getCodelength());
return out;
}
std::ostream& operator<<(std::ostream& out, const BiasedMapEquation& mapEq)
{
return mapEq.print(out);
}
// ===================================================
// Init
// ===================================================
void BiasedMapEquation::init(const Config& config)
{
Log(3) << "BiasedMapEquation::init()...\n";
preferredNumModules = config.preferredNumberOfModules;
useEntropyBiasCorrection = config.entropyBiasCorrection;
entropyBiasCorrectionMultiplier = config.entropyBiasCorrectionMultiplier;
}
void BiasedMapEquation::initNetwork(InfoNode& root)
{
Log(3) << "BiasedMapEquation::initNetwork()...\n";
Base::initNetwork(root);
}
void BiasedMapEquation::initPartition(std::vector<InfoNode*>& nodes)
{
calculateCodelength(nodes);
}
// ===================================================
// Codelength
// ===================================================
double BiasedMapEquation::calcNumModuleCost(unsigned int numModules) const
{
if (preferredNumModules == 0) return 0;
int deltaNumModules = numModules - preferredNumModules;
return 1 * std::abs(deltaNumModules);
}
double BiasedMapEquation::calcIndexEntropyBiasCorrection(unsigned int numModules) const
{
return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * (numModules - 1) / (2 * s_totalDegree) : 0;
}
double BiasedMapEquation::calcModuleEntropyBiasCorrection() const
{
return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * s_numNodes / (2 * s_totalDegree) : 0;
}
double BiasedMapEquation::calcEntropyBiasCorrection(unsigned int numModules) const
{
return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * (numModules - 1 + s_numNodes) / (2 * s_totalDegree) : 0;
}
void BiasedMapEquation::calculateCodelength(std::vector<InfoNode*>& nodes)
{
calculateCodelengthTerms(nodes);
calculateCodelengthFromCodelengthTerms();
currentNumModules = nodes.size();
biasedCost = calcNumModuleCost(currentNumModules);
indexEntropyBiasCorrection = calcIndexEntropyBiasCorrection(currentNumModules);
moduleEntropyBiasCorrection = calcModuleEntropyBiasCorrection();
}
double BiasedMapEquation::calcCodelength(const InfoNode& parent) const
{
return parent.isLeafModule()
? calcCodelengthOnModuleOfLeafNodes(parent)
: calcCodelengthOnModuleOfModules(parent);
}
double BiasedMapEquation::calcCodelengthOnModuleOfModules(const InfoNode& parent) const
{
double L = Base::calcCodelengthOnModuleOfModules(parent);
if (!useEntropyBiasCorrection)
return L;
return L + entropyBiasCorrectionMultiplier * parent.childDegree() / (2 * s_totalDegree);
}
double BiasedMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
{
double L = Base::calcCodelength(parent);
if (!useEntropyBiasCorrection)
return L;
return L + entropyBiasCorrectionMultiplier * parent.childDegree() / (2 * s_totalDegree);
}
int BiasedMapEquation::getDeltaNumModulesIfMoving(unsigned int oldModule,
unsigned int newModule,
std::vector<unsigned int>& moduleMembers)
{
bool removeOld = moduleMembers[oldModule] == 1;
bool createNew = moduleMembers[newModule] == 0;
int deltaNumModules = removeOld && !createNew ? -1 : (!removeOld && createNew ? 1 : 0);
return deltaNumModules;
}
double BiasedMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers)
{
double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
if (preferredNumModules == 0)
return deltaL;
int deltaNumModules = getDeltaNumModulesIfMoving(oldModuleDelta.module, newModuleDelta.module, moduleMembers);
double deltaBiasedCost = calcNumModuleCost(currentNumModules + deltaNumModules) - biasedCost;
double deltaEntropyBiasCorrection = calcEntropyBiasCorrection(currentNumModules + deltaNumModules) - getEntropyBiasCorrection();
return deltaL + deltaBiasedCost + deltaEntropyBiasCorrection;
}
// ===================================================
// Consolidation
// ===================================================
void BiasedMapEquation::updateCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers)
{
Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
if (preferredNumModules == 0)
return;
int deltaNumModules = getDeltaNumModulesIfMoving(oldModuleDelta.module, newModuleDelta.module, moduleMembers);
currentNumModules += deltaNumModules;
biasedCost = calcNumModuleCost(currentNumModules);
indexEntropyBiasCorrection = calcIndexEntropyBiasCorrection(currentNumModules);
moduleEntropyBiasCorrection = calcModuleEntropyBiasCorrection();
}
void BiasedMapEquation::consolidateModules(std::vector<InfoNode*>& modules)
{
unsigned int numModules = 0;
for (auto& module : modules) {
if (module == nullptr)
continue;
++numModules;
}
currentNumModules = numModules;
}
#if 0
// ===================================================
// Debug
// ===================================================
void BiasedMapEquation::printDebug() const
{
std::cout << "BiasedMapEquation\n";
Base::printDebug();
}
#endif
} // namespace infomap
@@ -0,0 +1,180 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef BIASED_MAPEQUATION_H_
#define BIASED_MAPEQUATION_H_
#include "MapEquation.h"
#include "FlowData.h"
#include "../utils/Log.h"
#include <vector>
#include <set>
#include <map>
#include <utility>
namespace infomap {
class InfoNode;
class StateNetwork;
class BiasedMapEquation : private MapEquation<> {
using Base = MapEquation<>;
public:
using FlowDataType = FlowData;
using DeltaFlowDataType = DeltaFlow;
// ===================================================
// Getters
// ===================================================
double getIndexCodelength() const override;
double getModuleCodelength() const override;
double getCodelength() const override;
double getEntropyBiasCorrection() const;
// ===================================================
// IO
// ===================================================
std::ostream& print(std::ostream& out) const override;
friend std::ostream& operator<<(std::ostream&, const BiasedMapEquation&);
// ===================================================
// Init
// ===================================================
void init(const Config& config) override;
void initTree(InfoNode& /*root*/) override { }
void initNetwork(InfoNode& root) override;
using Base::initSuperNetwork;
using Base::initSubNetwork;
void initPartition(std::vector<InfoNode*>& nodes) override;
// ===================================================
// Codelength
// ===================================================
double calcCodelength(const InfoNode& parent) const override;
using Base::addMemoryContributions;
double getDeltaCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers) override;
// ===================================================
// Consolidation
// ===================================================
void updateCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers) override;
void consolidateModules(std::vector<InfoNode*>& modules) override;
#if 0
// ===================================================
// Debug
// ===================================================
void printDebug() const override;
#endif
private:
// ===================================================
// Private member functions
// ===================================================
double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override;
double calcCodelengthOnModuleOfModules(const InfoNode& parent) const override;
static int getDeltaNumModulesIfMoving(unsigned int oldModule, unsigned int newModule, std::vector<unsigned int>& moduleMembers);
// ===================================================
// Init
// ===================================================
// ===================================================
// Codelength
// ===================================================
void calculateCodelength(std::vector<InfoNode*>& nodes) override;
using Base::calculateCodelengthTerms;
using Base::calculateCodelengthFromCodelengthTerms;
double calcNumModuleCost(unsigned int numModules) const;
double calcIndexEntropyBiasCorrection(unsigned int numModules) const;
double calcModuleEntropyBiasCorrection() const;
double calcEntropyBiasCorrection(unsigned int numModules) const;
// ===================================================
// Consolidation
// ===================================================
public:
// ===================================================
// Public member variables
// ===================================================
using Base::codelength;
using Base::indexCodelength;
using Base::moduleCodelength;
private:
// ===================================================
// Private member variables
// ===================================================
using Base::enter_log_enter;
using Base::enterFlow;
using Base::enterFlow_log_enterFlow;
using Base::exit_log_exit;
using Base::flow_log_flow; // node.(flow + exitFlow)
using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same
// For hierarchical
using Base::exitNetworkFlow;
using Base::exitNetworkFlow_log_exitNetworkFlow;
// For biased
unsigned int preferredNumModules = 0;
unsigned int currentNumModules = 0;
double biasedCost = 0.0;
// For entropy bias correction
bool useEntropyBiasCorrection = false;
double entropyBiasCorrectionMultiplier = 1;
double indexEntropyBiasCorrection = 0;
double moduleEntropyBiasCorrection = 0;
static double s_totalDegree;
static unsigned int s_numNodes;
public:
static void setNetworkProperties(const StateNetwork& network);
};
} // namespace infomap
#endif // BIASED_MAPEQUATION_H_
@@ -0,0 +1,165 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef FLOWDATA_H_
#define FLOWDATA_H_
#include <ostream>
#include <utility>
namespace infomap {
struct FlowData {
double flow = 0.0;
double enterFlow = 0.0;
double exitFlow = 0.0;
double teleportFlow = 0.0;
double teleportSourceFlow = 0.0;
double teleportWeight = 0.0;
double danglingFlow = 0.0;
FlowData() = default;
FlowData(double flow) : flow(flow) { }
FlowData& operator+=(const FlowData& other)
{
flow += other.flow;
enterFlow += other.enterFlow;
exitFlow += other.exitFlow;
teleportFlow += other.teleportFlow;
teleportSourceFlow += other.teleportSourceFlow;
teleportWeight += other.teleportWeight;
danglingFlow += other.danglingFlow;
return *this;
}
FlowData& operator-=(const FlowData& other)
{
flow -= other.flow;
enterFlow -= other.enterFlow;
exitFlow -= other.exitFlow;
teleportFlow -= other.teleportFlow;
teleportSourceFlow -= other.teleportSourceFlow;
teleportWeight -= other.teleportWeight;
danglingFlow -= other.danglingFlow;
return *this;
}
friend std::ostream& operator<<(std::ostream& out, const FlowData& data)
{
return out << "flow: " << data.flow << ", enter: " << data.enterFlow << ", exit: " << data.exitFlow
<< ", teleWeight: " << data.teleportWeight << ", danglingFlow: " << data.danglingFlow
<< ", teleFlow: " << data.teleportFlow;
}
};
struct DeltaFlow {
unsigned int module = 0;
double deltaExit = 0.0;
double deltaEnter = 0.0;
unsigned int count = 0;
explicit DeltaFlow(unsigned int module, double deltaExit, double deltaEnter)
: module(module),
deltaExit(deltaExit),
deltaEnter(deltaEnter) { }
DeltaFlow() = default;
DeltaFlow(const DeltaFlow&) = default;
DeltaFlow(DeltaFlow&&) = default;
DeltaFlow& operator=(const DeltaFlow&) = default;
DeltaFlow& operator=(DeltaFlow&&) = default;
virtual ~DeltaFlow() = default;
DeltaFlow& operator+=(const DeltaFlow& other)
{
module = other.module;
deltaExit += other.deltaExit;
deltaEnter += other.deltaEnter;
++count;
return *this;
}
virtual void reset()
{
module = 0;
deltaExit = 0.0;
deltaEnter = 0.0;
count = 0;
}
friend void swap(DeltaFlow& first, DeltaFlow& second) noexcept
{
std::swap(first.module, second.module);
std::swap(first.deltaExit, second.deltaExit);
std::swap(first.deltaEnter, second.deltaEnter);
std::swap(first.count, second.count);
}
friend std::ostream& operator<<(std::ostream& out, const DeltaFlow& data)
{
return out << "module: " << data.module << ", deltaEnter: " << data.deltaEnter << ", deltaExit: " << data.deltaExit << ", count: " << data.count;
}
};
struct MemDeltaFlow : DeltaFlow {
double sumDeltaPlogpPhysFlow = 0.0;
double sumPlogpPhysFlow = 0.0;
MemDeltaFlow() = default;
explicit MemDeltaFlow(unsigned int module, double deltaExit, double deltaEnter, double sumDeltaPlogpPhysFlow = 0.0, double sumPlogpPhysFlow = 0.0)
: DeltaFlow(module, deltaExit, deltaEnter),
sumDeltaPlogpPhysFlow(sumDeltaPlogpPhysFlow),
sumPlogpPhysFlow(sumPlogpPhysFlow) { }
MemDeltaFlow& operator+=(const MemDeltaFlow& other)
{
DeltaFlow::operator+=(other);
sumDeltaPlogpPhysFlow += other.sumDeltaPlogpPhysFlow;
sumPlogpPhysFlow += other.sumPlogpPhysFlow;
return *this;
}
void reset() override
{
DeltaFlow::reset();
sumDeltaPlogpPhysFlow = 0.0;
sumPlogpPhysFlow = 0.0;
}
friend void swap(MemDeltaFlow& first, MemDeltaFlow& second) noexcept
{
swap(static_cast<DeltaFlow&>(first), static_cast<DeltaFlow&>(second));
std::swap(first.sumDeltaPlogpPhysFlow, second.sumDeltaPlogpPhysFlow);
std::swap(first.sumPlogpPhysFlow, second.sumPlogpPhysFlow);
}
friend std::ostream& operator<<(std::ostream& out, const MemDeltaFlow& data)
{
return out << "module: " << data.module << ", deltaEnter: " << data.deltaEnter << ", deltaExit: " << data.deltaExit << ", count: " << data.count << ", sumDeltaPlogpPhysFlow: " << data.sumDeltaPlogpPhysFlow << ", sumPlogpPhysFlow: " << data.sumPlogpPhysFlow;
}
};
struct PhysData {
unsigned int physNodeIndex;
double sumFlowFromM2Node; // The amount of flow from the memory node in this physical node
explicit PhysData(unsigned int physNodeIndex, double sumFlowFromM2Node = 0.0)
: physNodeIndex(physNodeIndex), sumFlowFromM2Node(sumFlowFromM2Node) { }
friend std::ostream& operator<<(std::ostream& out, const PhysData& data)
{
return out << "physNodeIndex: " << data.physNodeIndex << ", sumFlowFromM2Node: " << data.sumFlowFromM2Node;
}
};
} // namespace infomap
#endif // FLOWDATA_H_
@@ -0,0 +1,26 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "InfoEdge.h"
#include "InfoNode.h"
namespace infomap {
InfoNode& infomap::InfoEdge::other(InfoNode& node) const
{
return (node == *source) ? *target : *source;
}
std::ostream& operator<<(std::ostream& out, const InfoEdge& edge)
{
return out << "(" << *edge.source << ") -> (" << *edge.target << "), flow: "
<< edge.data.flow;
}
} // namespace infomap
@@ -0,0 +1,47 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOEDGE_H_
#define INFOEDGE_H_
#include <iostream>
namespace infomap {
struct EdgeData {
public:
EdgeData() = default;
EdgeData(double weight, double flow) : weight(weight), flow(flow) { }
double weight;
double flow;
};
class InfoNode;
class InfoEdge {
public:
InfoEdge(InfoNode& source, InfoNode& target, double weight, double flow)
: data(weight, flow),
source(&source),
target(&target) { }
InfoNode& other(InfoNode& node) const;
friend std::ostream& operator<<(std::ostream& out, const InfoEdge& edge);
EdgeData data;
InfoNode* source;
InfoNode* target;
};
} // namespace infomap
#endif // INFOEDGE_H_
@@ -0,0 +1,405 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "InfoNode.h"
#include "InfomapBase.h"
#include <algorithm>
namespace infomap {
InfoNode::~InfoNode() noexcept
{
if (m_infomap != nullptr) {
delete m_infomap;
}
deleteChildren();
if (next != nullptr)
next->previous = previous;
if (previous != nullptr)
previous->next = next;
if (parent != nullptr) {
if (parent->firstChild == this)
parent->firstChild = next;
if (parent->lastChild == this)
parent->lastChild = previous;
}
// Delete outgoing edges.
// TODO: Renders ingoing edges invalid. Assume or assert that all nodes on the same level are deleted?
for (edge_iterator outEdgeIt(begin_outEdge());
outEdgeIt != end_outEdge();
++outEdgeIt) {
delete *outEdgeIt;
}
}
InfomapBase& InfoNode::setInfomap(InfomapBase* infomap)
{
disposeInfomap();
m_infomap = infomap;
if (infomap == nullptr)
throw std::logic_error("InfoNode::setInfomap(...) called with null infomap");
return *m_infomap;
}
InfomapBase& InfoNode::getInfomap()
{
if (m_infomap == nullptr)
throw std::logic_error("InfoNode::getInfomap() called but infomap is null");
return *m_infomap;
}
const InfomapBase& InfoNode::getInfomap() const
{
if (m_infomap == nullptr)
throw std::logic_error("InfoNode::getInfomap() called but infomap is null");
return *m_infomap;
}
InfoNode* InfoNode::getInfomapRoot() noexcept
{
return m_infomap != nullptr ? &m_infomap->root() : nullptr;
}
InfoNode const* InfoNode::getInfomapRoot() const noexcept
{
return m_infomap != nullptr ? &m_infomap->root() : nullptr;
}
bool InfoNode::disposeInfomap() noexcept
{
if (m_infomap != nullptr) {
delete m_infomap;
m_infomap = nullptr;
return true;
}
return false;
}
unsigned int InfoNode::depth() const noexcept
{
unsigned int depth = 0;
InfoNode* n = parent;
while (n != nullptr) {
++depth;
n = n->parent;
}
return depth;
}
unsigned int InfoNode::firstDepthBelow() const noexcept
{
unsigned int depthBelow = 0;
InfoNode* child = firstChild;
while (child != nullptr) {
++depthBelow;
child = child->firstChild;
}
return depthBelow;
}
unsigned int InfoNode::childIndex() const noexcept
{
unsigned int childIndex = 0;
const InfoNode* n(this);
while (n->previous) {
n = n->previous;
++childIndex;
}
return childIndex;
}
std::vector<unsigned int> InfoNode::calculatePath() const noexcept
{
const InfoNode* current = this;
std::vector<unsigned int> path;
while (current->parent != nullptr) {
path.push_back(current->childIndex() + 1);
current = current->parent;
if (current->owner != nullptr) {
current = current->owner;
}
}
std::reverse(path.begin(), path.end());
return path;
}
unsigned int InfoNode::infomapChildDegree() const noexcept
{
return m_infomap == nullptr ? childDegree() : m_infomap->root().childDegree();
}
void InfoNode::addChild(InfoNode* child) noexcept
{
if (firstChild == nullptr) {
child->previous = nullptr;
firstChild = child;
} else {
child->previous = lastChild;
lastChild->next = child;
}
lastChild = child;
child->next = nullptr;
child->parent = this;
++m_childDegree;
}
void InfoNode::releaseChildren() noexcept
{
firstChild = nullptr;
lastChild = nullptr;
m_childDegree = 0;
}
InfoNode& InfoNode::replaceChildrenWithOneNode()
{
if (childDegree() == 1)
return *firstChild;
if (firstChild == nullptr)
throw std::logic_error("replaceChildrenWithOneNode called on a node without any children.");
if (firstChild->firstChild == nullptr)
throw std::logic_error("replaceChildrenWithOneNode called on a node without any grandchildren.");
auto* middleNode = new InfoNode();
InfoNode::child_iterator nodeIt = begin_child();
unsigned int numOriginalChildrenLeft = m_childDegree;
auto d0 = m_childDegree;
do {
InfoNode* n = nodeIt.current();
++nodeIt;
middleNode->addChild(n);
} while (--numOriginalChildrenLeft != 0);
releaseChildren();
addChild(middleNode);
auto d1 = middleNode->replaceChildrenWithGrandChildren();
if (d1 != d0)
throw std::logic_error("replaceChildrenWithOneNode replaced different number of children as having before");
return *middleNode;
}
unsigned int InfoNode::replaceChildrenWithGrandChildren() noexcept
{
if (firstChild == nullptr)
return 0;
InfoNode::child_iterator nodeIt = begin_child();
unsigned int numOriginalChildrenLeft = m_childDegree;
unsigned int numChildrenReplaced = 0;
do {
InfoNode* n = nodeIt.current();
++nodeIt;
numChildrenReplaced += n->replaceWithChildren();
} while (--numOriginalChildrenLeft != 0);
return numChildrenReplaced;
}
unsigned int InfoNode::replaceWithChildren() noexcept
{
if (isLeaf() || isRoot())
return 0;
// Re-parent children
unsigned int deltaChildDegree = 0;
InfoNode* child = firstChild;
do {
child->parent = parent;
child = child->next;
++deltaChildDegree;
} while (child != nullptr);
parent->m_childDegree += deltaChildDegree - 1; // -1 as this node is deleted
firstChild->previous = previous;
lastChild->next = next;
if (parent->firstChild == this) {
parent->firstChild = firstChild;
} else {
previous->next = firstChild;
}
if (parent->lastChild == this) {
parent->lastChild = lastChild;
} else {
next->previous = lastChild;
}
// Release connected nodes before delete, otherwise children are deleted and neighbours are reconnected.
firstChild = nullptr;
lastChild = nullptr;
next = nullptr;
previous = nullptr;
parent = nullptr;
delete this;
return 1;
}
void InfoNode::replaceChildrenWithGrandChildrenDebug() noexcept
{
if (firstChild == nullptr)
return;
InfoNode::child_iterator nodeIt = begin_child();
unsigned int numOriginalChildrenLeft = m_childDegree;
do {
InfoNode* n = nodeIt.current();
++nodeIt;
n->replaceWithChildrenDebug();
} while (--numOriginalChildrenLeft != 0);
}
void InfoNode::replaceWithChildrenDebug() noexcept
{
if (isLeaf() || isRoot())
return;
// Re-parent children
unsigned int deltaChildDegree = 0;
InfoNode* child = firstChild;
do {
child->parent = parent;
child = child->next;
++deltaChildDegree;
} while (child != nullptr);
parent->m_childDegree += deltaChildDegree - 1; // -1 as this node is deleted
if (parent->firstChild == this) {
parent->firstChild = firstChild;
} else {
previous->next = firstChild;
firstChild->previous = previous;
}
if (parent->lastChild == this) {
parent->lastChild = lastChild;
} else {
next->previous = lastChild;
lastChild->next = next;
}
// Release connected nodes before delete, otherwise children are deleted and neighbours are reconnected.
firstChild = nullptr;
lastChild = nullptr;
next = nullptr;
previous = nullptr;
parent = nullptr;
delete this;
}
void InfoNode::remove(bool removeChildren) noexcept
{
firstChild = removeChildren ? nullptr : firstChild;
delete this;
}
void InfoNode::deleteChildren() noexcept
{
if (firstChild == nullptr)
return;
child_iterator nodeIt = begin_child();
do {
InfoNode* n = nodeIt.current();
++nodeIt;
delete n;
} while (nodeIt.current() != nullptr);
firstChild = nullptr;
lastChild = nullptr;
m_childDegree = 0;
}
void InfoNode::calcChildDegree() noexcept
{
m_childrenChanged = false;
if (firstChild == nullptr)
m_childDegree = 0;
else if (firstChild == lastChild)
m_childDegree = 1;
else {
m_childDegree = 0;
for (auto& child : children()) {
(void)child;
++m_childDegree;
}
}
}
void InfoNode::setChildDegree(unsigned int value) noexcept
{
m_childDegree = value;
m_childrenChanged = false;
}
void InfoNode::initClean() noexcept
{
releaseChildren();
previous = next = parent = nullptr;
physicalNodes.clear();
}
void InfoNode::sortChildrenOnFlow(bool recurse) noexcept
{
if (childDegree() == 0)
return;
std::vector<InfoNode*> nodes(childDegree());
double lastFlow = 1.0;
bool isSorted = true;
unsigned int i = 0;
for (InfoNode& child : children()) {
if (child.data.flow > lastFlow) {
isSorted = false;
}
nodes[i] = &child;
lastFlow = child.data.flow;
++i;
}
if (!isSorted) {
std::sort(nodes.begin(), nodes.end(), [](const InfoNode* a, const InfoNode* b) {
return b->data.flow < a->data.flow;
});
releaseChildren();
for (auto node : nodes) {
addChild(node);
}
}
if (recurse) {
for (InfoNode& child : children()) {
auto newRoot = child.getInfomapRoot();
InfoNode& node = newRoot ? *newRoot : child;
node.sortChildrenOnFlow(recurse);
}
}
}
unsigned int InfoNode::collapseChildren() noexcept
{
std::swap(collapsedFirstChild, firstChild);
std::swap(collapsedLastChild, lastChild);
unsigned int numCollapsedChildren = childDegree();
releaseChildren();
return numCollapsedChildren;
}
unsigned int InfoNode::expandChildren()
{
bool haveCollapsedChildren = collapsedFirstChild != nullptr;
if (haveCollapsedChildren) {
if (firstChild != nullptr || lastChild != nullptr)
throw std::logic_error("Expand collapsed children called on a node that already has children.");
std::swap(collapsedFirstChild, firstChild);
std::swap(collapsedLastChild, lastChild);
calcChildDegree();
return childDegree();
}
return 0;
}
} // namespace infomap
@@ -0,0 +1,374 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFONODE_H_
#define INFONODE_H_
#include "FlowData.h"
#include "InfoEdge.h"
#include "iterators/infomapIterators.h"
#include "iterators/IterWrapper.h"
#include "../utils/MetaCollection.h"
#include <stdexcept>
#include <memory>
#include <iostream>
#include <vector>
#include <limits>
namespace infomap {
class InfomapBase;
class InfoNode {
public:
using child_iterator = ChildIterator<InfoNode*>;
using const_child_iterator = ChildIterator<InfoNode const*>;
using infomap_child_iterator = InfomapChildIterator<InfoNode*>;
using const_infomap_child_iterator = InfomapChildIterator<InfoNode const*>;
using tree_iterator = TreeIterator<InfoNode*>;
using const_tree_iterator = TreeIterator<InfoNode const*>;
using leaf_node_iterator = LeafNodeIterator<InfoNode*>;
using const_leaf_node_iterator = LeafNodeIterator<InfoNode const*>;
using leaf_module_iterator = LeafModuleIterator<InfoNode*>;
using const_leaf_module_iterator = LeafModuleIterator<InfoNode const*>;
using post_depth_first_iterator = DepthFirstIterator<InfoNode*, false>;
using const_post_depth_first_iterator = DepthFirstIterator<InfoNode const*, false>;
using edge_iterator = std::vector<InfoEdge*>::iterator;
using const_edge_iterator = std::vector<InfoEdge*>::const_iterator;
using edge_iterator_wrapper = IterWrapper<edge_iterator>;
using const_edge_iterator_wrapper = IterWrapper<const_edge_iterator>;
using infomap_iterator_wrapper = IterWrapper<tree_iterator>;
using const_infomap_iterator_wrapper = IterWrapper<const_tree_iterator>;
using child_iterator_wrapper = IterWrapper<child_iterator>;
using const_child_iterator_wrapper = IterWrapper<const_child_iterator>;
using infomap_child_iterator_wrapper = IterWrapper<infomap_child_iterator>;
using const_infomap_child_iterator_wrapper = IterWrapper<const_infomap_child_iterator>;
public:
FlowData data;
unsigned int index = 0; // Temporary index used in finding best module
unsigned int stateId = 0; // Unique state node id for the leaf nodes
unsigned int physicalId = 0; // Physical id equals stateId for first order networks, otherwise can be non-unique
unsigned int layerId = 0; // Layer id for multilayer networks
std::vector<int> metaData; // Categorical value for each meta data dimension
InfoNode* owner = nullptr; // Infomap owner (if this is an Infomap root)
InfoNode* parent = nullptr;
InfoNode* previous = nullptr; // sibling
InfoNode* next = nullptr; // sibling
InfoNode* firstChild = nullptr;
InfoNode* lastChild = nullptr;
InfoNode* collapsedFirstChild = nullptr;
InfoNode* collapsedLastChild = nullptr;
double codelength = 0.0; // TODO: Better design for hierarchical stuff!?
bool dirty = false;
std::vector<PhysData> physicalNodes;
MetaCollection metaCollection; // For modules
std::vector<unsigned int> stateNodes; // For physically aggregated nodes
private:
unsigned int m_childDegree = 0;
bool m_childrenChanged = false;
unsigned int m_numLeafMembers = 0;
std::vector<InfoEdge*> m_outEdges;
std::vector<InfoEdge*> m_inEdges;
InfomapBase* m_infomap = nullptr;
public:
InfoNode(const FlowData& flowData)
: data(flowData) {};
// For first order nodes, physicalId equals stateId
InfoNode(const FlowData& flowData, unsigned int stateId)
: data(flowData), stateId(stateId), physicalId(stateId) {};
InfoNode(const FlowData& flowData, unsigned int stateId, unsigned int physicalId)
: data(flowData), stateId(stateId), physicalId(physicalId) {};
InfoNode(const FlowData& flowData, unsigned int stateId, unsigned int physicalId, unsigned int layerId)
: data(flowData), stateId(stateId), physicalId(physicalId), layerId(layerId) {};
InfoNode() = default;
InfoNode(const InfoNode& other)
: data(other.data),
index(other.index),
stateId(other.stateId),
physicalId(other.physicalId),
layerId(other.layerId),
metaData(other.metaData),
parent(other.parent),
previous(other.previous),
next(other.next),
firstChild(other.firstChild),
lastChild(other.lastChild),
collapsedFirstChild(other.collapsedFirstChild),
collapsedLastChild(other.collapsedLastChild),
codelength(other.codelength),
dirty(other.dirty),
metaCollection(other.metaCollection),
m_childDegree(other.m_childDegree),
m_childrenChanged(other.m_childrenChanged),
m_numLeafMembers(other.m_numLeafMembers) { }
~InfoNode() noexcept;
InfoNode& operator=(const InfoNode& other)
{
data = other.data;
index = other.index;
stateId = other.stateId;
physicalId = other.physicalId;
layerId = other.layerId;
metaData = other.metaData;
parent = other.parent;
previous = other.previous;
next = other.next;
firstChild = other.firstChild;
lastChild = other.lastChild;
collapsedFirstChild = other.collapsedFirstChild;
collapsedLastChild = other.collapsedLastChild;
codelength = other.codelength;
dirty = other.dirty;
metaCollection = other.metaCollection;
m_childDegree = other.m_childDegree;
m_childrenChanged = other.m_childrenChanged;
m_numLeafMembers = other.m_numLeafMembers;
return *this;
}
// ---------------------------- Getters ----------------------------
unsigned int getMetaData(unsigned int dimension = 0) noexcept
{
if (dimension >= metaData.size()) {
return 0;
}
auto meta = metaData[dimension];
return meta < 0 ? 0 : static_cast<unsigned int>(meta);
}
// ---------------------------- Infomap ----------------------------
InfomapBase& getInfomap();
const InfomapBase& getInfomap() const;
InfomapBase& setInfomap(InfomapBase*);
InfoNode* getInfomapRoot() noexcept;
InfoNode const* getInfomapRoot() const noexcept;
/**
* Dispose the Infomap instance if it exists
* @return true if an existing Infomap instance was deleted
*/
bool disposeInfomap() noexcept;
/**
* Number of physical nodes in memory nodes
*/
unsigned int numPhysicalNodes() const noexcept { return physicalNodes.size(); }
// ---------------------------- Tree iterators ----------------------------
// Default iteration on children
child_iterator begin() noexcept { return { this }; }
child_iterator end() noexcept { return { nullptr }; }
const_child_iterator begin() const noexcept { return { this }; }
const_child_iterator end() const noexcept { return { nullptr }; }
child_iterator begin_child() noexcept { return { this }; }
child_iterator end_child() noexcept { return { nullptr }; }
const_child_iterator begin_child() const noexcept { return { this }; }
const_child_iterator end_child() const noexcept { return { nullptr }; }
child_iterator_wrapper children() noexcept { return { { this }, { nullptr } }; }
const_child_iterator_wrapper children() const noexcept { return { { this }, { nullptr } }; }
infomap_child_iterator_wrapper infomap_children() noexcept { return { { this }, { nullptr } }; }
const_infomap_child_iterator_wrapper infomap_children() const noexcept { return { { this }, { nullptr } }; }
post_depth_first_iterator begin_post_depth_first() noexcept { return { this }; }
leaf_node_iterator begin_leaf_nodes() noexcept { return { this }; }
leaf_module_iterator begin_leaf_modules() noexcept { return { this }; }
tree_iterator begin_tree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) noexcept { return { this, static_cast<int>(maxClusterLevel) }; }
tree_iterator end_tree() noexcept { return { nullptr }; }
const_tree_iterator begin_tree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) const noexcept { return { this, static_cast<int>(maxClusterLevel) }; }
const_tree_iterator end_tree() const noexcept { return { nullptr }; }
infomap_iterator_wrapper infomapTree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) noexcept { return { { this, static_cast<int>(maxClusterLevel) }, { nullptr } }; }
const_infomap_iterator_wrapper infomapTree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) const noexcept { return { { this, static_cast<int>(maxClusterLevel) }, { nullptr } }; }
// ---------------------------- Graph iterators ----------------------------
edge_iterator begin_outEdge() noexcept { return m_outEdges.begin(); }
edge_iterator end_outEdge() noexcept { return m_outEdges.end(); }
edge_iterator begin_inEdge() noexcept { return m_inEdges.begin(); }
edge_iterator end_inEdge() noexcept { return m_inEdges.end(); }
edge_iterator_wrapper outEdges() noexcept { return { m_outEdges }; }
edge_iterator_wrapper inEdges() noexcept { return { m_inEdges }; }
// ---------------------------- Capacity ----------------------------
unsigned int childDegree() const noexcept { return m_childDegree; }
bool isLeaf() const noexcept { return firstChild == nullptr; }
// TODO: Safe to assume all children are leaves if first child is leaf?
bool isLeafModule() const noexcept { return m_infomap == nullptr && firstChild != nullptr && firstChild->firstChild == nullptr; }
bool isRoot() const noexcept { return parent == nullptr; }
unsigned int depth() const noexcept;
unsigned int firstDepthBelow() const noexcept;
unsigned int numLeafMembers() const noexcept { return m_numLeafMembers; }
bool isDangling() const noexcept { return m_outEdges.empty(); }
unsigned int outDegree() const noexcept { return m_outEdges.size(); }
unsigned int inDegree() const noexcept { return m_inEdges.size(); }
unsigned int degree() const noexcept { return outDegree() + inDegree(); }
// ---------------------------- Order ----------------------------
bool isFirst() const noexcept { return !parent || parent->firstChild == this; }
bool isLast() const noexcept { return !parent || parent->lastChild == this; }
unsigned int childIndex() const noexcept;
// Generate 1-based tree path
std::vector<unsigned int> calculatePath() const noexcept;
unsigned int infomapChildDegree() const noexcept;
unsigned int id() const noexcept { return stateId; }
// ---------------------------- Operators ----------------------------
bool operator==(const InfoNode& rhs) const noexcept { return this == &rhs; }
bool operator!=(const InfoNode& rhs) const noexcept { return this != &rhs; }
friend std::ostream& operator<<(std::ostream& out, const InfoNode& node) noexcept
{
if (node.isLeaf())
out << "[" << node.physicalId << "]";
else
out << "[module]";
return out;
}
// ---------------------------- Mutators ----------------------------
/**
* Clear a cloned node to initial state
*/
void initClean() noexcept;
void sortChildrenOnFlow(bool recurse = true) noexcept;
/**
* Release the children and store the child pointers for later expansion
* @return the number of children collapsed
*/
unsigned int collapseChildren() noexcept;
/**
* Expand collapsed children
* @return the number of collapsed children expanded
*/
unsigned int expandChildren();
// ------ OLD -----
// After change, set the child degree if known instead of lazily computing it by traversing the linked list
void setChildDegree(unsigned int value) noexcept;
void setNumLeafNodes(unsigned int value) noexcept { m_numLeafMembers = value; }
void addChild(InfoNode* child) noexcept;
void releaseChildren() noexcept;
/**
* If not already having a single child, replace children
* with a single new node, assuming grandchildren.
* @return the single child
*/
InfoNode& replaceChildrenWithOneNode();
/**
* @return 1 if the node is removed, otherwise 0
*/
unsigned int replaceWithChildren() noexcept;
void replaceWithChildrenDebug() noexcept;
/**
* @return The number of children removed
*/
unsigned int replaceChildrenWithGrandChildren() noexcept;
void replaceChildrenWithGrandChildrenDebug() noexcept;
void remove(bool removeChildren) noexcept;
void deleteChildren() noexcept;
void addOutEdge(InfoNode& target, double weight, double flow = 0.0) noexcept
{
auto* edge = new InfoEdge(*this, target, weight, flow);
m_outEdges.push_back(edge);
target.m_inEdges.push_back(edge);
}
private:
void calcChildDegree() noexcept;
};
} // namespace infomap
#endif // INFONODE_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,586 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_BASE_H_
#define INFOMAP_BASE_H_
#include "InfomapConfig.h"
#include "InfoEdge.h"
#include "InfoNode.h"
#include "InfomapOptimizerBase.h"
#include "iterators/InfomapIterator.h"
#include "../io/ClusterMap.h"
#include "../io/Network.h"
#include "../io/Output.h"
#include "../utils/Log.h"
#include "../utils/Date.h"
#include "../utils/Stopwatch.h"
#include <vector>
#include <deque>
#include <map>
#include <limits>
#include <string>
#include <iostream>
#include <sstream>
namespace infomap {
namespace detail {
class PartitionQueue;
struct PerLevelStat;
} // namespace detail
class InfomapBase : public InfomapConfig<InfomapBase> {
template <typename Objective>
friend class InfomapOptimizer;
void initOptimizer(bool forceNoMemory = false);
public:
using PartitionQueue = detail::PartitionQueue;
InfomapBase() : InfomapConfig<InfomapBase>() { initOptimizer(); }
explicit InfomapBase(const Config& conf) : InfomapConfig<InfomapBase>(conf), m_network(conf) { initOptimizer(); }
explicit InfomapBase(const std::string& flags, bool isCli = false) : InfomapConfig<InfomapBase>(flags, isCli)
{
initOptimizer();
m_network.setConfig(*this);
m_initialParameters = m_currentParameters = flags;
}
virtual ~InfomapBase() = default;
// ===================================================
// Iterators
// ===================================================
InfomapIterator iterTree(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapIteratorPhysical iterTreePhysical(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapModuleIterator iterModules(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapLeafModuleIterator iterLeafModules(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapLeafIterator iterLeafNodes(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapLeafIteratorPhysical iterLeafNodesPhysical(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapIterator begin(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
InfomapIterator end() const { return InfomapIterator(nullptr); }
// ===================================================
// Getters
// ===================================================
Network& network() { return m_network; }
const Network& network() const { return m_network; }
InfoNode& root() { return m_root; }
const InfoNode& root() const { return m_root; }
unsigned int numLeafNodes() const { return m_leafNodes.size(); }
const std::vector<InfoNode*>& leafNodes() const { return m_leafNodes; }
unsigned int numTopModules() const { return m_root.childDegree(); }
unsigned int numActiveModules() const { return m_optimizer->numActiveModules(); }
unsigned int numNonTrivialTopModules() const { return m_numNonTrivialTopModules; }
bool haveModules() const { return !m_root.isLeaf() && !m_root.firstChild->isLeaf(); }
bool haveNonTrivialModules() const { return numNonTrivialTopModules() > 0; }
/**
* Number of node levels below the root in current Infomap instance, 1 if no modules
*/
unsigned int numLevels() const;
/**
* Get maximum depth of any child in the tree, following possible sub Infomap instances
*/
unsigned int maxTreeDepth() const;
double getCodelength() const { return m_optimizer->getCodelength(); }
double getMetaCodelength(bool unweighted = false) const { return m_optimizer->getMetaCodelength(unweighted); }
double codelength() const { return m_hierarchicalCodelength; }
const std::vector<double>& codelengths() const { return m_codelengths; }
double getIndexCodelength() const { return m_optimizer->getIndexCodelength(); }
double getModuleCodelength() const { return m_hierarchicalCodelength - m_optimizer->getIndexCodelength(); }
double getHierarchicalCodelength() const { return m_hierarchicalCodelength; }
double getOneLevelCodelength() const { return m_oneLevelCodelength; }
double getRelativeCodelengthSavings() const
{
auto oneLevelCodelength = getOneLevelCodelength();
return oneLevelCodelength < 1e-16 ? 0 : 1.0 - codelength() / oneLevelCodelength;
}
double getEntropyRate() { return m_entropyRate; }
double getMaxEntropy() { return m_maxEntropy; }
double getMaxFlow() { return m_maxFlow; }
const Date& getStartDate() const { return m_startDate; }
const Stopwatch& getElapsedTime() const { return m_elapsedTime; }
std::vector<InfoNode*>& activeNetwork() const { return *m_activeNetwork; }
std::map<unsigned int, std::vector<unsigned int>> getMultilevelModules(bool states = false);
// ===================================================
// IO
// ===================================================
std::ostream& toString(std::ostream& out) const { return m_optimizer->toString(out); }
// ===================================================
// Run
// ===================================================
using InitialPartition = std::map<unsigned int, unsigned int>;
const InitialPartition& getInitialPartition() const { return m_initialPartition; }
InfomapBase& setInitialPartition(const InitialPartition& moduleIds)
{
m_initialPartition = moduleIds;
return *this;
}
void run(const std::string& parameters = "");
void run(Network& network);
private:
bool isFullNetwork() const { return m_isMain && m_aggregationLevel == 0; }
bool isFirstLoop() const { return m_tuneIterationIndex == 0 && isFullNetwork(); }
InfomapBase* getNewInfomapInstance() const { return new InfomapBase(getConfig()); }
InfomapBase* getNewInfomapInstanceWithoutMemory() const
{
auto im = new InfomapBase();
im->initOptimizer(true);
return im;
}
InfomapBase& getSubInfomap(InfoNode& node) const
{
return node.setInfomap(getNewInfomapInstance())
.setIsMain(false)
.setSubLevel(m_subLevel + 1)
.setNonMainConfig(*this);
}
InfomapBase& getSuperInfomap(InfoNode& node) const
{
return node.setInfomap(getNewInfomapInstanceWithoutMemory())
.setIsMain(false)
.setSubLevel(m_subLevel + SUPER_LEVEL_ADDITION)
.setNonMainConfig(*this);
}
/**
* Only the main infomap reads an external cluster file if exist
*/
InfomapBase& setIsMain(bool isMain)
{
m_isMain = isMain;
return *this;
}
InfomapBase& setSubLevel(unsigned int level)
{
m_subLevel = level;
return *this;
}
bool isTopLevel() const { return (m_subLevel & (SUPER_LEVEL_ADDITION - 1)) == 0; }
bool isSuperLevelOnTopLevel() const { return m_subLevel == SUPER_LEVEL_ADDITION; }
bool isMainInfomap() const { return m_isMain; }
bool haveHardPartition() const { return !m_originalLeafNodes.empty(); }
// ===================================================
// Run: *
// ===================================================
InfomapBase& initNetwork(Network& network);
InfomapBase& initNetwork(InfoNode& parent, bool asSuperNetwork = false);
void generateSubNetwork(Network& network);
void generateSubNetwork(InfoNode& parent);
/**
* Init categorical meta data on all nodes from a file with the following format:
* # nodeId metaData
* 1 1
* 2 1
* 3 2
* 4 2
* 5 3
*
*/
InfomapBase& initMetaData(const std::string& metaDataFile);
/**
* Provide an initial partition of the network.
*
* @param clusterDataFile A .clu file containing cluster data.
* @param hard If true, the provided clusters will not be splitted. This reduces the
* effective network size during the optimization phase but the hard partitions are
* after that replaced by the original nodes.
*/
InfomapBase& initPartition(const std::string& clusterDataFile, bool hard = false, const Network* network = nullptr);
/**
* Provide an initial partition of the network.
*
* @param clusterIds map from nodeId to clusterId, doesn't have to be complete
* @param hard If true, the provided clusters will not be splitted. This reduces the
* effective network size during the optimization phase but the hard partitions are
* after that replaced by the original nodes.
*/
InfomapBase& initPartition(const std::map<unsigned int, unsigned int>& clusterIds, bool hard = false);
/**
* Provide an initial partition of the network.
*
* @param clusters Each sub-vector contain node IDs for all nodes that should be merged.
* @param hard If true, the provided clusters will not be splitted. This reduces the
* effective network size during the optimization phase but the hard partitions are
* after that replaced by the original nodes.
*/
InfomapBase& initPartition(std::vector<std::vector<unsigned int>>& clusters, bool hard = false);
/**
* Provide an initial partition of the network.
*
* @param modules Module indices for each node
*/
InfomapBase& initPartition(std::vector<unsigned int>& modules, bool hard = false);
/**
* Provide an initial hierarchical partition of the network
*
* @param tree A tree path for each node
*/
InfomapBase& initTree(const NodePaths& tree);
void init();
void runPartition()
{
if (twoLevel)
partition();
else
hierarchicalPartition();
}
void restoreHardPartition();
void writeResult(int trial = -1);
// ===================================================
// runPartition: *
// ===================================================
void hierarchicalPartition();
void partition();
// ===================================================
// runPartition: init: *
// ===================================================
/**
* Done in network?
*/
void initEnterExitFlow();
void aggregateFlowValuesFromLeafToRoot();
// Init terms that is constant for the whole network
void initTree() { return m_optimizer->initTree(); }
void initNetwork() { return m_optimizer->initNetwork(); }
void initSuperNetwork() { return m_optimizer->initSuperNetwork(); }
double calcCodelength(const InfoNode& parent) const { return m_optimizer->calcCodelength(parent); }
/**
* Calculate and store codelength on all modules in the tree
* @param includeRoot Also calculate the codelength on the root node
* @return the hierarchical codelength
*/
double calcCodelengthOnTree(InfoNode& root, bool includeRoot = true) const;
// ===================================================
// Run: Partition: *
// ===================================================
void setActiveNetworkFromLeafs() { m_activeNetwork = &m_leafNodes; }
void setActiveNetworkFromChildrenOfRoot();
void initPartition() { return m_optimizer->initPartition(); }
void findTopModulesRepeatedly(unsigned int maxLevels);
unsigned int fineTune();
unsigned int coarseTune();
/**
* Return the number of effective core loops, i.e. not the last if not at coreLoopLimit
*/
unsigned int optimizeActiveNetwork() { return m_optimizer->optimizeActiveNetwork(); }
void moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules)
{
return m_optimizer->moveActiveNodesToPredefinedModules(modules);
}
void consolidateModules(bool replaceExistingModules = true)
{
return m_optimizer->consolidateModules(replaceExistingModules);
}
unsigned int calculateNumNonTrivialTopModules() const;
unsigned int calculateMaxDepth() const;
// ===================================================
// Partition: findTopModulesRepeatedly: *
// ===================================================
/**
* Return true if restored to consolidated optimization state
*/
bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false)
{
return m_optimizer->restoreConsolidatedOptimizationPointIfNoImprovement(forceRestore);
}
// ===================================================
// Run: Hierarchical Partition: *
// ===================================================
/**
* Find super modules applying the whole two-level algorithm on the
* top modules iteratively
* @param levelLimit The maximum number of super module levels allowed
* @return number of levels created
*/
unsigned int findHierarchicalSuperModules(unsigned int superLevelLimit = std::numeric_limits<unsigned int>::max());
/**
* Find super modules fast by merge and consolidate top modules iteratively
* @param levelLimit The maximum number of super module levels allowed
* @return number of levels created
*/
unsigned int findHierarchicalSuperModulesFast(unsigned int superLevelLimit = std::numeric_limits<unsigned int>::max());
void transformNodeFlowToEnterFlow(InfoNode& parent);
void resetFlowOnModules();
unsigned int removeModules();
unsigned int removeSubModules(bool recalculateCodelengthOnTree);
unsigned int recursivePartition();
void queueTopModules(PartitionQueue& partitionQueue);
void queueLeafModules(PartitionQueue& partitionQueue);
bool processPartitionQueue(PartitionQueue& queue, PartitionQueue& nextLevel) const;
public:
// ===================================================
// Output: *
// ===================================================
/**
* Write tree to a .tree file.
* @param filename the filename for the output file. If empty, use default
* based on output directory and input file name
* @param states if memory network, print the state-level network without merging physical nodes within modules
* @return the filename written to
*/
std::string writeTree(const std::string& filename = "", bool states = false) { return infomap::writeTree(*this, m_network, filename, states); }
/**
* Write flow tree to a .ftree file.
* This is the same as a .tree file but appended with links aggregated
* within modules on all levels in the tree
* @param filename the filename for the output file. If empty, use default
* based on output directory and input file name
* @param states if memory network, print the state-level network without merging physical nodes within modules
* @return the filename written to
*/
std::string writeFlowTree(const std::string& filename = "", bool states = false) { return infomap::writeFlowTree(*this, m_network, filename, states); }
/**
* Write Newick tree to a .tre file.
* @param filename the filename for the output file. If empty, use default
* based on output directory and input file name
* @param states if memory network, print the state-level network without merging physical nodes within modules
* @return the filename written to
*/
std::string writeNewickTree(const std::string& filename = "", bool states = false) { return infomap::writeNewickTree(*this, filename, states); }
std::string writeJsonTree(const std::string& filename = "", bool states = false, bool writeLinks = false) { return infomap::writeJsonTree(*this, m_network, filename, states, writeLinks); }
std::string writeCsvTree(const std::string& filename = "", bool states = false) { return infomap::writeCsvTree(*this, m_network, filename, states); }
/**
* Write tree to a .clu file.
* @param filename the filename for the output file. If empty, use default
* based on output directory and input file name
* @param states if memory network, print the state-level network without merging physical nodes within modules
* @param moduleIndexLevel the depth from the root on which to advance module index.
* Value 1 (default) will give the module index on the coarsest level, 2 the level below and so on.
* Value -1 will give the module index for the lowest level, i.e. the finest modular structure.
* @return the filename written to
*/
std::string writeClu(const std::string& filename = "", bool states = false, int moduleIndexLevel = 1) { return infomap::writeClu(*this, m_network, filename, states, moduleIndexLevel); }
private:
#if 0
// ===================================================
// Debug: *
// ===================================================
void printDebug() const { return m_optimizer->printDebug(); }
#endif
// ===================================================
// Members
// ===================================================
protected:
InfoNode m_root;
std::vector<InfoNode*> m_leafNodes;
std::vector<InfoNode*> m_moduleNodes;
std::vector<InfoNode*>* m_activeNetwork = nullptr;
std::vector<InfoNode*> m_originalLeafNodes;
Network m_network;
InitialPartition m_initialPartition = {}; // nodeId -> moduleId
const unsigned int SUPER_LEVEL_ADDITION = 1 << 20;
bool m_isMain = true;
unsigned int m_subLevel = 0;
bool m_calculateEnterExitFlow = false;
double m_oneLevelCodelength = 0.0;
unsigned int m_numNonTrivialTopModules = 0;
unsigned int m_tuneIterationIndex = 0;
bool m_isCoarseTune = false;
unsigned int m_aggregationLevel = 0;
double m_hierarchicalCodelength = 0.0;
std::vector<double> m_codelengths;
double m_entropyRate = 0.0;
double m_maxEntropy = 0.0;
double m_maxFlow = 0.0;
double m_sumDanglingFlow = 0.0;
Date m_startDate;
Date m_endDate;
Stopwatch m_elapsedTime = Stopwatch(false);
std::string m_initialParameters;
std::string m_currentParameters;
std::unique_ptr<InfomapOptimizerBase> m_optimizer;
};
/**
* Print per level statistics
*/
unsigned int printPerLevelCodelength(const InfoNode& parent, std::ostream& out);
void aggregatePerLevelCodelength(const InfoNode& parent, std::vector<detail::PerLevelStat>& perLevelStat, unsigned int level = 0);
namespace detail {
struct PerLevelStat {
double codelength() const { return indexLength + leafLength; }
unsigned int numNodes() const { return numModules + numLeafNodes; }
unsigned int numModules = 0;
unsigned int numLeafNodes = 0;
double indexLength = 0.0;
double leafLength = 0.0;
};
class PartitionQueue {
using PendingModule = InfoNode*;
std::deque<PendingModule> m_queue;
public:
unsigned int level = 1;
unsigned int numNonTrivialModules = 0;
double flow = 0.0;
double nonTrivialFlow = 0.0;
bool skip = false;
double indexCodelength = 0.0; // Consolidated
double leafCodelength = 0.0; // Consolidated
double moduleCodelength = 0.0; // Left to improve on next level
using size_t = std::deque<PendingModule>::size_type;
void swap(PartitionQueue& other) noexcept
{
std::swap(level, other.level);
std::swap(numNonTrivialModules, other.numNonTrivialModules);
std::swap(flow, other.flow);
std::swap(nonTrivialFlow, other.nonTrivialFlow);
std::swap(skip, other.skip);
std::swap(indexCodelength, other.indexCodelength);
std::swap(leafCodelength, other.leafCodelength);
std::swap(moduleCodelength, other.moduleCodelength);
m_queue.swap(other.m_queue);
}
size_t size() const { return m_queue.size(); }
void resize(size_t size) { m_queue.resize(size); }
PendingModule& operator[](size_t i) { return m_queue[i]; }
};
} // namespace detail
} // namespace infomap
#endif // INFOMAP_BASE_H_
@@ -0,0 +1,137 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_CONFIG_H_
#define INFOMAP_CONFIG_H_
#include "../io/Config.h"
#include "../utils/Random.h"
#include "../utils/Log.h"
#include <string>
namespace infomap {
template <typename Infomap>
class InfomapConfig : public Config {
public:
InfomapConfig() = default;
InfomapConfig(const std::string& flags, bool isCli = false) : InfomapConfig(Config(flags, isCli)) { }
InfomapConfig(const Config& conf) : Config(conf), m_rand(/* conf.seedToRandomNumberGenerator */)
{
Log::precision(conf.verboseNumberPrecision);
}
virtual ~InfomapConfig() = default;
InfomapConfig(const InfomapConfig&) = default;
InfomapConfig& operator=(const InfomapConfig&) = default;
InfomapConfig(InfomapConfig&&) noexcept = default;
InfomapConfig& operator=(InfomapConfig&&) noexcept = default;
private:
Infomap& get()
{
return static_cast<Infomap&>(*this);
}
protected:
Random m_rand;
public:
Config& getConfig()
{
return *this;
}
const Config& getConfig() const
{
return *this;
}
Infomap& setConfig(const Config& conf)
{
*this = conf;
/* m_rand.seed(conf.seedToRandomNumberGenerator); */
Log::precision(conf.verboseNumberPrecision);
return get();
}
Infomap& setNonMainConfig(const Config& conf)
{
cloneAsNonMain(conf);
return get();
}
Infomap& setNumTrials(unsigned int N)
{
numTrials = N;
return get();
}
Infomap& setVerbosity(unsigned int level)
{
verbosity = level;
return get();
}
Infomap& setTwoLevel(bool value)
{
twoLevel = value;
return get();
}
Infomap& setTuneIterationLimit(unsigned int value)
{
tuneIterationLimit = value;
return get();
}
Infomap& setFastHierarchicalSolution(unsigned int level)
{
fastHierarchicalSolution = level;
return get();
}
Infomap& setOnlySuperModules(bool value)
{
onlySuperModules = value;
return get();
}
Infomap& setNoCoarseTune(bool value)
{
noCoarseTune = value;
return get();
}
Infomap& setNoInfomap(bool value = true)
{
noInfomap = value;
return get();
}
Infomap& setMarkovTime(double codeRate)
{
markovTime = codeRate;
return get();
}
Infomap& setDirected(bool value)
{
directed = value;
flowModel = directed ? FlowModel::directed : FlowModel::undirected;
return get();
}
};
} // namespace infomap
#endif // INFOMAP_CONFIG_H_
@@ -0,0 +1,766 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_OPTIMIZER_H_
#define INFOMAP_OPTIMIZER_H_
#include "InfomapOptimizerBase.h"
#include "InfomapBase.h"
#include "../utils/VectorMap.h"
#include "../utils/infomath.h"
#include "InfoNode.h"
#include "FlowData.h"
#include <set>
#include <utility>
namespace infomap {
template <typename Objective>
class InfomapOptimizer : public InfomapOptimizerBase {
using FlowDataType = FlowData;
using DeltaFlowDataType = typename Objective::DeltaFlowDataType;
public:
void init(InfomapBase* infomap) override
{
m_infomap = infomap;
m_objective.init(infomap->getConfig());
this->setInterruptionHandler(infomap->getConfig().interruptionHandler);
}
// ===================================================
// IO
// ===================================================
std::ostream& toString(std::ostream& out) const override { return out << m_objective; }
// ===================================================
// Getters
// ===================================================
double getCodelength() const override { return m_objective.getCodelength(); }
double getIndexCodelength() const override { return m_objective.getIndexCodelength(); }
double getModuleCodelength() const override { return m_objective.getModuleCodelength(); }
double getMetaCodelength(bool unweighted = false) const override;
protected:
unsigned int numActiveModules() const override { return m_infomap->activeNetwork().size() - m_emptyModules.size(); }
// ===================================================
// Run: Init: *
// ===================================================
// Init terms that is constant for the whole network
void initTree() override;
void initNetwork() override;
void initSuperNetwork() override;
double calcCodelength(const InfoNode& parent) const override { return m_objective.calcCodelength(parent); }
// ===================================================
// Run: Partition: *
// ===================================================
void initPartition() override;
void moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules) override;
bool moveNodeToPredefinedModule(InfoNode& current, unsigned int module);
unsigned int optimizeActiveNetwork() override;
unsigned int tryMoveEachNodeIntoBestModule() override;
unsigned int tryMoveEachNodeIntoBestModuleInParallel() override;
void consolidateModules(bool replaceExistingModules = true) override;
bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) override;
#if 0
// ===================================================
// Debug: *
// ===================================================
void printDebug() override { m_objective.printDebug(); }
#endif
// ===================================================
// Protected members
// ===================================================
InfomapBase* m_infomap = nullptr;
Objective m_objective;
Objective m_consolidatedObjective;
std::vector<FlowDataType> m_moduleFlowData;
std::vector<unsigned int> m_moduleMembers;
std::vector<unsigned int> m_emptyModules;
};
// ===================================================
// Getters
// ===================================================
template <>
inline double InfomapOptimizer<MetaMapEquation>::getMetaCodelength(bool unweighted) const
{
return m_objective.getMetaCodelength(unweighted);
}
template <typename Objective>
inline double InfomapOptimizer<Objective>::getMetaCodelength(bool /*unweighted*/) const
{
return 0.0;
}
// ===================================================
// Run: Init: *
// ===================================================
template <typename Objective>
inline void InfomapOptimizer<Objective>::initTree()
{
Log(4) << "InfomapOptimizer::initTree()...\n";
m_objective.initTree(m_infomap->root());
}
template <typename Objective>
inline void InfomapOptimizer<Objective>::initNetwork()
{
Log(4) << "InfomapOptimizer::initNetwork()...\n";
m_objective.initNetwork(m_infomap->root());
if (!m_infomap->isMainInfomap())
m_objective.initSubNetwork(m_infomap->root()); // TODO: Already called in initNetwork?
}
template <typename Objective>
inline void InfomapOptimizer<Objective>::initSuperNetwork()
{
Log(4) << "InfomapOptimizer::initSuperNetwork()...\n";
m_objective.initSuperNetwork(m_infomap->root());
}
// ===================================================
// Run: Partition: *
// ===================================================
template <typename Objective>
void InfomapOptimizer<Objective>::initPartition()
{
auto& network = m_infomap->activeNetwork();
Log(4) << "InfomapOptimizer::initPartition() with " << network.size() << " nodes...\n";
// Init one module for each node
auto numNodes = network.size();
m_moduleFlowData.resize(numNodes);
m_moduleMembers.assign(numNodes, 1);
m_emptyModules.clear();
m_emptyModules.reserve(numNodes);
unsigned int i = 0;
for (auto& nodePtr : network) {
InfoNode& node = *nodePtr;
node.index = i; // Unique module index for each node
m_moduleFlowData[i] = node.data;
node.dirty = true;
++i;
}
m_objective.initPartition(network);
}
template <typename Objective>
void InfomapOptimizer<Objective>::moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules)
{
auto& network = m_infomap->activeNetwork();
auto numNodes = network.size();
if (modules.size() != numNodes)
throw std::length_error("Size of predefined modules differ from size of active network.");
for (unsigned int i = 0; i < numNodes; ++i) {
moveNodeToPredefinedModule(*network[i], modules[i]);
}
}
template <typename Objective>
bool InfomapOptimizer<Objective>::moveNodeToPredefinedModule(InfoNode& current, unsigned int newModule)
{
unsigned int oldM = current.index;
unsigned int newM = newModule;
if (newM == oldM) {
return false;
}
DeltaFlowDataType oldModuleDelta(oldM, 0.0, 0.0);
DeltaFlowDataType newModuleDelta(newM, 0.0, 0.0);
// For all outlinks
for (auto& e : current.outEdges()) {
auto& edge = *e;
unsigned int otherModule = edge.target->index;
if (otherModule == oldM) {
oldModuleDelta.deltaExit += edge.data.flow;
} else if (otherModule == newM) {
newModuleDelta.deltaExit += edge.data.flow;
}
}
// For all inlinks
for (auto& e : current.inEdges()) {
auto& edge = *e;
unsigned int otherModule = edge.source->index;
if (otherModule == oldM) {
oldModuleDelta.deltaEnter += edge.data.flow;
} else if (otherModule == newM) {
newModuleDelta.deltaEnter += edge.data.flow;
}
}
// For recorded teleportation
if (m_infomap->recordedTeleportation) {
auto& oldModuleFlowData = m_moduleFlowData[oldM];
double deltaEnterOld = (oldModuleFlowData.teleportFlow - current.data.teleportFlow) * current.data.teleportWeight;
double deltaExitOld = current.data.teleportFlow * (oldModuleFlowData.teleportWeight - current.data.teleportWeight);
oldModuleDelta.deltaEnter += deltaEnterOld;
oldModuleDelta.deltaExit += deltaExitOld;
auto& newModuleFlowData = m_moduleFlowData[newM];
double deltaEnterNew = current.data.teleportFlow * newModuleFlowData.teleportWeight;
double deltaExitNew = newModuleFlowData.teleportFlow * current.data.teleportWeight;
newModuleDelta.deltaEnter += deltaEnterNew;
newModuleDelta.deltaExit += deltaExitNew;
}
// Update empty module vector
if (m_moduleMembers[newM] == 0) {
m_emptyModules.pop_back();
}
if (m_moduleMembers[current.index] == 1) {
m_emptyModules.push_back(oldM);
}
m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, m_moduleFlowData, m_moduleMembers);
m_moduleMembers[oldM] -= 1;
m_moduleMembers[newM] += 1;
current.index = newM;
return true;
}
template <typename Objective>
inline unsigned int InfomapOptimizer<Objective>::optimizeActiveNetwork()
{
unsigned int coreLoopCount = 0;
unsigned int numEffectiveLoops = 0;
double oldCodelength = m_objective.getCodelength();
unsigned int loopLimit = m_infomap->coreLoopLimit;
unsigned int minRandLoop = 2;
if (loopLimit >= minRandLoop && m_infomap->randomizeCoreLoopLimit)
loopLimit = m_infomap->m_rand.randInt(minRandLoop, loopLimit);
if (m_infomap->m_aggregationLevel > 0 || m_infomap->m_isCoarseTune) {
loopLimit = 20;
}
do {
++coreLoopCount;
unsigned int numNodesMoved = m_infomap->innerParallelization
? tryMoveEachNodeIntoBestModuleInParallel()
: tryMoveEachNodeIntoBestModule();
// Break if not enough improvement
if (numNodesMoved == 0 || m_objective.getCodelength() >= oldCodelength - m_infomap->minimumCodelengthImprovement)
break;
++numEffectiveLoops;
oldCodelength = m_objective.getCodelength();
} while (coreLoopCount != loopLimit);
return numEffectiveLoops;
}
template <typename Objective>
unsigned int InfomapOptimizer<Objective>::tryMoveEachNodeIntoBestModule()
{
// Get random enumeration of nodes
auto& network = m_infomap->activeNetwork();
std::vector<unsigned int> nodeEnumeration(network.size());
m_infomap->m_rand.getRandomizedIndexVector(nodeEnumeration);
auto numNodes = nodeEnumeration.size();
unsigned int numMoved = 0;
// Create map with module links
VectorMap<DeltaFlowDataType> deltaFlow(numNodes);
for (unsigned int i = 0; i < numNodes; ++i) {
InfoNode& current = *network[nodeEnumeration[i]];
this->checkInterruption();
if (!current.dirty)
continue;
// If other nodes have moved here, don't move away on first loop
if (m_moduleMembers[current.index] > 1 && m_infomap->isFirstLoop() && m_infomap->tuneIterationLimit != 1)
continue;
// If no links connecting this node with other nodes, it won't move into others,
// and others won't move into this. TODO: Always best leave it alone?
// For memory networks, don't skip try move to same physical node!
deltaFlow.startRound();
// For all outlinks
for (auto& e : current.outEdges()) {
auto& edge = *e;
InfoNode* neighbour = edge.target;
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, edge.data.flow, 0.0));
}
// For all inlinks
for (auto& e : current.inEdges()) {
auto& edge = *e;
InfoNode* neighbour = edge.source;
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, 0.0, edge.data.flow));
}
// For not moving
deltaFlow.add(current.index, DeltaFlowDataType(current.index, 0.0, 0.0));
DeltaFlowDataType& oldModuleDelta = deltaFlow[current.index];
oldModuleDelta.module = current.index; // Make sure index is correct if created new
// Option to move to empty module (if node not already alone)
if (m_moduleMembers[current.index] > 1 && !m_emptyModules.empty()) {
deltaFlow.add(m_emptyModules.back(), DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0));
}
// For memory networks
m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow);
auto& moduleDeltaEnterExit = deltaFlow.values();
unsigned int numModuleLinks = deltaFlow.size();
// For recorded teleportation
if (m_infomap->recordedTeleportation) {
for (unsigned int j = 0; j < numModuleLinks; ++j) {
auto& deltaEnterExit = moduleDeltaEnterExit[j];
auto moduleIndex = deltaEnterExit.module;
if (moduleIndex == current.index) {
auto& oldModuleFlowData = m_moduleFlowData[moduleIndex];
double deltaEnterOld = (oldModuleFlowData.teleportFlow - current.data.teleportFlow) * current.data.teleportWeight;
double deltaExitOld = current.data.teleportFlow * (oldModuleFlowData.teleportWeight - current.data.teleportWeight);
deltaFlow.add(moduleIndex, DeltaFlowDataType(moduleIndex, deltaExitOld, deltaEnterOld));
} else {
auto& newModuleFlowData = m_moduleFlowData[moduleIndex];
double deltaEnterNew = newModuleFlowData.teleportFlow * current.data.teleportWeight;
double deltaExitNew = current.data.teleportFlow * newModuleFlowData.teleportWeight;
deltaFlow.add(moduleIndex, DeltaFlowDataType(moduleIndex, deltaExitNew, deltaEnterNew));
}
}
}
// Randomize link order for optimized search
std::vector<unsigned int> moduleEnumeration(numModuleLinks);
m_infomap->m_rand.getRandomizedIndexVector(moduleEnumeration);
DeltaFlowDataType bestDeltaModule(oldModuleDelta);
double bestDeltaCodelength = 0.0;
DeltaFlowDataType strongestConnectedModule(oldModuleDelta);
double deltaCodelengthOnStrongestConnectedModule = 0.0;
// Find the move that minimizes the description length
for (unsigned int k = 0; k < numModuleLinks; ++k) {
auto j = moduleEnumeration[k];
unsigned int otherModule = moduleDeltaEnterExit[j].module;
if (otherModule != current.index) {
double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current,
oldModuleDelta,
moduleDeltaEnterExit[j],
m_moduleFlowData,
m_moduleMembers);
if (deltaCodelength < bestDeltaCodelength - m_infomap->minimumSingleNodeCodelengthImprovement) {
bestDeltaModule = moduleDeltaEnterExit[j];
bestDeltaCodelength = deltaCodelength;
}
// Save strongest connected module to prefer if codelength improvement equal
if (moduleDeltaEnterExit[j].deltaExit > strongestConnectedModule.deltaExit) {
strongestConnectedModule = moduleDeltaEnterExit[j];
deltaCodelengthOnStrongestConnectedModule = deltaCodelength;
}
}
}
// Prefer strongest connected module if equal delta codelength
if (strongestConnectedModule.module != bestDeltaModule.module && deltaCodelengthOnStrongestConnectedModule <= bestDeltaCodelength + m_infomap->minimumSingleNodeCodelengthImprovement) {
bestDeltaModule = strongestConnectedModule;
}
// Make best possible move
if (bestDeltaModule.module != current.index) {
unsigned int bestModuleIndex = bestDeltaModule.module;
// Update empty module vector
if (m_moduleMembers[bestModuleIndex] == 0) {
m_emptyModules.pop_back();
}
if (m_moduleMembers[current.index] == 1) {
m_emptyModules.push_back(current.index);
}
m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, bestDeltaModule, m_moduleFlowData, m_moduleMembers);
m_moduleMembers[current.index] -= 1;
m_moduleMembers[bestModuleIndex] += 1;
unsigned int oldModuleIndex = current.index;
current.index = bestModuleIndex;
++numMoved;
InfoNode* nodeInOldModule = &current;
unsigned int numLinkedNodesInOldModule = 0;
// Mark neighbours as dirty
for (auto& e : current.outEdges()) {
e->target->dirty = true;
if (e->target->index == oldModuleIndex) {
nodeInOldModule = e->target;
++numLinkedNodesInOldModule;
}
}
for (auto& e : current.inEdges()) {
e->source->dirty = true;
if (e->source->index == oldModuleIndex) {
nodeInOldModule = e->source;
++numLinkedNodesInOldModule;
}
}
// Move single connected nodes to same module
if (numLinkedNodesInOldModule == 1 && m_moduleMembers[oldModuleIndex] == 1) {
moveNodeToPredefinedModule(*nodeInOldModule, bestModuleIndex);
++numMoved;
// Mark neighbours as dirty
if (nodeInOldModule->degree() > 1) {
for (auto& e : nodeInOldModule->outEdges())
e->target->dirty = true;
for (auto& e : nodeInOldModule->inEdges())
e->source->dirty = true;
}
}
} else {
current.dirty = false;
}
}
return numMoved;
}
/**
* Minimize the codelength by trying to move each node into best module, in parallel.
*
* For each node:
* 1. Calculate the change in codelength for a move to each of its neighbouring modules or to an empty module
* 2. Move to the one that reduces the codelength the most, if any.
*
* @return The number of nodes moved.
*/
template <typename Objective>
unsigned int InfomapOptimizer<Objective>::tryMoveEachNodeIntoBestModuleInParallel()
{
// Get random enumeration of nodes
auto& network = m_infomap->activeNetwork();
std::vector<unsigned int> nodeEnumeration(network.size());
m_infomap->m_rand.getRandomizedIndexVector(nodeEnumeration);
auto numNodes = nodeEnumeration.size();
unsigned int numMoved = 0;
unsigned int numInvalidMoves = 0;
#pragma omp parallel for schedule(dynamic) // Use dynamic scheduling as some threads could end early
for (unsigned int i = 0; i < numNodes; ++i) {
// Pick nodes in random order
InfoNode& current = *network[nodeEnumeration[i]];
if (!current.dirty)
continue;
// If other nodes have moved here, don't move away on first loop
if (m_moduleMembers[current.index] > 1 && m_infomap->isFirstLoop() && m_infomap->tuneIterationLimit != 1)
continue;
// If no links connecting this node with other nodes, it won't move into others,
// and others won't move into this. TODO: Always best leave it alone?
// For memory networks, don't skip try move to same physical node!
// Create map with module links
VectorMap<DeltaFlowDataType> deltaFlow(numNodes);
// For all outlinks
for (auto& e : current.outEdges()) {
auto& edge = *e;
InfoNode* neighbour = edge.target;
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, edge.data.flow, 0.0));
}
// For all inlinks
for (auto& e : current.inEdges()) {
auto& edge = *e;
InfoNode* neighbour = edge.source;
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, 0.0, edge.data.flow));
}
// For not moving
deltaFlow.add(current.index, DeltaFlowDataType(current.index, 0.0, 0.0));
DeltaFlowDataType& oldModuleDelta = deltaFlow[current.index];
oldModuleDelta.module = current.index; // Make sure index is correct if created new
// Option to move to empty module (if node not already alone)
if (m_moduleMembers[current.index] > 1 && !m_emptyModules.empty()) {
// deltaFlow[m_emptyModules.back()] += DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0);
deltaFlow.add(m_emptyModules.back(), DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0));
}
// For memory networks
m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow);
auto& moduleDeltaEnterExit = deltaFlow.values();
unsigned int numModuleLinks = deltaFlow.size();
// Randomize link order for optimized search
if (numModuleLinks > 2) {
for (unsigned int j = 0; j < numModuleLinks - 2; ++j) {
unsigned int randPos = m_infomap->m_rand.randInt(j + 1, numModuleLinks - 1);
swap(moduleDeltaEnterExit[j], moduleDeltaEnterExit[randPos]);
}
}
DeltaFlowDataType bestDeltaModule(oldModuleDelta);
double bestDeltaCodelength = 0.0;
DeltaFlowDataType strongestConnectedModule(oldModuleDelta);
double deltaCodelengthOnStrongestConnectedModule = 0.0;
// Find the move that minimizes the description length
for (unsigned int j = 0; j < deltaFlow.size(); ++j) {
unsigned int otherModule = moduleDeltaEnterExit[j].module;
if (otherModule != current.index) {
double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current,
oldModuleDelta,
moduleDeltaEnterExit[j],
m_moduleFlowData,
m_moduleMembers);
if (deltaCodelength < bestDeltaCodelength - m_infomap->minimumSingleNodeCodelengthImprovement) {
bestDeltaModule = moduleDeltaEnterExit[j];
bestDeltaCodelength = deltaCodelength;
}
// Save strongest connected module to prefer if codelength improvement equal
if (moduleDeltaEnterExit[j].deltaExit > strongestConnectedModule.deltaExit) {
strongestConnectedModule = moduleDeltaEnterExit[j];
deltaCodelengthOnStrongestConnectedModule = deltaCodelength;
}
}
}
// Prefer strongest connected module if equal delta codelength
if (strongestConnectedModule.module != bestDeltaModule.module && deltaCodelengthOnStrongestConnectedModule <= bestDeltaCodelength + m_infomap->minimumSingleNodeCodelengthImprovement) {
bestDeltaModule = strongestConnectedModule;
}
// Make best possible move
if (bestDeltaModule.module == current.index) {
current.dirty = false;
continue;
} else {
#pragma omp critical(moveUpdate)
{
unsigned int bestModuleIndex = bestDeltaModule.module;
unsigned int oldModuleIndex = current.index;
bool validMove = bestModuleIndex == m_emptyModules.back()
// Check validity of move to empty target
? m_moduleMembers[oldModuleIndex] > 1 && !m_emptyModules.empty()
// Not valid if the best module is empty now but not when decided
: m_moduleMembers[bestModuleIndex] > 0;
if (validMove) {
// Recalculate delta codelength for proposed move to see if still an improvement
oldModuleDelta = DeltaFlowDataType(oldModuleIndex, 0.0, 0.0);
DeltaFlowDataType newModuleDelta(bestModuleIndex, 0.0, 0.0);
// For all outlinks
for (auto& e : current.outEdges()) {
auto& edge = *e;
unsigned int otherModule = edge.target->index;
if (otherModule == oldModuleIndex)
oldModuleDelta.deltaExit += edge.data.flow;
else if (otherModule == bestModuleIndex)
newModuleDelta.deltaExit += edge.data.flow;
}
// For all inlinks
for (auto& e : current.inEdges()) {
auto& edge = *e;
unsigned int otherModule = edge.source->index;
if (otherModule == oldModuleIndex)
oldModuleDelta.deltaEnter += edge.data.flow;
else if (otherModule == bestModuleIndex)
newModuleDelta.deltaEnter += edge.data.flow;
}
// For memory networks
m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow);
double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current,
oldModuleDelta,
newModuleDelta,
m_moduleFlowData,
m_moduleMembers);
if (deltaCodelength < 0.0 - m_infomap->minimumSingleNodeCodelengthImprovement) {
// Update empty module vector
if (m_moduleMembers[bestModuleIndex] == 0) {
m_emptyModules.pop_back();
}
if (m_moduleMembers[oldModuleIndex] == 1) {
m_emptyModules.push_back(oldModuleIndex);
}
m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, bestDeltaModule, m_moduleFlowData, m_moduleMembers);
m_moduleMembers[oldModuleIndex] -= 1;
m_moduleMembers[bestModuleIndex] += 1;
current.index = bestModuleIndex;
++numMoved;
// Mark neighbours as dirty
for (auto& e : current.outEdges())
e->target->dirty = true;
for (auto& e : current.inEdges())
e->source->dirty = true;
} else {
++numInvalidMoves;
}
} else {
++numInvalidMoves;
}
}
}
}
return numMoved + numInvalidMoves;
}
template <typename Objective>
inline void InfomapOptimizer<Objective>::consolidateModules(bool replaceExistingModules)
{
auto& network = m_infomap->activeNetwork();
auto numNodes = network.size();
std::vector<InfoNode*> modules(numNodes, nullptr);
InfoNode& firstActiveNode = *network[0];
auto level = firstActiveNode.depth();
auto leafLevel = m_infomap->numLevels();
if (leafLevel == 1)
replaceExistingModules = false;
// Release children pointers on current parent(s) to put new modules between
for (auto& n : network) {
n->parent->releaseChildren(); // Safe to call multiple times
}
// Create the new module nodes and re-parent the active network from its common parent to the new module level
for (unsigned int i = 0; i < numNodes; ++i) {
InfoNode* node = network[i];
unsigned int moduleIndex = node->index;
if (modules[moduleIndex] == nullptr) {
modules[moduleIndex] = new InfoNode(m_moduleFlowData[moduleIndex]);
modules[moduleIndex]->index = moduleIndex;
node->parent->addChild(modules[moduleIndex]);
}
modules[moduleIndex]->addChild(node);
}
using NodePair = std::pair<unsigned int, unsigned int>;
using EdgeMap = std::map<NodePair, double>;
EdgeMap moduleLinks;
for (auto& node : network) {
unsigned int module1 = node->index;
for (auto& e : node->outEdges()) {
InfoEdge& edge = *e;
unsigned int module2 = edge.target->index;
if (module1 != module2) {
// Use new variables to not swap module1
unsigned int m1 = module1, m2 = module2;
// If undirected, the order may be swapped to aggregate the edge on an opposite one
if (m_infomap->isUndirectedClustering() && m1 > m2)
std::swap(m1, m2);
auto ret = moduleLinks.insert(std::make_pair(NodePair(m1, m2), edge.data.flow));
if (!ret.second) {
ret.first->second += edge.data.flow;
}
}
}
}
// Add the aggregated edge flow structure to the new modules
for (auto& e : moduleLinks) {
const auto& nodePair = e.first;
modules[nodePair.first]->addOutEdge(*modules[nodePair.second], 0.0, e.second);
}
if (replaceExistingModules) {
if (level == 1) {
Log(4) << "Consolidated super modules, removing old modules...\n";
for (auto& node : network)
node->replaceWithChildren();
} else if (level == 2) {
Log(4) << "Consolidated sub-modules, removing modules...\n";
unsigned int moduleIndex = 0;
for (InfoNode& module : m_infomap->root()) {
// Store current modular structure on the sub-modules
for (auto& subModule : module)
subModule.index = moduleIndex;
++moduleIndex;
}
m_infomap->root().replaceChildrenWithGrandChildren();
}
}
// Calculate the number of non-trivial modules
m_infomap->m_numNonTrivialTopModules = 0;
for (auto& module : m_infomap->root()) {
if (module.childDegree() != 1)
++m_infomap->m_numNonTrivialTopModules;
}
m_objective.consolidateModules(modules);
m_consolidatedObjective = m_objective;
}
template <typename Objective>
inline bool InfomapOptimizer<Objective>::restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore)
{
if (forceRestore || m_objective.getCodelength() >= m_consolidatedObjective.getCodelength() - m_infomap->minimumSingleNodeCodelengthImprovement) {
m_objective = m_consolidatedObjective;
return true;
}
return false;
}
} /* namespace infomap */
#endif // INFOMAP_OPTIMIZER_H_
@@ -0,0 +1,113 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_OPTIMIZER_BASE_H_
#define INFOMAP_OPTIMIZER_BASE_H_
#include "InfomapBase.h"
#include "InfoNode.h"
#include "FlowData.h"
#include <vector>
namespace infomap {
class InfomapOptimizerBase {
friend class InfomapBase;
using FlowDataType = FlowData;
public:
InfomapOptimizerBase() = default;
virtual ~InfomapOptimizerBase() = default;
virtual void init(InfomapBase* infomap) = 0;
// ===================================================
// IO
// ===================================================
virtual std::ostream& toString(std::ostream& out) const = 0;
// ===================================================
// Getters
// ===================================================
virtual double getCodelength() const = 0;
virtual double getIndexCodelength() const = 0;
virtual double getModuleCodelength() const = 0;
virtual double getMetaCodelength(bool /*unweighted*/ = false) const { return 0.0; }
void setInterruptionHandler(interruptionHandlerFn *interruptionHandler) {
this->interruptionHandler = interruptionHandler;
}
protected:
virtual unsigned int numActiveModules() const = 0;
void checkInterruption() {
if (interruptionHandler) {
if (interruptionHandler()) {
throw infomap::InterruptException();
}
}
}
// ===================================================
// Run: Init: *
// ===================================================
// Init terms that is constant for the whole network
virtual void initTree() = 0;
virtual void initNetwork() = 0;
virtual void initSuperNetwork() = 0;
virtual double calcCodelength(const InfoNode& parent) const = 0;
// ===================================================
// Run: Partition: *
// ===================================================
virtual void initPartition() = 0;
virtual void moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules) = 0;
virtual unsigned int optimizeActiveNetwork() = 0;
virtual unsigned int tryMoveEachNodeIntoBestModule() = 0;
// virtual unsigned int tryMoveEachNodeIntoBestModuleLocal() = 0;
virtual unsigned int tryMoveEachNodeIntoBestModuleInParallel() = 0;
virtual void consolidateModules(bool replaceExistingModules = true) = 0;
virtual bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) = 0;
#if 0
// ===================================================
// Debug: *
// ===================================================
virtual void printDebug() = 0;
#endif
private:
interruptionHandlerFn *interruptionHandler = NULL;
};
} /* namespace infomap */
#endif // INFOMAP_OPTIMIZER_BASE_H_
@@ -0,0 +1,339 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef MAPEQUATION_H_
#define MAPEQUATION_H_
#include "../utils/infomath.h"
#include "../utils/convert.h"
#include "../io/Config.h"
#include "../utils/Log.h"
#include "../utils/VectorMap.h"
#include "InfoNode.h"
#include "FlowData.h"
#include <vector>
#include <map>
#include <iostream>
namespace infomap {
class InfoNode;
template <typename FlowDataType = FlowData, typename DeltaFlowDataType = DeltaFlow>
class MapEquation {
using ME = MapEquation<FlowDataType, DeltaFlowDataType>;
public:
MapEquation() = default;
MapEquation(const MapEquation& other) = default;
MapEquation& operator=(const MapEquation& other) = default;
MapEquation(MapEquation&& other) noexcept = default;
MapEquation& operator=(MapEquation&& other) noexcept = default;
virtual ~MapEquation() = default;
// ===================================================
// Getters
// ===================================================
virtual double getIndexCodelength() const { return indexCodelength; }
virtual double getModuleCodelength() const { return moduleCodelength; }
virtual double getCodelength() const { return codelength; }
// ===================================================
// IO
// ===================================================
virtual std::ostream& print(std::ostream& out) const
{
return out << indexCodelength << " + " << moduleCodelength << " = " << io::toPrecision(codelength);
}
// ===================================================
// Init
// ===================================================
virtual void init(const Config&)
{
Log(3) << "MapEquation::init()...\n";
}
virtual void initTree(InfoNode& /*root*/) = 0;
virtual void initNetwork(InfoNode& root)
{
Log(3) << "MapEquation::initNetwork()...\n";
nodeFlow_log_nodeFlow = 0.0;
for (InfoNode& node : root) {
nodeFlow_log_nodeFlow += infomath::plogp(node.data.flow);
}
ME::initSubNetwork(root);
}
virtual void initSuperNetwork(InfoNode& root)
{
Log(3) << "MapEquation::initSuperNetwork()...\n";
nodeFlow_log_nodeFlow = 0.0;
for (InfoNode& node : root) {
nodeFlow_log_nodeFlow += infomath::plogp(node.data.enterFlow);
}
}
virtual void initSubNetwork(InfoNode& root)
{
exitNetworkFlow = root.data.exitFlow;
exitNetworkFlow_log_exitNetworkFlow = infomath::plogp(exitNetworkFlow);
}
virtual void initPartition(std::vector<InfoNode*>& nodes) { ME::calculateCodelength(nodes); }
// ===================================================
// Codelength
// ===================================================
virtual double calcCodelength(const InfoNode& parent) const
{
return parent.isLeafModule() ? ME::calcCodelengthOnModuleOfLeafNodes(parent) : ME::calcCodelengthOnModuleOfModules(parent);
}
virtual void addMemoryContributions(InfoNode& /*current*/, DeltaFlowDataType& /*oldModuleDelta*/, DeltaFlowDataType& /*newModuleDelta*/) { }
virtual void addMemoryContributions(InfoNode& /*current*/, DeltaFlowDataType& /*oldModuleDelta*/, VectorMap<DeltaFlowDataType>& /*moduleDeltaFlow*/) { }
virtual double getDeltaCodelengthOnMovingNode(InfoNode& current,
DeltaFlowDataType& oldModuleDelta,
DeltaFlowDataType& newModuleDelta,
std::vector<FlowDataType>& moduleFlowData,
std::vector<unsigned int>& /*moduleMembers*/);
// ===================================================
// Consolidation
// ===================================================
virtual void updateCodelengthOnMovingNode(InfoNode& current,
DeltaFlowDataType& oldModuleDelta,
DeltaFlowDataType& newModuleDelta,
std::vector<FlowDataType>& moduleFlowData,
std::vector<unsigned int>& /*moduleMembers*/);
virtual void consolidateModules(std::vector<InfoNode*>& /*modules*/) = 0;
// ===================================================
// Debug
// ===================================================
#if 0
virtual void printDebug() const
{
std::cout << "(enterFlow_log_enterFlow: " << enterFlow_log_enterFlow << ", "
<< "enter_log_enter: " << enter_log_enter << ", "
<< "exitNetworkFlow_log_exitNetworkFlow: " << exitNetworkFlow_log_exitNetworkFlow << ") ";
}
#endif
protected:
// ===================================================
// Protected member functions
// ===================================================
virtual double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const;
virtual double calcCodelengthOnModuleOfModules(const InfoNode& parent) const;
virtual void calculateCodelength(std::vector<InfoNode*>& nodes)
{
ME::calculateCodelengthTerms(nodes);
ME::calculateCodelengthFromCodelengthTerms();
}
virtual void calculateCodelengthTerms(std::vector<InfoNode*>& nodes);
virtual void calculateCodelengthFromCodelengthTerms()
{
indexCodelength = enterFlow_log_enterFlow - enter_log_enter - exitNetworkFlow_log_exitNetworkFlow;
moduleCodelength = -exit_log_exit + flow_log_flow - nodeFlow_log_nodeFlow;
codelength = indexCodelength + moduleCodelength;
}
public:
// ===================================================
// Public member variables
// ===================================================
double codelength = 0.0;
double indexCodelength = 0.0;
double moduleCodelength = 0.0;
protected:
// ===================================================
// Protected member variables
// ===================================================
double nodeFlow_log_nodeFlow = 0.0; // constant while the leaf network is the same
double flow_log_flow = 0.0; // node.(flow + exitFlow)
double exit_log_exit = 0.0;
double enter_log_enter = 0.0;
double enterFlow = 0.0;
double enterFlow_log_enterFlow = 0.0;
// For hierarchical
double exitNetworkFlow = 0.0;
double exitNetworkFlow_log_exitNetworkFlow = 0.0;
};
template <typename FlowDataType, typename DeltaFlowDataType>
double MapEquation<FlowDataType, DeltaFlowDataType>::getDeltaCodelengthOnMovingNode(InfoNode& current, DeltaFlowDataType& oldModuleDelta, DeltaFlowDataType& newModuleDelta, std::vector<FlowDataType>& moduleFlowData, std::vector<unsigned int>&)
{
using infomath::plogp;
unsigned int oldModule = oldModuleDelta.module;
unsigned int newModule = newModuleDelta.module;
double deltaEnterExitOldModule = oldModuleDelta.deltaEnter + oldModuleDelta.deltaExit;
double deltaEnterExitNewModule = newModuleDelta.deltaEnter + newModuleDelta.deltaExit;
double delta_enter = plogp(enterFlow + deltaEnterExitOldModule - deltaEnterExitNewModule) - enterFlow_log_enterFlow;
double delta_enter_log_enter = -plogp(moduleFlowData[oldModule].enterFlow)
- plogp(moduleFlowData[newModule].enterFlow)
+ plogp(moduleFlowData[oldModule].enterFlow - current.data.enterFlow + deltaEnterExitOldModule)
+ plogp(moduleFlowData[newModule].enterFlow + current.data.enterFlow - deltaEnterExitNewModule);
double delta_exit_log_exit = -plogp(moduleFlowData[oldModule].exitFlow)
- plogp(moduleFlowData[newModule].exitFlow)
+ plogp(moduleFlowData[oldModule].exitFlow - current.data.exitFlow + deltaEnterExitOldModule)
+ plogp(moduleFlowData[newModule].exitFlow + current.data.exitFlow - deltaEnterExitNewModule);
double delta_flow_log_flow = -plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow)
- plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow)
+ plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow
- current.data.exitFlow - current.data.flow + deltaEnterExitOldModule)
+ plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow
+ current.data.exitFlow + current.data.flow - deltaEnterExitNewModule);
double deltaL = delta_enter - delta_enter_log_enter - delta_exit_log_exit + delta_flow_log_flow;
return deltaL;
}
template <typename FlowDataType, typename DeltaFlowDataType>
void MapEquation<FlowDataType, DeltaFlowDataType>::updateCodelengthOnMovingNode(InfoNode& current, DeltaFlowDataType& oldModuleDelta, DeltaFlowDataType& newModuleDelta, std::vector<FlowDataType>& moduleFlowData, std::vector<unsigned int>&)
{
using infomath::plogp;
unsigned int oldModule = oldModuleDelta.module;
unsigned int newModule = newModuleDelta.module;
double deltaEnterExitOldModule = oldModuleDelta.deltaEnter + oldModuleDelta.deltaExit;
double deltaEnterExitNewModule = newModuleDelta.deltaEnter + newModuleDelta.deltaExit;
enterFlow -= moduleFlowData[oldModule].enterFlow + moduleFlowData[newModule].enterFlow;
enter_log_enter -= plogp(moduleFlowData[oldModule].enterFlow) + plogp(moduleFlowData[newModule].enterFlow);
exit_log_exit -= plogp(moduleFlowData[oldModule].exitFlow) + plogp(moduleFlowData[newModule].exitFlow);
flow_log_flow -= plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow);
moduleFlowData[oldModule] -= current.data;
moduleFlowData[newModule] += current.data;
moduleFlowData[oldModule].enterFlow += deltaEnterExitOldModule;
moduleFlowData[oldModule].exitFlow += deltaEnterExitOldModule;
moduleFlowData[newModule].enterFlow -= deltaEnterExitNewModule;
moduleFlowData[newModule].exitFlow -= deltaEnterExitNewModule;
enterFlow += moduleFlowData[oldModule].enterFlow + moduleFlowData[newModule].enterFlow;
enter_log_enter += plogp(moduleFlowData[oldModule].enterFlow) + plogp(moduleFlowData[newModule].enterFlow);
exit_log_exit += plogp(moduleFlowData[oldModule].exitFlow) + plogp(moduleFlowData[newModule].exitFlow);
flow_log_flow += plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow);
enterFlow_log_enterFlow = plogp(enterFlow);
indexCodelength = enterFlow_log_enterFlow - enter_log_enter - exitNetworkFlow_log_exitNetworkFlow;
moduleCodelength = -exit_log_exit + flow_log_flow - nodeFlow_log_nodeFlow;
codelength = indexCodelength + moduleCodelength;
}
template <typename FlowDataType, typename DeltaFlowDataType>
double MapEquation<FlowDataType, DeltaFlowDataType>::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
{
double parentFlow = parent.data.flow;
double parentExit = parent.data.exitFlow;
double totalParentFlow = parentFlow + parentExit;
if (totalParentFlow < 1e-16)
return 0.0;
double indexLength = 0.0;
for (const auto& node : parent) {
indexLength -= infomath::plogp(node.data.flow / totalParentFlow);
}
indexLength -= infomath::plogp(parentExit / totalParentFlow);
indexLength *= totalParentFlow;
return indexLength;
}
template <typename FlowDataType, typename DeltaFlowDataType>
double MapEquation<FlowDataType, DeltaFlowDataType>::calcCodelengthOnModuleOfModules(const InfoNode& parent) const
{
double parentFlow = parent.data.flow;
double parentExit = parent.data.exitFlow;
if (parentFlow < 1e-16)
return 0.0;
// H(x) = -xlog(x), T = q + SUM(p), q = exitFlow, p = enterFlow
// Normal format
// L = q * -log(q/T) + SUM(p * -log(p/T))
// Compact format
// L = T * ( H(q/T) + SUM( H(p/T) ) )
// Expanded format
// L = q * -log(q) - q * -log(T) + SUM( p * -log(p) - p * -log(T) )
// = T * log(T) - q*log(q) - SUM( p*log(p) )
// = -H(T) + H(q) + SUM(H(p))
// As T is not known, use expanded format to avoid two loops
double sumEnter = 0.0;
double sumEnterLogEnter = 0.0;
for (const auto& node : parent) {
sumEnter += node.data.enterFlow; // rate of enter to finer level
sumEnterLogEnter += infomath::plogp(node.data.enterFlow);
}
// The possibilities from this module: Either exit to coarser level or enter one of its children
double totalCodewordUse = parentExit + sumEnter;
return infomath::plogp(totalCodewordUse) - sumEnterLogEnter - infomath::plogp(parentExit);
}
template <typename FlowDataType, typename DeltaFlowDataType>
void MapEquation<FlowDataType, DeltaFlowDataType>::calculateCodelengthTerms(std::vector<InfoNode*>& nodes)
{
enter_log_enter = 0.0;
flow_log_flow = 0.0;
exit_log_exit = 0.0;
enterFlow = 0.0;
// For each module
for (InfoNode* n : nodes) {
InfoNode& node = *n;
// own node/module codebook
flow_log_flow += infomath::plogp(node.data.flow + node.data.exitFlow);
// use of index codebook
enter_log_enter += infomath::plogp(node.data.enterFlow);
exit_log_exit += infomath::plogp(node.data.exitFlow);
enterFlow += node.data.enterFlow;
}
enterFlow += exitNetworkFlow;
enterFlow_log_enterFlow = infomath::plogp(enterFlow);
}
} // namespace infomap
#endif // MAPEQUATION_H_
@@ -0,0 +1,451 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "MemMapEquation.h"
#include "FlowData.h"
#include "InfoNode.h"
#include <vector>
#include <set>
#include <map>
#include <utility>
#include <algorithm>
namespace infomap {
// ===================================================
// IO
// ===================================================
std::ostream& MemMapEquation::print(std::ostream& out) const
{
return out << indexCodelength << " + " << moduleCodelength << " = " << io::toPrecision(codelength);
}
std::ostream& operator<<(std::ostream& out, const MemMapEquation& mapEq)
{
return mapEq.print(out);
}
// ===================================================
// Init
// ===================================================
void MemMapEquation::init(const Config& /*config*/)
{
Log(3) << "MemMapEquation::init()...\n";
}
void MemMapEquation::initNetwork(InfoNode& root)
{
initPhysicalNodes(root);
}
void MemMapEquation::initSuperNetwork(InfoNode& /*root*/)
{
// TODO: How use enterFlow instead of flow
}
void MemMapEquation::initSubNetwork(InfoNode& /*root*/)
{
// Base::initSubNetwork(root);
}
void MemMapEquation::initPartition(std::vector<InfoNode*>& nodes)
{
initPartitionOfPhysicalNodes(nodes);
calculateCodelength(nodes);
}
void MemMapEquation::initPhysicalNodes(InfoNode& root)
{
bool notInitiatedOnRoot = root.physicalNodes.empty();
if (notInitiatedOnRoot) {
// Assume leaf nodes directly under the root node
std::unordered_map<unsigned int, double> physicalNodes;
unsigned int maxPhysicalId = 0;
unsigned int minPhysicalId = std::numeric_limits<unsigned int>::max();
for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) {
InfoNode& node = *it;
physicalNodes[node.physicalId] += node.data.flow;
minPhysicalId = std::min(minPhysicalId, node.physicalId);
maxPhysicalId = std::max(maxPhysicalId, node.physicalId);
}
// Re-index physical nodes if necessary
std::map<unsigned int, unsigned int> toZeroBasedIndex;
if (maxPhysicalId - minPhysicalId + 1 > m_numPhysicalNodes) {
unsigned int zeroBasedPhysicalId = 0;
for (const auto& physNode : physicalNodes) {
toZeroBasedIndex.insert(std::make_pair(physNode.first, zeroBasedPhysicalId++));
}
}
for (const auto& physNode : physicalNodes) {
unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[physNode.first] : (physNode.first - minPhysicalId);
root.physicalNodes.emplace_back(zeroBasedIndex, physNode.second);
}
}
auto firstLeafIt = root.begin_leaf_nodes();
auto depth = firstLeafIt.depth();
bool notInitiatedOnLeafNodes = firstLeafIt->physicalNodes.empty();
if (notInitiatedOnLeafNodes) {
Log(3) << "MemMapEquation::initPhysicalNodesOnOriginalNetwork()...\n";
std::set<unsigned int> setOfPhysicalNodes;
unsigned int maxPhysicalId = 0;
unsigned int minPhysicalId = std::numeric_limits<unsigned int>::max();
for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) {
InfoNode& node = *it;
setOfPhysicalNodes.insert(node.physicalId);
maxPhysicalId = std::max(maxPhysicalId, node.physicalId);
minPhysicalId = std::min(minPhysicalId, node.physicalId);
}
m_numPhysicalNodes = setOfPhysicalNodes.size();
// Re-index physical nodes if necessary
std::map<unsigned int, unsigned int> toZeroBasedIndex;
if (maxPhysicalId - minPhysicalId + 1 > m_numPhysicalNodes) {
unsigned int zeroBasedPhysicalId = 0;
for (unsigned int physIndex : setOfPhysicalNodes) {
toZeroBasedIndex.insert(std::make_pair(physIndex, zeroBasedPhysicalId++));
}
}
for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) {
InfoNode& node = *it;
unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[node.physicalId] : (node.physicalId - minPhysicalId);
node.physicalNodes.emplace_back(zeroBasedIndex, node.data.flow);
}
// If leaf nodes was not directly under root, make sure leaf modules have
// physical nodes defined also
if (depth > 1) {
for (auto it(root.begin_leaf_modules()); !it.isEnd(); ++it) {
InfoNode& module = *it;
std::map<unsigned int, double> physToFlow;
for (auto& node : module) {
for (PhysData& physData : node.physicalNodes) {
physToFlow[physData.physNodeIndex] += physData.sumFlowFromM2Node;
}
}
for (auto& physFlow : physToFlow) {
module.physicalNodes.emplace_back(physFlow.first, physFlow.second);
}
}
}
} else {
// Either a sub-network (without modules) or the whole network with reconstructed tree
if (depth == 1) {
// new sub-network
Log(3) << "MemMapEquation::initPhysicalNodesOnSubNetwork()...\n";
std::set<unsigned int> setOfPhysicalNodes;
unsigned int maxPhysNodeIndex = 0;
unsigned int minPhysNodeIndex = std::numeric_limits<unsigned int>::max();
// Collect all physical nodes in this sub network
for (InfoNode& node : root) {
for (PhysData& physData : node.physicalNodes) {
setOfPhysicalNodes.insert(physData.physNodeIndex);
maxPhysNodeIndex = std::max(maxPhysNodeIndex, physData.physNodeIndex);
minPhysNodeIndex = std::min(minPhysNodeIndex, physData.physNodeIndex);
}
}
m_numPhysicalNodes = setOfPhysicalNodes.size();
// Re-index physical nodes if needed (not required when reconstructing tree)
if (maxPhysNodeIndex >= m_numPhysicalNodes) {
std::map<unsigned int, unsigned int> toZeroBasedIndex;
if (maxPhysNodeIndex - minPhysNodeIndex + 1 > m_numPhysicalNodes) {
unsigned int zeroBasedPhysicalId = 0;
for (unsigned int physIndex : setOfPhysicalNodes) {
toZeroBasedIndex.insert(std::make_pair(physIndex, zeroBasedPhysicalId++));
}
}
for (InfoNode& node : root) {
for (PhysData& physData : node.physicalNodes) {
unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[physData.physNodeIndex] : (physData.physNodeIndex - minPhysNodeIndex);
physData.physNodeIndex = zeroBasedIndex;
}
}
}
} else {
// whole network with reconstructed tree
for (auto it(root.begin_leaf_modules()); !it.isEnd(); ++it) {
InfoNode& module = *it;
std::map<unsigned int, double> physToFlow;
for (auto& node : module) {
for (PhysData& physData : node.physicalNodes) {
physToFlow[physData.physNodeIndex] += physData.sumFlowFromM2Node;
}
}
for (auto& physFlow : physToFlow) {
module.physicalNodes.emplace_back(physFlow.first, physFlow.second);
}
}
}
}
}
void MemMapEquation::initPartitionOfPhysicalNodes(std::vector<InfoNode*>& nodes)
{
Log(4) << "MemMapEquation::initPartitionOfPhysicalNodes()...\n";
m_physToModuleToMemNodes.clear();
m_physToModuleToMemNodes.resize(m_numPhysicalNodes);
for (auto& n : nodes) {
InfoNode& node = *n;
unsigned int moduleIndex = node.index; // Assume unique module index for all nodes in this initiation phase
for (PhysData& physData : node.physicalNodes) {
m_physToModuleToMemNodes[physData.physNodeIndex].insert(m_physToModuleToMemNodes[physData.physNodeIndex].end(),
std::make_pair(moduleIndex, MemNodeSet(1, physData.sumFlowFromM2Node)));
}
}
m_memoryContributionsAdded = false;
}
// ===================================================
// Codelength
// ===================================================
void MemMapEquation::calculateCodelength(std::vector<InfoNode*>& nodes)
{
calculateCodelengthTerms(nodes);
calculateNodeFlow_log_nodeFlow();
calculateCodelengthFromCodelengthTerms();
}
void MemMapEquation::calculateNodeFlow_log_nodeFlow()
{
nodeFlow_log_nodeFlow = 0.0;
for (unsigned int i = 0; i < m_numPhysicalNodes; ++i) {
const ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[i];
for (const auto& moduleToMemNode : moduleToMemNodes)
nodeFlow_log_nodeFlow += infomath::plogp(moduleToMemNode.second.sumFlow);
}
}
double MemMapEquation::calcCodelength(const InfoNode& parent) const
{
if (parent.isLeafModule()) {
return calcCodelengthOnModuleOfLeafNodes(parent);
}
// Use first-order model on index codebook
return Base::calcCodelengthOnModuleOfModules(parent);
}
double MemMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
{
if (parent.numPhysicalNodes() == 0) {
return Base::calcCodelength(parent); // Infomap root node
}
// TODO: For ordinary networks, flow should be used instead of enter flow
// for leaf nodes, what about memory networks? sumFlowFromM2Node vs sumEnterFlowFromM2Node?
double parentFlow = parent.data.flow;
double parentExit = parent.data.exitFlow;
double totalParentFlow = parentFlow + parentExit;
if (totalParentFlow < 1e-16)
return 0.0;
double indexLength = 0.0;
for (const PhysData& physData : parent.physicalNodes) {
indexLength -= infomath::plogp(physData.sumFlowFromM2Node / totalParentFlow);
}
indexLength -= infomath::plogp(parentExit / totalParentFlow);
indexLength *= totalParentFlow;
return indexLength;
}
void MemMapEquation::addMemoryContributions(InfoNode& current,
MemDeltaFlow& oldModuleDelta,
VectorMap<MemDeltaFlow>& moduleDeltaFlow)
{
// Overlapping modules
/*
* delta = old.first + new.first + old.second - new.second.
* Two cases: (p(x) = plogp(x))
* Moving to a module that already have that physical node: (old: p1, p2, new p3, moving p2 -> old:p1, new p2,p3)
* Then old.second = new.second = plogp(physicalNodeSize) -> cancelation -> delta = p(p1) - p(p1+p2) + p(p2+p3) - p(p3)
* Moving to a module that not have that physical node: (old: p1, p2, new -, moving p2 -> old: p1, new: p2)
* Then new.first = new.second = 0 -> delta = p(p1) - p(p1+p2) + p(p2).
*/
auto& physicalNodes = current.physicalNodes;
unsigned int numPhysicalNodes = physicalNodes.size();
for (unsigned int i = 0; i < numPhysicalNodes; ++i) {
PhysData& physData = physicalNodes[i];
ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex];
for (const auto& moduleToMemNode : moduleToMemNodes) {
unsigned int moduleIndex = moduleToMemNode.first;
auto& memNodeSet = moduleToMemNode.second;
if (moduleIndex == current.index) // From where the multiple assigned node is moved
{
double oldPhysFlow = memNodeSet.sumFlow;
double newPhysFlow = memNodeSet.sumFlow - physData.sumFlowFromM2Node;
oldModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
oldModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
} else // To where the multiple assigned node is moved
{
double oldPhysFlow = memNodeSet.sumFlow;
double newPhysFlow = memNodeSet.sumFlow + physData.sumFlowFromM2Node;
double sumDeltaPlogpPhysFlow = infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
double sumPlogpPhysFlow = infomath::plogp(physData.sumFlowFromM2Node);
moduleDeltaFlow.add(moduleIndex, MemDeltaFlow(moduleIndex, 0.0, 0.0, sumDeltaPlogpPhysFlow, sumPlogpPhysFlow));
}
}
}
m_memoryContributionsAdded = true;
}
double MemMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current,
MemDeltaFlow& oldModuleDelta,
MemDeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers)
{
double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
double delta_nodeFlow_log_nodeFlow = oldModuleDelta.sumDeltaPlogpPhysFlow + newModuleDelta.sumDeltaPlogpPhysFlow + oldModuleDelta.sumPlogpPhysFlow - newModuleDelta.sumPlogpPhysFlow;
return deltaL - delta_nodeFlow_log_nodeFlow;
}
// ===================================================
// Consolidation
// ===================================================
void MemMapEquation::updateCodelengthOnMovingNode(InfoNode& current,
MemDeltaFlow& oldModuleDelta,
MemDeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers)
{
Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
if (m_memoryContributionsAdded)
updatePhysicalNodes(current, oldModuleDelta.module, newModuleDelta.module);
else
addMemoryContributionsAndUpdatePhysicalNodes(current, oldModuleDelta, newModuleDelta);
double delta_nodeFlow_log_nodeFlow = oldModuleDelta.sumDeltaPlogpPhysFlow + newModuleDelta.sumDeltaPlogpPhysFlow + oldModuleDelta.sumPlogpPhysFlow - newModuleDelta.sumPlogpPhysFlow;
nodeFlow_log_nodeFlow += delta_nodeFlow_log_nodeFlow;
moduleCodelength -= delta_nodeFlow_log_nodeFlow;
codelength -= delta_nodeFlow_log_nodeFlow;
}
void MemMapEquation::updatePhysicalNodes(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex)
{
// For all multiple assigned nodes
for (const auto& physData : current.physicalNodes) {
ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex];
// Remove contribution to old module
auto overlapIt = moduleToMemNodes.find(oldModuleIndex);
if (overlapIt == moduleToMemNodes.end())
throw std::length_error(io::Str() << "Couldn't find old module " << oldModuleIndex << " in physical node " << physData.physNodeIndex);
MemNodeSet& oldMemNodeSet = overlapIt->second;
oldMemNodeSet.sumFlow -= physData.sumFlowFromM2Node;
if (--oldMemNodeSet.numMemNodes == 0)
moduleToMemNodes.erase(overlapIt);
// Add contribution to new module
overlapIt = moduleToMemNodes.find(bestModuleIndex);
if (overlapIt == moduleToMemNodes.end()) {
moduleToMemNodes.insert(std::make_pair(bestModuleIndex, MemNodeSet(1, physData.sumFlowFromM2Node)));
} else {
MemNodeSet& newMemNodeSet = overlapIt->second;
++newMemNodeSet.numMemNodes;
newMemNodeSet.sumFlow += physData.sumFlowFromM2Node;
}
}
}
void MemMapEquation::addMemoryContributionsAndUpdatePhysicalNodes(InfoNode& current, MemDeltaFlow& oldModuleDelta, MemDeltaFlow& newModuleDelta)
{
unsigned int oldModuleIndex = oldModuleDelta.module;
unsigned int bestModuleIndex = newModuleDelta.module;
// For all multiple assigned nodes
for (const auto& physData : current.physicalNodes) {
ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex];
// Remove contribution to old module
auto overlapIt = moduleToMemNodes.find(oldModuleIndex);
if (overlapIt == moduleToMemNodes.end())
throw std::length_error("Couldn't find old module among physical node assignments.");
MemNodeSet& oldMemNodeSet = overlapIt->second;
double oldPhysFlow = oldMemNodeSet.sumFlow;
double newPhysFlow = oldMemNodeSet.sumFlow - physData.sumFlowFromM2Node;
oldModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
oldModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
oldMemNodeSet.sumFlow -= physData.sumFlowFromM2Node;
if (--oldMemNodeSet.numMemNodes == 0)
moduleToMemNodes.erase(overlapIt);
// Add contribution to new module
overlapIt = moduleToMemNodes.find(bestModuleIndex);
if (overlapIt == moduleToMemNodes.end()) {
moduleToMemNodes.insert(std::make_pair(bestModuleIndex, MemNodeSet(1, physData.sumFlowFromM2Node)));
oldPhysFlow = 0.0;
newPhysFlow = physData.sumFlowFromM2Node;
newModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
newModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
} else {
MemNodeSet& newMemNodeSet = overlapIt->second;
oldPhysFlow = newMemNodeSet.sumFlow;
newPhysFlow = newMemNodeSet.sumFlow + physData.sumFlowFromM2Node;
newModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
newModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
++newMemNodeSet.numMemNodes;
newMemNodeSet.sumFlow += physData.sumFlowFromM2Node;
}
}
}
void MemMapEquation::consolidateModules(std::vector<InfoNode*>& modules)
{
std::map<unsigned int, std::map<unsigned int, unsigned int>> validate;
for (unsigned int i = 0; i < m_numPhysicalNodes; ++i) {
ModuleToMemNodes& modToMemNodes = m_physToModuleToMemNodes[i];
for (const auto& modToMemNode : modToMemNodes) {
if (++validate[modToMemNode.first][i] > 1)
throw std::domain_error("[InfomapGreedy::consolidateModules] Error updating physical nodes: duplication error");
modules[modToMemNode.first]->physicalNodes.emplace_back(i, modToMemNode.second.sumFlow);
}
}
}
#if 0
// ===================================================
// Debug
// ===================================================
void MemMapEquation::printDebug() const
{
std::cout << "MemMapEquation::m_numPhysicalNodes: " << m_numPhysicalNodes << "\n";
Base::printDebug();
}
#endif
} // namespace infomap
@@ -0,0 +1,174 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef MEM_MAPEQUATION_H_
#define MEM_MAPEQUATION_H_
#include "MapEquation.h"
#include "FlowData.h"
#include "../utils/Log.h"
#include <vector>
#include <set>
#include <map>
#include <utility>
namespace infomap {
class InfoNode;
struct MemNodeSet;
class MemMapEquation : private MapEquation<FlowData, MemDeltaFlow> {
using Base = MapEquation<FlowData, MemDeltaFlow>;
public:
using FlowDataType = FlowData;
using DeltaFlowDataType = MemDeltaFlow;
// ===================================================
// Getters
// ===================================================
using Base::getIndexCodelength;
using Base::getModuleCodelength;
using Base::getCodelength;
// ===================================================
// IO
// ===================================================
std::ostream& print(std::ostream& out) const override;
friend std::ostream& operator<<(std::ostream&, const MemMapEquation&);
// ===================================================
// Init
// ===================================================
void init(const Config& config) override;
void initTree(InfoNode& /*root*/) override { }
void initNetwork(InfoNode& root) override;
void initSuperNetwork(InfoNode& root) override;
void initSubNetwork(InfoNode& root) override;
void initPartition(std::vector<InfoNode*>& nodes) override;
// ===================================================
// Codelength
// ===================================================
double calcCodelength(const InfoNode& parent) const override;
void addMemoryContributions(InfoNode& current, MemDeltaFlow& oldModuleDelta, VectorMap<MemDeltaFlow>& moduleDeltaFlow) override;
double getDeltaCodelengthOnMovingNode(InfoNode& current,
MemDeltaFlow& oldModuleDelta,
MemDeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers) override;
// ===================================================
// Consolidation
// ===================================================
void updateCodelengthOnMovingNode(InfoNode& current,
MemDeltaFlow& oldModuleDelta,
MemDeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers) override;
void consolidateModules(std::vector<InfoNode*>& modules) override;
#if 0
// ===================================================
// Debug
// ===================================================
void printDebug() const override;
#endif
private:
// ===================================================
// Private member functions
// ===================================================
double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override;
// ===================================================
// Init
// ===================================================
void initPhysicalNodes(InfoNode& root);
void initPartitionOfPhysicalNodes(std::vector<InfoNode*>& nodes);
// ===================================================
// Codelength
// ===================================================
void calculateCodelength(std::vector<InfoNode*>& nodes) override;
using Base::calculateCodelengthTerms;
using Base::calculateCodelengthFromCodelengthTerms;
void calculateNodeFlow_log_nodeFlow();
// ===================================================
// Consolidation
// ===================================================
void updatePhysicalNodes(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex);
void addMemoryContributionsAndUpdatePhysicalNodes(InfoNode& current, MemDeltaFlow& oldModuleDelta, MemDeltaFlow& newModuleDelta);
public:
// ===================================================
// Public member variables
// ===================================================
using Base::codelength;
using Base::indexCodelength;
using Base::moduleCodelength;
private:
// ===================================================
// Private member variables
// ===================================================
using Base::enter_log_enter;
using Base::enterFlow;
using Base::enterFlow_log_enterFlow;
using Base::exit_log_exit;
using Base::flow_log_flow; // node.(flow + exitFlow)
using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same
// For hierarchical
using Base::exitNetworkFlow;
using Base::exitNetworkFlow_log_exitNetworkFlow;
using ModuleToMemNodes = std::map<unsigned int, MemNodeSet>;
std::vector<ModuleToMemNodes> m_physToModuleToMemNodes; // vector[physicalNodeID] map<moduleID, {#memNodes, sumFlow}>
unsigned int m_numPhysicalNodes = 0;
bool m_memoryContributionsAdded = false;
};
struct MemNodeSet {
MemNodeSet(unsigned int numMemNodes, double sumFlow) : numMemNodes(numMemNodes), sumFlow(sumFlow) { }
unsigned int numMemNodes; // use counter to check for zero to avoid round-off errors in sumFlow
double sumFlow;
};
} // namespace infomap
#endif // MEM_MAPEQUATION_H_
@@ -0,0 +1,271 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "MetaMapEquation.h"
#include "FlowData.h"
#include "InfoNode.h"
#include <vector>
#include <utility>
namespace infomap {
double MetaMapEquation::getModuleCodelength() const
{
return moduleCodelength + metaCodelength * metaDataRate;
}
double MetaMapEquation::getCodelength() const
{
return codelength + metaCodelength * metaDataRate;
}
// ===================================================
// IO
// ===================================================
std::ostream& MetaMapEquation::print(std::ostream& out) const
{
return out << indexCodelength << " + " << moduleCodelength << " + " << metaCodelength << " = " << io::toPrecision(getCodelength());
}
std::ostream& operator<<(std::ostream& out, const MetaMapEquation& mapEq)
{
return mapEq.print(out);
}
// ===================================================
// Init
// ===================================================
void MetaMapEquation::init(const Config& config)
{
Log(3) << "MetaMapEquation::init()...\n";
numMetaDataDimensions = config.numMetaDataDimensions;
metaDataRate = config.metaDataRate;
weightByFlow = !config.unweightedMetaData;
}
void MetaMapEquation::initTree(InfoNode& root)
{
Log(3) << "MetaMapEquation::initTree()...\n";
initMetaNodes(root);
}
void MetaMapEquation::initNetwork(InfoNode& root)
{
Log(3) << "MetaMapEquation::initNetwork()...\n";
Base::initNetwork(root);
m_unweightedNodeFlow = 1.0 / root.childDegree();
}
void MetaMapEquation::initPartition(std::vector<InfoNode*>& nodes)
{
initPartitionOfMetaNodes(nodes);
calculateCodelength(nodes);
}
void MetaMapEquation::initMetaNodes(InfoNode& root)
{
bool notInitiated = root.firstChild->metaCollection.empty();
if (notInitiated) {
Log(3) << "MetaMapEquation::initMetaNodes()...\n";
for (auto it = root.begin_post_depth_first(); !it.isEnd(); ++it) {
auto& node = *it;
if (node.isRoot()) {
break;
}
if (node.isLeaf()) {
if (!node.metaData.empty()) {
// TODO: Use flow here and move weightByFlow choice to metaCollection, using flowCount?
double flow = weightByFlow ? node.data.flow : m_unweightedNodeFlow;
node.parent->metaCollection.add(node.metaData[0], flow);
} else {
throw std::length_error("A node is missing meta data using MetaMapEquation");
}
} else {
node.parent->metaCollection.add(node.metaCollection);
}
}
}
}
void MetaMapEquation::initPartitionOfMetaNodes(std::vector<InfoNode*>& nodes)
{
Log(4) << "MetaMapEquation::initPartitionOfMetaNodes()...\n";
m_moduleToMetaCollection.clear();
for (auto& n : nodes) {
InfoNode& node = *n;
unsigned int moduleIndex = node.index; // Assume unique module index for all nodes in this initiation phase
if (node.metaCollection.empty()) {
if (!node.metaData.empty()) {
double flow = weightByFlow ? node.data.flow : m_unweightedNodeFlow;
node.metaCollection.add(node.metaData[0], flow);
} else
throw std::length_error("A node is missing meta data using MetaMapEquation");
}
m_moduleToMetaCollection[moduleIndex] = node.metaCollection;
}
}
// ===================================================
// Codelength
// ===================================================
void MetaMapEquation::calculateCodelength(std::vector<InfoNode*>& nodes)
{
calculateCodelengthTerms(nodes);
calculateCodelengthFromCodelengthTerms();
metaCodelength = 0.0;
// Treat each node as a single module
for (InfoNode* n : nodes) {
InfoNode& node = *n;
metaCodelength += node.metaCollection.calculateEntropy();
}
}
double MetaMapEquation::calcCodelength(const InfoNode& parent) const
{
return parent.isLeafModule() ? calcCodelengthOnModuleOfLeafNodes(parent) : Base::calcCodelengthOnModuleOfModules(parent);
}
double MetaMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
{
double indexLength = Base::calcCodelength(parent);
// Meta addition
MetaCollection metaCollection;
for (const InfoNode& node : parent) {
if (!node.metaCollection.empty())
metaCollection.add(node.metaCollection);
else
metaCollection.add(node.metaData[0], weightByFlow ? node.data.flow : m_unweightedNodeFlow); // TODO: Initiate to collection and use all dimensions
}
double _metaCodelength = metaCollection.calculateEntropy();
return indexLength + metaDataRate * _metaCodelength;
}
double MetaMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers)
{
double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
double deltaMetaL = 0.0;
unsigned int oldModuleIndex = oldModuleDelta.module;
unsigned int newModuleIndex = newModuleDelta.module;
// Remove codelength of old and new module before changes
deltaMetaL -= getCurrentModuleMetaCodelength(oldModuleIndex, current, 0);
deltaMetaL -= getCurrentModuleMetaCodelength(newModuleIndex, current, 0);
// Add codelength of old module with current node removed
deltaMetaL += getCurrentModuleMetaCodelength(oldModuleIndex, current, -1);
// Add codelength of old module with current node added
deltaMetaL += getCurrentModuleMetaCodelength(newModuleIndex, current, 1);
return deltaL + deltaMetaL * metaDataRate;
}
double MetaMapEquation::getCurrentModuleMetaCodelength(unsigned int module, InfoNode& current, int addRemoveOrNothing)
{
auto& currentMetaCollection = m_moduleToMetaCollection[module];
double moduleMetaCodelength = 0.0;
if (addRemoveOrNothing == 0) {
moduleMetaCodelength = currentMetaCollection.calculateEntropy();
}
// If add or remove, do the change, calculate new codelength and then undo the change
else if (addRemoveOrNothing == 1) {
currentMetaCollection.add(current.metaCollection);
moduleMetaCodelength = currentMetaCollection.calculateEntropy();
currentMetaCollection.remove(current.metaCollection);
} else {
currentMetaCollection.remove(current.metaCollection);
moduleMetaCodelength = currentMetaCollection.calculateEntropy();
currentMetaCollection.add(current.metaCollection);
}
return moduleMetaCodelength;
}
// ===================================================
// Consolidation
// ===================================================
void MetaMapEquation::updateCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers)
{
Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
double deltaMetaL = 0.0;
unsigned int oldModuleIndex = oldModuleDelta.module;
unsigned int newModuleIndex = newModuleDelta.module;
// Remove codelength of old and new module before changes
deltaMetaL -= getCurrentModuleMetaCodelength(oldModuleIndex, current, 0);
deltaMetaL -= getCurrentModuleMetaCodelength(newModuleIndex, current, 0);
// Update meta data from moving node
updateMetaData(current, oldModuleIndex, newModuleIndex);
// Add codelength of old and new module after changes
deltaMetaL += getCurrentModuleMetaCodelength(oldModuleIndex, current, 0);
deltaMetaL += getCurrentModuleMetaCodelength(newModuleIndex, current, 0);
metaCodelength += deltaMetaL;
}
void MetaMapEquation::updateMetaData(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex)
{
// Remove meta id from old module (can be a set of meta ids when moving submodules in coarse tune)
auto& oldMetaCollection = m_moduleToMetaCollection[oldModuleIndex];
oldMetaCollection.remove(current.metaCollection);
// Add meta id to new module
auto& newMetaCollection = m_moduleToMetaCollection[bestModuleIndex];
newMetaCollection.add(current.metaCollection);
}
void MetaMapEquation::consolidateModules(std::vector<InfoNode*>& modules)
{
for (auto& module : modules) {
if (module == nullptr)
continue;
module->metaCollection = m_moduleToMetaCollection[module->index];
}
}
#if 0
// ===================================================
// Debug
// ===================================================
void MetaMapEquation::printDebug() const
{
std::cout << "MetaMapEquation\n";
Base::printDebug();
}
#endif
} // namespace infomap
@@ -0,0 +1,185 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef META_MAPEQUATION_H_
#define META_MAPEQUATION_H_
#include "MapEquation.h"
#include "FlowData.h"
#include "../utils/Log.h"
#include "../utils/MetaCollection.h"
#include <vector>
#include <set>
#include <map>
#include <utility>
namespace infomap {
class InfoNode;
class MetaMapEquation : private MapEquation<> {
using Base = MapEquation<>;
public:
using FlowDataType = FlowData;
using DeltaFlowDataType = DeltaFlow;
// ===================================================
// Getters
// ===================================================
using Base::getIndexCodelength;
double getModuleCodelength() const override;
double getCodelength() const override;
double getMetaCodelength(bool unweighted = false) const
{
return unweighted ? metaCodelength : metaDataRate * metaCodelength;
};
// ===================================================
// IO
// ===================================================
std::ostream& print(std::ostream& out) const override;
friend std::ostream& operator<<(std::ostream&, const MetaMapEquation&);
// ===================================================
// Init
// ===================================================
void init(const Config& config) override;
void initTree(InfoNode& root) override;
void initNetwork(InfoNode& root) override;
using Base::initSuperNetwork;
using Base::initSubNetwork;
void initPartition(std::vector<InfoNode*>& nodes) override;
// ===================================================
// Codelength
// ===================================================
double calcCodelength(const InfoNode& parent) const override;
using Base::addMemoryContributions;
double getDeltaCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers) override;
// ===================================================
// Consolidation
// ===================================================
void updateCodelengthOnMovingNode(InfoNode& current,
DeltaFlow& oldModuleDelta,
DeltaFlow& newModuleDelta,
std::vector<FlowData>& moduleFlowData,
std::vector<unsigned int>& moduleMembers) override;
void consolidateModules(std::vector<InfoNode*>& modules) override;
#if 0
// ===================================================
// Debug
// ===================================================
void printDebug() const override;
#endif
private:
// ===================================================
// Private member functions
// ===================================================
double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override;
// ===================================================
// Init
// ===================================================
void initMetaNodes(InfoNode& root);
void initPartitionOfMetaNodes(std::vector<InfoNode*>& nodes);
// ===================================================
// Codelength
// ===================================================
void calculateCodelength(std::vector<InfoNode*>& nodes) override;
using Base::calculateCodelengthTerms;
using Base::calculateCodelengthFromCodelengthTerms;
// ===================================================
// Consolidation
// ===================================================
void updateMetaData(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex);
public:
// ===================================================
// Public member variables
// ===================================================
using Base::codelength;
using Base::indexCodelength;
using Base::moduleCodelength;
private:
// ===================================================
// Private member functions
// ===================================================
/**
* Get meta codelength of module of current node
* @param addRemoveOrNothing +1, -1 or 0 to calculate codelength
* as if current node was added, removed or untouched in current module
*/
double getCurrentModuleMetaCodelength(unsigned int module, InfoNode& current, int addRemoveOrNothing);
// ===================================================
// Private member variables
// ===================================================
using Base::enter_log_enter;
using Base::enterFlow;
using Base::enterFlow_log_enterFlow;
using Base::exit_log_exit;
using Base::flow_log_flow; // node.(flow + exitFlow)
using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same
// For hierarchical
using Base::exitNetworkFlow;
using Base::exitNetworkFlow_log_exitNetworkFlow;
// For meta data
using ModuleMetaMap = std::map<unsigned int, MetaCollection>; // moduleId -> (metaId -> count)
ModuleMetaMap m_moduleToMetaCollection;
unsigned int numMetaDataDimensions = 0;
double metaDataRate = 1.0;
bool weightByFlow = true;
double metaCodelength = 0.0;
double m_unweightedNodeFlow = 0.0;
};
} // namespace infomap
#endif // META_MAPEQUATION_H_
@@ -0,0 +1,329 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "StateNetwork.h"
#include "../utils/FlowCalculator.h"
#include "../utils/Log.h"
#include "../io/SafeFile.h"
#include <stdexcept>
#include <utility>
namespace infomap {
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNode(const StateNode& node)
{
auto ret = m_nodes.insert(StateNetwork::NodeMap::value_type(node.id, node));
if (ret.second) {
// If state node didn't exist, also create the associated physical node
addPhysicalNode(node.physicalId);
if (node.id != node.physicalId) {
m_haveMemoryInput = true;
}
}
return ret;
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNode(unsigned int id, unsigned int physId)
{
m_higherOrderInputMethodCalled = true;
return addStateNode(StateNode(id, physId));
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id)
{
return addStateNode(StateNode(id));
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id, double weight)
{
auto ret = addNode(id);
auto& node = ret.first->second;
node.weight = weight;
return ret;
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id, std::string name)
{
m_names[id] = std::move(name);
return addNode(id);
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id, std::string name, double weight)
{
m_names[id] = std::move(name);
return addNode(id, weight);
}
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId)
{
auto& physNode = m_physNodes[physId];
physNode.physId = physId;
m_sumNodeWeight += 1.0;
return physNode;
}
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, double weight)
{
auto& physNode = addPhysicalNode(physId);
physNode.weight = weight;
m_sumNodeWeight += weight;
return physNode;
}
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, const std::string& name)
{
auto& physNode = addPhysicalNode(physId);
m_names[physId] = name;
m_sumNodeWeight += 1.0;
return physNode;
}
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, double weight, const std::string& name)
{
auto& physNode = addPhysicalNode(physId);
physNode.weight = weight;
m_sumNodeWeight += weight;
m_names[physId] = name;
return physNode;
}
std::pair<std::map<unsigned int, std::string>::iterator, bool> StateNetwork::addName(unsigned int id, const std::string& name)
{
return m_names.insert(std::make_pair(id, name));
}
bool StateNetwork::addLink(unsigned int sourceId, unsigned int targetId, double weight)
{
if (weight < m_config.weightThreshold || weight <= 0) {
++m_numLinksIgnoredByWeightThreshold;
m_totalLinkWeightIgnored += weight;
return false;
}
m_totalLinkWeightAdded += weight;
if (sourceId == targetId) {
++m_numSelfLinksFound;
if (m_config.noSelfLinks) {
return false;
}
++m_numSelfLinks;
m_sumSelfLinkWeight += weight;
}
addNode(sourceId);
addNode(targetId);
++m_numLinks;
m_sumLinkWeight += weight;
bool addedNewLink = true;
// Aggregate link weights if they are defined more than once
auto& outLinks = m_nodeLinkMap[sourceId];
if (outLinks.empty()) {
outLinks[targetId] = weight;
} else {
auto ret = outLinks.insert(std::make_pair(StateNode(targetId), LinkData(weight)));
auto& linkData = ret.first->second;
if (!ret.second) {
linkData.weight += weight;
++m_numAggregatedLinks;
--m_numLinks;
addedNewLink = false;
} else {
linkData.weight = weight;
}
}
m_outWeights[sourceId] += weight;
return addedNewLink;
}
bool StateNetwork::addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight)
{
return addLink(sourceId, targetId, static_cast<double>(weight));
}
bool StateNetwork::removeLink(unsigned int sourceId, unsigned int targetId)
{
auto itSource = m_nodeLinkMap.find(sourceId);
if (itSource == m_nodeLinkMap.end()) {
return false;
}
auto& targetMap = itSource->second;
auto itTarget = targetMap.find(targetId);
if (itTarget == targetMap.end()) {
return false;
}
double weight = itTarget->second.weight;
targetMap.erase(itTarget);
if (targetMap.empty()) {
m_nodeLinkMap.erase(itSource);
} else {
--m_numAggregatedLinks;
}
--m_numLinks;
m_sumLinkWeight -= weight;
m_totalLinkWeightAdded -= weight;
if (sourceId == targetId) {
--m_numSelfLinksFound;
--m_numSelfLinks;
m_sumSelfLinkWeight -= weight;
}
m_outWeights[sourceId] -= weight;
return true;
}
bool StateNetwork::undirectedToDirected()
{
// Collect links in separate data structure to not risk iterating newly added links
if (!m_config.isUndirectedFlow()) {
return false;
}
std::deque<StateLink> oppositeLinks;
for (auto& linkIt : m_nodeLinkMap) {
unsigned int sourceId = linkIt.first.id;
const auto& subLinks = linkIt.second;
for (auto& subIt : subLinks) {
unsigned int targetId = subIt.first.id;
if (targetId == sourceId) {
continue; // Self-links are treated as directed on undirected networks
}
double weight = subIt.second.weight;
oppositeLinks.emplace_back(targetId, sourceId, weight);
}
}
for (auto& link : oppositeLinks) {
addLink(link.source, link.target, link.weight);
}
return true;
}
void StateNetwork::clearLinks()
{
m_nodeLinkMap.clear();
}
void StateNetwork::clear()
{
m_nodes.clear();
m_nodeLinkMap.clear();
m_physNodes.clear();
m_outWeights.clear();
m_names.clear();
m_haveDirectedInput = false;
m_haveMemoryInput = false;
m_numStateNodesFound = 0;
m_numLinks = 0;
m_numSelfLinksFound = 0;
m_sumLinkWeight = 0.0;
m_numSelfLinks = 0;
m_sumSelfLinkWeight = 0.0;
m_numAggregatedLinks = 0;
m_totalLinkWeightAdded = 0.0;
m_numLinksIgnoredByWeightThreshold = 0;
m_totalLinkWeightIgnored = 0.0;
}
void StateNetwork::writeStateNetwork(const std::string& filename) const
{
if (filename.empty())
throw std::runtime_error("writeStateNetwork called with empty filename");
SafeOutFile outFile(filename);
outFile << "# v" << INFOMAP_VERSION << "\n"
<< "# ./Infomap " << m_config.parsedString << "\n";
if (!m_names.empty()) {
outFile << "*Vertices\n";
for (auto& nameIt : m_names) {
auto& physId = nameIt.first;
auto& name = nameIt.second;
outFile << physId << " \"" << name << "\"\n";
}
}
outFile << "*States\n";
outFile << "# stateId physicalId\n";
for (const auto& nodeIt : nodes()) {
const auto& node = nodeIt.second;
outFile << node.id << " " << node.physicalId;
// Optional name
if (!node.name.empty())
outFile << " \"" << node.name << "\"";
outFile << "\n";
}
outFile << "*Links\n";
for (auto& linkIt : m_nodeLinkMap) {
for (auto& subIt : linkIt.second) {
outFile << linkIt.first.id << " " << subIt.first.id << " " << subIt.second.weight << "\n";
}
}
}
void StateNetwork::writePajekNetwork(const std::string& filename, bool printFlow) const
{
if (filename.empty())
throw std::runtime_error("writePajekNetwork called with empty filename");
SafeOutFile outFile(filename);
outFile << "# v" << INFOMAP_VERSION << "\n"
<< "# ./Infomap " << m_config.parsedString << "\n";
if (haveMemoryInput())
outFile << "# State network as physical network\n";
outFile << "*Vertices\n";
outFile << "#id name " << (printFlow ? "flow" : "weight") << "\n";
for (const auto& nodeIt : nodes()) {
const auto& node = nodeIt.second;
outFile << node.id << " \"";
// Name, default to id
const auto& nameIt = haveMemoryInput() ? m_names.end() : m_names.find(node.id);
if (nameIt != m_names.end())
outFile << nameIt->second;
else
outFile << node.id;
outFile << "\" " << (printFlow ? node.flow : node.weight) << "\n";
}
outFile << (m_config.printAsUndirected() ? "*Edges" : "*Arcs") << "\n";
outFile << "#source target " << (printFlow ? "flow" : "weight") << "\n";
for (auto& linkIt : m_nodeLinkMap) {
for (auto& subIt : linkIt.second) {
auto& linkData = subIt.second;
outFile << linkIt.first.id << " " << subIt.first.id << " " << (printFlow ? linkData.flow : linkData.weight) << "\n";
}
}
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNodeWithAutogeneratedId(unsigned int physId)
{
// Keys sorted with std::less comparator, so last key is the largest
unsigned int stateId = m_nodes.empty() ? 0 : m_nodes.crbegin()->first + 1;
return addStateNode(stateId, physId);
}
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNodeWithDeterministicId(unsigned int physId, unsigned int layerId, unsigned int numLayersLog2)
{
unsigned int stateId = physId << (numLayersLog2 + 1) | layerId;
return addStateNode(stateId, physId);
}
} // namespace infomap
@@ -0,0 +1,216 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef STATE_NETWORK_H_
#define STATE_NETWORK_H_
#include "../io/Config.h"
#include <string>
#include <map>
#include <utility>
#include <vector>
#include <utility>
namespace infomap {
class StateNetwork {
public:
struct StateNode {
unsigned int id = 0;
unsigned int physicalId = 0;
std::string name;
unsigned int layerId = 0;
double weight = 1.0;
double flow = 0.0;
double enterFlow = 0.0;
double exitFlow = 0.0;
double teleFlow = 0.0;
StateNode(unsigned int id = 0) : id(id), physicalId(id) { }
StateNode(unsigned int id, unsigned int physicalId) : id(id), physicalId(physicalId) { }
StateNode(unsigned int id, unsigned int physicalId, std::string name) : id(id), physicalId(physicalId), name(std::move(name)) { }
bool operator==(const StateNode& rhs) const { return id == rhs.id; }
bool operator!=(const StateNode& rhs) const { return id != rhs.id; }
bool operator<(const StateNode& rhs) const { return id < rhs.id; }
};
struct PhysNode {
unsigned int physId = 0;
double weight = 1.0;
PhysNode(unsigned int physId) : physId(physId) { }
PhysNode(unsigned int physId, double weight) : physId(physId), weight(weight) { }
PhysNode(double weight = 1.0) : weight(weight) { }
};
struct LinkData {
double weight = 1.0;
double flow = 0.0;
LinkData(double weight = 1.0) : weight(weight) { }
LinkData& operator+=(double w)
{
weight += w;
return *this;
}
};
struct StateLink {
StateLink(unsigned int sourceIndex = 0, unsigned int targetIndex = 0, double weight = 0.0)
: source(sourceIndex),
target(targetIndex),
weight(weight),
flow(weight) { }
unsigned int source;
unsigned int target;
double weight;
double flow;
};
// Unique state id to state node
using NodeMap = std::map<unsigned int, StateNode>;
using OutLinkMap = std::map<StateNode, LinkData>;
using NodeLinkMap = std::map<StateNode, OutLinkMap>;
protected:
friend class FlowCalculator;
// Config
Config m_config;
// Network
bool m_haveDirectedInput = false;
bool m_haveMemoryInput = false;
bool m_higherOrderInputMethodCalled = false;
NodeMap m_nodes; // Nodes indexed by state id (equal physical id for first-order networks)
NodeLinkMap m_nodeLinkMap;
unsigned int m_numStateNodesFound = 0;
double m_sumNodeWeight = 0.0;
unsigned int m_numLinks = 0;
double m_sumLinkWeight = 0.0;
unsigned int m_numSelfLinksFound = 0;
unsigned int m_numSelfLinks = 0;
double m_sumSelfLinkWeight = 0.0;
unsigned int m_numAggregatedLinks = 0;
double m_totalLinkWeightAdded = 0.0;
unsigned int m_numLinksIgnoredByWeightThreshold = 0;
double m_totalLinkWeightIgnored = 0.0;
std::map<unsigned int, double> m_outWeights;
bool m_haveNodeWeights = false;
bool m_haveStateNodeWeights = false;
bool m_haveFileInput = false;
// Attributes
std::map<unsigned int, std::string> m_names;
std::map<unsigned int, PhysNode> m_physNodes;
// Bipartite
unsigned int m_bipartiteStartId = 0;
public:
StateNetwork() : m_config(Config()) { }
StateNetwork(Config config) : m_config(std::move(config)) { }
virtual ~StateNetwork() = default;
StateNetwork(const StateNetwork&) = delete;
StateNetwork& operator=(const StateNetwork&) = delete;
StateNetwork(StateNetwork&&) = delete;
StateNetwork& operator=(StateNetwork&&) = delete;
// Config
void setConfig(const Config& config) { m_config = config; }
// Mutators
std::pair<NodeMap::iterator, bool> addStateNode(const StateNode& node);
std::pair<NodeMap::iterator, bool> addStateNode(unsigned int id, unsigned int physId);
std::pair<NodeMap::iterator, bool> addNode(unsigned int id);
std::pair<NodeMap::iterator, bool> addNode(unsigned int id, std::string name);
std::pair<NodeMap::iterator, bool> addNode(unsigned int id, double weight);
std::pair<NodeMap::iterator, bool> addNode(unsigned int id, std::string, double weight);
PhysNode& addPhysicalNode(unsigned int physId);
PhysNode& addPhysicalNode(unsigned int physId, double weight);
PhysNode& addPhysicalNode(unsigned int physId, const std::string& name);
PhysNode& addPhysicalNode(unsigned int physId, double weight, const std::string& name);
std::pair<std::map<unsigned int, std::string>::iterator, bool> addName(unsigned int id, const std::string&);
bool addLink(unsigned int sourceId, unsigned int targetId, double weight = 1.0);
bool addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight);
/**
* Remove link
* Note: It will not remove nodes if they become dangling
*/
bool removeLink(unsigned int sourceId, unsigned int targetId);
// Expand each undirected link to two opposite directed links
bool undirectedToDirected();
/**
* Clear all network data and reset to default state.
*/
virtual void clear();
/**
* Clear link data but keep node data.
*/
virtual void clearLinks();
// Getters
const NodeMap& nodes() const { return m_nodes; }
unsigned int numNodes() const { return m_nodes.size(); }
unsigned int numPhysicalNodes() const { return m_physNodes.size(); }
double sumNodeWeight() const { return m_sumNodeWeight; }
const NodeLinkMap& nodeLinkMap() const { return m_nodeLinkMap; }
NodeLinkMap& nodeLinkMap() { return m_nodeLinkMap; }
unsigned int numLinks() const { return m_numLinks; }
double sumLinkWeight() const { return m_sumLinkWeight; }
unsigned int numSelfLinks() const { return m_numSelfLinks; }
double sumSelfLinkWeight() const { return m_sumSelfLinkWeight; }
// Use convention of counting self-links only once, treating them as directed
double sumWeightedDegree() const { return 2 * sumLinkWeight() - (m_config.isUndirectedFlow() ? sumSelfLinkWeight() : 0); }
unsigned int sumDegree() const { return 2 * numLinks() - (m_config.isUndirectedFlow() ? numSelfLinks() : 0); }
std::map<unsigned int, double>& outWeights() { return m_outWeights; }
std::map<unsigned int, std::string>& names() { return m_names; }
const std::map<unsigned int, std::string>& names() const { return m_names; }
bool haveNodeWeights() const { return m_haveNodeWeights; }
bool haveStateNodeWeights() const { return m_haveStateNodeWeights; }
bool haveFileInput() const { return m_haveFileInput; }
virtual const std::map<unsigned int, std::vector<int>>& metaData() const = 0;
bool haveDirectedInput() const { return m_haveDirectedInput; }
bool haveMemoryInput() const { return m_haveMemoryInput; }
bool higherOrderInputMethodCalled() const { return m_higherOrderInputMethodCalled; }
// Bipartite
bool isBipartite() const { return m_bipartiteStartId > 0; }
unsigned int bipartiteStartId() const { return m_bipartiteStartId; }
void setBipartiteStartId(unsigned int value) { m_bipartiteStartId = value; }
/**
* Write state network to file.
*/
void writeStateNetwork(const std::string& filename) const;
/**
* Write state network as first-order Pajek network, where
* state nodes are treated as physical nodes.
* For a non-memory input, the state nodes are equivalent to
* physical nodes.
*/
void writePajekNetwork(const std::string& filename, bool printFlow = false) const;
protected:
std::pair<NodeMap::iterator, bool> addStateNodeWithAutogeneratedId(unsigned int physId);
std::pair<NodeMap::iterator, bool> addStateNodeWithDeterministicId(unsigned int physId, unsigned int layerId, unsigned int numLayersLog2);
};
} // namespace infomap
#endif // STATE_NETWORK_H_
@@ -0,0 +1,238 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "InfomapIterator.h"
#include "../InfoNode.h"
#include <iostream>
#include <utility> // std::pair
namespace infomap {
InfomapIterator& InfomapIterator::operator++() noexcept
{
const auto root = m_current->getInfomapRoot();
auto current = root ? root : m_current;
if (current->firstChild) {
current = current->firstChild;
++m_depth;
m_path.push_back(1);
} else {
// Current node is a leaf
// Presupposes that the next pointer can't reach out from the current parent.
bool tryNext = true;
while (tryNext) {
tryNext = false;
while (!current->next) {
if (current->owner) {
current = current->owner;
if (current == m_root) { // Check if back to beginning
m_current = nullptr;
return *this;
}
tryNext = true;
break;
}
if (current->parent) {
current = current->parent;
--m_depth;
m_path.pop_back();
if (current == m_root) { // Check if back to beginning
m_current = nullptr;
return *this;
}
} else { // null also if no children in first place
m_current = nullptr;
return *this;
}
}
}
current = current->next;
if (!current->isLeaf() && (static_cast<unsigned int>(m_moduleIndexLevel) >= m_depth)) {
++m_moduleIndex;
}
++m_path.back();
}
m_current = current;
return *this;
}
double InfomapIterator::modularCentrality() const noexcept
{
if (m_current->parent == nullptr) {
// The root node has no modular centrality
return 0.0;
}
const auto p_m = m_current->parent->data.flow;
const auto p_u = m_current->data.flow;
const auto p_diff = p_m - p_u;
if (p_diff > 0.0) {
return -p_diff * std::log2(p_diff / p_m);
}
return 0.0;
}
// -------------------------------------
// InfomapModuleIterator
// -------------------------------------
InfomapIterator& InfomapModuleIterator::operator++() noexcept
{
InfomapIterator::operator++();
while (!isEnd() && m_current->isLeaf()) {
InfomapIterator::operator++();
}
return *this;
}
// -------------------------------------
// InfomapLeafModuleIterator
// -------------------------------------
void InfomapLeafModuleIterator::init() noexcept
{
while (!isEnd() && !m_current->isLeafModule()) {
InfomapIterator::operator++();
}
}
InfomapIterator& InfomapLeafModuleIterator::operator++() noexcept
{
InfomapIterator::operator++();
while (!isEnd() && !m_current->isLeafModule()) {
InfomapIterator::operator++();
}
return *this;
}
// -------------------------------------
// InfomapLeafIterator
// -------------------------------------
void InfomapLeafIterator::init() noexcept
{
while (!isEnd() && !m_current->isLeaf()) {
InfomapIterator::operator++();
}
}
InfomapIterator& InfomapLeafIterator::operator++() noexcept
{
InfomapIterator::operator++();
while (!isEnd() && !m_current->isLeaf()) {
InfomapIterator::operator++();
}
return *this;
}
// -------------------------------------
// InfomapIteratorPhysical
// -------------------------------------
InfomapIterator& InfomapIteratorPhysical::operator++() noexcept
{
if (m_physNodes.empty()) {
// Iterate modules
InfomapIterator::operator++();
if (isEnd()) {
return *this;
}
if (m_current->isLeaf()) {
// Copy current iterator to restart after iterating through the leaf nodes
auto firstLeafIt = *this;
// If on a leaf node, loop through and aggregate to physical nodes
while (!isEnd() && m_current->isLeaf()) {
auto ret = m_physNodes.insert(std::make_pair(m_current->physicalId, InfoNode(*m_current)));
auto& physNode = ret.first->second;
if (ret.second) {
// New physical node, use same parent as the state leaf node
physNode.parent = m_current->parent;
} else {
// Not inserted, add flow to existing physical node
// TODO: If exitFlow should be correct, flow between memory nodes within same physical node should be subtracted.
physNode.data += m_current->data;
}
physNode.stateNodes.push_back(m_current->stateId);
InfomapIterator::operator++();
}
// Store current iterator to continue with after iterating physical leaf nodes
m_oldIter = *this;
// Reset path/depth/moduleIndex to values for first leaf node
m_path = firstLeafIt.m_path;
m_depth = firstLeafIt.m_depth;
m_moduleIndex = firstLeafIt.m_moduleIndex;
// Set current node to the first physical node
m_physIter = m_physNodes.begin();
m_current = &m_physIter->second;
}
} else {
// Iterate physical nodes instead of leaf state nodes
++m_physIter;
++m_path.back();
if (m_physIter == m_physNodes.end()) {
// End of leaf nodes
m_physNodes.clear();
m_path.pop_back();
// reset iterator to the one after the leaf nodes
*this = m_oldIter;
} else {
// Set iterator node to the currently iterated physical node
m_current = &m_physIter->second;
}
}
return *this;
}
// -------------------------------------
// InfomapLeafIteratorPhysical
// -------------------------------------
void InfomapLeafIteratorPhysical::init() noexcept
{
while (!isEnd() && !m_current->isLeaf()) {
InfomapIteratorPhysical::operator++();
}
}
InfomapIterator& InfomapLeafIteratorPhysical::operator++() noexcept
{
InfomapIteratorPhysical::operator++();
while (!isEnd() && !m_current->isLeaf()) {
InfomapIteratorPhysical::operator++();
}
return *this;
}
// -------------------------------------
// InfomapParentIterator
// -------------------------------------
InfomapParentIterator& InfomapParentIterator::operator++() noexcept
{
m_current = m_current->parent;
if (m_current != nullptr && m_current->owner != nullptr) {
m_current = m_current->owner;
}
return *this;
}
} // namespace infomap
@@ -0,0 +1,341 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_ITERATOR_H_
#define INFOMAP_ITERATOR_H_
#include <deque>
#include <map>
#include <cmath>
namespace infomap {
class InfoNode;
/**
* Pre processing depth first iterator that explores sub-Infomap instances
* Note:
* This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
*/
struct InfomapIterator {
protected:
InfoNode* m_root = nullptr;
InfoNode* m_current = nullptr;
int m_moduleIndexLevel = -1;
unsigned int m_moduleIndex = 0;
std::deque<unsigned int> m_path; // The tree path to current node (indexing starting from one!)
unsigned int m_depth = 0;
public:
InfomapIterator() = default;
InfomapIterator(InfoNode* nodePointer, int moduleIndexLevel = -1)
: m_root(nodePointer), m_current(nodePointer), m_moduleIndexLevel(moduleIndexLevel) { }
virtual ~InfomapIterator() = default;
InfomapIterator(const InfomapIterator&) = default;
InfomapIterator& operator=(const InfomapIterator&) = default;
InfomapIterator(InfomapIterator&&) noexcept = default;
InfomapIterator& operator=(InfomapIterator&&) noexcept = default;
InfoNode* current() noexcept { return m_current; }
const InfoNode* current() const noexcept { return m_current; }
InfoNode& operator*() noexcept { return *m_current; }
const InfoNode& operator*() const noexcept { return *m_current; }
InfoNode* operator->() noexcept { return m_current; }
const InfoNode* operator->() const noexcept { return m_current; }
bool operator==(const InfomapIterator& other) const noexcept { return m_current == other.m_current; }
bool operator!=(const InfomapIterator& other) const noexcept { return m_current != other.m_current; }
virtual InfomapIterator& operator++() noexcept;
virtual InfomapIterator operator++(int) noexcept
{
InfomapIterator copy(*this);
++(*this);
return copy;
}
virtual InfomapIterator& stepForward() noexcept
{
++(*this);
return *this;
}
const std::deque<unsigned int>& path() const noexcept { return m_path; }
unsigned int moduleIndex() const noexcept { return m_moduleIndex; }
unsigned int moduleId() const noexcept { return m_moduleIndex + 1; }
unsigned int childIndex() const noexcept { return m_path.empty() ? 0 : m_path.back() - 1; }
unsigned int depth() const noexcept { return m_depth; }
double modularCentrality() const noexcept;
bool isEnd() const noexcept { return m_current == nullptr; }
};
struct InfomapModuleIterator : public InfomapIterator {
public:
InfomapModuleIterator() : InfomapIterator() { }
InfomapModuleIterator(InfoNode* nodePointer, int moduleIndexLevel = -1) : InfomapIterator(nodePointer, moduleIndexLevel) { }
~InfomapModuleIterator() override = default;
InfomapModuleIterator(const InfomapModuleIterator&) = default;
InfomapModuleIterator& operator=(const InfomapModuleIterator&) = default;
InfomapModuleIterator(InfomapModuleIterator&&) = default;
InfomapModuleIterator& operator=(InfomapModuleIterator&&) = default;
InfomapIterator& operator++() noexcept override;
InfomapIterator operator++(int) noexcept override
{
InfomapModuleIterator copy(*this);
++(*this);
return std::move(copy);
}
using InfomapIterator::childIndex;
using InfomapIterator::current;
using InfomapIterator::depth;
using InfomapIterator::modularCentrality;
using InfomapIterator::path;
};
struct InfomapLeafModuleIterator : public InfomapIterator {
public:
InfomapLeafModuleIterator() : InfomapIterator() { }
InfomapLeafModuleIterator(InfoNode* nodePointer, int moduleIndexLevel = -1)
: InfomapIterator(nodePointer, moduleIndexLevel) { init(); }
~InfomapLeafModuleIterator() override = default;
InfomapLeafModuleIterator(const InfomapLeafModuleIterator& other) : InfomapIterator(other) { init(); }
InfomapLeafModuleIterator& operator=(const InfomapLeafModuleIterator&) = default;
InfomapLeafModuleIterator(InfomapLeafModuleIterator&&) = default;
InfomapLeafModuleIterator& operator=(InfomapLeafModuleIterator&&) = default;
/**
* Iterate to first leaf module
*/
void init() noexcept;
InfomapIterator& operator++() noexcept override;
InfomapIterator operator++(int) noexcept override
{
InfomapLeafModuleIterator copy(*this);
++(*this);
return std::move(copy);
}
using InfomapIterator::childIndex;
using InfomapIterator::current;
using InfomapIterator::depth;
using InfomapIterator::modularCentrality;
using InfomapIterator::path;
};
struct InfomapLeafIterator : public InfomapIterator {
public:
InfomapLeafIterator() : InfomapIterator() { }
InfomapLeafIterator(InfoNode* nodePointer, int moduleIndexLevel = -1)
: InfomapIterator(nodePointer, moduleIndexLevel) { init(); }
~InfomapLeafIterator() override = default;
InfomapLeafIterator(const InfomapLeafIterator& other) : InfomapIterator(other) { init(); }
InfomapLeafIterator& operator=(const InfomapLeafIterator&) = default;
InfomapLeafIterator(InfomapLeafIterator&&) = default;
InfomapLeafIterator& operator=(InfomapLeafIterator&&) = default;
/**
* Iterate to first leaf node
*/
void init() noexcept;
InfomapIterator& operator++() noexcept override;
InfomapIterator operator++(int) noexcept override
{
InfomapLeafIterator copy(*this);
++(*this);
return std::move(copy);
}
using InfomapIterator::childIndex;
using InfomapIterator::current;
using InfomapIterator::depth;
using InfomapIterator::modularCentrality;
using InfomapIterator::path;
};
/**
* Iterate over the whole tree, collecting physical nodes within same leaf modules
* Note: The physical nodes are created when entering the parent module and removed
* when leaving the module. The tree will not be modified.
*/
struct InfomapIteratorPhysical : public InfomapIterator {
protected:
std::map<unsigned int, InfoNode> m_physNodes;
std::map<unsigned int, InfoNode>::iterator m_physIter;
InfomapIterator m_oldIter;
public:
InfomapIteratorPhysical() : InfomapIterator() { }
InfomapIteratorPhysical(InfoNode* nodePointer, int moduleIndexLevel = -1)
: InfomapIterator(nodePointer, moduleIndexLevel) { }
~InfomapIteratorPhysical() override = default;
InfomapIteratorPhysical(const InfomapIteratorPhysical&) = default;
InfomapIteratorPhysical(const InfomapIterator& other) : InfomapIterator(other) { }
InfomapIteratorPhysical(InfomapIteratorPhysical&&) = default;
InfomapIteratorPhysical& operator=(const InfomapIteratorPhysical&) = default;
// Don't allow moving from this iterator as we use the old iterator in operator++
InfomapIteratorPhysical& operator=(InfomapIteratorPhysical&&) = delete;
InfomapIteratorPhysical& operator=(const InfomapIterator& other)
{
InfomapIterator::operator=(other);
return *this;
}
InfomapIterator& operator++() noexcept override;
InfomapIterator operator++(int) noexcept override
{
InfomapIteratorPhysical copy(*this);
++(*this);
return std::move(copy);
}
using InfomapIterator::childIndex;
using InfomapIterator::current;
using InfomapIterator::depth;
using InfomapIterator::modularCentrality;
using InfomapIterator::path;
};
/**
* Iterate over all physical leaf nodes, joining physical nodes within same leaf modules
* Note: The physical nodes are created when entering the parent module and removed
* when leaving the module. The tree will not be modified.
*/
struct InfomapLeafIteratorPhysical : public InfomapIteratorPhysical {
public:
InfomapLeafIteratorPhysical() : InfomapIteratorPhysical() { }
InfomapLeafIteratorPhysical(InfoNode* nodePointer, int moduleIndexLevel = -1)
: InfomapIteratorPhysical(nodePointer, moduleIndexLevel) { init(); }
InfomapLeafIteratorPhysical(const InfomapLeafIteratorPhysical& other)
: InfomapIteratorPhysical(other) { init(); }
~InfomapLeafIteratorPhysical() override = default;
InfomapLeafIteratorPhysical(InfomapLeafIteratorPhysical&&) = default;
InfomapLeafIteratorPhysical& operator=(const InfomapLeafIteratorPhysical&) = default;
// Don't allow moving from this iterator as we use the old iterator in operator++
InfomapLeafIteratorPhysical& operator=(InfomapLeafIteratorPhysical&&) = delete;
/**
* Iterate to first leaf node
*/
void init() noexcept;
InfomapIterator& operator++() noexcept override;
InfomapIterator operator++(int) noexcept override
{
InfomapLeafIteratorPhysical copy(*this);
++(*this);
return std::move(copy);
}
using InfomapIteratorPhysical::childIndex;
using InfomapIteratorPhysical::current;
using InfomapIteratorPhysical::depth;
using InfomapIteratorPhysical::modularCentrality;
using InfomapIteratorPhysical::path;
};
/**
* Iterate parent by parent until it is nullptr,
* moving up through possible sub infomap instances
* on the way
*/
struct InfomapParentIterator {
protected:
InfoNode* m_current = nullptr;
public:
InfomapParentIterator() = default;
InfomapParentIterator(InfoNode* nodePointer) : m_current(nodePointer) { }
~InfomapParentIterator() = default;
InfomapParentIterator(const InfomapParentIterator&) = default;
InfomapParentIterator& operator=(const InfomapParentIterator&) = default;
InfomapParentIterator(InfomapParentIterator&&) = default;
InfomapParentIterator& operator=(InfomapParentIterator&&) = default;
InfoNode* current() noexcept { return m_current; }
const InfoNode* current() const noexcept { return m_current; }
InfoNode& operator*() noexcept { return *m_current; }
const InfoNode& operator*() const noexcept { return *m_current; }
InfoNode* operator->() noexcept { return m_current; }
const InfoNode* operator->() const noexcept { return m_current; }
bool operator==(const InfomapParentIterator& other) const noexcept { return m_current == other.m_current; }
bool operator!=(const InfomapParentIterator& other) const noexcept { return m_current != other.m_current; }
InfomapParentIterator& operator++() noexcept;
InfomapParentIterator operator++(int) noexcept
{
InfomapParentIterator copy(*this);
++(*this);
return copy;
}
InfomapParentIterator& stepForward() noexcept
{
++(*this);
return *this;
}
bool isEnd() const noexcept { return m_current == nullptr; }
};
} // namespace infomap
#endif // INFOMAP_ITERATOR_H_
@@ -0,0 +1,32 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef ITER_WRAPPER_H_
#define ITER_WRAPPER_H_
namespace infomap {
template <typename Iter>
class IterWrapper {
Iter m_begin, m_end;
public:
IterWrapper(Iter begin, Iter end) : m_begin(begin), m_end(end) { }
template <typename Container>
IterWrapper(Container& container) : m_begin(container.begin()), m_end(container.end()) { }
Iter begin() noexcept { return m_begin; };
Iter end() noexcept { return m_end; };
};
} // namespace infomap
#endif // ITER_WRAPPER_H_
@@ -0,0 +1,369 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMAP_ITERATORS_H_
#define INFOMAP_ITERATORS_H_
#include "treeIterators.h"
#include <deque>
#include <limits>
namespace infomap {
/**
* Child iterator.
*/
template <typename NodePointerType> // pointer or const pointer
class InfomapChildIterator {
using iterator_category = std::bidirectional_iterator_tag;
using value_type = typename iterator_traits<NodePointerType>::value_type;
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
using reference = typename iterator_traits<NodePointerType>::reference;
using pointer = typename iterator_traits<NodePointerType>::pointer;
protected:
NodePointerType m_root = nullptr;
NodePointerType m_current = nullptr;
public:
InfomapChildIterator() = default;
InfomapChildIterator(const NodePointerType& nodePointer)
: m_root(nodePointer), m_current(nodePointer) { init(); }
InfomapChildIterator(const InfomapChildIterator& other)
: m_root(other.m_root), m_current(other.m_current) { }
InfomapChildIterator& operator=(const InfomapChildIterator& other)
{
m_root = other.m_root;
m_current = other.m_current;
return *this;
}
void init()
{
if (m_root != nullptr) {
NodePointerType infomapRoot = m_root->getInfomapRoot();
if (infomapRoot != nullptr) {
m_root = infomapRoot;
}
}
m_current = m_root == nullptr ? nullptr : m_root->firstChild;
}
pointer current() const { return m_current; }
reference operator*() const { return *m_current; }
pointer operator->() const { return m_current; }
bool operator==(const InfomapChildIterator& rhs) const { return m_current == rhs.m_current; }
bool operator!=(const InfomapChildIterator& rhs) const { return m_current != rhs.m_current; }
bool isEnd() const { return m_current == nullptr; }
InfomapChildIterator& operator++()
{
m_current = m_current->next;
if (m_current != nullptr && m_current->parent != m_root) {
m_current = nullptr;
}
return *this;
}
InfomapChildIterator operator++(int)
{
InfomapChildIterator copy(*this);
++(*this);
return copy;
}
InfomapChildIterator& operator--()
{
m_current = m_current->previous;
if (m_current != nullptr && m_current->parent != m_root) {
m_current = nullptr;
}
return *this;
}
InfomapChildIterator operator--(int)
{
InfomapChildIterator copy(*this);
--(*this);
return copy;
}
};
/**
* Pre processing depth first iterator that explores sub-Infomap instances
* Note: This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
*/
template <typename NodePointerType>
class InfomapClusterIterator : public DepthFirstIteratorBase<NodePointerType> {
protected:
using Base = DepthFirstIteratorBase<NodePointerType>;
unsigned int m_moduleIndex = 0;
int m_moduleIndexLevel = -1;
using Base::m_current;
using Base::m_depth;
public:
InfomapClusterIterator() : Base() { }
InfomapClusterIterator(const NodePointerType& nodePointer, int moduleIndexLevel = -1)
: Base(nodePointer), m_moduleIndexLevel(moduleIndexLevel)
{
init();
}
InfomapClusterIterator(const InfomapClusterIterator& other)
: Base(other), m_moduleIndex(other.m_moduleIndex), m_moduleIndexLevel(other.m_moduleIndexLevel) { }
virtual void init() { moveToInfomapRootIfExist(); }
void moveToInfomapRootIfExist()
{
if (m_current != nullptr) {
NodePointerType infomapRoot = m_current->getInfomapRoot();
if (infomapRoot != nullptr) {
m_current = infomapRoot;
}
}
}
InfomapClusterIterator& operator=(const InfomapClusterIterator& other)
{
Base::operator=(other);
m_moduleIndex = other.m_moduleIndex;
m_moduleIndexLevel = other.m_moduleIndexLevel;
return *this;
}
InfomapClusterIterator& operator++()
{
NodePointerType curr = Base::m_current;
if (curr->firstChild != nullptr) {
curr = curr->firstChild;
++m_depth;
} else {
// Current node is a leaf
// Presupposes that the next pointer can't reach out from the current parent.
tryNext:
while (curr->next == nullptr) {
if (curr->parent != nullptr) {
curr = curr->parent;
--m_depth;
if (curr == Base::m_root) // Check if back to beginning
{
m_current = nullptr;
return *this;
}
if (m_moduleIndexLevel < 0) {
if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom
++m_moduleIndex;
} else if (static_cast<unsigned int>(m_moduleIndexLevel) == m_depth)
++m_moduleIndex;
} else {
NodePointerType infomapOwner = curr->owner;
if (infomapOwner != nullptr) {
curr = infomapOwner;
if (curr == Base::m_root) // Check if back to beginning
{
m_current = nullptr;
return *this;
}
goto tryNext;
} else // null also if no children in first place
{
m_current = nullptr;
return *this;
}
}
}
curr = curr->next;
}
m_current = curr;
moveToInfomapRootIfExist();
return *this;
}
InfomapClusterIterator operator++(int)
{
InfomapClusterIterator copy(*this);
++(*this);
return copy;
}
InfomapClusterIterator next()
{
InfomapClusterIterator copy(*this);
return ++copy;
}
InfomapClusterIterator& stepForward()
{
++(*this);
return *this;
}
unsigned int moduleIndex() const
{
return m_moduleIndex;
}
};
/**
* Pre processing depth first iterator that explores sub-Infomap instances
* Note: This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
*/
template <typename NodePointerType>
class InfomapDepthFirstIterator : public DepthFirstIteratorBase<NodePointerType> {
protected:
using Base = DepthFirstIteratorBase<NodePointerType>;
std::deque<unsigned int> m_path; // The child index path to current node
unsigned int m_moduleIndex = 0;
int m_moduleIndexLevel = -1;
using Base::m_current;
using Base::m_depth;
public:
InfomapDepthFirstIterator() : Base() { }
InfomapDepthFirstIterator(const NodePointerType& nodePointer, int moduleIndexLevel = -1)
: Base(nodePointer),
m_moduleIndexLevel(moduleIndexLevel)
{
init();
}
InfomapDepthFirstIterator(const InfomapDepthFirstIterator& other)
: Base(other),
m_path(other.m_path),
m_moduleIndex(other.m_moduleIndex),
m_moduleIndexLevel(other.m_moduleIndexLevel)
{
}
InfomapDepthFirstIterator& operator=(const InfomapDepthFirstIterator& other)
{
Base::operator=(other);
m_path = other.m_path;
m_moduleIndex = other.m_moduleIndex;
m_moduleIndexLevel = other.m_moduleIndexLevel;
return *this;
}
virtual void init()
{
moveToInfomapRootIfExist();
}
void moveToInfomapRootIfExist()
{
if (m_current != nullptr) {
NodePointerType infomapRoot = m_current->getInfomapRoot();
if (infomapRoot != nullptr) {
m_current = infomapRoot;
}
}
}
InfomapDepthFirstIterator& operator++()
{
NodePointerType curr = Base::m_current;
if (curr->firstChild != nullptr) {
curr = curr->firstChild;
++m_depth;
m_path.push_back(0);
} else {
// Current node is a leaf
// Presupposes that the next pointer can't reach out from the current parent.
tryNext:
while (curr->next == nullptr) {
if (curr->parent != nullptr) {
curr = curr->parent;
--m_depth;
m_path.pop_back();
if (curr == Base::m_root) // Check if back to beginning
{
m_current = nullptr;
return *this;
}
if (m_moduleIndexLevel < 0) {
if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom
++m_moduleIndex;
} else if (static_cast<unsigned int>(m_moduleIndexLevel) == m_depth)
++m_moduleIndex;
} else {
NodePointerType infomapOwner = curr->owner;
if (infomapOwner != nullptr) {
curr = infomapOwner;
if (curr == Base::m_root) // Check if back to beginning
{
m_current = nullptr;
return *this;
}
goto tryNext;
} else // null also if no children in first place
{
m_current = nullptr;
return *this;
}
}
}
curr = curr->next;
++m_path.back();
}
m_current = curr;
moveToInfomapRootIfExist();
return *this;
}
InfomapDepthFirstIterator operator++(int)
{
InfomapDepthFirstIterator copy(*this);
++(*this);
return copy;
}
InfomapDepthFirstIterator next()
{
InfomapDepthFirstIterator copy(*this);
return ++copy;
}
InfomapDepthFirstIterator& stepForward()
{
++(*this);
return *this;
}
const std::deque<unsigned int>& path() const
{
return m_path;
}
unsigned int moduleIndex() const
{
return m_moduleIndex;
}
};
} // namespace infomap
#endif // INFOMAP_ITERATORS_H_
@@ -0,0 +1,628 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef TREE_ITERATORS_H_
#define TREE_ITERATORS_H_
#include <cstddef>
#include <iterator>
#include <deque>
namespace infomap {
#ifndef ASSERT
#include <cassert>
#define ASSERT(x) assert(x)
#endif
using std::iterator_traits;
/**
* Child iterator.
*/
template <typename NodePointerType> // pointer or const pointer
class ChildIterator {
using iterator_category = std::bidirectional_iterator_tag;
using value_type = typename iterator_traits<NodePointerType>::value_type;
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
using reference = typename iterator_traits<NodePointerType>::reference;
using pointer = typename iterator_traits<NodePointerType>::pointer;
protected:
NodePointerType m_root = nullptr;
NodePointerType m_current = nullptr;
public:
ChildIterator() = default;
ChildIterator(const NodePointerType& nodePointer)
: m_root(nodePointer), m_current(nodePointer == nullptr ? nullptr : nodePointer->firstChild) { }
ChildIterator(const ChildIterator& other)
: m_root(other.m_root), m_current(other.m_current) { }
ChildIterator& operator=(const ChildIterator& other)
{
m_root = other.m_root;
m_current = other.m_current;
return *this;
}
pointer current() const { return m_current; }
reference operator*() const { return *m_current; }
pointer operator->() const { return m_current; }
bool operator==(const ChildIterator& rhs) const { return m_current == rhs.m_current; }
bool operator!=(const ChildIterator& rhs) const { return !(m_current == rhs.m_current); }
bool isEnd() const { return m_current == nullptr; }
ChildIterator& operator++()
{
m_current = m_current->next;
if (m_current != nullptr && m_current->parent != m_root) {
m_current = nullptr;
}
return *this;
}
ChildIterator operator++(int)
{
ChildIterator copy(*this);
++(*this);
return copy;
}
ChildIterator& operator--()
{
m_current = m_current->previous;
if (m_current != nullptr && m_current->parent != m_root) {
m_current = nullptr;
}
return *this;
}
ChildIterator operator--(int)
{
ChildIterator copy(*this);
--(*this);
return copy;
}
};
/**
* Tree iterator.
*/
template <typename NodePointerType> // pointer or const pointer
class TreeIterator {
using iterator_category = std::forward_iterator_tag;
using value_type = typename iterator_traits<NodePointerType>::value_type;
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
using reference = typename iterator_traits<NodePointerType>::reference;
using pointer = typename iterator_traits<NodePointerType>::pointer;
protected:
NodePointerType m_root = nullptr;
NodePointerType m_current = nullptr;
int m_moduleIndexLevel = -1;
unsigned int m_moduleIndex = 0;
std::deque<unsigned int> m_path; // The child index path to current node
unsigned int m_depth = 0;
public:
TreeIterator() = default;
TreeIterator(NodePointerType nodePointer, int moduleIndexLevel = -1)
: m_root(nodePointer),
m_current(nodePointer),
m_moduleIndexLevel(moduleIndexLevel) { }
TreeIterator(const TreeIterator& other)
: m_root(other.m_root),
m_current(other.m_current),
m_moduleIndexLevel(other.m_moduleIndexLevel),
m_moduleIndex(other.m_moduleIndex),
m_path(other.m_path),
m_depth(other.m_depth) { }
virtual ~TreeIterator() = default;
TreeIterator& operator=(const TreeIterator& other)
{
m_root = other.m_root;
m_current = other.m_current;
m_moduleIndexLevel = other.m_moduleIndexLevel;
m_moduleIndex = other.m_moduleIndex;
m_path = other.m_path;
m_depth = other.m_depth;
return *this;
}
pointer current() const { return m_current; }
reference operator*() const { return *m_current; }
pointer operator->() const { return m_current; }
bool operator==(const TreeIterator& rhs) const { return m_current == rhs.m_current; }
bool operator!=(const TreeIterator& rhs) const { return !(m_current == rhs.m_current); }
const std::deque<unsigned int>& path() const { return m_path; }
unsigned int moduleIndex() const { return m_moduleIndex; }
unsigned int depth() const { return m_depth; }
bool isEnd() const { return m_current == nullptr; }
TreeIterator& operator++()
{
NodePointerType curr = m_current;
NodePointerType infomapRoot = curr->getInfomapRoot();
if (infomapRoot != nullptr) {
curr = infomapRoot;
}
if (curr->firstChild != nullptr) {
curr = curr->firstChild;
++m_depth;
m_path.push_back(0);
} else {
// Current node is a leaf
// Presupposes that the next pointer can't reach out from the current parent.
tryNext:
while (curr->next == nullptr) {
if (curr->parent != nullptr) {
curr = curr->parent;
--m_depth;
m_path.pop_back();
if (curr == m_root) // Check if back to beginning
{
m_current = nullptr;
return *this;
}
if (m_moduleIndexLevel < 0) {
if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom
++m_moduleIndex;
} else if (static_cast<unsigned int>(m_moduleIndexLevel) == m_depth)
++m_moduleIndex;
} else {
NodePointerType infomapOwner = curr->owner;
if (infomapOwner != nullptr) {
curr = infomapOwner;
if (curr == m_root) // Check if back to beginning
{
m_current = nullptr;
return *this;
}
goto tryNext;
} else // null also if no children in first place
{
m_current = nullptr;
return *this;
}
}
}
curr = curr->next;
++m_path.back();
}
m_current = curr;
return *this;
}
TreeIterator operator++(int)
{
TreeIterator copy(*this);
++(*this);
return copy;
}
TreeIterator& stepForward()
{
++(*this);
return *this;
}
};
/**
* Base node iterator.
*/
template <typename NodePointerType, typename iterator_tag = std::bidirectional_iterator_tag>
struct node_iterator_base {
using iterator_category = iterator_tag;
using value_type = typename iterator_traits<NodePointerType>::value_type;
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
using reference = typename iterator_traits<NodePointerType>::reference;
using pointer = typename iterator_traits<NodePointerType>::pointer;
node_iterator_base() : m_current(nullptr) { }
node_iterator_base(const NodePointerType& nodePointer) : m_current(nodePointer) { }
node_iterator_base(const node_iterator_base& other) : m_current(other.m_current) { }
node_iterator_base& operator=(const node_iterator_base& other)
{
m_current = other.m_current;
return *this;
}
virtual ~node_iterator_base() = default;
pointer base() const { return m_current; }
reference operator*() const { return *m_current; }
pointer operator->() const { return m_current; }
bool operator==(const node_iterator_base& rhs) const { return m_current == rhs.m_current; }
bool operator!=(const node_iterator_base& rhs) const { return !(m_current == rhs.m_current); }
bool isEnd() const { return m_current == nullptr; }
protected:
NodePointerType m_current;
};
template <typename NodePointerType>
class DepthFirstIteratorBase : public node_iterator_base<NodePointerType> {
using Base = node_iterator_base<NodePointerType>;
public:
DepthFirstIteratorBase() : Base(), m_root(nullptr), m_depth(0) { }
DepthFirstIteratorBase(const NodePointerType& nodePointer) : Base(nodePointer), m_root(nodePointer), m_depth(0) { }
DepthFirstIteratorBase(const DepthFirstIteratorBase& other) : Base(other), m_root(other.m_root), m_depth(other.m_depth) { }
DepthFirstIteratorBase& operator=(const DepthFirstIteratorBase& other)
{
Base::operator=(other);
m_root = other.m_root;
m_depth = other.m_depth;
return *this;
}
unsigned int depth() const { return m_depth; }
protected:
NodePointerType m_root;
unsigned int m_depth;
using Base::m_current;
};
/**
* Pre processing depth first iterator
* Note:
* This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
*/
template <typename NodePointerType, bool pre_t = true>
class DepthFirstIterator : public DepthFirstIteratorBase<NodePointerType> {
using Base = DepthFirstIteratorBase<NodePointerType>;
public:
DepthFirstIterator() : Base() { }
DepthFirstIterator(const NodePointerType& nodePointer) : Base(nodePointer) { }
DepthFirstIterator(const DepthFirstIterator& other) : Base(other) { }
DepthFirstIterator& operator=(const DepthFirstIterator& other)
{
Base::operator=(other);
return *this;
}
DepthFirstIterator& operator++()
{
NodePointerType curr = Base::m_current;
if (curr->firstChild != nullptr) {
curr = curr->firstChild;
++Base::m_depth;
} else {
// Presupposes that the next pointer can't reach out from the current parent.
while (curr->next == nullptr) {
curr = curr->parent;
--Base::m_depth;
if (curr == Base::m_root || curr == nullptr) // 0 if no children in first place
{
Base::m_current = nullptr;
return *this;
}
}
curr = curr->next;
}
Base::m_current = curr;
return *this;
}
DepthFirstIterator operator++(int)
{
auto copy(*this);
++(*this);
return copy;
}
DepthFirstIterator next()
{
auto copy(*this);
return ++copy;
}
};
/**
* Post processing depth first iterator
* Note:
* This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
*/
template <typename NodePointerType>
class DepthFirstIterator<NodePointerType, false> : public DepthFirstIteratorBase<NodePointerType> {
using Base = DepthFirstIteratorBase<NodePointerType>;
public:
DepthFirstIterator() : Base() { }
DepthFirstIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); }
DepthFirstIterator(const DepthFirstIterator& other) : Base(other) { }
DepthFirstIterator& operator=(const DepthFirstIterator& other)
{
Base::operator=(other);
return *this;
}
void init()
{
if (Base::m_current != nullptr) {
while (Base::m_current->firstChild != nullptr) {
Base::m_current = Base::m_current->firstChild;
++Base::m_depth;
}
}
}
DepthFirstIterator& operator++()
{
// The root should be the last node
if (Base::m_current == Base::m_root) {
Base::m_current = nullptr;
return *this;
}
NodePointerType curr = Base::m_current;
if (curr->next != nullptr) {
curr = curr->next;
while (curr->firstChild != nullptr) {
curr = curr->firstChild;
++Base::m_depth;
}
} else {
curr = curr->parent;
--Base::m_depth;
}
Base::m_current = curr;
return *this;
}
DepthFirstIterator operator++(int)
{
DepthFirstIterator copy(*this);
++(*this);
return copy;
}
DepthFirstIterator
next()
{
DepthFirstIterator copy(*this);
return ++copy;
}
};
/**
* Leaf node iterator
*/
template <typename NodePointerType>
class LeafNodeIterator : public DepthFirstIteratorBase<NodePointerType> {
using Base = DepthFirstIteratorBase<NodePointerType>;
public:
LeafNodeIterator() : Base() { }
LeafNodeIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); }
LeafNodeIterator(const LeafNodeIterator& other) : Base(other) { }
LeafNodeIterator& operator=(const LeafNodeIterator& other)
{
Base::operator=(other);
return *this;
}
void init()
{
if (Base::m_current != nullptr) {
while (Base::m_current->firstChild != nullptr) {
Base::m_current = Base::m_current->firstChild;
++Base::m_depth;
}
}
}
LeafNodeIterator& operator++()
{
ASSERT(Base::m_current != nullptr);
while (Base::m_current->next == nullptr || Base::m_current->next->parent != Base::m_current->parent) {
Base::m_current = Base::m_current->parent;
--Base::m_depth;
if (Base::m_current == nullptr)
return *this;
}
Base::m_current = Base::m_current->next;
if (Base::m_current != nullptr) {
while (Base::m_current->firstChild != nullptr) {
Base::m_current = Base::m_current->firstChild;
++Base::m_depth;
}
}
return *this;
}
LeafNodeIterator operator++(int)
{
LeafNodeIterator copy(*this);
++(*this);
return copy;
}
LeafNodeIterator next()
{
LeafNodeIterator copy(*this);
return ++copy;
}
};
/**
* Leaf module iterator
*/
template <typename NodePointerType>
class LeafModuleIterator : public DepthFirstIteratorBase<NodePointerType> {
using Base = DepthFirstIteratorBase<NodePointerType>;
public:
LeafModuleIterator() : Base() { }
LeafModuleIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); }
LeafModuleIterator(const LeafModuleIterator& other) : Base(other) { }
LeafModuleIterator& operator=(const LeafModuleIterator& other)
{
Base::operator=(other);
init();
return *this;
}
void init()
{
if (Base::m_current != nullptr) {
if (Base::m_current->firstChild == nullptr) {
Base::m_current = nullptr; // End directly if no module
} else {
while (Base::m_current->firstChild->firstChild != nullptr) {
Base::m_current = Base::m_current->firstChild;
++Base::m_depth;
}
}
}
}
LeafModuleIterator& operator++()
{
ASSERT(Base::m_current != nullptr);
while (Base::m_current->next == nullptr || Base::m_current->next->parent != Base::m_current->parent) {
Base::m_current = Base::m_current->parent;
--Base::m_depth;
if (Base::m_current == nullptr)
return *this;
}
Base::m_current = Base::m_current->next;
if (Base::m_current != nullptr) {
if (Base::m_current->firstChild == nullptr) {
Base::m_current = Base::m_current->parent;
} else {
while (Base::m_current->firstChild->firstChild != nullptr) {
Base::m_current = Base::m_current->firstChild;
++Base::m_depth;
}
}
}
return *this;
}
LeafModuleIterator operator++(int)
{
LeafModuleIterator copy(*this);
++(*this);
return copy;
}
LeafModuleIterator next()
{
LeafModuleIterator copy(*this);
return ++copy;
}
};
/**
* Sibling iterator.
*/
template <typename NodePointerType> // pointer or const pointer
class SiblingIterator : public node_iterator_base<NodePointerType> {
using Base = node_iterator_base<NodePointerType>;
public:
using self_type = SiblingIterator<NodePointerType>;
SiblingIterator() : Base() { }
SiblingIterator(const NodePointerType& nodePointer) : Base(nodePointer) { }
SiblingIterator(const SiblingIterator& other) : Base(other) { }
SiblingIterator& operator=(const SiblingIterator& other)
{
Base::operator=(other);
return *this;
}
SiblingIterator& operator++()
{
ASSERT(Base::m_current != nullptr);
Base::m_current = Base::m_current->next;
return *this;
}
SiblingIterator operator++(int)
{
SiblingIterator copy(*this);
++(*this);
return copy;
}
SiblingIterator& operator--()
{
ASSERT(Base::m_current != nullptr);
Base::m_current = Base::m_current->previous;
return *this;
}
SiblingIterator operator--(int)
{
SiblingIterator copy(*this);
--(*this);
return copy;
}
};
} // namespace infomap
#endif // TREE_ITERATORS_H_
@@ -0,0 +1,189 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "ClusterMap.h"
#include "SafeFile.h"
#include "../utils/Log.h"
#include "../utils/FileURI.h"
#include <sstream>
namespace infomap {
void ClusterMap::readClusterData(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId)
{
FileURI file(filename);
m_extension = file.getExtension();
if (m_extension == "tree" || m_extension == "ftree") {
return readTree(filename, includeFlow, layerNodeToStateId);
}
if (m_extension == "clu") {
return readClu(filename, includeFlow, layerNodeToStateId);
}
throw std::runtime_error(io::Str() << "Input cluster data from file '" << filename << "' is of unknown extension '" << m_extension << "'. Must be 'clu' or 'tree'.");
}
/**
* Sample from .tree file
# Codelength = 3.46227314 bits.
# path flow name physicalId
1:1:1 0.0384615 "1" 1
1:1:2 0.025641 "2" 2
1:1:3 0.0384615 "3" 3
1:2:1 0.0384615 "4" 4
*/
void ClusterMap::readTree(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId)
{
bool isMultilayer = layerNodeToStateId != nullptr;
SafeInFile input(filename);
std::string line;
std::istringstream lineStream;
std::istringstream pathStream;
m_nodePaths.clear();
unsigned int lineNr = 0;
while (!std::getline(input, line).fail()) {
++lineNr;
if (line.length() == 0)
continue;
if (line[0] == '#') {
continue;
}
if (line[0] == '*') {
break;
}
lineStream.clear();
lineStream.str(line);
std::string pathString;
double flow;
std::string name;
unsigned int stateId;
unsigned int nodeId;
unsigned int layerId;
if (!(lineStream >> pathString))
throw std::runtime_error(io::Str() << "Couldn't parse tree path from line '" << line << "'");
if (!(lineStream >> flow))
throw std::runtime_error(io::Str() << "Couldn't parse node flow from line '" << line << "'");
// Get the name by extracting the rest of the stream until the first quotation mark and then the last.
if (!getline(lineStream, name, '"'))
throw std::runtime_error(io::Str() << "Can't parse node name from line " << lineNr << " ('" << line << "').");
if (!getline(lineStream, name, '"'))
throw std::runtime_error(io::Str() << "Can't parse node name from line " << lineNr << " ('" << line << "').");
if (!(lineStream >> stateId))
throw std::runtime_error(io::Str() << "Couldn't parse node id from line '" << line << "'");
if (lineStream >> nodeId) {
m_isHigherOrder = true;
} else if (m_isHigherOrder) {
throw std::runtime_error(io::Str() << "Missing state id for node on line '" << line << "'.");
}
if (isMultilayer && !(lineStream >> layerId))
throw std::runtime_error(io::Str() << "Couldn't parse layer id from line '" << line << "'");
bool multilayerNodeFound = false;
if (isMultilayer) {
// get new state id from map
auto it = layerNodeToStateId->find(layerId);
if (it != layerNodeToStateId->end()) {
auto nodeIdToStateId = it->second.find(nodeId);
if (nodeIdToStateId != it->second.end()) {
stateId = nodeIdToStateId->second;
multilayerNodeFound = true;
}
}
}
if (isMultilayer && !multilayerNodeFound) {
continue;
}
pathStream.clear();
pathStream.str(pathString);
unsigned int childNumber;
Path path;
while (pathStream >> childNumber) {
pathStream.get(); // Extract the delimiting character also
if (childNumber == 0)
throw std::runtime_error("There is a '0' in the tree path, lowest allowed integer is 1.");
path.push_back(childNumber); // Keep 1-based indexing in path
}
m_nodePaths.emplace_back(stateId, path);
if (includeFlow)
m_flowData[stateId] = flow;
}
}
void ClusterMap::readClu(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId)
{
auto isMultilayer = layerNodeToStateId != nullptr;
Log() << "Read initial partition from '" << filename << "'... " << std::flush;
SafeInFile input(filename);
std::string line;
std::istringstream lineStream;
std::map<unsigned int, unsigned int> clusterData;
while (!std::getline(input, line).fail()) {
if (line.length() == 0 || line[0] == '#' || line[0] == '*')
continue;
lineStream.clear();
lineStream.str(line);
// # state_id module flow node_id layer_id
unsigned int stateId;
unsigned int nodeId;
unsigned int moduleId;
unsigned int layerId;
if (!(lineStream >> stateId >> moduleId))
throw std::runtime_error(io::Str() << "Couldn't parse node key and cluster id from line '" << line << "'");
auto flow = 0.0;
if (lineStream >> flow) {
if (includeFlow)
m_flowData[stateId] = flow;
}
auto multilayerNodeFound = false;
if (isMultilayer) {
if (!(lineStream >> nodeId))
throw std::runtime_error(io::Str() << "Couldn't parse node key from line '" << line << "'");
if (!(lineStream >> layerId))
throw std::runtime_error(io::Str() << "Couldn't parse layer id from line '" << line << "'");
// get new state id from map
auto it = layerNodeToStateId->find(layerId);
if (it != layerNodeToStateId->end()) {
auto nodeIdToStateId = it->second.find(nodeId);
if (nodeIdToStateId != it->second.end()) {
stateId = nodeIdToStateId->second;
multilayerNodeFound = true;
}
}
}
if (isMultilayer && !multilayerNodeFound) {
continue;
}
m_clusterIds[stateId] = moduleId;
}
}
} // namespace infomap
@@ -0,0 +1,52 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef CLUSTER_MAP_H_
#define CLUSTER_MAP_H_
#include <string>
#include <map>
#include <vector>
#include <deque>
namespace infomap {
using Path = std::deque<unsigned int>; // 1-based indexing
using NodePath = std::pair<unsigned int, Path>;
using NodePaths = std::vector<NodePath>;
class ClusterMap {
public:
void readClusterData(const std::string& filename, bool includeFlow = false, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId = nullptr);
const std::map<unsigned int, unsigned int>& clusterIds() const noexcept
{
return m_clusterIds;
}
const NodePaths& nodePaths() const noexcept { return m_nodePaths; }
const std::string& extension() const noexcept { return m_extension; }
private:
void readTree(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId = nullptr);
void readClu(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId = nullptr);
std::map<unsigned int, unsigned int> m_clusterIds;
std::map<unsigned int, double> m_flowData;
NodePaths m_nodePaths;
std::string m_extension;
bool m_isHigherOrder = false;
};
} // namespace infomap
#endif // CLUSTER_MAP_H_
+264
View File
@@ -0,0 +1,264 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "Config.h"
#include "ProgramInterface.h"
#include "SafeFile.h"
#include "../utils/FileURI.h"
#include "../utils/Log.h"
#include <vector>
#include <stdexcept>
namespace infomap {
constexpr int FlowModel::undirected;
constexpr int FlowModel::directed;
constexpr int FlowModel::undirdir;
constexpr int FlowModel::outdirdir;
constexpr int FlowModel::rawdir;
constexpr int FlowModel::precomputed;
Config::Config(const std::string& flags, bool isCLI) : isCLI(isCLI)
{
ProgramInterface api("Infomap",
"Implementation of the Infomap clustering algorithm based on the Map Equation (see www.mapequation.org)",
INFOMAP_VERSION);
api.setGroups({ "Input", "Algorithm", "Accuracy", "Output" });
std::vector<std::string> optionalOutputDir; // Used if !isCLI
// --------------------- Input options ---------------------
if (isCLI) {
api.addNonOptionArgument(networkFile, "network_file", "File containing the network data. Assumes a link list format if no Pajek formatted heading.", "Input");
} else {
api.addOptionArgument(networkFile, "input", "File containing the network data. Assumes a link list format if no Pajek formatted heading.", ArgType::path, "Input");
}
api.addOptionArgument(skipAdjustBipartiteFlow, "skip-adjust-bipartite-flow", "Skip distributing all flow from the bipartite nodes to the primary nodes.", "Input", true);
api.addOptionArgument(bipartiteTeleportation, "bipartite-teleportation", "Teleport like the bipartite flow instead of two-step (unipartite) teleportation.", "Input", true);
api.addOptionArgument(weightThreshold, "weight-threshold", "Limit the number of links to read from the network. Ignore links with less weight than the threshold.", ArgType::number, "Input", 0.0, true);
bool deprecated_includeSelfLinks = false;
api.addOptionArgument(deprecated_includeSelfLinks, 'k', "include-self-links", "DEPRECATED. Include self links by default now, exclude with --no-self-links.", "Input", true).setHidden(true);
api.addOptionArgument(noSelfLinks, "no-self-links", "Exclude self links in the input network.", "Input", true);
api.addOptionArgument(nodeLimit, "node-limit", "Limit the number of nodes to read from the network. Ignore links connected to ignored nodes.", ArgType::integer, "Input", 1u, true);
api.addOptionArgument(matchableMultilayerIds, "matchable-multilayer-ids", "Construct state ids from node and layer ids that are consistent across networks for the same max number of layers. Set to at least the largest layer id among networks to match.", ArgType::integer, "Input", 1u, true);
api.addOptionArgument(clusterDataFile, 'c', "cluster-data", "Provide an initial two-level (clu format) or multi-layer (tree format) solution.", ArgType::path, "Input");
api.addOptionArgument(assignToNeighbouringModule, "assign-to-neighbouring-module", "Assign nodes without module assignments (from --cluster-data) to the module assignment of a neighbouring node if possible.", "Input", true);
api.addOptionArgument(metaDataFile, "meta-data", "Provide meta data (clu format) that should be encoded.", ArgType::path, "Input", true);
api.addOptionArgument(metaDataRate, "meta-data-rate", "Metadata encoding rate. Default is to encode each step.", ArgType::number, "Input", 0.0, true);
api.addOptionArgument(unweightedMetaData, "meta-data-unweighted", "Don't weight meta data by node flow.", "Input", true);
api.addOptionArgument(noInfomap, "no-infomap", "Don't run the optimizer. Useful to calculate codelength of provided cluster data or to print non-modular statistics.", "Input");
// --------------------- Output options ---------------------
api.addOptionArgument(outName, "out-name", "Name for the output files, e.g. [output_directory]/[out-name].tree", ArgType::string, "Output", true);
api.addOptionArgument(noFileOutput, '0', "no-file-output", "Don't write output to file.", "Output", true);
api.addOptionArgument(printTree, "tree", "Write a tree file with the modular hierarchy. Automatically enabled if no other output is specified.", "Output");
api.addOptionArgument(printFlowTree, "ftree", "Write a ftree file with the modular hierarchy including aggregated links between (nested) modules. (Used by Network Navigator)", "Output");
api.addOptionArgument(printClu, "clu", "Write a clu file with the top cluster ids for each node.", "Output");
api.addOptionArgument(cluLevel, "clu-level", "For clu output, print modules at specified depth from root. Use -1 for bottom level modules.", ArgType::integer, "Output", -1, true);
api.addOptionArgument(outputFormats, 'o', "output", "Comma-separated output formats without spaces, e.g. -o clu,tree,ftree. Options: clu, tree, ftree, newick, json, csv, network, states, flow.", ArgType::list, "Output", true);
api.addOptionArgument(hideBipartiteNodes, "hide-bipartite-nodes", "Project bipartite solution to unipartite.", "Output", true);
api.addOptionArgument(printAllTrials, "print-all-trials", "Print all trials to separate files.", "Output", true);
// --------------------- Core algorithm options ---------------------
api.addOptionArgument(twoLevel, '2', "two-level", "Optimize a two-level partition of the network. Default is multi-level.", "Algorithm");
std::string flowModelArg;
api.addOptionArgument(flowModelArg, 'f', "flow-model", "Specify flow model. Options: undirected, directed, undirdir, outdirdir, rawdir, precomputed.", ArgType::option, "Algorithm");
api.addOptionArgument(directed, 'd', "directed", "Assume directed links. Shorthand for '--flow-model directed'.", "Algorithm");
api.addOptionArgument(recordedTeleportation, 'e', "recorded-teleportation", "If teleportation is used to calculate the flow, also record it when minimizing codelength.", "Algorithm", true);
api.addOptionArgument(useNodeWeightsAsFlow, "use-node-weights-as-flow", "Use node weights (from api or after names in Pajek format) as flow, normalized to sum to 1", "Algorithm", true);
api.addOptionArgument(teleportToNodes, "to-nodes", "Teleport to nodes instead of to links, assuming uniform node weights if no such input data.", "Algorithm", true);
api.addOptionArgument(teleportationProbability, 'p', "teleportation-probability", "Probability of teleporting to a random node or link.", ArgType::probability, "Algorithm", 0.0, 1.0, true);
api.addOptionArgument(regularized, "regularized", "Effectively add a fully connected Bayesian prior network to not overfit due to missing links. Implies recorded teleportation", "Algorithm", true);
api.addOptionArgument(regularizationStrength, "regularization-strength", "Adjust relative strength of Bayesian prior network with this multiplier.", ArgType::number, "Algorithm", 0.0, true);
api.addOptionArgument(entropyBiasCorrection, "entropy-corrected", "Correct for negative entropy bias in small samples (many modules).", "Algorithm", true);
api.addOptionArgument(entropyBiasCorrectionMultiplier, "entropy-correction-strength", "Increase or decrease the default entropy correction with this factor.", ArgType::number, "Algorithm", true);
api.addOptionArgument(markovTime, "markov-time", "Scale link flow to change the cost of moving between modules. Higher values results in fewer modules.", ArgType::number, "Algorithm", 0.0, true);
api.addOptionArgument(variableMarkovTime, "variable-markov-time", "Increase Markov time locally to level out link flow. Reduces risk of overpartitioning sparse areas while keeping high resolution in dense areas.", "Algorithm", true);
api.addOptionArgument(variableMarkovTimeDamping, "variable-markov-damping", "Damping parameter for variable Markov time, to scale with local effective degree (0) or local entropy (1).", ArgType::number, "Algorithm", true);
api.addOptionArgument(variableMarkovTimeMinLocalScale, "variable-markov-min-scale", "Minimum local scale for nodes with zero entropy to avoid division by zero. Local Markov time is max scale divided by local scale.", ArgType::number, "Algorithm", true);
// api.addOptionArgument(markovTimeNoSelfLinks, "markov-time-no-self-links", "For testing.", "Algorithm", true);
api.addOptionArgument(preferredNumberOfModules, "preferred-number-of-modules", "Penalize solutions the more they differ from this number.", ArgType::integer, "Algorithm", 1u, true);
api.addOptionArgument(multilayerRelaxRate, "multilayer-relax-rate", "Probability to relax the constraint to move only in the current layer.", ArgType::probability, "Algorithm", 0.0, 1.0, true);
api.addOptionArgument(multilayerRelaxLimit, "multilayer-relax-limit", "Number of neighboring layers in each direction to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true);
api.addOptionArgument(multilayerRelaxLimitUp, "multilayer-relax-limit-up", "Number of neighboring layers with higher id to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true);
api.addOptionArgument(multilayerRelaxLimitDown, "multilayer-relax-limit-down", "Number of neighboring layers with lower id to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true);
api.addOptionArgument(multilayerRelaxByJensenShannonDivergence, "multilayer-relax-by-jsd", "Relax proportional to the out-link similarity measured by the Jensen-Shannon divergence.", "Algorithm", true);
// --------------------- Performance and accuracy options ---------------------
// api.addOptionArgument(seedToRandomNumberGenerator, 's', "seed", "A seed (integer) to the random number generator for reproducible results.", ArgType::integer, "Accuracy", 1ul);
api.addOptionArgument(numTrials, 'N', "num-trials", "Number of outer-most loops to run before picking the best solution.", ArgType::integer, "Accuracy", 1u);
api.addOptionArgument(coreLoopLimit, 'M', "core-loop-limit", "Limit the number of loops that tries to move each node into the best possible module.", ArgType::integer, "Accuracy", 1u, true);
api.addOptionArgument(levelAggregationLimit, 'L', "core-level-limit", "Limit the number of times the core loops are reapplied on existing modular network to search bigger structures.", ArgType::integer, "Accuracy", 1u, true);
api.addOptionArgument(tuneIterationLimit, 'T', "tune-iteration-limit", "Limit the number of main iterations in the two-level partition algorithm. 0 means no limit.", ArgType::integer, "Accuracy", 1u, true);
api.addOptionArgument(minimumCodelengthImprovement, "core-loop-codelength-threshold", "Minimum codelength threshold for accepting a new solution in core loop.", ArgType::number, "Accuracy", 0.0, true);
api.addOptionArgument(minimumRelativeTuneIterationImprovement, "tune-iteration-relative-threshold", "Set codelength improvement threshold of each new tune iteration to 'f' times the initial two-level codelength.", ArgType::number, "Accuracy", 0.0, true);
api.addIncrementalOptionArgument(fastHierarchicalSolution, 'F', "fast-hierarchical-solution", "Find top modules fast. Use -FF to keep all fast levels. Use -FFF to skip recursive part.", "Accuracy", true);
api.addOptionArgument(preferModularSolution, "prefer-modular-solution", "Prefer modular solutions even if they are worse than putting all nodes in one module.", "Accuracy", true);
api.addOptionArgument(innerParallelization, "inner-parallelization", "Parallelize the inner-most loop for greater speed. This may give some accuracy tradeoff.", "Accuracy", true);
api.addOptionalNonOptionArguments(optionalOutputDir, "out_directory", "Directory to write the results to.", "Output");
api.addIncrementalOptionArgument(verbosity, 'v', "verbose", "Verbose output on the console. Add additional 'v' flags to increase verbosity up to -vvv.", "Output");
api.addOptionArgument(silent, "silent", "No output on the console.", "Output");
api.parseArgs(flags);
if (deprecated_includeSelfLinks) {
throw std::runtime_error("The --include-self-links flag is deprecated to include self links by default. Use --no-self-links to exclude.");
}
if (!optionalOutputDir.empty())
outDirectory = optionalOutputDir[0];
if (!isCLI && outDirectory.empty())
noFileOutput = true;
if (!noFileOutput && outDirectory.empty() && isCLI) {
throw std::runtime_error("Missing out_directory");
}
if (flowModelArg == "directed" || directed) {
setFlowModel(FlowModel::directed);
} else if (flowModelArg == "undirected") {
setFlowModel(FlowModel::undirected);
} else if (flowModelArg == "undirdir") {
setFlowModel(FlowModel::undirdir);
} else if (flowModelArg == "outdirdir") {
setFlowModel(FlowModel::outdirdir);
} else if (flowModelArg == "rawdir") {
setFlowModel(FlowModel::rawdir);
} else if (flowModelArg == "precomputed") {
setFlowModel(FlowModel::precomputed);
} else if (!flowModelArg.empty()) {
throw std::runtime_error(io::Str() << "Unrecognized flow model: '" << flowModelArg << "'");
}
if (regularized) {
recordedTeleportation = true;
}
if (*--outDirectory.end() != '/')
outDirectory.append("/");
if (haveOutput() && !isDirectoryWritable(outDirectory))
throw std::runtime_error(io::Str() << "Can't write to directory '" << outDirectory << "'. Check that the directory exists and that you have write permissions.");
if (outName.empty()) {
outName = !networkFile.empty() ? FileURI(networkFile).getName() : "no-name";
}
if (noInfomap) {
numTrials = 1;
}
parsedString = flags;
parsedOptions = api.getUsedOptionArguments();
if (printAllTrials && numTrials < 2) {
printAllTrials = false;
}
adaptDefaults();
Log::init(verbosity, silent, verboseNumberPrecision);
}
void Config::adaptDefaults()
{
auto outputs = io::split(outputFormats, ',');
for (std::string& o : outputs) {
if (o == "clu") {
printClu = true;
} else if (o == "tree") {
printTree = true;
} else if (o == "ftree") {
printFlowTree = true;
} else if (o == "newick") {
printNewick = true;
} else if (o == "json") {
printJson = true;
} else if (o == "csv") {
printCsv = true;
} else if (o == "network") {
printPajekNetwork = true;
} else if (o == "flow") {
printFlowNetwork = true;
} else if (o == "states") {
printStateNetwork = true;
} else {
throw std::runtime_error(io::Str() << "Unrecognized output format: '" << o << "'.");
}
}
// Of no output format specified, use tree as default (if not used as a library).
if (isCLI && !haveModularResultOutput()) {
printTree = true;
}
}
std::ostream& operator<<(std::ostream& out, FlowModel f)
{
return out << flowModelToString(f);
}
} // namespace infomap
+268
View File
@@ -0,0 +1,268 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef CONFIG_H_
#define CONFIG_H_
#include "../utils/Date.h"
#include "../version.h"
#include "ProgramInterface.h"
#include <stdexcept>
#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include <limits>
namespace infomap {
struct FlowModel {
static constexpr int undirected = 0;
static constexpr int directed = 1;
static constexpr int undirdir = 2;
static constexpr int outdirdir = 3;
static constexpr int rawdir = 4;
static constexpr int precomputed = 5;
int value = 0;
FlowModel(int val) : value(val) { }
FlowModel& operator=(int val)
{
value = val;
return *this;
}
operator int&() { return value; }
operator int() const { return value; }
};
std::ostream& operator<<(std::ostream& out, FlowModel f);
inline const char* flowModelToString(const FlowModel& flowModel)
{
switch (flowModel) {
case FlowModel::directed:
return "directed";
case FlowModel::undirdir:
return "undirdir";
case FlowModel::outdirdir:
return "outdirdir";
case FlowModel::rawdir:
return "rawdir";
case FlowModel::precomputed:
return "precomputed";
case FlowModel::undirected:
default:
return "undirected";
}
}
struct Config {
// Input
bool isCLI = false;
std::string networkFile;
std::vector<std::string> additionalInput;
bool stateInput = false;
bool stateOutput = false;
bool multilayerInput = false;
double weightThreshold = 0.0;
bool bipartite = false;
bool skipAdjustBipartiteFlow = false;
bool bipartiteTeleportation = false;
bool noSelfLinks = false; // Replaces includeSelfLinks
unsigned int nodeLimit = 0;
unsigned int matchableMultilayerIds = 0;
std::string clusterDataFile;
std::string metaDataFile;
double metaDataRate = 1.0;
bool unweightedMetaData = false;
unsigned int numMetaDataDimensions = 0;
bool clusterDataIsHard = false; // FIXME Not used
bool assignToNeighbouringModule = false;
bool noInfomap = false;
FlowModel flowModel = FlowModel::undirected;
bool flowModelIsSet = false;
bool directed = false;
bool useNodeWeightsAsFlow = false;
bool teleportToNodes = false;
double markovTime = 1.0;
bool variableMarkovTime = false;
double variableMarkovTimeDamping = 1.0; // 0 for linear scaling, 1 for log scaled.
double variableMarkovTimeMinLocalScale = 1; // Correspond to two links in undirected unweighted networks. Avoids division by zero.
bool markovTimeNoSelfLinks = false;
double multilayerRelaxRate = 0.15;
int multilayerRelaxLimit = -1; // Amount of layers allowed to jump up or down
int multilayerRelaxLimitUp = -1; // One-sided limit to higher layers
int multilayerRelaxLimitDown = -1; // One-sided limit to lower layers
double multilayerJSRelaxRate = 0.15;
bool multilayerRelaxByJensenShannonDivergence = false;
int multilayerJSRelaxLimit = -1;
// Clustering
bool twoLevel = false;
bool noCoarseTune = false;
bool recordedTeleportation = false;
bool regularized = false; // Add a Bayesian prior network with recorded teleportation (sets recordedTeleportation and teleportToNodes to true)
double regularizationStrength = 1.0; // Scale Bayesian prior constant ln(N)/N with this factor
double teleportationProbability = 0.15;
unsigned int preferredNumberOfModules = 0;
bool entropyBiasCorrection = false;
double entropyBiasCorrectionMultiplier = 1;
/* unsigned long seedToRandomNumberGenerator = 123; */
// Performance and accuracy
unsigned int numTrials = 1;
double minimumCodelengthImprovement = 1e-10;
double minimumSingleNodeCodelengthImprovement = 1e-16;
bool randomizeCoreLoopLimit = false;
unsigned int coreLoopLimit = 10;
unsigned int levelAggregationLimit = 0;
unsigned int tuneIterationLimit = 0; // Iterations of fine-tune/coarse-tune in two-level partition
double minimumRelativeTuneIterationImprovement = 1e-5;
bool onlySuperModules = false;
unsigned int fastHierarchicalSolution = 0;
bool preferModularSolution = false;
bool innerParallelization = false;
// Output
std::string outDirectory;
std::string outName;
std::string outputFormats;
bool printTree = false;
bool printFlowTree = false;
bool printNewick = false;
bool printJson = false;
bool printCsv = false;
bool printClu = false;
bool printAllTrials = false;
int cluLevel = 1; // Write modules at specified depth from root. 1, 2, ... or -1 for bottom level
bool printFlowNetwork = false;
bool printPajekNetwork = false;
bool printStateNetwork = false;
bool noFileOutput = false;
unsigned int verbosity = 0;
unsigned int verboseNumberPrecision = 9;
bool silent = false;
bool hideBipartiteNodes = false;
// Other
Date startDate;
std::string version = INFOMAP_VERSION;
std::string parsedString;
std::vector<ParsedOption> parsedOptions;
infomap::interruptionHandlerFn *interruptionHandler = NULL;
Config() = default;
explicit Config(const std::string& flags, bool isCLI = false);
Config& cloneAsNonMain(const Config& other)
{
isCLI = other.isCLI;
networkFile = other.networkFile;
additionalInput = other.additionalInput;
stateInput = other.stateInput;
stateOutput = other.stateOutput;
multilayerInput = other.multilayerInput;
weightThreshold = other.weightThreshold;
bipartite = other.bipartite;
skipAdjustBipartiteFlow = other.skipAdjustBipartiteFlow;
bipartiteTeleportation = other.bipartiteTeleportation;
noSelfLinks = other.noSelfLinks;
nodeLimit = other.nodeLimit;
matchableMultilayerIds = other.matchableMultilayerIds;
metaDataRate = other.metaDataRate;
unweightedMetaData = other.unweightedMetaData;
numMetaDataDimensions = other.numMetaDataDimensions;
assignToNeighbouringModule = other.assignToNeighbouringModule;
noInfomap = other.noInfomap;
flowModel = other.flowModel;
flowModelIsSet = other.flowModelIsSet;
directed = other.directed;
useNodeWeightsAsFlow = other.useNodeWeightsAsFlow;
teleportToNodes = other.teleportToNodes;
markovTime = other.markovTime;
variableMarkovTime = other.variableMarkovTime;
variableMarkovTimeDamping = other.variableMarkovTimeDamping;
markovTimeNoSelfLinks = other.markovTimeNoSelfLinks;
multilayerRelaxRate = other.multilayerRelaxRate;
multilayerRelaxLimit = other.multilayerRelaxLimit;
multilayerRelaxLimitUp = other.multilayerRelaxLimitUp;
multilayerRelaxLimitDown = other.multilayerRelaxLimitDown;
multilayerJSRelaxRate = other.multilayerJSRelaxRate;
multilayerRelaxByJensenShannonDivergence = other.multilayerRelaxByJensenShannonDivergence;
multilayerJSRelaxLimit = other.multilayerJSRelaxLimit;
twoLevel = other.twoLevel;
noCoarseTune = other.noCoarseTune;
recordedTeleportation = other.recordedTeleportation;
regularized = other.regularized;
regularizationStrength = other.regularizationStrength;
teleportationProbability = other.teleportationProbability;
entropyBiasCorrection = other.entropyBiasCorrection;
entropyBiasCorrectionMultiplier = other.entropyBiasCorrectionMultiplier;
// seedToRandomNumberGenerator = other.seedToRandomNumberGenerator;
minimumCodelengthImprovement = other.minimumCodelengthImprovement;
minimumSingleNodeCodelengthImprovement = other.minimumSingleNodeCodelengthImprovement;
randomizeCoreLoopLimit = other.randomizeCoreLoopLimit;
minimumRelativeTuneIterationImprovement = other.minimumRelativeTuneIterationImprovement;
preferModularSolution = other.preferModularSolution;
innerParallelization = other.innerParallelization;
outDirectory = other.outDirectory;
outName = other.outName;
outputFormats = other.outputFormats;
verbosity = other.verbosity;
verboseNumberPrecision = other.verboseNumberPrecision;
startDate = other.startDate;
version = other.version;
return *this;
}
void adaptDefaults();
void setStateInput() { stateInput = true; }
void setStateOutput() { stateOutput = true; }
void setMultilayerInput() { multilayerInput = true; }
void setFlowModel(FlowModel value)
{
flowModel = value;
flowModelIsSet = true;
}
bool isUndirectedClustering() const { return flowModel == FlowModel::undirected; }
bool isUndirectedFlow() const { return flowModel == FlowModel::undirected || flowModel == FlowModel::undirdir; }
bool printAsUndirected() const { return isUndirectedClustering(); }
bool isMultilayerNetwork() const { return multilayerInput || !additionalInput.empty(); }
bool isBipartite() const { return bipartite; }
bool haveMemory() const { return stateInput; }
bool printStates() const { return stateOutput; }
bool haveMetaData() const { return !metaDataFile.empty() || numMetaDataDimensions != 0; }
bool haveOutput() const { return !noFileOutput; }
bool haveModularResultOutput() const
{
return printTree || printFlowTree || printNewick || printJson || printCsv || printClu;
}
};
} // namespace infomap
#endif // CONFIG_H_
@@ -0,0 +1,990 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "Network.h"
#include "../io/SafeFile.h"
#include "../utils/FileURI.h"
#include "../utils/Log.h"
#include <cmath>
#include <iostream>
#include <algorithm>
namespace infomap {
using std::make_pair;
void Network::init()
{
initValidHeadings();
m_multilayerStateIdBitShift = static_cast<unsigned int>(std::ceil(std::log2(m_config.matchableMultilayerIds)));
}
void Network::initValidHeadings()
{
auto& headingsPajek = m_validHeadings["pajek"];
headingsPajek.insert("*vertices");
headingsPajek.insert("*edges");
headingsPajek.insert("*arcs");
auto& headingsLinklist = m_validHeadings["link-list"];
headingsLinklist.insert("*links");
headingsLinklist.insert("*edges");
headingsLinklist.insert("*arcs");
auto& headingsBipartite = m_validHeadings["bipartite"];
headingsBipartite.insert("*vertices");
headingsBipartite.insert("*bipartite");
auto& headingsStates = m_validHeadings["states"];
headingsStates.insert("*vertices");
headingsStates.insert("*states");
headingsStates.insert("*edges");
headingsStates.insert("*arcs");
headingsStates.insert("*links");
headingsStates.insert("*contexts");
auto& ignoreHeadingsStates = m_ignoreHeadings["states"];
ignoreHeadingsStates.insert("*edges");
ignoreHeadingsStates.insert("*contexts");
auto& headingsMultilayer = m_validHeadings["multilayer"];
headingsMultilayer.insert("*vertices");
headingsMultilayer.insert("*multiplex");
headingsMultilayer.insert("*multilayer");
headingsMultilayer.insert("*intra");
headingsMultilayer.insert("*inter");
auto& headingsGeneral = m_validHeadings["general"];
headingsGeneral.insert("*vertices");
headingsGeneral.insert("*states");
headingsGeneral.insert("*multilayer");
headingsGeneral.insert("*intra");
headingsGeneral.insert("*inter");
headingsGeneral.insert("*paths");
headingsGeneral.insert("*edges");
headingsGeneral.insert("*arcs");
headingsGeneral.insert("*links");
headingsGeneral.insert("*contexts");
headingsGeneral.insert("*bipartite");
auto& ignoreHeadingsGeneral = m_ignoreHeadings["general"];
ignoreHeadingsGeneral.insert("*contexts");
}
void Network::clear()
{
StateNetwork::clear();
m_networks.clear();
m_interLinks.clear();
m_layerNodeToStateId.clear();
m_sumIntraOutWeight.clear();
m_layers.clear();
m_numInterLayerLinks = 0;
m_numIntraLayerLinks = 0;
// Bipartite
m_bipartiteStartId = 0;
// Meta data
m_metaData.clear();
m_numMetaDataColumns = 0;
}
void Network::readInputData(std::string filename, bool accumulate)
{
if (!accumulate) {
clear();
}
if (filename.empty())
filename = m_config.networkFile;
if (filename.empty()) {
throw std::runtime_error("No input file to read network");
}
FileURI networkFilename(filename, false);
parseNetwork(filename);
printSummary();
}
void Network::parseNetwork(const std::string& filename)
{
Log() << "Parsing " << (m_config.isUndirectedFlow() ? "undirected" : "directed") << " network from file '" << filename << "'...\n";
parseNetwork(filename, m_validHeadings["general"], m_ignoreHeadings["general"]);
}
void Network::parseNetwork(const std::string& filename, const InsensitiveStringSet& validHeadings, const InsensitiveStringSet& ignoreHeadings, const std::string& startHeading)
{
m_haveFileInput = true;
SafeInFile input(filename);
// Parse standard links by default until possible heading is reached
std::string heading = startHeading.length() > 0 ? startHeading : parseLinks(input);
while (heading.length() > 0 && heading[0] == '*') {
std::string headingLowerCase = io::tolower(io::firstWord(heading));
if (validHeadings.count(headingLowerCase) == 0) {
throw std::runtime_error(io::Str() << "Unrecognized heading in network file: '" << headingLowerCase << "'.");
}
if (ignoreHeadings.count(headingLowerCase) > 0) {
heading = ignoreSection(input, headingLowerCase);
} else if (headingLowerCase == "*vertices") {
heading = parseVertices(input, heading);
} else if (headingLowerCase == "*states") {
heading = parseStateNodes(input, heading);
} else if (headingLowerCase == "*edges") {
if (!m_config.isUndirectedFlow())
Log() << "\n --> Notice: Links marked as undirected but parsed as directed.\n";
heading = parseLinks(input);
} else if (headingLowerCase == "*arcs") {
if (m_config.isUndirectedFlow())
Log() << "\n --> Notice: Links marked as directed but parsed as undirected.\n";
heading = parseLinks(input);
} else if (headingLowerCase == "*links") {
heading = parseLinks(input);
} else if (headingLowerCase == "*multilayer" || headingLowerCase == "*multiplex") {
heading = parseMultilayerLinks(input);
} else if (headingLowerCase == "*intra") {
heading = parseMultilayerIntraLinks(input);
} else if (headingLowerCase == "*inter") {
heading = parseMultilayerInterLinks(input);
} else if (headingLowerCase == "*bipartite") {
heading = parseBipartiteLinks(input, heading);
} else {
heading = ignoreSection(input, headingLowerCase);
}
}
postProcessInputData();
Log() << "Done!\n";
}
void Network::postProcessInputData()
{
if (!m_networks.empty()) {
generateStateNetworkFromMultilayer();
}
if (!haveMemoryInput()) {
// If no memory input, add physical nodes as state nodes to not miss unconnected nodes
for (auto& it : m_physNodes) {
addNode(it.second.physId, it.second.weight);
}
}
}
void Network::readMetaData(const std::string& filename)
{
Log() << "Parsing meta data from '" << filename << "'...\n";
SafeInFile input(filename);
std::string line;
while (!std::getline(input, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
m_extractor.clear();
m_extractor.str(line);
unsigned int nodeId;
if (!(m_extractor >> nodeId))
throw std::runtime_error(io::Str() << "Can't parse node id from line '" << line << "'");
std::vector<int> metaData;
unsigned int metaId;
while (m_extractor >> metaId) {
metaData.push_back(metaId);
}
if (metaData.empty())
throw std::runtime_error(io::Str() << "Can't parse any meta data from line '" << line << "'");
addMetaData(nodeId, metaData);
}
Log() << " -> Parsed " << m_numMetaDataColumns << " columns of meta data for " << m_metaData.size() << " nodes.\n";
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// HELPER METHODS
//
//////////////////////////////////////////////////////////////////////////////////////////
std::string Network::parseVertices(std::ifstream& file, const std::string& /*heading*/)
{
Log() << " Parsing vertices...\n"
<< std::flush;
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
m_extractor.clear();
m_extractor.str(line);
unsigned int id = 0;
if (!(m_extractor >> id))
throw std::runtime_error(io::Str() << "Can't parse node id from line '" << line << "'");
auto nameStart = line.find_first_of('\"');
auto nameEnd = line.find_last_of('\"');
std::string name;
if (nameStart < nameEnd) {
name = std::string(line.begin() + nameStart + 1, line.begin() + nameEnd);
line = line.substr(nameEnd + 1);
m_extractor.clear();
m_extractor.str(line);
} else {
if (!(m_extractor >> name))
throw std::runtime_error(io::Str() << "Can't parse node name from line '" << line << "'");
}
double weight = 1.0;
if ((m_extractor >> weight)) {
m_haveNodeWeights = true;
if (weight < 0)
throw std::runtime_error(io::Str() << "Negative node weight (" << weight << ") from line '" << line << "'");
}
addPhysicalNode(id, weight, name);
}
Log() << " -> " << m_physNodes.size() << " physical nodes added\n";
return line;
}
std::string Network::parseStateNodes(std::ifstream& file, const std::string& /*heading*/)
{
m_higherOrderInputMethodCalled = true;
Log() << " Parsing state nodes...\n"
<< std::flush;
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
StateNode stateNode;
parseStateNode(line, stateNode);
addStateNode(stateNode);
addPhysicalNode(stateNode.physicalId);
++m_numStateNodesFound;
}
Log() << " -> " << m_numStateNodesFound << " state nodes added\n";
return line;
}
std::string Network::parseLinks(std::ifstream& file)
{
// This is the default action, so check for links before printing
bool parsingLinks = false;
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
if (!parsingLinks) {
parsingLinks = true;
Log() << " Parsing links...\n"
<< std::flush;
}
unsigned int n1, n2;
double weight;
parseLink(line, n1, n2, weight);
addLink(n1, n2, weight);
}
if (parsingLinks)
Log() << " -> " << m_numLinks << " links\n";
return line;
}
std::string Network::parseMultilayerLinks(std::ifstream& file)
{
Log() << " Parsing multilayer links...\n"
<< std::flush;
if (m_config.matchableMultilayerIds > 0) {
Log() << " Creating matchable state ids using: nodeId << (log2(" << m_config.matchableMultilayerIds << ") + 1) | layerId\n";
}
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
unsigned int layer1, n1, layer2, n2;
double weight;
parseMultilayerLink(line, layer1, n1, layer2, n2, weight);
// TODO: This explicit multilayer format can allow undirected but not the inter/intra format, clear?
addMultilayerLink(layer1, n1, layer2, n2, weight);
}
Log() << " -> " << (m_numIntraLayerLinks + m_numInterLayerLinks) << " links in " << m_layers.size() << " layers\n";
Log() << " -> " << m_numIntraLayerLinks << " intra-layer links\n";
Log() << " -> " << m_numInterLayerLinks << " inter-layer links\n";
return line;
}
std::string Network::parseMultilayerIntraLinks(std::ifstream& file)
{
Log() << " Parsing intra-layer links...\n"
<< std::flush;
if (m_config.matchableMultilayerIds > 0) {
Log() << " Creating matchable state ids using: nodeId << (log2(" << m_config.matchableMultilayerIds << ") + 1) | layerId\n";
}
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
unsigned int layer, n1, n2;
double weight;
parseMultilayerIntraLink(line, layer, n1, n2, weight);
addMultilayerIntraLink(layer, n1, n2, weight);
}
Log() << " -> " << m_numIntraLayerLinks << " intra-layer links\n";
return line;
}
std::string Network::parseMultilayerInterLinks(std::ifstream& file)
{
Log() << " Parsing inter-layer links...\n"
<< std::flush;
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
unsigned int layer1, n, layer2;
double weight;
parseMultilayerInterLink(line, layer1, n, layer2, weight);
addMultilayerInterLink(layer1, n, layer2, weight);
}
Log() << " -> " << m_numInterLayerLinks << " inter-layer links\n";
return line;
}
std::string Network::parseBipartiteLinks(std::ifstream& file, const std::string& heading)
{
Log() << " Parsing bipartite links...\n";
// Extract break point for bipartite links
m_extractor.clear();
m_extractor.str(heading);
std::string tmp;
if (!(m_extractor >> tmp >> m_bipartiteStartId))
throw std::runtime_error(io::Str() << "Can't parse bipartite start id from line '" << heading << "'");
Log() << " -> Using bipartite start id " << m_bipartiteStartId << "\n";
m_config.bipartite = true;
std::string line;
while (!std::getline(file, line).fail()) {
if (line.length() == 0 || line[0] == '#')
continue;
if (line[0] == '*')
break;
unsigned int n1, n2;
double weight;
parseLink(line, n1, n2, weight);
bool sourceIsFeature = n1 >= m_bipartiteStartId;
bool targetIsFeature = n2 >= m_bipartiteStartId;
if (sourceIsFeature == targetIsFeature) {
throw std::runtime_error(io::Str() << "Bipartite link '" << line << "' must cross bipartite start id " << m_bipartiteStartId << ".");
}
addLink(n1, n2, weight);
}
return line;
}
std::string Network::ignoreSection(std::ifstream& file, const std::string& heading)
{
Log() << "(Ignoring section " << heading << ") ";
std::string line;
while (!std::getline(file, line).fail()) {
if (line[0] == '*')
break;
}
return line;
}
void Network::parseStateNode(const std::string& line, StateNetwork::StateNode& stateNode)
{
m_extractor.clear();
m_extractor.str(line);
if (!(m_extractor >> stateNode.id >> stateNode.physicalId))
throw std::runtime_error(io::Str() << "Can't parse any state node from line '" << line << "'");
// Optional name enclosed in double quotes
auto nameStart = line.find_first_of('\"', m_extractor.tellg());
auto nameEnd = line.find_last_of('\"');
if (nameStart < nameEnd) {
stateNode.name = std::string(line.begin() + nameStart + 1, line.begin() + nameEnd);
m_extractor.seekg(nameEnd + 1);
}
// Optional weight, default to 1.0
if ((m_extractor >> stateNode.weight)) {
m_haveStateNodeWeights = true;
if (stateNode.weight < 0)
throw std::runtime_error(io::Str() << "Negative state node weight (" << stateNode.weight << ") from line '" << line << "'");
}
}
void Network::parseLink(const std::string& line, unsigned int& n1, unsigned int& n2, double& weight)
{
m_extractor.clear();
m_extractor.str(line);
if (!(m_extractor >> n1 >> n2))
throw std::runtime_error(io::Str() << "Can't parse link data from line '" << line << "'");
(m_extractor >> weight) || (weight = 1.0);
}
void Network::parseMultilayerLink(const std::string& line, unsigned int& layer1, unsigned int& n1, unsigned int& layer2, unsigned int& n2, double& weight)
{
m_extractor.clear();
m_extractor.str(line);
if (!(m_extractor >> layer1 >> n1 >> layer2 >> n2))
throw std::runtime_error(io::Str() << "Can't parse multilayer link data from line '" << line << "'");
(m_extractor >> weight) || (weight = 1.0);
}
void Network::parseMultilayerIntraLink(const std::string& line, unsigned int& layer, unsigned int& n1, unsigned int& n2, double& weight)
{
m_extractor.clear();
m_extractor.str(line);
if (!(m_extractor >> layer >> n1 >> n2))
throw std::runtime_error(io::Str() << "Can't parse intra-multilayer link data from line '" << line << "'");
(m_extractor >> weight) || (weight = 1.0);
}
void Network::parseMultilayerInterLink(const std::string& line, unsigned int& layer1, unsigned int& n, unsigned int& layer2, double& weight)
{
m_extractor.clear();
m_extractor.str(line);
if (!(m_extractor >> layer1 >> n >> layer2))
throw std::runtime_error(io::Str() << "Can't parse inter-multilayer link data from line '" << line << "'");
(m_extractor >> weight) || (weight = 1.0);
if (layer1 == layer2)
throw std::runtime_error(io::Str() << "Inter-layer link from line '" << line << "' doesn't go between different layers.");
// TODO: Same as intra-layer self-link?
}
void Network::printSummary()
{
Log() << "-------------------------------------\n";
if (haveMemoryInput()) {
Log() << " -> " << numNodes() << " state nodes\n";
Log() << " -> " << numPhysicalNodes() << " physical nodes\n";
} else {
if (m_bipartiteStartId > 0)
Log() << " -> " << numNodes() << " bipartite nodes\n";
else
Log() << " -> " << numNodes() << " nodes\n";
}
Log() << " -> " << numLinks() << " links with total weight " << m_totalLinkWeightAdded << "\n";
if (m_numLinksIgnoredByWeightThreshold > 0) {
Log() << " -> " << m_numLinksIgnoredByWeightThreshold << " links ignored by weight threshold with total weight " << m_totalLinkWeightIgnored << " (" << io::toPrecision(m_totalLinkWeightIgnored / (m_totalLinkWeightIgnored + m_totalLinkWeightAdded) * 100, 1, true) << "%)\n";
}
}
void Network::addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight)
{
m_higherOrderInputMethodCalled = true;
if (weight < m_config.weightThreshold) {
++m_numLinksIgnoredByWeightThreshold;
m_totalLinkWeightIgnored += weight;
return;
}
unsigned int stateId1 = addMultilayerNode(layer1, n1);
unsigned int stateId2 = addMultilayerNode(layer2, n2);
if (stateId1 == stateId2) {
// TODO: Handle self-links?
}
if (layer1 == layer2) {
++m_numIntraLayerLinks;
m_sumIntraOutWeight[layer1][n1] += weight; // TODO: Not used? Add on target also if undirected (not inter/intra format)?
} else {
++m_numInterLayerLinks;
}
addLink(stateId1, stateId2, weight);
}
void Network::generateStateNetworkFromMultilayer()
{
// As inter-layer links is directed to neighbouring nodes in target layer,
// the symmetry is broken so we need directed links for inter-layer flow
m_haveDirectedInput = true;
if (m_config.isUndirectedFlow()) {
// TODO: Don't allow undirdir/outdirdir/rawdir?
// Expand each undirected intra-layer link to two opposite directed links
Log() << " -> Expanding undirected links to directed...\n";
for (auto& layerIt : m_networks) {
auto& network = layerIt.second;
network.undirectedToDirected();
}
}
if (!m_interLinks.empty()) {
generateStateNetworkFromMultilayerWithInterLinks();
} else {
generateStateNetworkFromMultilayerWithSimulatedInterLinks();
}
m_networks.clear();
m_interLinks.clear();
}
void Network::generateStateNetworkFromMultilayerWithInterLinks()
{
Log() << "Generating state network from multilayer networks with inter-layer links...\n"
<< std::flush;
// First add intra-layer links
for (auto& layerIt : m_networks) {
unsigned int layer1 = layerIt.first;
auto& network = layerIt.second;
for (auto& linkIt : network.nodeLinkMap()) {
auto& source = linkIt.first;
const auto& subLinks = linkIt.second;
for (auto& subIt : subLinks) {
auto& target = subIt.first;
double linkWeight = subIt.second.weight;
addMultilayerLink(layer1, source.physicalId, layer1, target.physicalId, linkWeight);
}
}
}
Log() << "Connecting layers...\n";
// Connect layers with inter-layer links spread out in target layer
for (auto& it : m_interLinks) {
auto& layerNode = it.first;
unsigned int layer1 = layerNode.layer;
unsigned int physId = layerNode.node;
unsigned int stateId1 = addMultilayerNode(layer1, physId);
for (auto& it2 : it.second) {
unsigned int layer2 = it2.first;
double interWeight = it2.second;
auto& targetNetwork = m_networks[layer2];
std::map<StateNode, std::map<StateNode, LinkData>>& targetLinks = targetNetwork.nodeLinkMap();
auto& outlinks = targetLinks[StateNode(physId)];
if (outlinks.empty()) {
continue;
}
auto& targetOutWeights = targetNetwork.outWeights();
double sumIntraOutWeightTargetLayer = targetOutWeights[physId];
for (auto& outLink : outlinks) {
auto& targetPhysId = outLink.first.physicalId;
auto& linkData = outLink.second;
double intraWeight = linkData.weight;
unsigned int stateId2i = addMultilayerNode(layer2, targetPhysId);
double weight = sumIntraOutWeightTargetLayer == 0.0 ? 0.0 : interWeight * intraWeight / sumIntraOutWeightTargetLayer;
addLink(stateId1, stateId2i, weight);
++m_numInterLayerLinks; // TODO: Count all as one?
}
}
}
if (m_config.isUndirectedFlow()) {
// For undirected inter-layer links, expand and add in other direction too
for (auto& it : m_interLinks) {
auto& layerNode = it.first;
unsigned int layer2 = layerNode.layer;
unsigned int physId = layerNode.node;
auto& targetNetwork = m_networks[layer2];
std::map<StateNode, std::map<StateNode, LinkData>>& targetLinks = targetNetwork.nodeLinkMap();
auto& outlinks = targetLinks[StateNode(physId)];
if (outlinks.empty()) {
continue;
}
auto& targetOutWeights = targetNetwork.outWeights();
double sumIntraOutWeightTargetLayer = targetOutWeights[physId];
for (auto& it2 : it.second) {
unsigned int layer1 = it2.first;
double interWeight = it2.second;
unsigned int stateId1 = addMultilayerNode(layer1, physId);
for (auto& outLink : outlinks) {
auto& targetPhysId = outLink.first.physicalId;
auto& linkData = outLink.second;
double intraWeight = linkData.weight;
unsigned int stateId2i = addMultilayerNode(layer2, targetPhysId);
double weight = sumIntraOutWeightTargetLayer == 0.0 ? 0.0 : interWeight * intraWeight / sumIntraOutWeightTargetLayer;
addLink(stateId1, stateId2i, weight);
++m_numInterLayerLinks; // TODO: Count all as one?
}
}
}
}
}
void Network::generateStateNetworkFromMultilayerWithSimulatedInterLinks()
{
Log() << "Generating state network from multilayer networks with simulated inter-layer links...\n"
<< std::flush;
double relaxRate = m_config.multilayerRelaxRate;
int maxRelaxLimit = m_networks.size();
int relaxLimitSymmetric = m_config.multilayerRelaxLimit < 0 ? maxRelaxLimit : m_config.multilayerRelaxLimit;
int relaxLimitDown = m_config.multilayerRelaxLimitDown < 0 ? relaxLimitSymmetric : std::min(relaxLimitSymmetric, m_config.multilayerRelaxLimitDown);
int relaxLimitUp = m_config.multilayerRelaxLimitUp < 0 ? relaxLimitSymmetric : std::min(relaxLimitSymmetric, m_config.multilayerRelaxLimitUp);
auto haveUpOrDownLimit = m_config.multilayerRelaxLimitDown >= 0 || m_config.multilayerRelaxLimitUp >= 0;
Log() << "-> " << m_networks.size() << " networks\n";
Log() << "-> Relax rate: " << relaxRate << "\n";
if (haveUpOrDownLimit) {
Log() << "-> Relax limit up: " << relaxLimitUp << (relaxLimitUp == maxRelaxLimit ? " (no limit)\n" : "\n");
Log() << "-> Relax limit down: " << relaxLimitDown << (relaxLimitDown == maxRelaxLimit ? " (no limit)\n" : "\n");
} else if (m_config.multilayerRelaxLimit >= 0) {
Log() << "-> Relax limit: " << m_config.multilayerRelaxLimit << "\n";
}
auto withinRelaxLimit = [relaxLimitDown, relaxLimitUp](auto& layer1, auto& layer2) {
int diff = layer1 - layer2;
return layer1 >= layer2 ? diff <= relaxLimitDown : -diff <= relaxLimitUp;
};
if (m_config.multilayerRelaxByJensenShannonDivergence) {
Log() << "-> Using Jensen-Shannon Divergence\n";
for (unsigned int nodeId = 0; nodeId <= m_maxNodeIdInIntraLayerNetworks; ++nodeId) {
unsigned int layer2from = 0;
// Calculate Jensen-Shannon similarity between all layers such that layer1 >= layer2,
// and then use its symmetry for layer2 > layer1
std::map<unsigned int, std::map<unsigned int, double>> jsRelaxWeights;
std::map<unsigned int, double> jsTotWeight;
for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1) {
unsigned int layer2to = layer1 + 1;
// Limit possible jumps to close by layers
if (m_config.multilayerRelaxLimit >= 0) {
layer2from = ((int)layer1 - m_config.multilayerRelaxLimit) < 0 ? 0 : layer1 - m_config.multilayerRelaxLimit;
}
auto& layer1LinkMap = m_networks[layer1].nodeLinkMap();
auto& layer1OutLinks = layer1LinkMap[StateNode(nodeId)];
// Skip dangling nodes, because they have no information to calculate similarity
if (layer1OutLinks.empty())
continue;
double sumOutLinkWeightLayer1 = m_networks[layer1].outWeights()[nodeId];
for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2) {
auto& layer2LinkMap = m_networks[layer2].nodeLinkMap();
auto& layer2OutLinks = layer2LinkMap[StateNode(nodeId)];
if (layer2OutLinks.empty())
continue;
double sumOutLinkWeightLayer2 = m_networks[layer2].outWeights()[nodeId];
bool intersect;
double div = calculateJensenShannonDivergence(intersect, layer1OutLinks, sumOutLinkWeightLayer1, layer2OutLinks, sumOutLinkWeightLayer2);
double jsWeight = 1.0 - div;
if (intersect && (jsWeight >= m_config.multilayerJSRelaxLimit)) {
jsTotWeight[layer1] += jsWeight;
jsRelaxWeights[layer1][layer2] = jsWeight;
if (layer1 != layer2) {
jsTotWeight[layer2] += jsWeight;
jsRelaxWeights[layer2][layer1] = jsWeight;
}
}
}
}
// Second loop over all pairs of layers
unsigned int layer2to = m_networks.size();
for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1) {
// Limit possible jumps to close by layers
if (m_config.multilayerRelaxLimit >= 0) {
layer2from = ((int)layer1 - m_config.multilayerRelaxLimit) < 0 ? 0 : layer1 - m_config.multilayerRelaxLimit;
layer2to = (layer1 + m_config.multilayerRelaxLimit) > m_networks.size() ? m_networks.size() : layer1 + m_config.multilayerRelaxLimit;
}
double sumOutLinkWeightLayer1 = m_networks[layer1].outWeights()[nodeId];
auto jsRelaxWeightsLayer1It = jsRelaxWeights.find(layer1);
auto jsTotWeightIt = jsTotWeight.find(layer1);
// Create inter-links to the intra-connected nodes in other layers
for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2) {
if (jsRelaxWeightsLayer1It != jsRelaxWeights.end()) {
auto jsRelaxWeightsIt = jsRelaxWeightsLayer1It->second.find(layer2);
if (jsRelaxWeightsIt != jsRelaxWeightsLayer1It->second.end()) {
bool isIntra = layer2 == layer1;
// Create inter-links to the outgoing nodes in the target layer
double linkWeightNormalizationFactor;
if (isIntra) {
linkWeightNormalizationFactor = 1;
} else {
linkWeightNormalizationFactor = jsRelaxWeightsIt->second * relaxRate / (1.0 - relaxRate) * sumOutLinkWeightLayer1 / jsTotWeightIt->second;
}
auto& targetLinks = m_networks[layer2].nodeLinkMap();
auto& targetOutlinks = targetLinks[StateNode(nodeId)];
if (targetOutlinks.empty()) {
continue;
}
for (auto& outLink : targetOutlinks) {
auto& n2 = outLink.first.physicalId;
auto& linkData = outLink.second;
double intraWeight = linkData.weight;
// Add intra link weight as teleport weight to source node
unsigned int stateId1 = addMultilayerNode(layer1, nodeId, intraWeight);
unsigned int stateId2i = addMultilayerNode(layer2, n2, 0.0);
double weight = intraWeight == 0.0 ? 0.0 : linkWeightNormalizationFactor * intraWeight;
addLink(stateId1, stateId2i, weight);
++m_numInterLayerLinks;
}
}
}
}
}
}
return;
}
for (auto& it1 : m_networks) {
auto layer1 = it1.first;
auto& network1 = it1.second;
for (auto& n1It : network1.nodes()) {
auto& n1 = n1It.first;
unsigned int stateId1 = addMultilayerNode(layer1, n1);
double sumOutLinkWeightLayer1 = network1.outWeights()[n1];
double sumOutWeightAllLayers = 0.0;
for (auto& it2 : m_networks) {
auto layer2 = it2.first;
if (!withinRelaxLimit(layer1, layer2)) {
continue;
}
auto& network2 = it2.second;
sumOutWeightAllLayers += network2.outWeights()[n1];
}
if (sumOutWeightAllLayers <= 0) {
continue;
}
for (auto& it2 : m_networks) {
auto layer2 = it2.first;
if (!withinRelaxLimit(layer1, layer2)) {
continue;
}
auto& network2 = it2.second;
bool isIntra = layer2 == layer1;
double linkWeightNormalizationFactor = relaxRate / sumOutWeightAllLayers;
if (isIntra) {
linkWeightNormalizationFactor += (1.0 - relaxRate) / sumOutLinkWeightLayer1;
}
auto& targetLinks = network2.nodeLinkMap();
auto& targetOutlinks = targetLinks[StateNode(n1)];
if (targetOutlinks.empty()) {
continue;
}
for (auto& outLink : targetOutlinks) {
auto& n2 = outLink.first.physicalId;
auto& linkData = outLink.second;
double intraWeight = linkData.weight;
unsigned int stateId2i = addMultilayerNode(layer2, n2);
double weight = intraWeight == 0.0 ? 0.0 : linkWeightNormalizationFactor * intraWeight;
addLink(stateId1, stateId2i, weight);
++m_numInterLayerLinks; // TODO: Count all as one?
}
}
}
}
}
double Network::calculateJensenShannonDivergence(bool& intersect, const OutLinkMap& layer1OutLinks, double sumOutLinkWeightLayer1, const OutLinkMap& layer2OutLinks, double sumOutLinkWeightLayer2)
{
intersect = false;
double h1 = 0.0; // The entropy rate of the node in the first layer
double h2 = 0.0; // The entropy rate of the node in the second layer
double h12 = 0.0; // The entropy rate of the lumped node
// The out-link weights of the nodes
double ow1 = sumOutLinkWeightLayer1;
double ow2 = sumOutLinkWeightLayer2;
// Normalized weights over node in layer 1 and 2
double pi1 = ow1 / (ow1 + ow2);
double pi2 = ow2 / (ow1 + ow2);
auto layer1OutLinkIt = layer1OutLinks.begin();
auto layer2OutLinkIt = layer2OutLinks.begin();
auto layer1OutLinkItEnd = layer1OutLinks.end();
auto layer2OutLinkItEnd = layer2OutLinks.end();
while (layer1OutLinkIt != layer1OutLinkItEnd && layer2OutLinkIt != layer2OutLinkItEnd) {
int diff = layer1OutLinkIt->first.id - layer2OutLinkIt->first.id;
if (diff < 0) {
// If the first state node has a link that the second has not
double p1 = layer1OutLinkIt->second.weight / ow1;
h1 -= p1 * log2(p1);
double p12 = pi1 * layer1OutLinkIt->second.weight / ow1;
h12 -= p12 * log2(p12);
layer1OutLinkIt++;
} else if (diff > 0) {
// If the second state node has a link that the second has not
double p2 = layer2OutLinkIt->second.weight / ow2;
h2 -= p2 * log2(p2);
double p12 = pi2 * layer2OutLinkIt->second.weight / ow2;
h12 -= p12 * log2(p12);
layer2OutLinkIt++;
} else { // If both state nodes have the link
intersect = true;
double p1 = layer1OutLinkIt->second.weight / ow1;
h1 -= p1 * log2(p1);
double p2 = layer2OutLinkIt->second.weight / ow2;
h2 -= p2 * log2(p2);
double p12 = pi1 * layer1OutLinkIt->second.weight / ow1 + pi2 * layer2OutLinkIt->second.weight / ow2;
h12 -= p12 * log2(p12);
layer1OutLinkIt++;
layer2OutLinkIt++;
}
}
while (layer1OutLinkIt != layer1OutLinkItEnd) {
// If the first state node has a link that the second has not
double p1 = layer1OutLinkIt->second.weight / ow1;
h1 -= p1 * log2(p1);
double p12 = pi1 * layer1OutLinkIt->second.weight / ow1;
h12 -= p12 * log2(p12);
layer1OutLinkIt++;
}
while (layer2OutLinkIt != layer2OutLinkItEnd) {
// If the second state node has a link that the second has not
double p2 = layer2OutLinkIt->second.weight / ow2;
h2 -= p2 * log2(p2);
double p12 = pi2 * layer2OutLinkIt->second.weight / ow2;
h12 -= p12 * log2(p12);
layer2OutLinkIt++;
}
double div = (pi1 + pi2) * h12 - pi1 * h1 - pi2 * h2;
// Fix precision problems
if (div < 0.0)
div = 0.0;
else if (div > 1.0)
div = 1.0;
return div;
}
void Network::simulateInterLayerLinks()
{
}
void Network::addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight)
{
m_higherOrderInputMethodCalled = true;
bool added = m_networks[layer].addLink(n1, n2, weight);
if (added) {
++m_numIntraLayerLinks;
m_maxNodeIdInIntraLayerNetworks = std::max(m_maxNodeIdInIntraLayerNetworks, std::max(n1, n2));
}
}
void Network::addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight)
{
if (layer1 == layer2) {
throw std::runtime_error(io::Str() << "Inter-layer link (layer1, node, layer2): " << layer1 << ", " << n << ", " << layer2 << " must have layer1 != layer2");
}
m_higherOrderInputMethodCalled = true;
auto& interLinks = m_interLinks[LayerNode(layer1, n)];
auto it = interLinks.find(layer2);
if (it == interLinks.end()) {
++m_numInterLayerLinks;
}
interLinks[layer2] += interWeight;
}
unsigned int Network::addMultilayerNode(unsigned int layerId, unsigned int physicalId, double weight)
{
m_higherOrderInputMethodCalled = true;
// Create state node if not already exist, return state node id
auto& layerIt = m_layerNodeToStateId[layerId];
auto it = layerIt.find(physicalId);
if (it != layerIt.end()) {
return it->second;
}
bool matchableMultilayerIds = m_config.matchableMultilayerIds != 0;
if (matchableMultilayerIds && layerId > m_config.matchableMultilayerIds) {
throw std::runtime_error(io::Str() << "Cannot add node with layer " << layerId << " to network with matchable multilayer ids using largest layer id " << m_config.matchableMultilayerIds);
}
auto ret = matchableMultilayerIds
? addStateNodeWithDeterministicId(physicalId, layerId, m_multilayerStateIdBitShift)
: addStateNodeWithAutogeneratedId(physicalId);
auto& stateNode = ret.first->second;
stateNode.layerId = layerId;
stateNode.weight = weight;
m_layerNodeToStateId[layerId][physicalId] = stateNode.id;
m_layers.insert(layerId);
return stateNode.id;
}
void Network::addMetaData(unsigned int nodeId, int meta)
{
std::vector<int> metaData(1, meta);
addMetaData(nodeId, metaData);
}
void Network::addMetaData(unsigned int nodeId, const std::vector<int>& metaData)
{
m_metaData[nodeId] = metaData;
if (m_numMetaDataColumns == 0) {
m_numMetaDataColumns = metaData.size();
} else if (metaData.size() != m_numMetaDataColumns) {
throw std::runtime_error(io::Str() << "Must have same number of dimensions in meta data, error trying to add meta data '" << io::stringify(metaData, ",") << "' on node " << nodeId << ".");
}
}
} // namespace infomap
+214
View File
@@ -0,0 +1,214 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef NETWORK_H_
#define NETWORK_H_
#include "Config.h"
#include "../core/StateNetwork.h"
#include <string>
#include <map>
#include <utility>
#include <vector>
#include <set>
#include <utility>
#include <limits>
#include <sstream>
#include <locale>
namespace infomap {
struct LayerNode;
class Network : public StateNetwork {
private:
// Helpers
std::istringstream m_extractor;
// Multilayer
std::map<unsigned int, Network> m_networks; // intra-layer links
std::map<LayerNode, std::map<unsigned int, double>> m_interLinks;
// { layer -> { physId -> stateId }}
std::map<unsigned int, std::map<unsigned int, unsigned int>> m_layerNodeToStateId;
std::map<unsigned int, std::map<unsigned int, double>> m_sumIntraOutWeight;
std::set<unsigned int> m_layers;
unsigned int m_numInterLayerLinks = 0;
unsigned int m_numIntraLayerLinks = 0;
unsigned int m_maxNodeIdInIntraLayerNetworks = 0;
unsigned int m_multilayerStateIdBitShift = 0;
// Meta data
std::map<unsigned int, std::vector<int>> m_metaData;
unsigned int m_numMetaDataColumns = 0;
using InsensitiveStringSet = std::set<std::string, io::InsensitiveCompare>;
std::map<std::string, InsensitiveStringSet> m_ignoreHeadings;
std::map<std::string, InsensitiveStringSet> m_validHeadings; // {
// { "pajek", {"*Vertices", "*Edges", "*Arcs"} },
// { "link-list", {"*Links"} },
// { "bipartite", {"*Vertices", "*Bipartite"} },
// { "general", {"*Vertices", "*States", "*Edges", "*Arcs", "*Links", "*Context"} }
// };
public:
Network() : StateNetwork() { init(); }
explicit Network(const Config& config) : StateNetwork(config) { init(); }
explicit Network(const std::string& flags) : StateNetwork(Config(flags)) { init(); }
~Network() override = default;
Network(const Network&) = delete;
Network& operator=(const Network&) = delete;
Network(Network&&) = delete;
Network& operator=(Network&&) = delete;
void clear() override;
/**
* Parse network data from file and generate network
* @param filename input network
* @param accumulate add to possibly existing network data (default), else clear before.
*/
virtual void readInputData(std::string filename = "", bool accumulate = true);
/**
* Init categorical meta data on all nodes from a file with the following format:
* # nodeId metaData
* 1 1
* 2 1
* 3 2
* 4 2
* 5 3
* @param filename input filename for metadata
*/
virtual void readMetaData(const std::string& filename);
unsigned int numMetaDataColumns() const { return m_numMetaDataColumns; }
const std::map<unsigned int, std::vector<int>>& metaData() const override { return m_metaData; }
bool isMultilayerNetwork() const { return !m_layerNodeToStateId.empty(); }
const std::map<unsigned int, std::map<unsigned int, unsigned int>>& layerNodeToStateId() const { return m_layerNodeToStateId; }
void postProcessInputData();
void generateStateNetworkFromMultilayer();
void generateStateNetworkFromMultilayerWithInterLinks();
void generateStateNetworkFromMultilayerWithSimulatedInterLinks();
void simulateInterLayerLinks();
/**
* Create state node corresponding to this multilayer node if not already exist
* @return state node id
*/
unsigned int addMultilayerNode(unsigned int layerId, unsigned int physicalId, double weight = 1.0);
void addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight);
/**
* Create an intra-layer link
*/
void addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight);
/**
* Create links between (layer1,n) and (layer2,m) for all m connected to n in layer 2.
* The weight is distributed proportionally.
* TODO: This is done later..
*/
void addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight);
void addMetaData(unsigned int nodeId, int meta);
void addMetaData(unsigned int nodeId, const std::vector<int>& metaData);
private:
void init();
void initValidHeadings();
void parseNetwork(const std::string& filename);
void parseNetwork(const std::string& filename, const InsensitiveStringSet& validHeadings, const InsensitiveStringSet& ignoreHeadings, const std::string& startHeading = "");
// Helper methods
/**
* Parse vertices under the heading
* @return The line after the vertices
*/
std::string parseVertices(std::ifstream& file, const std::string& heading);
std::string parseStateNodes(std::ifstream& file, const std::string& heading);
std::string parseLinks(std::ifstream& file);
/**
* Parse multilayer links from a *multilayer section
*/
std::string parseMultilayerLinks(std::ifstream& file);
/**
* Parse multilayer links from an *intra section
*/
std::string parseMultilayerIntraLinks(std::ifstream& file);
/**
* Parse multilayer links from an *inter section
*/
std::string parseMultilayerInterLinks(std::ifstream& file);
std::string parseBipartiteLinks(std::ifstream& file, const std::string& heading);
static std::string ignoreSection(std::ifstream& file, const std::string& heading);
void parseStateNode(const std::string& line, StateNetwork::StateNode& stateNode);
/**
* Parse a string of link data.
* If no weight data can be extracted, the default value 1.0 will be used.
* @throws an error if not both node ids can be extracted.
*/
void parseLink(const std::string& line, unsigned int& n1, unsigned int& n2, double& weight);
/**
* Parse a string of multilayer link data.
* If no weight data can be extracted, the default value 1.0 will be used.
* @throws an error if not both node and layer ids can be extracted.
*/
void parseMultilayerLink(const std::string& line, unsigned int& layer1, unsigned int& n1, unsigned int& layer2, unsigned int& n2, double& weight);
/**
* Parse a string of intra-multilayer link data.
* If no weight data can be extracted, the default value 1.0 will be used.
* @throws an error if not both node and layer ids can be extracted.
*/
void parseMultilayerIntraLink(const std::string& line, unsigned int& layer, unsigned int& n1, unsigned int& n2, double& weight);
/**
* Parse a string of inter-multilayer link data.
* If no weight data can be extracted, the default value 1.0 will be used.
* @throws an error if not both node and layer ids can be extracted.
*/
void parseMultilayerInterLink(const std::string& line, unsigned int& layer1, unsigned int& n, unsigned int& layer2, double& weight);
static double calculateJensenShannonDivergence(bool& intersect, const OutLinkMap& layer1OutLinks, double sumOutLinkWeightLayer1, const OutLinkMap& layer2OutLinks, double sumOutLinkWeightLayer2);
void printSummary();
};
struct LayerNode {
unsigned int layer, node;
explicit LayerNode(unsigned int layer = 0, unsigned int node = 0) : layer(layer), node(node) { }
bool operator<(const LayerNode other) const
{
return layer == other.layer ? node < other.node : layer < other.layer;
}
};
} // namespace infomap
#endif // NETWORK_H_
+629
View File
@@ -0,0 +1,629 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "Output.h"
#include "../core/InfomapBase.h"
#include "../core/StateNetwork.h"
#include "../io/SafeFile.h"
namespace infomap {
std::string getOutputFilename(const InfomapBase& im, const std::string& filename, const std::string& ext, bool states)
{
if (!filename.empty()) {
return filename;
}
auto defaultFilename = im.outDirectory + im.outName;
if (im.haveMemory() && states) {
defaultFilename += "_states";
}
return defaultFilename + ext;
}
std::string getOutputFileHeader(const InfomapBase& im, const StateNetwork& network, bool states)
{
std::string bipartiteInfo = io::Str() << "\n# bipartite start id " << network.bipartiteStartId();
return io::Str() << "# v" << INFOMAP_VERSION << "\n"
<< "# ./Infomap " << im.parsedString << "\n"
<< "# started at " << im.getStartDate() << "\n"
<< "# completed in " << im.getElapsedTime().getElapsedTimeInSec() << " s\n"
<< "# partitioned into " << im.maxTreeDepth() << " levels with " << im.numTopModules() << " top modules\n"
<< "# codelength " << im.codelength() << " bits\n"
<< "# relative codelength savings " << im.getRelativeCodelengthSavings() * 100 << "%\n"
<< "# flow model " << flowModelToString(im.flowModel)
<< (im.haveMemory() ? "\n# higher order" : "")
<< (im.haveMemory() ? states ? "\n# state level" : "\n# physical level" : "")
<< (network.isBipartite() ? bipartiteInfo : "");
}
std::string getNodeName(const std::map<unsigned int, std::string>& names, const InfoNode& node)
{
try {
return names.at(node.physicalId);
} catch (...) {
return io::stringify(node.physicalId);
}
}
std::string writeClu(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states, int moduleIndexLevel)
{
auto outputFilename = getOutputFilename(im, filename, ".clu", states);
SafeOutFile outFile { outputFilename };
outFile << std::setprecision(9);
outFile << getOutputFileHeader(im, network, states) << "\n";
outFile << "# module level " << moduleIndexLevel << "\n";
outFile << std::resetiosflags(std::ios::floatfield) << std::setprecision(6);
if (states) {
outFile << "# state_id module flow node_id";
if (im.isMultilayerNetwork())
outFile << " layer_id";
outFile << '\n';
} else {
outFile << "# node_id module flow\n";
}
const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes;
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
if (im.haveMemory() && !states) {
for (auto it(im.iterTreePhysical(moduleIndexLevel)); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
outFile << node.physicalId << " " << it.moduleId() << " " << node.data.flow << "\n";
}
}
} else {
for (auto it(im.iterTree(moduleIndexLevel)); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
if (states) {
outFile << node.stateId << " " << it.moduleId() << " " << node.data.flow << " " << node.physicalId;
if (im.isMultilayerNetwork())
outFile << " " << node.layerId;
outFile << "\n";
} else
outFile << node.physicalId << " " << it.moduleId() << " " << node.data.flow << "\n";
}
}
}
return outputFilename;
}
void writeTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states)
{
auto oldPrecision = outStream.precision();
outStream << std::setprecision(9);
outStream << getOutputFileHeader(im, network, states) << "\n";
outStream << std::setprecision(6);
if (states) {
outStream << "# path flow name state_id node_id";
if (im.isMultilayerNetwork())
outStream << " layer_id";
outStream << '\n';
} else {
outStream << "# path flow name node_id\n";
}
const auto shouldHideBipartiteNodes = !im.printFlowTree && im.isBipartite() && im.hideBipartiteNodes;
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
// TODO: Make a general iterator where merging physical nodes depend on a parameter rather than type to be able to DRY here
if (im.haveMemory() && !states) {
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
auto& path = it.path();
outStream << io::stringify(path, ":") << " " << node.data.flow << " \"" << getNodeName(network.names(), node) << "\" " << node.physicalId << '\n';
}
}
} else {
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
auto& path = it.path();
outStream << io::stringify(path, ":") << " " << node.data.flow << " \"" << getNodeName(network.names(), node) << "\" ";
if (states) {
outStream << node.stateId << " " << node.physicalId;
if (im.isMultilayerNetwork())
outStream << " " << node.layerId;
outStream << '\n';
} else {
outStream << node.physicalId << '\n';
}
}
}
}
outStream << std::setprecision(oldPrecision);
}
using Link = std::pair<unsigned int, unsigned int>;
using LinkMap = std::map<Link, double>;
std::map<std::string, LinkMap> aggregateModuleLinks(InfomapBase& im, bool states)
{
// Aggregate links between each module. Rest is aggregated as exit flow
// Links on nodes within sub infomap instances doesn't have links outside the root
// so iterate over links on main instance and map to infomap tree iterator
bool mergePhysicalNodes = im.haveMemory() && !states;
// Map state id to parent in infomap tree iterator
std::map<unsigned int, InfoNode*> stateIdToParent;
std::map<unsigned int, unsigned int> stateIdToChildIndex;
if (mergePhysicalNodes) {
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
if (it->isLeaf()) {
for (auto stateId : it->stateNodes) {
stateIdToParent[stateId] = it->parent;
stateIdToChildIndex[stateId] = it.childIndex();
}
} else {
// Use stateId to store depth on modules to simplify link aggregation
it->stateId = it.depth();
it->index = it.childIndex();
}
}
} else {
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
if (it->isLeaf()) {
stateIdToParent[it->stateId] = it->parent;
stateIdToChildIndex[it->stateId] = it.childIndex();
} else {
// Use stateId to store depth on modules to simplify link aggregation
it->stateId = it.depth();
it->index = it.childIndex();
}
}
}
std::map<std::string, LinkMap> moduleLinks;
for (auto& leaf : im.leafNodes()) {
for (auto& link : leaf->outEdges()) {
double flow = link->data.flow;
InfoNode* sourceParent = stateIdToParent[link->source->stateId];
InfoNode* targetParent = stateIdToParent[link->target->stateId];
auto sourceDepth = sourceParent->calculatePath().size() + 1;
auto targetDepth = targetParent->calculatePath().size() + 1;
auto sourceChildIndex = stateIdToChildIndex[link->source->stateId];
auto targetChildIndex = stateIdToChildIndex[link->target->stateId];
auto sourceParentIt = InfomapParentIterator(sourceParent);
auto targetParentIt = InfomapParentIterator(targetParent);
// Iterate to same depth
// First raise target
while (targetDepth > sourceDepth) {
++targetParentIt;
--targetDepth;
}
// Raise source to same depth
while (sourceDepth > targetDepth) {
++sourceParentIt;
--sourceDepth;
}
auto currentDepth = sourceDepth;
// Add link if same parent
while (currentDepth > 0) {
if (sourceParentIt == targetParentIt) {
// Skip self-links
if (sourceChildIndex != targetChildIndex) {
auto parentId = io::stringify(sourceParentIt->calculatePath(), ":");
auto& linkMap = moduleLinks[parentId];
linkMap[std::make_pair(sourceChildIndex + 1, targetChildIndex + 1)] += flow;
}
}
sourceChildIndex = sourceParentIt->index;
targetChildIndex = targetParentIt->index;
++sourceParentIt;
++targetParentIt;
--currentDepth;
}
}
}
return moduleLinks;
}
void writeTreeLinks(InfomapBase& im, std::ostream& outStream, bool states)
{
auto oldPrecision = outStream.precision();
outStream << std::setprecision(6);
auto moduleLinks = aggregateModuleLinks(im, states);
outStream << "*Links " << (im.isUndirectedFlow() ? "undirected" : "directed") << "\n";
outStream << "#*Links path enterFlow exitFlow numEdges numChildren\n";
// Use stateId to store depth on modules to optimize link aggregation
for (auto it(im.iterModules()); !it.isEnd(); ++it) {
auto parentId = io::stringify(it.path(), ":");
auto& module = *it;
auto& links = moduleLinks[parentId];
outStream << "*Links " << (parentId.empty() ? "root" : parentId) << " " << module.data.enterFlow << " " << module.data.exitFlow << " " << links.size() << " " << module.infomapChildDegree() << "\n";
for (auto itLink : links) {
unsigned int sourceId = itLink.first.first;
unsigned int targetId = itLink.first.second;
double flow = itLink.second;
outStream << sourceId << " " << targetId << " " << flow << "\n";
}
}
outStream << std::setprecision(oldPrecision);
}
void writeNewickTree(InfomapBase& im, std::ostream& outStream, bool states)
{
auto oldPrecision = outStream.precision();
outStream << std::setprecision(6);
auto isRoot = true;
unsigned int lastDepth = 0;
std::vector<double> flowStack;
auto writeNewickNode = [&](const InfoNode& node, unsigned int depth) {
if (depth > lastDepth || isRoot) {
outStream << "(";
flowStack.push_back(node.data.flow);
if (node.isLeaf())
outStream << (states ? node.stateId : node.physicalId) << ":" << node.data.flow;
} else if (depth == lastDepth) {
outStream << ",";
flowStack[flowStack.size() - 1] = node.data.flow;
if (node.isLeaf()) {
outStream << (states ? node.stateId : node.physicalId) << ":" << node.data.flow;
}
} else {
// depth < lastDepth
while (flowStack.size() > depth + 1) {
flowStack.pop_back();
outStream << "):" << flowStack.back();
}
flowStack[flowStack.size() - 1] = node.data.flow;
outStream << ",";
}
lastDepth = depth;
isRoot = false;
};
// TODO: Make a general iterator where merging physical nodes depend on a parameter rather than type to be able to DRY here
if (im.haveMemory() && !states) {
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
writeNewickNode(*it, it.depth());
}
} else {
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
writeNewickNode(*it, it.depth());
}
}
while (flowStack.size() > 1) {
flowStack.pop_back();
outStream << "):" << flowStack.back();
}
outStream << ");\n";
outStream << std::setprecision(oldPrecision);
}
void writeJsonTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states, bool writeLinks)
{
auto oldPrecision = outStream.precision();
outStream << "{";
outStream << "\"version\":\"v" << INFOMAP_VERSION << "\","
<< "\"args\":\"" << im.parsedString << "\","
<< "\"startedAt\":\"" << im.getStartDate() << "\","
<< "\"completedIn\":" << im.getElapsedTime().getElapsedTimeInSec() << ","
<< "\"codelength\":" << im.codelength() << ","
<< "\"numLevels\":" << im.maxTreeDepth() << ","
<< "\"numTopModules\":" << im.numTopModules() << ","
<< "\"relativeCodelengthSavings\":" << im.getRelativeCodelengthSavings() << ","
<< "\"directed\":" << (im.isUndirectedFlow() ? "false" : "true") << ","
<< "\"flowModel\": \"" << flowModelToString(im.flowModel) << "\","
<< "\"higherOrder\":" << (im.haveMemory() ? "true" : "false") << ",";
if (im.haveMemory()) {
outStream << "\"stateLevel\":" << (states ? "true" : "false") << ",";
}
if (im.isBipartite()) {
outStream << "\"bipartiteStartId\":" << network.bipartiteStartId() << ",";
}
outStream << std::setprecision(6);
outStream << "\"nodes\":[";
const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes;
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
auto metaData = network.metaData();
auto writeMeta = [&metaData](auto& outStream, auto nodeId) {
outStream << "\"metadata\":{";
auto meta = metaData[nodeId];
for (unsigned int i = 0; i < meta.size(); ++i) {
outStream << '"' << i << "\":"
<< '"' << meta[i] << '"'; // metadata class as string to highlight that this is a categorical variable
if (i < meta.size() - 1)
outStream << ',';
}
outStream << "},";
};
// don't append a comma after the last entry
auto first = true;
if (im.haveMemory() && !states) {
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
const auto path = io::stringify(it.path(), ",");
if (first) {
first = false;
} else {
outStream << ",";
}
outStream << "{"
<< "\"path\":[" << path << "],"
<< "\"name\":\"" << getNodeName(network.names(), node) << "\","
<< "\"flow\":" << node.data.flow << ","
<< "\"mec\":" << it.modularCentrality() << ","
<< "\"id\":" << node.physicalId << "}";
}
}
} else {
const auto multilevelModules = im.getMultilevelModules(states);
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
if (first) {
first = false;
} else {
outStream << ",";
}
const auto path = io::stringify(it.path(), ", ");
const auto modules = im.haveModules() ? io::stringify(multilevelModules.at(states ? node.stateId : node.physicalId), ", ") : "1";
outStream << "{"
<< "\"path\":[" << path << "],"
<< "\"modules\":[" << modules << "],"
<< "\"name\":\"" << getNodeName(network.names(), node) << "\","
<< "\"flow\":" << node.data.flow << ","
<< "\"mec\":" << it.modularCentrality() << ",";
// can't currently use both memory and meta map equation
if (im.haveMetaData() && !states) {
writeMeta(outStream, node.physicalId);
}
if (states) {
outStream << "\"stateId\":" << node.stateId << ",";
if (im.isMultilayerNetwork())
outStream << "\"layerId\":" << node.layerId << ",";
}
outStream << "\"id\":" << node.physicalId << "}";
}
}
}
outStream << "],"; // tree
// -------------
// Write modules
// -------------
// Uses stateId to store depth on modules to optimize link aggregation
auto moduleLinks = aggregateModuleLinks(im, states);
first = true;
outStream << "\"modules\":[";
for (auto it(im.iterModules()); !it.isEnd(); ++it) {
const auto parentId = io::stringify(it.path(), ":");
const auto& module = *it;
const auto& links = moduleLinks[parentId];
const auto path = io::stringify(it.path(), ",");
if (first) {
first = false;
} else {
outStream << ",";
}
outStream << "{";
outStream << "\"path\":[" << (parentId.empty() ? "0" : path) << "],"
<< "\"enterFlow\":" << module.data.enterFlow << ','
<< "\"exitFlow\":" << module.data.exitFlow << ','
<< "\"numEdges\":" << links.size() << ','
<< "\"numChildren\":" << module.infomapChildDegree() << ','
<< "\"codelength\":" << module.codelength;
if (writeLinks) {
outStream << ","
<< "\"links\":[";
auto firstLink = true;
for (auto itLink : links) {
if (firstLink) {
firstLink = false;
} else {
outStream << ",";
}
unsigned int sourceId = itLink.first.first;
unsigned int targetId = itLink.first.second;
double flow = itLink.second;
outStream << "{\"source\":" << sourceId << ",\"target\":" << targetId << ",\"flow\":" << flow << "}";
}
outStream << "]"; // links
}
outStream << "}";
}
outStream << "]"; // modules
outStream << "}";
outStream << std::setprecision(oldPrecision);
}
void writeCsvTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states)
{
auto oldPrecision = outStream.precision();
outStream << std::setprecision(6);
outStream << "path,flow,name,";
if (im.haveMemory() && !states) {
outStream << "node_id\n";
} else {
if (states) {
outStream << "state_id,";
if (im.isMultilayerNetwork())
outStream << "layer_id,";
}
outStream << "node_id\n";
}
const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes;
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
if (im.haveMemory() && !states) {
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
const auto path = io::stringify(it.path(), ":");
outStream << path << ',' << node.data.flow << ",\"" << getNodeName(network.names(), node) << "\"," << node.physicalId << '\n';
}
}
} else {
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
InfoNode& node = *it;
if (node.isLeaf()) {
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
continue;
}
const auto path = io::stringify(it.path(), ":");
outStream << path << ',' << node.data.flow << ",\"" << getNodeName(network.names(), node) << "\",";
if (states) {
outStream << node.stateId << ',';
if (im.isMultilayerNetwork())
outStream << node.layerId << ',';
}
outStream << node.physicalId << '\n';
}
}
}
outStream << std::setprecision(oldPrecision);
}
std::string writeTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states)
{
auto outputFilename = getOutputFilename(im, filename, ".tree", states);
SafeOutFile outFile { outputFilename };
writeTree(im, network, outFile, states);
return outputFilename;
}
std::string writeFlowTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states)
{
auto outputFilename = getOutputFilename(im, filename, ".ftree", states);
SafeOutFile outFile { outputFilename };
writeTree(im, network, outFile, states);
writeTreeLinks(im, outFile, states);
return outputFilename;
}
std::string writeNewickTree(InfomapBase& im, const std::string& filename, bool states)
{
auto outputFilename = getOutputFilename(im, filename, ".nwk", states);
SafeOutFile outFile { outputFilename };
writeNewickTree(im, outFile, states);
return outputFilename;
}
std::string writeJsonTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states, bool writeLinks)
{
auto outputFilename = getOutputFilename(im, filename, ".json", states);
SafeOutFile outFile { outputFilename };
writeJsonTree(im, network, outFile, states, writeLinks);
return outputFilename;
}
std::string writeCsvTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states)
{
auto outputFilename = getOutputFilename(im, filename, ".csv", states);
SafeOutFile outFile { outputFilename };
writeCsvTree(im, network, outFile, states);
return outputFilename;
}
} // namespace infomap
+36
View File
@@ -0,0 +1,36 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef OUTPUT_H_
#define OUTPUT_H_
#include <map>
#include <string>
#include <utility>
namespace infomap {
class InfomapBase;
class StateNetwork;
std::string writeTree(InfomapBase&, const StateNetwork&, const std::string&, bool states);
std::string writeFlowTree(InfomapBase&, const StateNetwork&, const std::string&, bool states);
std::string writeNewickTree(InfomapBase&, const std::string&, bool states);
std::string writeJsonTree(InfomapBase&, const StateNetwork&, const std::string&, bool states, bool writeLinks);
std::string writeCsvTree(InfomapBase&, const StateNetwork&, const std::string&, bool states);
std::string writeClu(InfomapBase&, const StateNetwork&, const std::string&, bool states, int moduleIndexLevel);
} // namespace infomap
#endif // OUTPUT_H_
@@ -0,0 +1,362 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "ProgramInterface.h"
#include "../utils/Log.h"
#include "igraph_error.h"
#include <iostream>
#include <cstdlib>
#include <map>
#include <utility>
namespace infomap {
const std::string ArgType::integer = "integer";
const std::string ArgType::number = "number";
const std::string ArgType::string = "string";
const std::string ArgType::path = "path";
const std::string ArgType::probability = "probability";
const std::string ArgType::option = "option";
const std::string ArgType::list = "list";
const std::unordered_map<std::string, char> ArgType::toShort = {
{ "integer", 'n' },
{ "number", 'f' },
{ "string", 's' },
{ "path", 'p' },
{ "probability", 'P' },
{ "option", 'o' },
{ "list", 'l' },
};
ProgramInterface::ProgramInterface(std::string name, std::string shortDescription, std::string version)
: m_programName(std::move(name)),
m_shortProgramDescription(std::move(shortDescription)),
m_programVersion(std::move(version))
{
addIncrementalOptionArgument(m_displayHelp, 'h', "help", "Prints this help message. Use -hh to show advanced options.", "About");
addOptionArgument(m_displayVersion, 'V', "version", "Display program version information.", "About");
addOptionArgument(m_printJsonParameters, "print-json-parameters", "Print Infomap parameters in JSON.", "About").setHidden(true);
}
/* Modification for igraph: Disable functions that call forbidden
* functions such as exit(). */
#if 0
void ProgramInterface::exitWithUsage(bool showAdvanced) const
{
Log() << "Name:\n";
Log() << " " << m_programName << " - " << m_shortProgramDescription << '\n';
Log() << "\nUsage:\n";
Log() << " " << m_executableName;
for (auto& nonOptionArgument : m_nonOptionArguments)
if (showAdvanced || !nonOptionArgument->isAdvanced)
Log() << " " << nonOptionArgument->variableName;
if (!m_optionArguments.empty())
Log() << " [options]";
Log() << '\n';
if (!m_programDescription.empty())
Log() << "\nDescription:\n " << m_programDescription << '\n';
for (auto& nonOptionArgument : m_nonOptionArguments)
if (showAdvanced || !nonOptionArgument->isAdvanced)
Log() << "\n[" << nonOptionArgument->variableName << "]\n " << nonOptionArgument->description << '\n';
if (!m_optionArguments.empty())
Log() << "\n[options]\n";
// First stringify the options part to get the maximum length
std::deque<std::string> optionStrings(m_optionArguments.size());
std::string::size_type maxLength = 0;
for (unsigned int i = 0; i < m_optionArguments.size(); ++i) {
auto& opt = *m_optionArguments[i];
bool haveShort = opt.shortName != '\0';
std::string optArgShort = opt.requireArgument ? (io::Str() << "<" << ArgType::toShort.at(opt.argumentName) << ">") : opt.incrementalArgument ? "[+]"
: std::string(3, ' ');
std::string optArgLong = opt.requireArgument ? (io::Str() << "<" << opt.argumentName << ">") : opt.incrementalArgument ? "[+]"
: std::string(3, ' ');
std::string shortOption = haveShort ? (io::Str() << " -" << opt.shortName << optArgShort) : std::string(7, ' ');
optionStrings[i] = io::Str() << shortOption << " --" << opt.longName << " " << optArgLong;
if (optionStrings[i].length() > maxLength)
maxLength = optionStrings[i].length();
}
std::vector<std::string> groups { "About" };
for (auto& group : m_groups) {
if (group != "About")
groups.push_back(group);
}
if (m_groups.empty())
groups.emplace_back("All");
for (const auto& group : groups) {
if (group != "All") {
Log() << "\n"
<< group << "\n";
Log() << std::string(group.length(), '-') << "\n";
}
for (unsigned int i = 0; i < m_optionArguments.size(); ++i) {
auto& opt = *m_optionArguments[i];
if (group == "All" || opt.group == group) {
std::string::size_type numSpaces = maxLength + 3 - optionStrings[i].length();
if (showAdvanced || !opt.isAdvanced) {
Log() << optionStrings[i] << std::string(numSpaces, ' ') << opt.description;
if (!opt.printNumericValue().empty())
Log() << " (Default: " << opt.printNumericValue() << ")";
Log() << "\n";
}
}
}
}
Log() << '\n';
std::exit(0);
}
void ProgramInterface::exitWithVersionInformation() const
{
Log() << m_programName << " version " << m_programVersion;
#ifdef _OPENMP
Log() << " compiled with OpenMP";
#endif
Log() << '\n';
Log() << "See www.mapequation.org for terms of use.\n";
std::exit(0);
}
#endif
void ProgramInterface::exitWithError(const std::string& message) const
{
/* Modification for igraph: This function must never be called
* when using Infomap through igraph. The function is disabled
* to eliminate forbidden references to exit() and std::cerr. */
IGRAPH_FATALF("Infomap called exitWithError() with message '%s'.",
message.c_str());
#if 0
Log() << m_programName << " version " << m_programVersion;
#ifdef _OPENMP
Log() << " compiled with OpenMP";
#endif
Log() << std::endl;
std::cerr << message << std::endl;
Log() << "Usage: " << m_executableName;
for (auto& nonOptionArgument : m_nonOptionArguments)
if (!nonOptionArgument->isAdvanced)
Log() << " " << nonOptionArgument->variableName;
if (!m_optionArguments.empty())
Log() << " [options]";
Log() << ". Run with option '-h' for more information.\n";
std::exit(1);
#endif
}
std::string toJson(const std::string& key, const std::string& value)
{
return io::Str() << '"' << key << "\": \"" << value << '"';
}
std::string toJson(const std::string& key, bool value)
{
return io::Str() << '"' << key << "\": " << (value ? "true" : "false");
}
template <typename Value>
std::string toJson(const std::string& key, Value value)
{
return io::Str() << '"' << key << "\": " << value;
}
std::string toJson(const Option& opt)
{
return io::Str() << "{ "
<< toJson("long", std::string(io::Str() << "--" << opt.longName)) << ", "
<< toJson("short", opt.shortName != '\0' ? std::string(io::Str() << "-" << opt.shortName) : "") << ", "
<< toJson("description", opt.description) << ", "
<< toJson("group", opt.group) << ", "
<< toJson("required", opt.requireArgument) << ", "
<< toJson("advanced", opt.isAdvanced) << ", "
<< toJson("incremental", opt.incrementalArgument) << ", "
<< (opt.requireArgument
? (io::Str() << toJson("longType", opt.argumentName) << ", "
<< toJson("shortType", std::string(1, ArgType::toShort.at(opt.argumentName))) << ", "
<< toJson("default", opt.printValue()))
: toJson("default", false))
<< " }";
}
/* Modification for igraph: Disable functions that call forbidden
* functions such as exit(). */
#if 0
void ProgramInterface::exitWithJsonParameters() const
{
Log() << "{\n \"parameters\": [\n";
for (unsigned int i = 0; i < m_optionArguments.size(); ++i) {
auto& opt = *m_optionArguments[i];
if (opt.hidden)
continue;
Log() << " " << toJson(opt);
if (i < m_optionArguments.size() - 1) {
Log() << ",\n";
} else {
Log() << "\n";
}
}
Log() << " ]\n}";
std::exit(0);
}
#endif
void ProgramInterface::parseArgs(const std::string& args)
{
// Map the options on short and long name, and check for duplication
std::map<char, Option*> shortOptionMap;
std::map<std::string, Option*> longOptionMap;
for (auto& optionArgument : m_optionArguments) {
auto& opt = *optionArgument;
if (opt.shortName != '\0') {
auto it = shortOptionMap.find(opt.shortName);
if (it != shortOptionMap.end())
throw std::runtime_error(io::Str() << "Duplication of option '" << opt.shortName << "'");
shortOptionMap.insert(std::make_pair(opt.shortName, &opt));
}
auto it = longOptionMap.find(opt.longName);
if (it != longOptionMap.end())
throw std::runtime_error(io::Str() << "Duplication of option \"" << opt.longName << "\"");
longOptionMap.insert(std::make_pair(opt.longName, &opt));
}
// Split the flags on whitespace
std::vector<std::string> flags;
std::istringstream argStream(args);
{
std::string arg;
while (!(argStream >> arg).fail())
flags.push_back(arg);
}
std::deque<std::string> nonOpts;
try {
for (unsigned int i = 0; i < flags.size(); ++i) {
bool flagValue = true;
unsigned int numArgsLeft = flags.size() - i - 1;
const std::string& arg = flags[i];
if (arg.length() == 0)
throw std::runtime_error("Illegal argument ''");
if (arg[0] != '-') {
nonOpts.push_back(arg);
} else {
if (arg.length() < 2)
throw std::runtime_error("Illegal argument '-'");
if (arg[1] == '-') {
// Long option
if (arg.length() < 3)
throw std::runtime_error("Illegal argument '--'");
std::string longOpt = arg.substr(2);
auto it = longOptionMap.find(longOpt);
if (it == longOptionMap.end()) {
// Unrecognized option, check if it negates a recognised option with the '--no-' prefix
if (longOpt.compare(0, 3, "no-") == 0 && longOptionMap.find(std::string(longOpt, 3)) != longOptionMap.end()) {
longOpt = std::string(longOpt, 3);
it = longOptionMap.find(longOpt);
flagValue = false;
} else {
throw std::runtime_error(io::Str() << "Unrecognized option: '--" << longOpt << "'");
}
}
auto& opt = *it->second;
if (!opt.requireArgument || opt.incrementalArgument)
opt.set(flagValue);
else {
if (numArgsLeft == 0)
throw std::runtime_error(io::Str() << "Option '" << opt.longName << "' requires argument");
++i;
if (!opt.parse(flags[i]))
throw std::runtime_error(io::Str() << "Cannot parse '" << flags[i] << "' as argument to option '" << opt.longName << "'. ");
}
} else {
// Short option(s)
for (unsigned int j = 1; j < arg.length(); ++j) {
char o = arg[j];
unsigned int numCharsLeft = arg.length() - j - 1;
auto it = shortOptionMap.find(o);
if (it == shortOptionMap.end())
throw std::runtime_error(io::Str() << "Unrecognized option: '-" << o << "'");
auto& opt = *it->second;
if (!opt.requireArgument || opt.incrementalArgument)
opt.set(flagValue);
else {
std::string optArg;
if (numCharsLeft > 0) {
optArg = arg.substr(j + 1);
j = arg.length() - 1;
} else if (numArgsLeft) {
++i;
optArg = flags[i];
} else
throw std::runtime_error(io::Str() << "Option '" << opt.longName << "' requires argument");
if (!opt.parse(optArg))
throw std::runtime_error(io::Str() << "Cannot parse '" << optArg << "' as argument to option '" << opt.longName << "'. ");
}
}
}
}
/* Modification for igraph: Disable calls to functions that
* needed to be removed because they referenced exit(). */
#if 0
if (m_displayHelp > 0)
exitWithUsage(m_displayHelp > 1);
if (m_displayVersion)
exitWithVersionInformation();
if (m_printJsonParameters)
exitWithJsonParameters();
#endif
}
} catch (std::exception& e) {
exitWithError(e.what());
}
if (nonOpts.size() < numRequiredArguments())
exitWithError("Missing required arguments.");
unsigned int i = 0;
unsigned int numVectorArguments = nonOpts.size() - (m_nonOptionArguments.size() - 1);
while (!nonOpts.empty()) {
std::string arg = nonOpts.front();
nonOpts.pop_front();
if (m_nonOptionArguments[i]->isOptionalVector && numVectorArguments == 0)
++i;
if (!m_nonOptionArguments[i]->parse(arg))
exitWithError("Argument error.");
if (!m_nonOptionArguments[i]->isOptionalVector || --numVectorArguments == 0)
++i;
}
}
std::vector<ParsedOption> ProgramInterface::getUsedOptionArguments() const
{
std::vector<ParsedOption> opts;
unsigned int numFlags = m_optionArguments.size();
for (unsigned int i = 0; i < numFlags; ++i) {
auto& opt = *m_optionArguments[i];
if (opt.used && opt.longName != "negate-next")
opts.emplace_back(opt);
}
return opts;
}
} // namespace infomap
@@ -0,0 +1,423 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef PROGRAM_INTERFACE_H_
#define PROGRAM_INTERFACE_H_
#include "../utils/convert.h"
#include <stdexcept>
#include <utility>
#include <vector>
#include <deque>
#include <memory>
#include <string>
#include <sstream>
#include <iostream>
#include <unordered_map>
namespace infomap {
class InterruptException : public std::exception {
public:
InterruptException() {};
};
typedef bool interruptionHandlerFn(void);
struct ArgType {
static const std::string integer;
static const std::string number;
static const std::string string;
static const std::string path;
static const std::string probability;
static const std::string option;
static const std::string list;
static const std::unordered_map<std::string, char> toShort;
};
struct Option {
Option(char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, bool requireArgument = false, std::string argName = "")
: shortName(shortName),
longName(std::move(longName)),
description(std::move(desc)),
group(std::move(group)),
isAdvanced(isAdvanced),
requireArgument(requireArgument),
incrementalArgument(false),
argumentName(std::move(argName)) { }
virtual ~Option() = default;
Option(const Option&) = default;
Option& operator=(const Option&) = default;
Option(Option&&) = default;
Option& operator=(Option&&) = default;
virtual bool parse(std::string const&)
{
used = true;
return true;
}
virtual void set(bool value)
{
used = true;
negated = !value;
};
Option& setHidden(bool value)
{
hidden = value;
return *this;
}
virtual std::ostream& printValue(std::ostream& out) const { return out; }
virtual std::string printValue() const { return ""; }
virtual std::string printNumericValue() const { return ""; }
friend std::ostream& operator<<(std::ostream& out, const Option& option)
{
out << option.longName;
if (option.requireArgument) {
out << " = ";
option.printValue(out);
}
return out;
}
char shortName;
std::string longName;
std::string description;
std::string group;
bool isAdvanced;
bool requireArgument;
bool incrementalArgument;
std::string argumentName;
bool hidden = false;
bool used = false;
bool negated = false;
};
struct IncrementalOption : Option {
IncrementalOption(unsigned int& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced)
: Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, false), target(target)
{
incrementalArgument = true;
}
bool parse(std::string const& value) override
{
Option::parse(value);
return ++target;
}
void set(bool value) override
{
Option::set(value);
if (value) {
++target;
} else if (target > 0) {
--target;
}
}
std::ostream& printValue(std::ostream& out) const override { return out << target; }
std::string printValue() const override { return io::Str() << target; }
unsigned int& target;
};
template <typename T>
struct ArgumentOption : Option {
ArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName)
: Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, true, std::move(argName)), target(target) { }
bool parse(std::string const& value) override
{
Option::parse(value);
return io::stringToValue(value, target);
}
std::string printValue() const override { return io::Str() << target; }
std::ostream& printValue(std::ostream& out) const override { return out << target; }
std::string printNumericValue() const override { return TypeInfo<T>::isNumeric() ? printValue() : ""; }
T& target;
};
template <typename T>
struct LowerBoundArgumentOption : ArgumentOption<T> {
LowerBoundArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName, T minValue)
: ArgumentOption<T>(target, shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, std::move(argName)), minValue(minValue) { }
bool parse(std::string const& value) override
{
auto ok = ArgumentOption<T>::parse(value);
if (ArgumentOption<T>::target < minValue) return false;
return ok;
}
T minValue;
};
template <typename T>
struct LowerUpperBoundArgumentOption : LowerBoundArgumentOption<T> {
LowerUpperBoundArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName, T minValue, T maxValue)
: LowerBoundArgumentOption<T>(target, shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, std::move(argName), minValue), maxValue(maxValue) { }
bool parse(std::string const& value) override
{
auto ok = LowerBoundArgumentOption<T>::parse(value);
if (LowerBoundArgumentOption<T>::target > maxValue) return false;
return ok;
}
T maxValue;
};
template <>
struct ArgumentOption<bool> : Option {
ArgumentOption(bool& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced)
: Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, false), target(target) { }
bool parse(std::string const& value) override
{
Option::parse(value);
return target = true;
}
void set(bool value) override
{
Option::set(value);
target = value;
}
std::ostream& printValue(std::ostream& out) const override { return out << target; }
std::string printValue() const override { return io::Str() << target; }
std::string printNumericValue() const override { return ""; }
bool& target;
};
struct ParsedOption {
explicit ParsedOption(const Option& opt)
: shortName(opt.shortName),
longName(opt.longName),
description(opt.description),
group(opt.group),
isAdvanced(opt.isAdvanced),
requireArgument(opt.requireArgument),
incrementalArgument(opt.incrementalArgument),
argumentName(opt.argumentName),
negated(opt.negated),
value(opt.printValue()) { }
friend std::ostream& operator<<(std::ostream& out, const ParsedOption& option)
{
if (option.negated)
out << "no ";
out << option.longName;
if (option.requireArgument)
out << " = " << option.value;
return out;
}
char shortName;
std::string longName;
std::string description;
std::string group;
bool isAdvanced;
bool requireArgument;
bool incrementalArgument;
std::string argumentName;
bool negated;
std::string value;
};
struct TargetBase {
TargetBase(std::string variableName, std::string desc, std::string group, bool isAdvanced)
: variableName(std::move(variableName)), description(std::move(desc)), group(std::move(group)), isOptionalVector(false), isAdvanced(isAdvanced) { }
virtual ~TargetBase() = default;
TargetBase(const TargetBase&) = default;
TargetBase& operator=(const TargetBase&) = default;
TargetBase(TargetBase&&) = default;
TargetBase& operator=(TargetBase&&) = default;
virtual bool parse(std::string const& value) = 0;
std::string variableName;
std::string description;
std::string group;
bool isOptionalVector = false;
bool isAdvanced;
};
template <typename T>
struct Target : TargetBase {
Target(T& target, std::string variableName, std::string desc, std::string group, bool isAdvanced)
: TargetBase(std::move(variableName), std::move(desc), std::move(group), isAdvanced), target(target) { }
bool parse(std::string const& value) override
{
return io::stringToValue(value, target);
}
T& target;
};
template <typename T>
struct OptionalTargets : TargetBase {
OptionalTargets(std::vector<T>& target, std::string variableName, std::string desc, std::string group, bool isAdvanced)
: TargetBase(std::move(variableName), std::move(desc), std::move(group), isAdvanced), targets(target)
{
isOptionalVector = true;
}
bool parse(std::string const& value) override
{
T target;
bool ok = io::stringToValue(value, target);
if (ok)
targets.push_back(target);
return ok;
}
std::vector<T>& targets;
};
class ProgramInterface {
public:
ProgramInterface(std::string name, std::string shortDescription, std::string version);
void setGroups(std::vector<std::string> groups) { m_groups = std::move(groups); }
template <typename T>
void addNonOptionArgument(T& target, std::string variableName, std::string desc, std::string group, bool isAdvanced = false)
{
m_nonOptionArguments.emplace_back(new Target<T>(target, std::move(variableName), std::move(desc), std::move(group), isAdvanced));
}
template <typename T>
void addOptionalNonOptionArguments(std::vector<T>& target, std::string variableName, std::string desc, std::string group, bool isAdvanced = false)
{
if (m_numOptionalNonOptionArguments != 0)
throw std::runtime_error("Can't have two non-option vector arguments");
++m_numOptionalNonOptionArguments;
m_nonOptionArguments.emplace_back(new OptionalTargets<T>(target, std::move(variableName), std::move(desc), std::move(group), isAdvanced));
}
Option& addOptionArgument(char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false)
{
auto* o = new Option(shortName, std::move(longName), std::move(description), std::move(group), isAdvanced);
m_optionArguments.emplace_back(o);
return *o;
}
Option& addIncrementalOptionArgument(unsigned int& target, char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false)
{
auto* o = new IncrementalOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced);
m_optionArguments.emplace_back(o);
return *o;
}
Option& addOptionArgument(bool& target, char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false)
{
auto* o = new ArgumentOption<bool>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced);
m_optionArguments.emplace_back(o);
return *o;
}
// Without shortName
Option& addOptionArgument(bool& target, std::string longName, std::string description, std::string group, bool isAdvanced = false)
{
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(group), isAdvanced);
}
template <typename T>
Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, bool isAdvanced = false)
{
auto* o = new ArgumentOption<T>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName));
m_optionArguments.emplace_back(o);
return *o;
}
template <typename T>
Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, bool isAdvanced = false)
{
auto* o = new LowerBoundArgumentOption<T>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName), minValue);
m_optionArguments.emplace_back(o);
return *o;
}
template <typename T>
Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, T maxValue, bool isAdvanced = false)
{
auto* o = new LowerUpperBoundArgumentOption<T>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName), minValue, maxValue);
m_optionArguments.emplace_back(o);
return *o;
}
// Without shortName
template <typename T>
Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, bool isAdvanced = false)
{
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), isAdvanced);
}
template <typename T>
Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, bool isAdvanced = false)
{
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), minValue, isAdvanced);
}
template <typename T>
Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, T maxValue, bool isAdvanced = false)
{
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), minValue, maxValue, isAdvanced);
}
void parseArgs(const std::string& args);
std::vector<ParsedOption> getUsedOptionArguments() const;
unsigned int numRequiredArguments() const { return m_nonOptionArguments.size() - m_numOptionalNonOptionArguments; }
private:
/* Modification for igraph: Disable functions that call forbidden
* functions such as exit(). */
#if 0
void exitWithUsage(bool showAdvanced) const;
void exitWithVersionInformation() const;
#endif
void exitWithError(const std::string& message) const;
#if 0
void exitWithJsonParameters() const;
#endif
std::deque<std::unique_ptr<Option>> m_optionArguments;
std::deque<std::unique_ptr<TargetBase>> m_nonOptionArguments;
std::string m_programName = "Infomap";
std::string m_shortProgramDescription;
std::string m_programVersion;
std::string m_programDescription;
std::vector<std::string> m_groups;
std::string m_executableName = "Infomap";
unsigned int m_displayHelp = 0;
bool m_displayVersion = false;
bool m_printJsonParameters = false;
unsigned int m_numOptionalNonOptionArguments = 0;
};
} // namespace infomap
#endif // PROGRAM_INTERFACE_H_
@@ -0,0 +1,87 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef SAFEFILE_H_
#define SAFEFILE_H_
#include "../utils/convert.h"
#include <iostream>
#include <fstream>
#include <ios>
#include <cstdio>
#include <stdexcept>
namespace infomap {
using std::ifstream;
using std::ofstream;
/**
* A wrapper for the C++ file stream class that automatically closes
* the file stream when the destructor is called. Allocate it on the
* stack to have it automatically closed when going out of scope.
*
* Note:
* In C++, the only code that can be guaranteed to be executed after an
* exception is thrown are the destructors of objects residing on the stack.
*
* You can exploit that fact to avoid resource leaks by tying all resources
* to the lifespan of an object allocated on the stack. This technique is
* called Resource Acquisition Is Initialization (RAII).
*
*/
class SafeInFile : public ifstream {
public:
SafeInFile(const std::string& filename, ios_base::openmode mode = ios_base::in)
: ifstream(filename, mode)
{
if (fail())
throw std::runtime_error(io::Str() << "Error opening file '" << filename << "'. Check that the path points to a file and that you have read permissions.");
}
~SafeInFile() override
{
if (is_open())
close();
}
};
class SafeOutFile : public ofstream {
public:
SafeOutFile(const std::string& filename, ios_base::openmode mode = ios_base::out)
: ofstream(filename, mode)
{
if (fail())
throw std::runtime_error(io::Str() << "Error opening file '" << filename << "'. Check that the directory you are writing to exists and that you have write permissions.");
}
~SafeOutFile() override
{
if (is_open())
close();
}
};
inline bool isDirectoryWritable(const std::string& dir)
{
std::string path = io::Str() << dir << "_1nf0m4p_.tmp";
bool ok = true;
try {
SafeOutFile out(path);
} catch (const std::runtime_error&) {
ok = false;
}
if (ok)
std::remove(path.c_str());
return ok;
}
} // namespace infomap
#endif // SAFEFILE_H_
+69
View File
@@ -0,0 +1,69 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef DATE_H_
#define DATE_H_
#include <ctime>
#include <cmath>
#include <ostream>
namespace infomap {
class ElapsedTime {
public:
ElapsedTime(double elapsedTime = 0.0) : m_elapsedTime(elapsedTime) { }
double getSeconds() const { return m_elapsedTime; }
friend std::ostream& operator<<(std::ostream& out, const ElapsedTime& elapsedTime)
{
auto temp = static_cast<unsigned int>(std::floor(elapsedTime.getSeconds()));
if (temp > 60) {
if (temp > 3600) {
if (temp > 86400) {
out << temp / 86400 << "d ";
temp %= 86400;
}
out << temp / 3600 << "h ";
temp %= 3600;
}
out << temp / 60 << "m ";
temp %= 60;
out << temp << "s";
} else {
out << temp << "s";
}
return out;
}
private:
double m_elapsedTime;
};
class Date {
public:
friend std::ostream& operator<<(std::ostream& out, const Date& date)
{
struct std::tm t = *localtime(&date.m_timeOfCreation);
return out << "" << (t.tm_year + 1900) << (t.tm_mon < 9 ? "-0" : "-") << (t.tm_mon + 1) << (t.tm_mday < 10 ? "-0" : "-") << t.tm_mday << (t.tm_hour < 10 ? " 0" : " ") << t.tm_hour << (t.tm_min < 10 ? ":0" : ":") << t.tm_min << (t.tm_sec < 10 ? ":0" : ":") << t.tm_sec << "";
}
ElapsedTime operator-(const Date& date) const
{
return { difftime(m_timeOfCreation, date.m_timeOfCreation) };
}
private:
std::time_t m_timeOfCreation = time(nullptr);
};
} // namespace infomap
#endif // DATE_H_
@@ -0,0 +1,50 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "FileURI.h"
#include "convert.h"
#include <stdexcept>
#include <utility>
using std::string;
namespace infomap {
FileURI::FileURI(string filename, bool requireExtension)
: m_filename(std::move(filename)), m_requireExtension(requireExtension)
{
auto getErrorMessage = [](const auto& name, auto requireExt) {
string s = io::Str() << "Filename '" << name << "' must match the pattern \"[dir/]name" << (requireExt ? ".extension\"" : "[.extension]\"");
return s;
};
auto name = m_filename;
auto pos = m_filename.find_last_of('/');
if (pos != string::npos) {
if (pos == m_filename.length()) // File could not end with slash
throw std::invalid_argument(getErrorMessage(m_filename, m_requireExtension));
m_directory = m_filename.substr(0, pos + 1); // Include the last slash in the directory
name = m_filename.substr(pos + 1); // No slash in the name
} else {
m_directory = "";
}
pos = name.find_last_of('.');
if (pos == string::npos || pos == 0 || pos == name.length() - 1) {
if (pos != string::npos || m_requireExtension)
throw std::invalid_argument(getErrorMessage(m_filename, m_requireExtension));
m_name = name;
m_extension = "";
} else {
m_name = name.substr(0, pos);
m_extension = name.substr(pos + 1);
}
}
} // namespace infomap
@@ -0,0 +1,54 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef FILEURI_H_
#define FILEURI_H_
#include <iostream>
#include <string>
namespace infomap {
/**
* Filename class to simplify handling of parts of a filename.
* If a path is path/to/file.ext, the member methods of this class give these parts:
* getFilename -> "path/to/file.ext"
* getName -> "file"
* getExtension -> "ext"
* Can throw std::invalid_argument on creation.
*/
class FileURI {
public:
FileURI() = default;
explicit FileURI(std::string filename, bool requireExtension = false);
const std::string& getFilename() const { return m_filename; }
const std::string& getName() const { return m_name; }
const std::string& getExtension() const { return m_extension; }
friend std::ostream& operator<<(std::ostream& out, const FileURI& file)
{
return out << file.getFilename();
}
private:
std::string m_filename;
bool m_requireExtension = false;
std::string m_directory;
std::string m_name;
std::string m_extension;
};
} // namespace infomap
#endif // FILEURI_H_
@@ -0,0 +1,898 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "FlowCalculator.h"
#include "../utils/Log.h"
#include "../utils/infomath.h"
#include "../core/StateNetwork.h"
#include <iostream>
#include <cmath>
#include <numeric>
#include <limits>
#include <algorithm>
#include <functional>
namespace infomap {
template <typename T>
inline void normalize(std::vector<T>& v, const T sum) noexcept
{
for (auto& numerator : v) {
numerator /= sum;
}
}
template <typename T>
inline void normalize(std::vector<T>& v) noexcept
{
const auto sum = std::accumulate(cbegin(v), cend(v), T {});
normalize(v, sum);
}
FlowCalculator::FlowCalculator(StateNetwork& network, const Config& config)
: numNodes(network.numNodes())
{
Log() << "Calculating global network flow using flow model '" << config.flowModel << "'... " << std::flush;
// Prepare data in sequence containers for fast access of individual elements
// Map to zero-based dense indexing
nodeFlow.assign(numNodes, 0.0);
nodeTeleportWeights.assign(numNodes, 0.0); // Fraction of teleportation flow landing on node i
nodeOutDegree.assign(numNodes, 0);
sumLinkOutWeight.assign(numNodes, 0.0);
unsigned int nodeIndex = 0;
const auto& nodeLinkMap = network.nodeLinkMap();
if (network.isBipartite()) {
// Preserve node order
for (const auto& node : network.nodes()) {
const auto nodeId = node.second.id;
nodeIndexMap[nodeId] = nodeIndex++;
}
auto bipartiteStartId = network.bipartiteStartId();
bipartiteStartIndex = nodeIndexMap[bipartiteStartId];
} else {
if (config.flowModel != FlowModel::directed) {
// Preserve node order
for (const auto& node : network.nodes()) {
const auto nodeId = node.second.id;
nodeIndexMap[nodeId] = nodeIndex++;
}
} else {
// Store dangling nodes out-of-order,
// with dangling nodes first to optimize calculation of dangling rank
for (const auto& node : network.nodes()) {
const auto isDangling = nodeLinkMap.find(node.second) == nodeLinkMap.end();
if (!isDangling) continue;
const auto& nodeId = node.second.id;
nodeIndexMap[nodeId] = nodeIndex++;
}
nonDanglingStartIndex = nodeIndex;
for (const auto& node : network.nodes()) {
const auto isDangling = nodeLinkMap.find(node.second) == nodeLinkMap.end();
if (isDangling) continue;
const auto& nodeId = node.second.id;
nodeIndexMap[nodeId] = nodeIndex++;
}
}
}
flowLinks.resize(network.numLinks(), { 0, 0, 0.0 });
sumLinkWeight = network.sumLinkWeight();
sumWeightedDegree = network.sumWeightedDegree();
if (network.isBipartite()) {
const auto bipartiteStartId = network.bipartiteStartId();
for (const auto& node : nodeLinkMap) {
const auto sourceIsFeature = node.first.id >= bipartiteStartId;
if (sourceIsFeature) continue;
bipartiteLinkStartIndex += node.second.size();
}
}
unsigned int linkIndex = 0;
unsigned int featureLinkIndex = bipartiteLinkStartIndex; // bipartite case
for (const auto& node : nodeLinkMap) {
const auto sourceId = node.first.id;
const auto sourceIndex = nodeIndexMap[sourceId];
for (const auto& link : node.second) {
const auto targetId = link.first.id;
const auto targetIndex = nodeIndexMap[targetId];
const auto linkWeight = link.second.weight;
++nodeOutDegree[sourceIndex];
sumLinkOutWeight[sourceIndex] += linkWeight;
nodeFlow[sourceIndex] += linkWeight / sumWeightedDegree;
if (network.isBipartite() && sourceId >= network.bipartiteStartId()) {
// Link from feature node to ordinary node
flowLinks[featureLinkIndex].source = sourceIndex;
flowLinks[featureLinkIndex].target = targetIndex;
flowLinks[featureLinkIndex].flow = linkWeight;
++featureLinkIndex;
} else {
// Ordinary link, or unipartite
flowLinks[linkIndex].source = sourceIndex;
flowLinks[linkIndex].target = targetIndex;
flowLinks[linkIndex].flow = linkWeight;
++linkIndex;
}
if (sourceIndex != targetIndex) {
if (config.isUndirectedFlow()) {
++nodeOutDegree[targetIndex];
sumLinkOutWeight[targetIndex] += linkWeight;
}
if (config.flowModel != FlowModel::outdirdir) {
nodeFlow[targetIndex] += linkWeight / sumWeightedDegree;
}
}
}
}
bool normalizeNodeFlow = false;
switch (config.flowModel) {
case FlowModel::undirected:
if (config.regularized) {
calcUndirectedRegularizedFlow(network, config);
} else {
calcUndirectedFlow();
}
break;
case FlowModel::directed:
if (network.isBipartite() && config.bipartiteTeleportation) {
calcDirectedBipartiteFlow(network, config);
} else {
if (config.regularized) {
calcDirectedRegularizedFlow(network, config);
} else {
calcDirectedFlow(network, config);
}
}
break;
case FlowModel::undirdir:
case FlowModel::outdirdir:
calcDirdirFlow(config);
normalizeNodeFlow = true;
break;
case FlowModel::rawdir:
calcRawdirFlow();
normalizeNodeFlow = true;
break;
case FlowModel::precomputed:
usePrecomputedFlow(network, config);
normalizeNodeFlow = true;
break;
}
finalize(network, config, normalizeNodeFlow);
}
void FlowCalculator::calcUndirectedFlow() noexcept
{
Log() << "\n -> Using undirected links.";
// Flow is outgoing transition probability times source node flow
// = w_ij / s_ij * s_ij / sum(s_ij) = w_ij / sum(s_ij)
// Count twice for non-loops to cover flow in both directions
// Assuming convention to treat self-links as directed
for (auto& link : flowLinks) {
link.flow /= sumWeightedDegree;
if (link.source != link.target) {
link.flow *= 2;
}
}
}
void FlowCalculator::calcDirdirFlow(const Config& config) noexcept
{
if (config.flowModel == FlowModel::outdirdir)
Log() << "\n -> Counting only ingoing links.";
else
Log() << "\n -> Using undirected links, switching to directed after steady state.";
// Take one last power iteration
const std::vector<double> nodeFlowSteadyState(nodeFlow);
nodeFlow.assign(numNodes, 0.0);
for (const auto& link : flowLinks) {
nodeFlow[link.target] += nodeFlowSteadyState[link.source] * link.flow / sumLinkOutWeight[link.source];
}
double sumNodeFlow = std::accumulate(cbegin(nodeFlow), cend(nodeFlow), 0.0);
// Update link data to represent flow instead of weight
for (auto& link : flowLinks) {
link.flow *= nodeFlowSteadyState[link.source] / sumLinkOutWeight[link.source] / sumNodeFlow;
}
}
void FlowCalculator::calcRawdirFlow() noexcept
{
Log() << "\n -> Using directed links with raw flow.";
Log() << "\n -> Total link weight: " << sumLinkWeight << ".";
// Treat the link weights as flow (after global normalization) and
// do one power iteration to set the node flow
nodeFlow.assign(numNodes, 0.0);
for (auto& link : flowLinks) {
link.flow /= sumLinkWeight;
nodeFlow[link.target] += link.flow;
}
}
void FlowCalculator::usePrecomputedFlow(const StateNetwork& network, const Config& config)
{
Log() << "\n -> Using directed links with precomputed flow from input data.";
Log() << "\n -> Total link flow: " << sumLinkWeight << ".";
if (network.haveFileInput()) {
if (network.haveMemoryInput() && !network.haveStateNodeWeights()) {
Log() << std::endl;
throw std::runtime_error("Missing node flow in input data. Should be passed as a third field under a *States section.");
}
if (!network.haveMemoryInput() && !network.haveNodeWeights()) {
Log() << std::endl;
throw std::runtime_error("Missing node flow in input data. Should be passed as a third field under a *Vertices section.");
}
}
// Treat the link weights as flow
nodeFlow.assign(numNodes, 0.0);
double sumFlow = 0.0;
for (const auto& nodeIt : network.nodes()) {
auto& node = nodeIt.second;
nodeFlow[nodeIndexMap[node.id]] = node.weight;
sumFlow += node.weight;
}
Log() << "\n -> Total node flow: " << sumFlow << ".";
if (infomath::isEqual(sumFlow, 0)) {
throw std::runtime_error("Missing node flow. Set it on the node weight property.");
}
if (!infomath::isEqual(sumFlow, 1)) {
if (infomath::isEqual(sumFlow, numNodes) && infomath::isEqual(nodeFlow[0], 1)) {
Log() << "\n Warning: Node flow sums to the number of nodes, is node flow provided or is default node weights used? Normalizing.";
} else {
Log() << "\n Warning: Node flow sums to " << sumFlow << ", normalizing.";
}
for (unsigned int i = 0; i < numNodes; ++i) {
nodeFlow[i] /= sumFlow;
}
}
}
struct IterationResult {
double alpha;
double beta;
};
template <typename Iteration>
IterationResult powerIterate(double alpha, Iteration&& iter)
{
unsigned int iterations = 0;
double beta = 1.0 - alpha;
double err = 0.0;
do {
double oldErr = err;
err = iter(iterations, alpha, beta);
// Perturb the system if equilibrium
if (std::abs(err - oldErr) < 1e-17) {
alpha += 1.0e-12;
beta = 1.0 - alpha;
}
++iterations;
} while (iterations < 200 && (err > 1.0e-15 || iterations < 50));
Log() << "\n -> PageRank calculation done in " << iterations << " iterations.\n";
return { alpha, beta };
}
void FlowCalculator::calcDirectedFlow(const StateNetwork& network, const Config& config) noexcept
{
Log() << "\n -> Using " << (config.recordedTeleportation ? "recorded" : "unrecorded") << " teleportation to " << (config.teleportToNodes ? "nodes" : "links") << ". " << std::flush;
// Calculate the teleport rate distribution
if (config.teleportToNodes) {
double sumNodeWeights = 0.0;
for (const auto& nodeIt : network.nodes()) {
auto& node = nodeIt.second;
nodeTeleportWeights[nodeIndexMap[node.id]] = node.weight;
sumNodeWeights += node.weight;
}
normalize(nodeTeleportWeights, sumNodeWeights);
} else {
// Teleport to links
// Teleport proportionally to out-degree, or in-degree if recorded teleportation.
for (const auto& link : flowLinks) {
auto toNode = config.recordedTeleportation ? link.target : link.source;
nodeTeleportWeights[toNode] += link.flow / sumLinkWeight;
}
}
// Normalize link weights with respect to its source nodes total out-link weight;
for (auto& link : flowLinks) {
if (sumLinkOutWeight[link.source] > 0) {
link.flow /= sumLinkOutWeight[link.source];
}
}
std::vector<double> nodeFlowTmp(numNodes, 0.0);
double danglingRank;
// Calculate PageRank
const auto iteration = [&](const auto iter, const double alpha, const double beta) {
danglingRank = std::accumulate(cbegin(nodeFlow), cbegin(nodeFlow) + nonDanglingStartIndex, 0.0);
// Flow from teleportation
const auto teleportationFlow = alpha + beta * danglingRank;
for (unsigned int i = 0; i < numNodes; ++i) {
nodeFlowTmp[i] = teleportationFlow * nodeTeleportWeights[i];
}
// Flow from links
for (const auto& link : flowLinks) {
nodeFlowTmp[link.target] += beta * link.flow * nodeFlow[link.source];
}
// Update node flow from the power iteration above and check if converged
double nodeFlowDiff = -1.0; // Start with -1.0 so we don't have to subtract it later
double error = 0.0;
for (unsigned int i = 0; i < numNodes; ++i) {
nodeFlowDiff += nodeFlowTmp[i];
error += std::abs(nodeFlowTmp[i] - nodeFlow[i]);
}
nodeFlow = nodeFlowTmp;
// Normalize if needed
if (std::abs(nodeFlowDiff) > 1.0e-10) {
Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") ";
normalize(nodeFlow, nodeFlowDiff + 1.0);
}
return error;
};
const auto result = powerIterate(config.teleportationProbability, iteration);
double sumNodeRank = 1.0;
double beta = result.beta;
if (!config.recordedTeleportation) {
// Take one last power iteration excluding the teleportation
// and normalize node flow
sumNodeRank = 1.0 - danglingRank;
nodeFlow.assign(numNodes, 0.0);
for (const auto& link : flowLinks) {
nodeFlow[link.target] += link.flow * nodeFlowTmp[link.source] / sumNodeRank;
}
beta = 1.0;
}
// Update the links with their global flow from the PageRank values.
// Note: beta is set to 1 if unrecorded teleportation
for (auto& link : flowLinks) {
link.flow *= beta * nodeFlowTmp[link.source] / sumNodeRank;
}
}
void FlowCalculator::calcDirectedRegularizedFlow(const StateNetwork& network, const Config& config) noexcept
{
Log() << "\n -> Using recorded teleportation to nodes according to a fully connected Bayesian prior. " << std::flush;
// Calculate node weights w_i = s_i/k_i, where s_i is the node strength (weighted degree) and k_i the (unweighted) degree
unsigned int N = network.numNodes();
std::vector<unsigned int> k_out(N, 0);
std::vector<unsigned int> k_in(N, 0);
std::vector<double> s_out(N, 0);
std::vector<double> s_in(N, 0);
double sum_s = sumWeightedDegree;
unsigned int sum_k = network.sumDegree();
double average_weight = sum_s / sum_k;
for (auto& link : flowLinks) {
k_out[link.source] += 1;
s_out[link.source] += link.flow;
k_in[link.target] += 1;
s_in[link.target] += link.flow;
}
double min_u_out = std::numeric_limits<double>::max();
double min_u_in = std::numeric_limits<double>::max();
for (unsigned int i = 0; i < N; ++i) {
if (k_out[i] > 0) {
min_u_out = std::min(min_u_out, s_out[i] / k_out[i]);
}
if (k_in[i] > 0) {
min_u_in = std::min(min_u_in, s_in[i] / k_in[i]);
}
}
auto u_out = [s_out, k_out, min_u_out](auto i) { return k_out[i] == 0 ? min_u_out : s_out[i] / k_out[i]; };
auto u_in = [s_in, k_in, min_u_in](auto i) { return k_in[i] == 0 ? min_u_in : s_in[i] / k_in[i]; };
unsigned int numNodesAsTeleportationTargets = config.noSelfLinks ? N - 1 : N;
double lambda = config.regularizationStrength * std::log(N) / numNodesAsTeleportationTargets;
double u_t = average_weight;
double sum_u_in = 0.0;
for (unsigned int i = 0; i < N; ++i) {
sum_u_in += u_in(i);
}
for (unsigned int i = 0; i < N; ++i) {
nodeTeleportWeights[i] = u_in(i) / sum_u_in;
}
std::function<double(unsigned int)> t_out_withoutSelfLinks = [lambda, u_t, u_out, u_in, sum_u_in](unsigned int i) { return lambda / u_t * u_out(i) * (sum_u_in - u_in(i)); };
std::function<double(unsigned int)> t_out_withSelfLinks = [lambda, u_t, u_out, sum_u_in](unsigned int i) { return lambda / u_t * u_out(i) * sum_u_in; };
auto t_out = config.noSelfLinks ? t_out_withoutSelfLinks : t_out_withSelfLinks;
std::vector<double> alpha(N, 0);
for (unsigned int i = 0; i < N; ++i) {
auto t_i = t_out(i);
alpha[i] = t_i / (s_out[i] + t_i); // = 1 for dangling nodes
if (config.noSelfLinks) {
// Inflate to adjust for no self-teleportation
// TODO: Check possible side-effects
alpha[i] /= 1 - nodeTeleportWeights[i];
}
}
// Normalize link weights with respect to its source nodes total out-link weight;
for (auto& link : flowLinks) {
if (sumLinkOutWeight[link.source] > 0) {
link.flow /= sumLinkOutWeight[link.source];
}
}
std::vector<double> nodeFlowTmp(numNodes, 0.0);
// Calculate PageRank
const auto iteration = [&](const auto iter) {
double teleTmp = 0.0;
for (unsigned int i = 0; i < N; ++i) {
teleTmp += alpha[i] * nodeFlow[i];
}
for (unsigned int i = 0; i < N; ++i) {
nodeFlowTmp[i] = nodeTeleportWeights[i] * (config.noSelfLinks ? (teleTmp - alpha[i] * nodeFlow[i]) : teleTmp);
}
// Flow from links
for (const auto& link : flowLinks) {
double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1);
nodeFlowTmp[link.target] += beta * link.flow * nodeFlow[link.source];
}
// Update node flow from the power iteration above and check if converged
double nodeFlowDiff = -1.0; // Start with -1.0 so we don't have to subtract it later
double error = 0.0;
for (unsigned int i = 0; i < numNodes; ++i) {
nodeFlowDiff += nodeFlowTmp[i];
error += std::abs(nodeFlowTmp[i] - nodeFlow[i]);
}
nodeFlow = nodeFlowTmp;
// Normalize if needed
if (std::abs(nodeFlowDiff) > 1.0e-10) {
Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") ";
normalize(nodeFlow, nodeFlowDiff + 1.0);
}
return error;
};
unsigned int iterations = 0;
double err = 0.0;
do {
double oldErr = err;
err = iteration(iterations);
// Perturb the system if equilibrium
if (std::abs(err - oldErr) < 1e-15) {
}
++iterations;
} while (iterations < 200 && (err > 1.0e-15 || iterations < 50));
Log() << "\n -> PageRank calculation done in " << iterations << " iterations.\n";
double sumNodeRank = 1.0;
for (auto& link : flowLinks) {
double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1);
link.flow *= beta * nodeFlow[link.source] / sumNodeRank;
}
nodeTeleportFlow.assign(numNodes, 0.0);
for (unsigned int i = 0; i < N; ++i) {
nodeTeleportFlow[i] = nodeFlow[i] * alpha[i];
}
}
void FlowCalculator::calcUndirectedRegularizedFlow(const StateNetwork& network, const Config& config) noexcept
{
Log() << "\n -> Using recorded teleportation to nodes according to a fully connected Bayesian prior. " << std::flush;
// Calculate node weights w_i = s_i/k_i, where s_i is the node strength (weighted degree) and k_i the (unweighted) degree
unsigned int N = network.numNodes();
std::vector<unsigned int> k(N, 0);
std::vector<double> s(N, 0);
double sum_s = sumWeightedDegree;
unsigned int sum_k = network.sumDegree();
double average_weight = sum_s / sum_k;
for (auto& link : flowLinks) {
k[link.source] += 1;
s[link.source] += link.flow;
if (link.source != link.target) {
k[link.target] += 1;
s[link.target] += link.flow;
}
}
double min_u = std::numeric_limits<double>::max();
for (unsigned int i = 0; i < N; ++i) {
if (k[i] > 0) {
min_u = std::min(min_u, s[i] / k[i]);
}
}
auto u = [s, k, min_u](auto i) { return k[i] == 0 ? min_u : s[i] / k[i]; };
unsigned int numNodesAsTeleportationTargets = config.noSelfLinks ? N - 1 : N;
double lambda = config.regularizationStrength * std::log(N) / numNodesAsTeleportationTargets;
double u_t = average_weight;
double sum_u = 0.0;
for (unsigned int i = 0; i < N; ++i) {
sum_u += u(i);
}
// nodeTeleportWeights is the fraction of teleportation flow landing on each node. This is proportional to u_in
for (unsigned int i = 0; i < N; ++i) {
nodeTeleportWeights[i] = u(i) / sum_u;
}
std::function<double(unsigned int)> t_withoutSelfLinks = [lambda, u_t, u, sum_u](unsigned int i) { return lambda / u_t * u(i) * (sum_u - u(i)); };
std::function<double(unsigned int)> t_withSelfLinks = [lambda, u_t, u, sum_u](unsigned int i) { return lambda / u_t * u(i) * sum_u; };
auto t = config.noSelfLinks ? t_withoutSelfLinks : t_withSelfLinks;
std::vector<double> alpha(N, 0);
double sum_t = 0.0;
for (unsigned int i = 0; i < N; ++i) {
auto t_i = t(i);
alpha[i] = t_i / (s[i] + t_i);
if (config.noSelfLinks) {
// Inflate to adjust for no self-teleportation
// TODO: No later side effects of cheating here? Need to normalize targets instead?
alpha[i] /= 1 - nodeTeleportWeights[i];
}
sum_t += t_i;
}
for (auto& link : flowLinks) {
if (sumLinkOutWeight[link.source] > 0) {
link.flow /= sumLinkOutWeight[link.source];
}
}
for (unsigned int i = 0; i < N; ++i) {
nodeFlow[i] = (s[i] + t(i)) / (sum_s + sum_t);
}
nodeTeleportFlow.assign(numNodes, 0.0);
for (unsigned int i = 0; i < N; ++i) {
nodeTeleportFlow[i] = nodeFlow[i] * alpha[i];
}
for (auto& link : flowLinks) {
// TODO: Side effect from inflating alpha, need real alpha here.
double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1);
link.flow *= beta * nodeFlow[link.source] * 2;
}
}
void FlowCalculator::calcDirectedBipartiteFlow(const StateNetwork& network, const Config& config) noexcept
{
Log() << "\n -> Using bipartite " << (config.recordedTeleportation ? "recorded" : "unrecorded") << " teleportation to " << (config.teleportToNodes ? "nodes" : "links") << ". " << std::flush;
const auto bipartiteStartId = network.bipartiteStartId();
if (config.teleportToNodes) {
for (const auto& nodeIt : network.nodes()) {
auto& node = nodeIt.second;
if (node.id < bipartiteStartId) {
nodeTeleportWeights[nodeIndexMap[node.id]] = node.weight;
}
}
} else {
// Teleport proportionally to out-degree, or in-degree if recorded teleportation.
// Two-step degree: sum of products between incoming and outgoing links from bipartite nodes
if (config.recordedTeleportation) {
for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) {
// target is an ordinary node
nodeTeleportWeights[link->target] += link->flow;
}
} else {
// Unrecorded teleportation
for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) {
// source is an ordinary node
nodeTeleportWeights[link->source] += link->flow;
}
}
}
normalize(nodeTeleportWeights);
nodeFlow = nodeTeleportWeights;
// Normalize link weights with respect to its source nodes total out-link weight;
for (auto& link : flowLinks) {
if (sumLinkOutWeight[link.source] > 0) {
link.flow /= sumLinkOutWeight[link.source];
}
}
std::vector<unsigned int> danglingIndices;
for (size_t i = 0; i < numNodes; ++i) {
if (nodeOutDegree[i] == 0) {
danglingIndices.push_back(i);
}
}
std::vector<double> nodeFlowTmp(numNodes, 0.0);
double danglingRank;
// Calculate two-step PageRank
const auto iteration = [&](const auto iter, const double alpha, const double beta) {
danglingRank = 0.0;
for (const auto& i : danglingIndices) {
danglingRank += nodeFlow[i];
}
// Flow from teleportation
const auto teleportationFlow = alpha + beta * danglingRank;
for (unsigned int i = 0; i < bipartiteStartIndex; ++i) {
nodeFlowTmp[i] = teleportationFlow * nodeTeleportWeights[i];
}
for (unsigned int i = bipartiteStartIndex; i < numNodes; ++i) {
nodeFlowTmp[i] = 0.0;
}
// Flow from links
// First step
for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) {
nodeFlow[link->target] += beta * link->flow * nodeFlow[link->source];
}
// Second step back to primary nodes
for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) {
nodeFlowTmp[link->target] += link->flow * nodeFlow[link->source];
}
// Update node flow from the power iteration above and check if converged
double nodeFlowDiff = -1.0;
double error = 0.0;
for (unsigned int i = 0; i < bipartiteStartIndex; ++i) {
nodeFlowDiff += nodeFlowTmp[i];
error += std::abs(nodeFlowTmp[i] - nodeFlow[i]);
}
nodeFlow = nodeFlowTmp;
// Normalize if needed
if (std::abs(nodeFlowDiff) > 1.0e-10) {
Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") ";
normalize(nodeFlow, nodeFlowDiff + 1.0);
}
return error;
};
const auto result = powerIterate(config.teleportationProbability, iteration);
double sumNodeRank = 1.0;
double beta = result.beta;
if (!config.recordedTeleportation) {
// Take one last power iteration excluding the teleportation (and normalize node flow to sum 1.0)
sumNodeRank = 1.0 - danglingRank;
nodeFlow.assign(numNodes, 0.0);
for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) {
nodeFlowTmp[link->target] += link->flow * nodeFlowTmp[link->source];
}
// Second step back to primary nodes
for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) {
nodeFlow[link->target] += link->flow * nodeFlowTmp[link->source];
}
beta = 1.0;
}
// Update the links with their global flow from the PageRank values.
// Note: beta is set to 1 if unrecorded teleportation
for (auto& link : flowLinks) {
link.flow *= beta * nodeFlowTmp[link.source] / sumNodeRank;
}
}
void FlowCalculator::finalize(StateNetwork& network, const Config& config, bool normalizeNodeFlow) noexcept
{
unsigned int N = network.numNodes();
// TODO: Skip bipartite flow adjustment for directed / rawdir / .. ?
if (network.isBipartite()) {
Log() << "\n -> Using bipartite links.";
if (!config.skipAdjustBipartiteFlow && !config.bipartiteTeleportation) {
// Only links between ordinary nodes and feature nodes in bipartite network
// Don't code feature nodes -> distribute all flow from those to ordinary nodes
for (auto& link : flowLinks) {
auto sourceIsFeature = link.source >= bipartiteStartIndex;
if (sourceIsFeature) {
nodeFlow[link.target] += link.flow;
nodeFlow[link.source] = 0.0; // Doesn't matter if done multiple times on each node.
} else {
nodeFlow[link.source] += link.flow;
nodeFlow[link.target] = 0.0; // Doesn't matter if done multiple times on each node.
}
// TODO: Should flow double before moving to nodes, does it cancel out in normalization?
// Markov time 2 on the full network will correspond to markov time 1 between the real nodes.
link.flow *= 2;
}
// TODO: Should flow double before moving to nodes, does it cancel out in normalization?
normalizeNodeFlow = true;
} else if (config.bipartiteTeleportation) {
for (auto& link : flowLinks) {
// Markov time 2 on the full network will correspond to markov time 1 between the real nodes.
link.flow *= 2;
}
}
}
if (config.useNodeWeightsAsFlow) {
Log() << "\n -> Using node weights as flow.";
for (auto& nodeIt : network.nodes()) {
auto& node = nodeIt.second;
nodeFlow[nodeIndexMap[node.id]] = node.weight;
}
normalizeNodeFlow = true;
}
if (normalizeNodeFlow) {
normalize(nodeFlow);
}
// Write back flow to network
double sumNodeFlow = 0.0;
double sumLinkFlow = 0.0;
unsigned int linkIndex = 0;
auto featureLinkIndex = bipartiteLinkStartIndex;
for (auto& node : network.m_nodeLinkMap) {
for (auto& link : node.second) {
auto& linkData = link.second;
if (network.isBipartite() && node.first.id >= network.bipartiteStartId()) {
linkData.flow = flowLinks[featureLinkIndex++].flow;
} else {
linkData.flow = flowLinks[linkIndex++].flow;
}
sumLinkFlow += linkData.flow;
}
}
for (auto& nodeIt : network.m_nodes) {
auto& node = nodeIt.second;
const auto nodeIndex = nodeIndexMap[node.id];
node.flow = nodeFlow[nodeIndex];
node.weight = nodeTeleportWeights[nodeIndex];
node.teleFlow = !nodeTeleportFlow.empty() ? nodeTeleportFlow[nodeIndex] : nodeFlow[nodeIndex] * (nodeOutDegree[nodeIndex] == 0 ? 1 : config.teleportationProbability);
node.enterFlow = node.flow;
node.exitFlow = node.flow;
if (!config.noSelfLinks) {
// Remove self-teleportation flow
node.enterFlow -= node.teleFlow * node.weight;
node.exitFlow -= node.teleFlow * node.weight;
// Remove self-link flow
unsigned int norm = config.isUndirectedFlow() ? 2 : 1;
auto& outLinks = network.m_nodeLinkMap[node.id];
for (auto& link : outLinks) {
auto& linkData = link.second;
if (node.id == link.first.id) {
node.enterFlow -= linkData.flow / norm;
node.exitFlow -= linkData.flow / norm;
break;
}
}
}
sumNodeFlow += node.flow;
}
// Enter/exit flow
if (!config.isUndirectedClustering() && !config.regularized) {
enterFlow.assign(N, 0);
exitFlow.assign(N, 0);
double alpha = config.teleportationProbability;
double sumDanglingFlow = 0.0;
for (unsigned int i = 0; i < N; ++i) {
if (nodeOutDegree[i] == 0) {
sumDanglingFlow += nodeFlow[i];
}
}
for (auto& nodeIt : network.m_nodes) {
auto& node = nodeIt.second;
const auto sourceIndex = nodeIndexMap[node.id];
auto& outLinks = network.m_nodeLinkMap[node.id];
double danglingFlow = outLinks.empty() ? node.flow : 0.0;
if (config.recordedTeleportation) {
// Don't let self-teleportation add to the enter/exit flow (i.e. multiply with (1.0 - node.data.teleportWeight))
exitFlow[sourceIndex] += alpha * node.flow * (1.0 - node.weight);
enterFlow[sourceIndex] += (alpha * (1.0 - node.flow) + (1 - alpha) * (sumDanglingFlow - danglingFlow)) * node.weight;
}
for (auto& link : outLinks) {
auto& linkData = link.second;
const auto targetIndex = nodeIndexMap[link.first.id];
exitFlow[sourceIndex] += linkData.flow;
enterFlow[targetIndex] += linkData.flow;
}
}
for (auto& nodeIt : network.m_nodes) {
auto& node = nodeIt.second;
const auto nodeIndex = nodeIndexMap[node.id];
node.enterFlow = enterFlow[nodeIndex];
node.exitFlow = exitFlow[nodeIndex];
}
}
Log() << "\n => Sum node flow: " << sumNodeFlow << ", sum link flow: " << sumLinkFlow << "\n";
}
} // namespace infomap
@@ -0,0 +1,75 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef FLOW_CALCULATOR_H_
#define FLOW_CALCULATOR_H_
#include <map>
#include <vector>
namespace infomap {
struct Config;
class StateNetwork;
namespace detail {
struct FlowLink {
unsigned int source;
unsigned int target;
double flow;
};
} // namespace detail
/**
* Calculate flow on network based on different flow models
*/
class FlowCalculator {
public:
FlowCalculator(StateNetwork&, const Config&);
private:
void calcUndirectedFlow() noexcept;
void calcDirectedFlow(const StateNetwork&, const Config&) noexcept;
void calcUndirectedRegularizedFlow(const StateNetwork&, const Config&) noexcept;
void calcDirectedRegularizedFlow(const StateNetwork&, const Config&) noexcept;
void calcDirectedBipartiteFlow(const StateNetwork&, const Config&) noexcept;
void calcDirdirFlow(const Config&) noexcept;
void calcRawdirFlow() noexcept;
void usePrecomputedFlow(const StateNetwork&, const Config&);
void finalize(StateNetwork&, const Config&, bool) noexcept;
unsigned int numNodes;
unsigned int nonDanglingStartIndex = 0;
unsigned int bipartiteStartIndex = 0;
unsigned int bipartiteLinkStartIndex = 0;
double sumLinkWeight = 0;
double sumWeightedDegree = 0;
std::map<unsigned int, unsigned int> nodeIndexMap;
std::vector<double> nodeFlow;
std::vector<double> nodeTeleportWeights;
std::vector<double> nodeTeleportFlow;
std::vector<double> enterFlow;
std::vector<double> exitFlow;
std::vector<double> sumLinkOutWeight;
std::vector<unsigned int> nodeOutDegree;
using FlowLink = detail::FlowLink;
std::vector<FlowLink> flowLinks;
};
inline void calculateFlow(StateNetwork& network, const Config& config)
{
FlowCalculator(network, config);
}
} // namespace infomap
#endif // FLOW_CALCULATOR_H_
@@ -0,0 +1,17 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#include "Log.h"
namespace infomap {
unsigned int Log::s_verboseLevel = 0;
bool Log::s_silent = false;
} // namespace infomap
+110
View File
@@ -0,0 +1,110 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef LOG_H_
#define LOG_H_
#include <iostream>
#include <limits>
#include <iomanip>
#include <type_traits>
namespace infomap {
struct hideIf;
class Log {
using ostreamFuncPtr = std::add_pointer_t<std::ostream&(std::ostream&)>;
public:
/**
* Log when level is below or equal Log::verboseLevel()
* and maxLevel is above or equal Log::verboseLevel()
*/
explicit Log(unsigned int level = 0, unsigned int maxLevel = std::numeric_limits<int>::max())
: m_level(level), m_maxLevel(maxLevel), m_visible(isVisible(m_level, m_maxLevel)) { }
bool isVisible() const { return isVisible(m_level, m_maxLevel); }
void hide(bool value) { m_visible = !value && isVisible(); }
Log& operator<<(const hideIf&) { return *this; }
template <typename T>
Log& operator<<(const T& data)
{
#if 0
if (m_visible)
m_ostream << data;
#endif
return *this;
}
Log& operator<<(ostreamFuncPtr f)
{
#if 0
if (m_visible)
m_ostream << f;
#endif
return *this;
}
static void init(unsigned int verboseLevel, bool silent, unsigned int numberPrecision)
{
setVerboseLevel(verboseLevel);
setSilent(silent);
Log() << std::setprecision(static_cast<int>(numberPrecision));
}
static bool isVisible(unsigned int level, unsigned int maxLevel)
{
return !s_silent && s_verboseLevel >= level && s_verboseLevel <= maxLevel;
}
static void setVerboseLevel(unsigned int level) { s_verboseLevel = level; }
static void setSilent(bool silent) { s_silent = silent; }
static bool isSilent() { return s_silent; }
/* precision() is patched in igraph to avoid references to std::cout */
static std::streamsize precision() { return 6; }
static std::streamsize precision(std::streamsize)
{
return precision();
}
private:
unsigned int m_level;
unsigned int m_maxLevel;
bool m_visible;
#if 0
std::ostream& m_ostream = std::cout;
#endif
static unsigned int s_verboseLevel;
static bool s_silent;
};
struct hideIf {
explicit hideIf(bool value) : hide(value) { }
friend Log& operator<<(Log& out, const hideIf& manip)
{
out.hide(manip.hide);
return out;
}
bool hide;
};
} // namespace infomap
#endif // LOG_H_
@@ -0,0 +1,153 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef META_COLLECTION_H_
#define META_COLLECTION_H_
#include "infomath.h"
#include <ostream>
#include <map>
namespace infomap {
struct FlowCount {
FlowCount() = default;
explicit FlowCount(double flow)
: flow(flow), count(1) { }
FlowCount& operator+=(const FlowCount& o)
{
flow += o.flow;
count += o.count;
return *this;
}
FlowCount& operator+=(double f)
{
flow += f;
++count;
return *this;
}
FlowCount& operator-=(const FlowCount& o)
{
flow -= o.flow;
count -= o.count;
return *this;
}
FlowCount& operator-=(double f)
{
flow -= f;
--count;
return *this;
}
friend std::ostream& operator<<(std::ostream& out, const FlowCount& o)
{
return out << o.flow << "/" << o.count;
}
void reset()
{
flow = 0.0;
count = 0;
}
bool empty() const { return count == 0; }
double flow = 0.0;
unsigned int count = 0;
};
using MetaToFlowCount = std::map<unsigned int, FlowCount>; // metaId -> (flow,count)
class MetaCollection {
protected:
FlowCount m_total;
MetaToFlowCount m_metaToFlowCount;
public:
unsigned int size() const { return m_metaToFlowCount.size(); }
bool empty() const { return m_metaToFlowCount.empty(); }
MetaToFlowCount::iterator begin() { return m_metaToFlowCount.begin(); }
MetaToFlowCount::iterator end() { return m_metaToFlowCount.end(); }
MetaToFlowCount::const_iterator begin() const { return m_metaToFlowCount.begin(); }
MetaToFlowCount::const_iterator end() const { return m_metaToFlowCount.end(); }
void add(unsigned int meta, double flow = 1.0)
{
m_total += flow;
m_metaToFlowCount[meta] += flow;
}
void add(unsigned int meta, const FlowCount& flow)
{
m_total += flow;
m_metaToFlowCount[meta] += flow;
}
void add(const MetaCollection& other)
{
for (auto& it : other) {
auto metaId = it.first;
auto& flowCount = it.second;
add(metaId, flowCount);
}
}
void remove(unsigned int meta, const FlowCount& flow)
{
m_total -= flow;
auto& metaFlowCount = m_metaToFlowCount[meta];
metaFlowCount -= flow;
if (metaFlowCount.empty())
m_metaToFlowCount.erase(meta);
}
void remove(const MetaCollection& other)
{
for (auto& it : other) {
auto metaId = it.first;
auto& flowCount = it.second;
remove(metaId, flowCount);
}
}
double calculateEntropy()
{
double metaCodelength = 0.0;
for (auto& it : m_metaToFlowCount) {
metaCodelength -= infomath::plogp(it.second.flow / m_total.flow);
}
return m_total.flow * metaCodelength;
}
void clear()
{
m_total.reset();
m_metaToFlowCount.clear();
}
friend std::ostream& operator<<(std::ostream& out, const MetaCollection& m)
{
out << "<( " << m.m_total << ": ";
for (auto& it : m.m_metaToFlowCount) {
out << "(" << it.first << "," << it.second << ") ";
}
out << ")>";
return out;
}
};
} // namespace infomap
#endif // META_COLLECTION_H_
@@ -0,0 +1,45 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef RANDOM_H_
#define RANDOM_H_
#include <igraph_random.h>
#include <vector>
#include <utility>
namespace infomap {
class Random {
igraph_rng_t* m_randGen;
public:
Random() : m_randGen(igraph_rng_default()) { }
unsigned int randInt(unsigned int min, unsigned int max)
{
return igraph_rng_get_integer(m_randGen, min, max);
}
/**
* Get a random permutation of indices of the size of the input vector
*/
void getRandomizedIndexVector(std::vector<unsigned int>& randomOrder)
{
unsigned int size = randomOrder.size();
for (unsigned int i = 0; i < size; ++i)
randomOrder[i] = i;
for (unsigned int i = 0; i < size; ++i)
std::swap(randomOrder[i], randomOrder[i + randInt(0, size - i - 1)]);
}
};
} // namespace infomap
#endif // RANDOM_H_
@@ -0,0 +1,103 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef STOPWATCH_H_
#define STOPWATCH_H_
#include <chrono>
#include <ratio>
#include <ostream>
#include <cmath>
namespace infomap {
class Stopwatch {
public:
using Clock = std::chrono::high_resolution_clock;
using TimeType = std::chrono::time_point<Clock>;
explicit Stopwatch(bool startImmediately)
: m_start(now()), m_stop(now()), m_running(false)
{
if (startImmediately) {
start();
}
}
void start()
{
m_start = Clock::now();
m_running = true;
}
void reset()
{
if (m_running)
m_start = Clock::now();
}
void stop()
{
if (m_running) {
m_stop = Clock::now();
m_running = false;
}
}
TimeType getCurrentTimePoint() const
{
return m_running ? Clock::now() : m_stop;
}
double getElapsedTimeInSec() const
{
std::chrono::duration<double> diff = getCurrentTimePoint() - m_start;
return diff.count();
}
double getElapsedTimeInMilliSec() const
{
std::chrono::duration<double, std::milli> diff = getCurrentTimePoint() - m_start;
return diff.count();
}
static TimeType now()
{
return Clock::now();
}
friend std::ostream& operator<<(std::ostream& out, const Stopwatch& stopwatch)
{
auto temp = static_cast<unsigned int>(std::floor(stopwatch.getElapsedTimeInMilliSec()));
if (temp > 60'000) {
if (temp > 3600'000) {
if (temp > 86'400'000) {
out << temp / 86'400'000 << "d ";
temp %= 86'400'000;
}
out << temp / 3600'000 << "h ";
temp %= 3600'000;
}
out << temp / 60'000 << "m ";
temp %= 60'000;
out << temp * 1.0 / 1000 << "s";
} else {
out << stopwatch.getElapsedTimeInSec() << "s";
}
return out;
}
private:
TimeType m_start, m_stop;
bool m_running;
};
} // namespace infomap
#endif // STOPWATCH_H_
@@ -0,0 +1,82 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef VECTOR_MAP_H_
#define VECTOR_MAP_H_
#include <vector>
#include <limits>
#include <map>
namespace infomap {
template <typename T>
class VectorMap {
public:
VectorMap(unsigned int capacity = 0)
: m_capacity(capacity),
m_values(capacity),
m_redirect(capacity, 0),
m_maxOffset(std::numeric_limits<unsigned int>::max() - 1 - capacity) { }
void startRound()
{
if (m_size > 0) {
m_offset += m_capacity;
m_size = 0;
}
if (m_offset > m_maxOffset) {
m_redirect.assign(m_capacity, 0);
m_offset = 1;
}
}
void add(unsigned int index, T value)
{
if (isSet(index)) {
m_values[m_redirect[index] - m_offset] += value;
} else {
m_redirect[index] = m_offset + m_size;
m_values[m_size] = value;
++m_size;
}
}
bool isSet(unsigned int index)
{
return m_redirect[index] >= m_offset;
}
unsigned int size()
{
return m_size;
}
T& operator[](unsigned int index)
{
return m_values[m_redirect[index] - m_offset];
}
std::vector<T>& values()
{
return m_values;
}
private:
unsigned int m_capacity = 0;
std::vector<T> m_values;
std::vector<unsigned int> m_redirect;
unsigned int m_maxOffset = std::numeric_limits<unsigned int>::max() - 1;
unsigned int m_offset = 1;
unsigned int m_size = 0;
};
} // namespace infomap
#endif // VECTOR_MAP_H_
@@ -0,0 +1,216 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef CONVERT_H_
#define CONVERT_H_
#include <stdexcept>
#include <iomanip>
#include <sstream>
#include <string>
#include <locale> // std::locale, std::tolower
#include <iostream>
#include <vector>
namespace infomap {
template <typename T>
struct TypeInfo {
static bool isNumeric() { return false; }
};
template <>
struct TypeInfo<bool> {
static bool isNumeric() { return false; }
};
template <>
struct TypeInfo<int> {
static bool isNumeric() { return true; }
};
template <>
struct TypeInfo<unsigned int> {
static bool isNumeric() { return true; }
};
template <>
struct TypeInfo<double> {
static bool isNumeric() { return true; }
};
namespace io {
inline std::string tolower(std::string str)
{
std::locale loc;
for (char& c : str)
c = std::tolower(c, loc);
return str;
}
template <typename T>
inline std::string stringify(T& x)
{
std::ostringstream o;
if (!(o << x))
throw std::runtime_error((o << "stringify(" << x << ")", o.str()));
return o.str();
}
template <>
inline std::string stringify(bool& x)
{
return x ? "true" : "false";
}
template <typename Container>
inline std::string stringify(const Container& cont, const std::string& delimiter)
{
std::ostringstream o;
if (cont.empty())
return "";
unsigned int maxIndex = cont.size() - 1;
for (unsigned int i = 0; i < maxIndex; ++i) {
if (!(o << cont[i]))
throw std::runtime_error((o << "stringify(container[" << i << "])", o.str()));
o << delimiter;
}
if (!(o << cont[maxIndex]))
throw std::runtime_error((o << "stringify(container[" << maxIndex << "])", o.str()));
return o.str();
}
struct InsensitiveCompare {
bool operator()(const std::string& a, const std::string& b) const
{
auto lhs = a.begin();
auto rhs = b.begin();
std::locale loc;
for (; lhs != a.end() && rhs != b.end(); ++lhs, ++rhs) {
auto lhs_val = std::tolower(*lhs, loc);
auto rhs_val = std::tolower(*rhs, loc);
if (lhs_val != rhs_val)
return lhs_val < rhs_val;
}
return (rhs != b.end());
}
};
inline std::vector<std::string>& split(const std::string& s, char delim, std::vector<std::string>& items)
{
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
if (item.length() > 0) {
items.push_back(item);
}
}
return items;
}
inline std::vector<std::string> split(const std::string& s, char delim)
{
std::vector<std::string> items;
split(s, delim, items);
return items;
}
class Str {
public:
Str() = default;
template <class T>
Str& operator<<(const T& t)
{
m_oss << stringify(t);
return *this;
}
Str& operator<<(std::ostream& (*f)(std::ostream&))
{
m_oss << f;
return *this;
}
operator std::string() const
{
return m_oss.str();
}
private:
std::ostringstream m_oss;
};
template <typename T>
inline bool stringToValue(std::string const& str, T& value)
{
std::istringstream istream(str);
return !!(istream >> value);
}
template <>
inline bool stringToValue(std::string const& str, unsigned int& value)
{
std::istringstream istream(str);
int target = 0;
istream >> target;
if (target < 0) return false;
value = target;
return true;
}
template <>
inline bool stringToValue(std::string const& str, unsigned long& value)
{
std::istringstream istream(str);
int target = 0;
istream >> target;
if (target < 0) return false;
value = target;
return true;
}
inline std::string firstWord(const std::string& line)
{
std::istringstream ss;
std::string buf;
ss.str(line);
ss >> buf;
return buf;
}
template <typename T>
inline std::string padValue(T value, const std::string::size_type size, bool rightAligned = true, const char paddingChar = ' ')
{
std::string valStr = stringify(value);
if (size == valStr.size())
return valStr;
if (size < valStr.size())
return valStr.substr(0, size);
if (!rightAligned)
return valStr.append(size - valStr.size(), paddingChar);
return std::string(size - valStr.size(), paddingChar).append(valStr);
}
inline std::string toPrecision(double value, unsigned int precision = 10, bool fixed = false)
{
std::ostringstream o;
if (fixed)
o << std::fixed << std::setprecision(static_cast<int>(precision));
else
o << std::setprecision(static_cast<int>(precision));
if (!(o << value))
throw std::runtime_error((o << "stringify(" << value << ")", o.str()));
return o.str();
}
} // namespace io
} // namespace infomap
#endif // CONVERT_H_
@@ -0,0 +1,57 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef INFOMATH_H_
#define INFOMATH_H_
#include <cmath>
#include <cstdlib>
namespace infomap {
namespace infomath {
using std::log2;
inline double plogp(double p)
{
return p > 0.0 ? p * log2(p) : 0.0;
}
inline double isEqual(double a, double b, double tol = 1e-8)
{
return std::abs(a - b) <= tol;
}
/**
* Tsallis entropy S_q of a uniform probability distribution of length n
*/
inline double tsallisEntropyUniform(double n, double q = 1)
{
if (isEqual(q, 1)) {
return std::log2(n);
}
return 1 / (q - 1) * (1 - pow(n, (1 - q))) / std::log(2);
}
/**
* Interpolate from linear (q = 0) to log (q = 1)
* linlog(k, 0) = k
* linlog(k, 1) = log2(k)
*/
inline double linlog(double k, double q = 1)
{
double baseCorrection = q <= 1 ? (1 - q) * std::log(2) + q : 1;
double offsetCorrection = q <= 1 ? 1 - q : 0;
return tsallisEntropyUniform(k, q) * baseCorrection + offsetCorrection;
}
} // namespace infomath
} // namespace infomap
#endif // INFOMATH_H_
+19
View File
@@ -0,0 +1,19 @@
/*******************************************************************************
Infomap software package for multi-level network clustering
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
This file is part of the Infomap software package.
See file LICENSE_GPLv3.txt for full license details.
For more information, see <http://www.mapequation.org>
******************************************************************************/
#ifndef VERSION_H_
#define VERSION_H_
namespace infomap {
const char* const INFOMAP_VERSION = "2.8.0";
}
#endif // VERSION_H_