/**********************************************************************************

 Infomap software package for multi-level network clustering

 Copyright (c) 2013, 2014 Daniel Edler, Martin Rosvall

 For more information, see <http://www.mapequation.org>


 This file is part of Infomap software package.

 Infomap software package is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 Infomap software package is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with Infomap software package.  If not, see <http://www.gnu.org/licenses/>.

 **********************************************************************************/


#include "MultiplexNetwork.h"
#include "../utils/FileURI.h"
#include "../io/convert.h"
#include "../io/SafeFile.h"
#include "../utils/Logger.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>

#include "../utils/infomath.h"

#ifdef NS_INFOMAP
namespace infomap
{
#endif

using std::make_pair;

void MultiplexNetwork::readInputData(std::string filename)
{
	if (filename.empty())
		filename = m_config.networkFile;
	if (m_config.inputFormat == "multilayer" || m_config.inputFormat == "multiplex")
		parseMultiplexNetwork(filename);
	else if (m_config.additionalInput.size() > 0)
		parseMultipleNetworks();
	else {
		// throw ImplementationError("No multiplex identified.");
		MemNetwork::readInputData(filename);
	}
}

/**
 * Example of general multilayer network:
 *
 * *Vertices 4
 * 1 "Node 1"
 * 2 "Node 2"
 * 3 "Node 3"
 * 4 "Node 4"
 * *Multilayer
 * # layer node layer node [weight]
 * 1 1 1 2 2
 * 1 1 2 2 1
 * 1 2 1 1 1
 * 1 3 2 2 1
 * 2 2 1 3 1
 * 2 3 2 2 1
 * 2 4 2 1 2
 * 2 4 1 2 1
 *
 * The *Vertices section and the *Multilayer line can be omitted.
 * More compact sections can be defined for intra-network links
 * and inter-network links by adding *Intra and *Inter lines
 * like below:
 *
 * *Intra
 * # layer node node weight
 * 1 1 2 1
 * 1 2 1 1
 * 1 2 3 1
 * 1 3 2 1
 * 1 3 1 1
 * 1 1 3 1
 * 1 2 4 1
 * 1 4 2 1
 * 2 4 5 1
 * 2 5 4 1
 * 2 5 6 1
 * 2 6 5 1
 * 2 6 4 1
 * 2 4 6 1
 * 2 3 6 1
 * 2 6 3 1
 * *Inter
 * # layer node layer weight
 * 1 3 1 2
 * 1 3 2 1
 * 2 3 2 1
 * 2 3 1 1
 * 1 4 1 1
 * 1 4 2 1
 * 2 4 2 2
 * 2 4 1 1
 *
 * Note:
 * *Inter links define unrecorded movements:
 * Each inter-link (layer1 node1 layer2) is expanded to a set of
 * multilayer links {layer1 node1 layer2 node2} for all node2 that
 * node1 points to in the second layer.
 *
 */
void MultiplexNetwork::parseMultiplexNetwork(std::string filename)
{
	Log() << "Parsing multilayer network from file '" << filename << "'... " << std::flush;

	SafeInFile input(filename.c_str());

	// Assume general multilayer links
	string line = parseMultiplexLinks(input);

	while (line.length() > 0 && line[0] == '*')
	{
		std::string header = io::firstWord(line);
		if (header == "*Vertices" || header == "*vertices") {
			line = parseVertices(input, line);
		}
		else if (header == "*Intra" || header == "*intra") {
			line = parseIntraLinks(input);
		}
		else if (header == "*Inter" || header == "*inter") {
			line = parseInterLinks(input);
		}
		else if (header == "*Multilayer" || header == "*multilayer") {
			line = parseMultiplexLinks(input);
		}
		else if (header == "*Multiplex" || header == "*multiplex") {
			Log() << "\nWarning: Header '*Multiplex' has been deprecated, use *Multilayer\n";
			line = parseMultiplexLinks(input);
		}
		else
			throw FileFormatError(io::Str() << "Unrecognized header in multilayer network file: '" << line << "'.");
	}

	Log() << "done!\n";

	finalizeAndCheckNetwork();

}

void MultiplexNetwork::parseMultipleNetworks()
{
	std::vector<std::string> networkFilenames;
	networkFilenames.push_back(m_config.networkFile);
	for (unsigned int i = 0; i < m_config.additionalInput.size(); ++i)
		networkFilenames.push_back(m_config.additionalInput[i]);

	for (unsigned int i = 0; i < networkFilenames.size(); ++i)
	{
		m_networks.push_back(Network(m_config));
		Log() << "[Network layer " << (i + 1) << " from file '" << networkFilenames[i] << "']:\n";
		m_networks[i].readInputData(networkFilenames[i]);
	}

	m_numNodes = adjustForDifferentNumberOfNodes();

	unsigned int numInterLinksFound = 0; //TODO: Assume last additional data is inter-layer data if #additionalInputs > 1 && multiplexRelaxRate < 0

	Log() << "Generating memory network... " << std::flush;

	bool simulateInterLayerLinks = m_config.multiplexJSRelaxRate >= 0.0 ||  m_config.multiplexRelaxRate >= 0.0 || numInterLinksFound == 0;
	if (simulateInterLayerLinks){
		if(m_config.multiplexJSRelaxRate >= 0.0)
			generateMemoryNetworkWithJensenShannonSimulatedInterLayerLinks();
		else
			generateMemoryNetworkWithSimulatedInterLayerLinks();
	}
	else
		generateMemoryNetworkWithInterLayerLinksFromData();

	finalizeAndCheckNetwork();
}

unsigned int MultiplexNetwork::adjustForDifferentNumberOfNodes()
{
	// Check maximum number of nodes
	unsigned int maxNumNodes = m_networks[0].numNodes();
	bool differentNodeCount = false;
	for (unsigned int layerIndex = 0; layerIndex < m_networks.size(); ++layerIndex)
	{
		unsigned int numNodesInLayer = m_networks[layerIndex].numNodes();
		if (numNodesInLayer != maxNumNodes)
			differentNodeCount = true;
		maxNumNodes = std::max(maxNumNodes, numNodesInLayer);

		// Take node names from networks if exist
		if (!m_networks[layerIndex].nodeNames().empty() && (m_nodeNames.empty() || numNodesInLayer > m_nodeNames.size()))
		{
			m_nodeNames.clear();
			m_networks[layerIndex].swapNodeNames(m_nodeNames);
		}
	}

	if (m_nodeNames.size() > 0 && m_nodeNames.size() < maxNumNodes) {
		m_nodeNames.reserve(maxNumNodes);
		for (unsigned int i = m_nodeNames.size(); i < maxNumNodes; ++i) {
			m_nodeNames.push_back(io::Str() << "_completion_node_" << (i + 1));
		}
	}

	if (differentNodeCount && m_config.multiplexAddMissingNodes)
	{
		Log() << "Adjusting to same set of physical nodes in each layer... " << std::flush;
		unsigned int numAdjusted = 0;
		for (unsigned int layerIndex = 0; layerIndex < m_networks.size(); ++layerIndex)
		{
			if (m_networks[layerIndex].numNodes() != maxNumNodes)
			{
//				Log() << "  Layer " << (layerIndex + 1) << ": " <<
//						m_networks[layerIndex].numNodes() << " -> " << maxNumNodes << " nodes." << std::endl;
				++numAdjusted;
				m_networks[layerIndex].finalizeAndCheckNetwork(false, maxNumNodes);
			}
		}
		Log() << "done! Adjusted " << numAdjusted << "/" << m_networks.size() << " networks to have " << maxNumNodes << " nodes." << std::endl;
	}

	return maxNumNodes;
}

bool MultiplexNetwork::createIntraLinksToNeighbouringNodesInTargetLayer(StateLinkMap::iterator stateSourceIt,
	unsigned int nodeIndex, unsigned int targetLayer, const LinkMap& targetLayerLinks,
	double linkWeightNormalizationFactor, double stateNodeWeightNormalizationFactor) {
	// Distribute inter-links to outgoing intra-links in the target layer
	LinkMap::const_iterator targetLayerOutLinkIt = targetLayerLinks.find(nodeIndex);
	bool linkAdded = false;
	if (targetLayerOutLinkIt != targetLayerLinks.end())
	{
		const std::map<unsigned int, double>& targetLayerOutLinks = targetLayerOutLinkIt->second;
		for (std::map<unsigned int, double>::const_iterator interIntraIt(targetLayerOutLinks.begin()); interIntraIt != targetLayerOutLinks.end(); ++interIntraIt)
		{
			unsigned int targetLayerTargetNodeIndex = interIntraIt->first;
			double targetLayerLinkWeight = interIntraIt->second;

			// double interIntraLinkWeight = scaledInterLinkWeight * targetLayerLinkWeight / sumOutWeights[layer2][nodeIndex];
			double interIntraLinkWeight = linkWeightNormalizationFactor * targetLayerLinkWeight;
			double stateNodeWeight = stateNodeWeightNormalizationFactor * targetLayerLinkWeight;

			// Add weight also as teleportation weight to first state node
			addStateLink(stateSourceIt, targetLayer, targetLayerTargetNodeIndex, interIntraLinkWeight, stateNodeWeight, 0.0);
			linkAdded = true;
		}
	}
	return linkAdded;
}

bool MultiplexNetwork::createIntraLinksToNeighbouringNodesInTargetLayer(unsigned int sourceLayer,
	unsigned int nodeIndex, unsigned int targetLayer, const LinkMap& targetLayerLinks,
	double linkWeightNormalizationFactor, double stateNodeWeightNormalizationFactor) {
	// Distribute inter-links to outgoing intra-links in the target layer
	LinkMap::const_iterator targetLayerOutLinkIt = targetLayerLinks.find(nodeIndex);
	bool linkAdded = false;
	// Log() << "    -> add state link between (" << sourceLayer << "," << nodeIndex << ") to (" << targetLayer << ", *)\n";
	if (targetLayerOutLinkIt != targetLayerLinks.end())
	{
		const std::map<unsigned int, double>& targetLayerOutLinks = targetLayerOutLinkIt->second;
		for (std::map<unsigned int, double>::const_iterator interIntraIt(targetLayerOutLinks.begin()); interIntraIt != targetLayerOutLinks.end(); ++interIntraIt)
		{
			unsigned int targetLayerTargetNodeIndex = interIntraIt->first;
			double targetLayerLinkWeight = interIntraIt->second;

			double interIntraLinkWeight = linkWeightNormalizationFactor * targetLayerLinkWeight;
			double stateNodeWeight = stateNodeWeightNormalizationFactor * targetLayerLinkWeight;

			// Add weight also as teleportation weight to first state node
			addStateLink(sourceLayer, nodeIndex, targetLayer, targetLayerTargetNodeIndex, interIntraLinkWeight, stateNodeWeight, 0.0);
			// Log() << "      -> added state link (" << sourceLayer << "," << nodeIndex << ") to (" << targetLayer << "," << targetLayerTargetNodeIndex << "), interIntraLinkWeight: " << interIntraLinkWeight << ", stateNodeWeight: " << stateNodeWeight << "\n";
			linkAdded = true;
		}
	}
	return linkAdded;
}

void MultiplexNetwork::generateMemoryNetworkWithInterLayerLinksFromData()
{
	Log() << "Generating memory network with inter layer links from data... " << std::flush;
	// First generate memory links from intra links (from ordinary links within each network)
	std::vector<std::vector<double> > sumOutWeights(m_networks.size());

	bool oldUndirected = m_config.isUndirected();
	std::vector<LinkMap> oppositeLinkMaps;
	if (oldUndirected) {
		oppositeLinkMaps.resize(m_networks.size());
		for (unsigned int i = 0; i < m_networks.size(); ++i) {
			m_networks[i].generateOppositeLinkMap(oppositeLinkMaps[i]);
		}
	}

	for (unsigned int layerIndex = 0; layerIndex < m_networks.size(); ++layerIndex)
	{
		sumOutWeights[layerIndex].assign(m_numNodes, 0.0);
		const LinkMap& linkMap = m_networks[layerIndex].linkMap();
		for (LinkMap::const_iterator linkIt(linkMap.begin()); linkIt != linkMap.end(); ++linkIt)
		{
			unsigned int n1 = linkIt->first;
			const std::map<unsigned int, double>& subLinks = linkIt->second;
			for (std::map<unsigned int, double>::const_iterator subIt(subLinks.begin()); subIt != subLinks.end(); ++subIt)
			{
				unsigned int n2 = subIt->first;
				double linkWeight = subIt->second;

				sumOutWeights[layerIndex][n1] += linkWeight;
				if (oldUndirected) {
					sumOutWeights[layerIndex][n2] += linkWeight;
				}
				addStateLink(layerIndex, n1, layerIndex, n2, linkWeight);
			}
		}
	}

	Log() << "connecting layers... " << std::flush;


	// Extract the self-layer links to be able to scale the inter-layer links correctly. Use the sum of intra out weights as default.
	std::vector<std::vector<double> > selfLayerWeights(sumOutWeights);
	for (std::map<StateNode, InterLinkMap>::const_iterator stateNodeIt(m_interLinks.begin()); stateNodeIt != m_interLinks.end(); ++stateNodeIt)
	{
		const StateNode& stateNode = stateNodeIt->first;
		unsigned int layer1 = stateNode.layer();
		unsigned int nodeIndex = stateNode.physIndex;
		const InterLinkMap& interLinkMap = stateNodeIt->second;
		for (InterLinkMap::const_iterator interLinkIt(interLinkMap.begin()); interLinkIt != interLinkMap.end(); ++interLinkIt)
		{
			unsigned int layer2 = interLinkIt->first;
			double linkWeight = interLinkIt->second;
			if (layer2 == layer1)
			{
				selfLayerWeights[layer1][nodeIndex] = linkWeight;
			}
		}
	}
	unsigned int numInterLinksIgnored = 0;
	// Then generate memory links from inter links (links between nodes in different layers)
	for (std::map<StateNode, InterLinkMap>::const_iterator stateNodeIt(m_interLinks.begin()); stateNodeIt != m_interLinks.end(); ++stateNodeIt)
	{
		const StateNode& stateNode = stateNodeIt->first;
		StateLinkMap::iterator stateSourceIt = m_stateLinks.lower_bound(stateNode);
		// Find source iterator to re-use in the loop below
		bool emptyMapInserted = false;
		if (stateSourceIt == m_stateLinks.end() || stateSourceIt->first != stateNode) {
			stateSourceIt = m_stateLinks.insert(stateSourceIt, std::make_pair(stateNode, std::map<StateNode, double>())); // TODO: Use C++11 for optimized insertion with hint from lower_bound
			emptyMapInserted = true;
		}
		bool stateSourceNodeAdded = false;
		unsigned int layer1 = stateNode.layer();
		unsigned int nodeIndex = stateNode.physIndex;
		const InterLinkMap& interLinkMap = stateNodeIt->second;
		for (InterLinkMap::const_iterator interLinkIt(interLinkMap.begin()); interLinkIt != interLinkMap.end(); ++interLinkIt)
		{
			unsigned int layer2 = interLinkIt->first;
			if (layer2 != layer1)
			{
				double interLinkWeight = interLinkIt->second;
				double scaledInterLinkWeight = interLinkWeight;
				double scaledOppositeInterLinkWeight = interLinkWeight;
	//			//Rescale with self-layer weight if possible
				if (selfLayerWeights[layer1][nodeIndex] > 1e-10) {
					scaledInterLinkWeight *= sumOutWeights[layer1][nodeIndex] / selfLayerWeights[layer1][nodeIndex];
				}
				if (selfLayerWeights[layer2][nodeIndex] > 1e-10) {
					scaledOppositeInterLinkWeight *= sumOutWeights[layer2][nodeIndex] / selfLayerWeights[layer2][nodeIndex];
				}
				// Switch to same physical node within target layer
				bool nonPhysicalSwitch = false;
				if (nonPhysicalSwitch)
				{
					addStateLink(stateSourceIt, layer2, nodeIndex, scaledInterLinkWeight, scaledInterLinkWeight, 0.0);
					stateSourceNodeAdded = true;
				}
				else
				{
					// Jump to state note in target layer and distribute inter link to connected intra links
					double weightNormalizationFactor = scaledInterLinkWeight / sumOutWeights[layer2][nodeIndex];
					if (oldUndirected) {
						// Distribute inter-links to outgoing intra-links in the target layer
						bool add1 = createIntraLinksToNeighbouringNodesInTargetLayer(stateSourceIt, nodeIndex, layer2, m_networks[layer2].linkMap(), weightNormalizationFactor, weightNormalizationFactor);
						
						// Distribute inter-link to incoming intra-links in the target layer
						bool add2 = createIntraLinksToNeighbouringNodesInTargetLayer(stateSourceIt, nodeIndex, layer2, oppositeLinkMaps[layer2], weightNormalizationFactor, weightNormalizationFactor);

						// Distribute inter-links to outgoing intra-links in the source layer
						double oppositeWeightNormalizationFactor = scaledOppositeInterLinkWeight / sumOutWeights[layer1][nodeIndex];
						createIntraLinksToNeighbouringNodesInTargetLayer(layer2, nodeIndex, layer1, m_networks[layer1].linkMap(), oppositeWeightNormalizationFactor, oppositeWeightNormalizationFactor);
						
						// Distribute inter-link to incoming intra-links in the source layer
						createIntraLinksToNeighbouringNodesInTargetLayer(layer2, nodeIndex, layer1, oppositeLinkMaps[layer1], oppositeWeightNormalizationFactor, oppositeWeightNormalizationFactor);
						
						stateSourceNodeAdded = add1 | add2;
					}
					else {
						// Distribute inter-link to the outgoing intra-links in the target layer
						stateSourceNodeAdded = createIntraLinksToNeighbouringNodesInTargetLayer(stateSourceIt, nodeIndex, layer2, m_networks[layer2].linkMap(), weightNormalizationFactor, weightNormalizationFactor);
						if (m_config.parseAsUndirected()) {
							// Treat inter-link as undirected and distribute to outgoing intra-links in the source layer too
							double oppositeWeightNormalizationFactor = scaledOppositeInterLinkWeight / sumOutWeights[layer1][nodeIndex];
							createIntraLinksToNeighbouringNodesInTargetLayer(layer2, nodeIndex, layer1, m_networks[layer1].linkMap(), oppositeWeightNormalizationFactor, oppositeWeightNormalizationFactor);
						}
					}
				}
			}
			else {
				++numInterLinksIgnored;
				throw FileFormatError(io::Str() << "\nLink '" <<
					(layer1 + m_indexOffset) << ", " << (nodeIndex + m_indexOffset) << ", " << (layer2 + m_indexOffset) <<
					"' is declared as an inter-layer link (layer1, node, layer2) but is not.");
			}
		}
		if (!stateSourceNodeAdded && emptyMapInserted) {
			// The added source was not used, remove it
			m_stateLinks.erase(stateSourceIt);
		}
	}
	Log() << "done!" << std::endl;
	if (numInterLinksIgnored > 0) {
		Log() << "  -> Warning: " << numInterLinksIgnored << " links defined as inter-layer links was same-layer links and ignored.\n";
	}
}

void MultiplexNetwork::generateMemoryNetworkWithSimulatedInterLayerLinks()
{
	if (m_numNodes == 0)
		return;
	// Simulate inter-layer links
	double relaxRate = m_config.multiplexRelaxRate < 0 ? 0.15 : m_config.multiplexRelaxRate; //TODO: Set default in config and use separate bool

	Log() << "Generating memory network with multilayer relax rate " << relaxRate << "... " << std::flush;
	
	bool oldUndirected = m_config.isUndirected();
	std::vector<LinkMap> oppositeLinkMaps;
	if (oldUndirected) {
		oppositeLinkMaps.resize(m_networks.size());
		for (unsigned int i = 0; i < m_networks.size(); ++i) {
			m_networks[i].generateOppositeLinkMap(oppositeLinkMaps[i]);
		}
	}

	for (unsigned int nodeIndex = 0; nodeIndex < m_numNodes; ++nodeIndex)
	{
		unsigned int layer2from = 0;
		unsigned int layer2to = m_networks.size();
		double sumOutLinkWeightAllLayers = 0.0;
		for (unsigned int i = layer2from; i < layer2to; ++i) {
			const std::vector<double>& sumLinkOutWeight = m_networks[i].sumLinkOutWeight();
			if (nodeIndex < sumLinkOutWeight.size())
				sumOutLinkWeightAllLayers += sumLinkOutWeight[nodeIndex];
		}
		// Log() << "\n===== Node index: " << nodeIndex << " / " << (m_numNodes - 1) << " ======" <<
		// "  sumOutLinkWeightAllLayers: " << sumOutLinkWeightAllLayers << "\n";

		for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1)
		{

			if (!m_networks[layer1].haveNode(nodeIndex)) {
				// Log() << "  SKIP layer " << layer1 << ", no node " << nodeIndex << "!\n";
				continue;
			}

			// Limit possible jumps to close by layers
			if(m_config.multiplexRelaxLimit >= 0){
				layer2from = ((int)layer1 - m_config.multiplexRelaxLimit) < 0 ? 0 : layer1 - m_config.multiplexRelaxLimit;
				layer2to = (layer1 + m_config.multiplexRelaxLimit) > m_networks.size() ? m_networks.size() : layer1 + m_config.multiplexRelaxLimit + 1;
				sumOutLinkWeightAllLayers = 0.0;
				for (unsigned int i = layer2from; i < layer2to; ++i) {
					const std::vector<double>& sumLinkOutWeight = m_networks[i].sumLinkOutWeight();
					if (nodeIndex < sumLinkOutWeight.size())
						sumOutLinkWeightAllLayers += sumLinkOutWeight[nodeIndex];
				}
			}

			double sumOutLinkWeightLayer1 = m_networks[layer1].sumLinkOutWeight()[nodeIndex];

			// Log() << "  Layer " << layer1 << ", sumOutLinkWeight: " << sumOutLinkWeightLayer1 << "\n";

			// StateNode stateSource(layer1, nodeIndex);
//			StateLinkMap::iterator stateSourceIt = m_stateLinks.lower_bound(stateSource);
//			if (stateSourceIt == m_stateLinks.end() || stateSourceIt->first != stateSource)
//				stateSourceIt = m_stateLinks.insert(stateSourceIt, std::make_pair(stateSource, std::map<StateNode, double>())); // TODO: Use C++11 for optimized insertion with hint from lower_bound

			// Create inter-links to the intra-connected nodes in other layers
			for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2)
			{
				// Log() << "  -> Connecting layer " << layer1 << " and " << layer2 << " (diff " << ((int)layer2 - (int)layer1) << ")...\n";
				bool isIntra = layer2 == layer1;

				// Create inter-links to the outgoing nodes in the target layer
				double linkWeightNormalizationFactor = relaxRate / sumOutLinkWeightAllLayers;
				if (isIntra) {
					linkWeightNormalizationFactor += (1.0 - relaxRate) / sumOutLinkWeightLayer1;
				}
				double stateNodeWeightNormalizationFactor = 1.0;

				// Log() << "    -> Layer " << layer2 << ", linkWeightNormalizationFactor: " << linkWeightNormalizationFactor << "\n";
				
				createIntraLinksToNeighbouringNodesInTargetLayer(layer1, nodeIndex, layer2, m_networks[layer2].linkMap(), linkWeightNormalizationFactor, stateNodeWeightNormalizationFactor);
					
				if (oldUndirected) {
					// Create inter-links to the incoming nodes in the target layer too					
					createIntraLinksToNeighbouringNodesInTargetLayer(layer1, nodeIndex, layer2, oppositeLinkMaps[layer2], linkWeightNormalizationFactor, stateNodeWeightNormalizationFactor);
				}
			}
		}
	}
	Log() << "done!" << std::endl;
}

void MultiplexNetwork::generateMemoryNetworkWithJensenShannonSimulatedInterLayerLinks()
{
	// Simulate inter-layer links
	double jsrelaxRate = m_config.multiplexJSRelaxRate < 0 ? 0.15 : m_config.multiplexJSRelaxRate; //TODO: Set default in config and use separate bool

	Log() << "Generating memory network with Jensen-Shannon-weighted multilayer relax rate " << jsrelaxRate << "... " << std::flush;
	
	std::vector<LinkMap> oppositeLinkMaps;
	bool oldUndirected = m_config.isUndirected();
	if (oldUndirected) {
		oppositeLinkMaps.resize(m_networks.size());
		for (unsigned int i = 0; i < m_networks.size(); ++i) {
			m_networks[i].generateOppositeLinkMap(oppositeLinkMaps[i]);
		}
	}

	for (unsigned int nodeIndex = 0; nodeIndex < m_numNodes; ++nodeIndex)
	{

		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.multiplexRelaxLimit >= 0){
				layer2from = ((int)layer1-m_config.multiplexRelaxLimit) < 0 ? 0 : layer1-m_config.multiplexRelaxLimit;			}

			const LinkMap& layer1LinkMap = m_networks[layer1].linkMap();
			LinkMap::const_iterator layer1OutLinksIt = layer1LinkMap.find(nodeIndex);
			double sumOutLinkWeightLayer1 = m_networks[layer1].sumLinkOutWeight()[nodeIndex];

			if(oldUndirected){
 				
				const LinkMap& layer1OppositeLinkMap = oppositeLinkMaps[layer1];
				LinkMap::const_iterator layer1OppositeOutLinksIt = layer1OppositeLinkMap.find(nodeIndex);

				// Will not link from dangling node (similarity 0)
				if((layer1OutLinksIt == layer1LinkMap.end()) && (layer1OppositeOutLinksIt == layer1OppositeLinkMap.end()))
					continue;

				// Include non-empty link maps
				std::vector<const InterLinkMap *> layer1LinksVec;
				if(layer1OutLinksIt != layer1LinkMap.end())
					layer1LinksVec.push_back(&layer1OutLinksIt->second);
				if(layer1OppositeOutLinksIt != layer1OppositeLinkMap.end())
					layer1LinksVec.push_back(&layer1OppositeOutLinksIt->second);

				for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2){

					const LinkMap& layer2LinkMap = m_networks[layer2].linkMap();
					LinkMap::const_iterator layer2OutLinksIt = layer2LinkMap.find(nodeIndex);						
					const LinkMap& layer2OppositeLinkMap = oppositeLinkMaps[layer2];
					LinkMap::const_iterator layer2OppositeOutLinksIt = layer2OppositeLinkMap.find(nodeIndex);

					// Will not link to dangling node (similarity 0)
					if((layer2OutLinksIt == layer2LinkMap.end()) && (layer2OppositeOutLinksIt == layer2OppositeLinkMap.end()))
						continue;

					// Include non-empty link maps
					std::vector<const InterLinkMap *> layer2LinksVec;
					if(layer2OutLinksIt != layer2LinkMap.end())
						layer2LinksVec.push_back(&layer2OutLinksIt->second);
					if(layer2OppositeOutLinksIt != layer2OppositeLinkMap.end())
						layer2LinksVec.push_back(&layer2OppositeOutLinksIt->second);
	
					double sumOutLinkWeightLayer2 = m_networks[layer2].sumLinkOutWeight()[nodeIndex];

					bool intersect;
					double div = calculateJensenShannonDivergence(intersect,layer1LinksVec,sumOutLinkWeightLayer1,layer2LinksVec,sumOutLinkWeightLayer2);
					double jsWeight = 1.0 - div;
					if(intersect && (jsWeight >= m_config.multiplexJSRelaxLimit)){						
						jsRelaxWeights[layer1][layer2] = jsWeight;
						jsTotWeight[layer1] += jsWeight*sumOutLinkWeightLayer2;
						if(layer1 != layer2){
							jsRelaxWeights[layer2][layer1] = jsWeight;
							jsTotWeight[layer2] += jsWeight*sumOutLinkWeightLayer1;
						}
					}
				}

			}
			else{

				// Skip dangling nodes, because they have no information to calculate similarity
 				if (layer1OutLinksIt == layer1LinkMap.end())
 					continue;

				for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2){
					const LinkMap& layer2LinkMap = m_networks[layer2].linkMap();
					LinkMap::const_iterator layer2OutLinksIt = layer2LinkMap.find(nodeIndex);
					if (layer2OutLinksIt == layer2LinkMap.end())
						continue;
	
					double sumOutLinkWeightLayer2 = m_networks[layer2].sumLinkOutWeight()[nodeIndex];
	
					bool intersect;
					double div = calculateJensenShannonDivergence(intersect,layer1OutLinksIt->second,sumOutLinkWeightLayer1,layer2OutLinksIt->second,sumOutLinkWeightLayer2);
					double jsWeight = 1.0 - div;
					if(intersect && (jsWeight >= m_config.multiplexRelaxLimit)){	
						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.multiplexRelaxLimit >= 0){
				layer2from = ((int)layer1-m_config.multiplexRelaxLimit) < 0 ? 0 : layer1-m_config.multiplexRelaxLimit;
				layer2to = (layer1+m_config.multiplexRelaxLimit) > m_networks.size() ? m_networks.size() : layer1+m_config.multiplexRelaxLimit;
			}

			double sumOutLinkWeightLayer1 = m_networks[layer1].sumLinkOutWeight()[nodeIndex];

			std::map<unsigned int,std::map<unsigned int,double> >::iterator jsRelaxWeightsLayer1It = jsRelaxWeights.find(layer1);
			std::map<unsigned int,double>::iterator 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()){
					std::map<unsigned int,double>::iterator 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
						// Normalize to cause outlinks on state nodes to sum to one
						double linkWeightNormalizationFactor = jsrelaxRate * jsRelaxWeightsIt->second / jsTotWeightIt->second;
						if (isIntra){
							linkWeightNormalizationFactor += (1.0 - jsrelaxRate) / sumOutLinkWeightLayer1;
						}
						
						double stateNodeWeightNormalizationFactor = 1.0;
						
						createIntraLinksToNeighbouringNodesInTargetLayer(layer1, nodeIndex, layer2, m_networks[layer2].linkMap(), linkWeightNormalizationFactor, stateNodeWeightNormalizationFactor);
						
						if (oldUndirected) {
							// Create inter-links to the incoming nodes in the target layer too					
							createIntraLinksToNeighbouringNodesInTargetLayer(layer1, nodeIndex, layer2, oppositeLinkMaps[layer2], linkWeightNormalizationFactor, stateNodeWeightNormalizationFactor);
						}
					}
				}
			}
		}
	}
	Log() << "done!" << std::endl;
}

double MultiplexNetwork::calculateJensenShannonDivergence(bool &intersect, const IntraLinkMap &layer1OutLinks, double sumOutLinkWeightLayer1, const IntraLinkMap &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);

	IntraLinkMap::const_iterator layer1OutLinkIt = layer1OutLinks.begin();
	IntraLinkMap::const_iterator layer2OutLinkIt = layer2OutLinks.begin();
	IntraLinkMap::const_iterator layer1OutLinkItEnd = layer1OutLinks.end();
	IntraLinkMap::const_iterator layer2OutLinkItEnd = layer2OutLinks.end();
	while(layer1OutLinkIt != layer1OutLinkItEnd && layer2OutLinkIt != layer2OutLinkItEnd){
		int diff = layer1OutLinkIt->first - layer2OutLinkIt->first;		
		if(diff < 0){
		// If the first state node has a link that the second has not
			double p1 = layer1OutLinkIt->second/ow1;
			h1 -= p1*log2(p1);
			double p12 = pi1*layer1OutLinkIt->second/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/ow2;
			h2 -= p2*log2(p2);
			double p12 = pi2*layer2OutLinkIt->second/ow2;
			h12 -= p12*log2(p12);
			layer2OutLinkIt++;
		}
		else{ // If both state nodes have the link
			intersect = true;
			double p1 = layer1OutLinkIt->second/ow1;
			h1 -= p1*log2(p1);
			double p2 = layer2OutLinkIt->second/ow2;
			h2 -= p2*log2(p2);
			double p12 = pi1*layer1OutLinkIt->second/ow1 + pi2*layer2OutLinkIt->second/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/ow1;
		h1 -= p1*log2(p1);
		double p12 = pi1*layer1OutLinkIt->second/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/ow2;
		h2 -= p2*log2(p2);
		double p12 = pi2*layer2OutLinkIt->second/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;
	
	// Log() << "\n" << div << " " << (pi1+pi2) << " " << h12 << " " << pi1 << " " << h1 << " " << pi2 << " " << h2;

	return div;

}

double MultiplexNetwork::calculateJensenShannonDivergence(bool &intersect, std::vector<const IntraLinkMap *> &layer1LinksVec, double sumOutLinkWeightLayer1, std::vector<const IntraLinkMap *> &layer2LinksVec, 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);

	IntraLinkMap::const_iterator *layer1linkIt;
	IntraLinkMap::const_iterator *layer2linkIt;

	std::vector<pair<IntraLinkMap::const_iterator,IntraLinkMap::const_iterator> > layer1OutLinkItVec;
	for(std::vector<const IntraLinkMap *>::iterator layer1linkPtrIt = layer1LinksVec.begin(); layer1linkPtrIt != layer1LinksVec.end(); layer1linkPtrIt++)
		layer1OutLinkItVec.push_back(make_pair((*layer1linkPtrIt)->begin(),(*layer1linkPtrIt)->end()));

	std::vector<pair<IntraLinkMap::const_iterator,IntraLinkMap::const_iterator> > layer2OutLinkItVec;
	for(std::vector<const IntraLinkMap *>::iterator layer2linkPtrIt = layer2LinksVec.begin(); layer2linkPtrIt != layer2LinksVec.end(); layer2linkPtrIt++)
		layer2OutLinkItVec.push_back(make_pair((*layer2linkPtrIt)->begin(),(*layer2linkPtrIt)->end()));

	while(undirLinkRemains(layer1OutLinkItVec) && undirLinkRemains(layer2OutLinkItVec)){
		
		layer1linkIt = getUndirLinkItPtr(layer1OutLinkItVec);
		layer2linkIt = getUndirLinkItPtr(layer2OutLinkItVec);

		int diff = (*layer1linkIt)->first - (*layer2linkIt)->first;	
		// Log() << "\n" << (*layer1linkIt)->first << " " << (*layer1linkIt)->second << " " << (*layer2linkIt)->first << " " << (*layer2linkIt)->second;
		
		if(diff < 0){
		// If the first state node has a link that the second has not
			double p1 = (*layer1linkIt)->second/ow1;
			h1 -= p1*log2(p1);
			double p12 = pi1*(*layer1linkIt)->second/ow1;
			h12 -= p12*log2(p12);
			(*layer1linkIt)++;
		}
		else if(diff > 0){
		// If the second state node has a link that the second has not
			double p2 = (*layer2linkIt)->second/ow2;
			h2 -= p2*log2(p2);
			double p12 = pi2*(*layer2linkIt)->second/ow2;
			h12 -= p12*log2(p12);
			(*layer2linkIt)++;
		}
		else{ // If both state nodes have the link
			intersect = true;
			double p1 = (*layer1linkIt)->second/ow1;
			h1 -= p1*log2(p1);
			double p2 = (*layer2linkIt)->second/ow2;
			h2 -= p2*log2(p2);
			double p12 = pi1*(*layer1linkIt)->second/ow1 + pi2*(*layer2linkIt)->second/ow2;
			h12 -= p12*log2(p12);
			(*layer1linkIt)++;
			(*layer2linkIt)++;
		}
	}

	while(undirLinkRemains(layer1OutLinkItVec)){
		// If the first state node has a link that the second has not

		layer1linkIt = getUndirLinkItPtr(layer1OutLinkItVec);

		double p1 = (*layer1linkIt)->second/ow1;
		h1 -= p1*log2(p1);
		double p12 = pi1*(*layer1linkIt)->second/ow1;
		h12 -= p12*log2(p12);
		(*layer1linkIt)++;
	}

	while(undirLinkRemains(layer2OutLinkItVec)){

		layer2linkIt = getUndirLinkItPtr(layer2OutLinkItVec);

		// If the second state node has a link that the second has not
		double p2 = (*layer2linkIt)->second/ow2;
		h2 -= p2*log2(p2);
		double p12 = pi2*(*layer2linkIt)->second/ow2;
		h12 -= p12*log2(p12);
		(*layer2linkIt)++;
	}	
	
	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;

}


MultiplexNetwork::IntraLinkMap::const_iterator *MultiplexNetwork::getUndirLinkItPtr(std::vector<pair<IntraLinkMap::const_iterator,IntraLinkMap::const_iterator> > &outLinkItVec){

	bool assigned = false;
	IntraLinkMap::const_iterator *linkIt = 0;

	for(std::vector<pair<IntraLinkMap::const_iterator,IntraLinkMap::const_iterator> >::iterator outLinkItVecIts = outLinkItVec.begin(); outLinkItVecIts != outLinkItVec.end(); outLinkItVecIts++){
		if(outLinkItVecIts->first != outLinkItVecIts->second){
			if(assigned){
				if(outLinkItVecIts->first->first < (*linkIt)->first){
					linkIt = &outLinkItVecIts->first;
				}
			}
			else{
				linkIt = &outLinkItVecIts->first;
				assigned = true;
			}
		}
	}

	return linkIt;

}

bool MultiplexNetwork::undirLinkRemains(std::vector<pair<IntraLinkMap::const_iterator,IntraLinkMap::const_iterator> > &outLinkItVec){

	for(std::vector<pair<IntraLinkMap::const_iterator,IntraLinkMap::const_iterator> >::iterator outLinkItVecIts = outLinkItVec.begin(); outLinkItVecIts != outLinkItVec.end(); outLinkItVecIts++)
		if(outLinkItVecIts->first != outLinkItVecIts->second)
			return true;

	return false;

}



void MultiplexNetwork::addMemoryNetworkFromMultiplexLinks()
{
	if (m_multiplexLinks.empty())
		return;
	Log() << "Adding memory network from multilayer links... " << std::flush;

	for (MultiplexLinkMap::const_iterator it(m_multiplexLinks.begin()); it != m_multiplexLinks.end(); ++it)
	{
		const StateNode& source(it->first);
		const std::map<StateNode, double>& subLinks(it->second);
		for (std::map<StateNode, double>::const_iterator subIt(subLinks.begin()); subIt != subLinks.end(); ++subIt)
		{
			const StateNode& target(subIt->first);
			double linkWeight = subIt->second;
			addStateLink(source.layer(), source.physIndex, target.layer(), target.physIndex, linkWeight);
		}
	}
	Log() << "done!" << std::endl;
}

std::string MultiplexNetwork::parseIntraLinks(std::ifstream& file)
{
	std::string line;
	while(!std::getline(file, line).fail())
	{
		if (line.length() == 0 || line[0] == '#')
			continue;

		if (line[0] == '*')
			break;

		unsigned int layerIndex, n1, n2;
		double weight;

		parseIntraLink(line, layerIndex, n1, n2, weight);

		while (m_networks.size() < layerIndex + 1)
			m_networks.push_back(Network(m_config));

		m_networks[layerIndex].addLink(n1, n2, weight);

		++m_numIntraLinksFound;
	}
	return line;
}

std::string MultiplexNetwork::parseInterLinks(std::ifstream& file)
{
	std::string line;
	while(!std::getline(file, line).fail())
	{
		if (line.length() == 0 || line[0] == '#')
			continue;

		if (line[0] == '*')
			break;

		unsigned int nodeIndex, layer1, layer2;
		double weight;

		parseInterLink(line, layer1, nodeIndex, layer2, weight);

		m_interLinks[StateNode(layer1, nodeIndex)][layer2] += weight;

		++m_numInterLinksFound;
		++m_interLinkLayers[layer1];
		++m_interLinkLayers[layer2];
	}
	return line;
}

void MultiplexNetwork::addMultiplexLink(int layer1, int node1, int layer2, int node2, double weight){
	m_multiplexLinks[StateNode(layer1, node1)][StateNode(layer2, node2)] += weight;

	++m_numMultiplexLinksFound;
	++m_multiplexLinkLayers[layer1];
	++m_multiplexLinkLayers[layer2];

}

std::string MultiplexNetwork::parseMultiplexLinks(std::ifstream& file)
{
	std::string line;
	while(!std::getline(file, line).fail())
	{
		if (line.length() == 0 || line[0] == '#')
			continue;

		if (line[0] == '*')
			break;

		unsigned int layer1, node1, layer2, node2;
		double weight;

		parseMultiplexLink(line, layer1, node1, layer2, node2, weight);

		addMultiplexLink(layer1, node1, layer2, node2, weight);

		if(layer1 == layer2)
			++m_numIntraLinksFound;
		else
			++m_numInterLinksFound;

	}
	return line;
}

/**
 * Link within the layer:
 *
 *   layer node node [weight]
 */
void MultiplexNetwork::parseIntraLink(const std::string& line, unsigned int& layerIndex, unsigned int& n1, unsigned int& n2, double& weight)
{
	m_extractor.clear();
	m_extractor.str(line);
	if (!(m_extractor >> layerIndex >> n1 >> n2))
		throw FileFormatError(io::Str() << "Can't parse multilayer intra link data (layer node1 node2) from line '" << line << "'");
	(m_extractor >> weight) || (weight = 1.0);
	layerIndex -= m_indexOffset;
	n1 -= m_indexOffset;
	n2 -= m_indexOffset;
}

/**
 * Link between layers:
 *
 *   layer node layer [weight]
 */
void MultiplexNetwork::parseInterLink(const std::string& line, unsigned int& layer1, unsigned int& node, unsigned int& layer2, double& weight)
{
	m_extractor.clear();
	m_extractor.str(line);
	if (!(m_extractor >> layer1 >> node >> layer2))
		throw FileFormatError(io::Str() << "Can't parse multilayer inter link data (layer1 node layer2) from line '" << line << "'");
	(m_extractor >> weight) || (weight = 1.0);
	layer1 -= m_indexOffset;
	node -= m_indexOffset;
	layer2 -= m_indexOffset;
}

/**
 * Parse general multiplex link:
 *
 *   layer1 node1 layer2 node2 [weight]
 */
void MultiplexNetwork::parseMultiplexLink(const std::string& line, unsigned int& layer1, unsigned int& node1, unsigned int& layer2, unsigned int& node2, double& weight)
{
	m_extractor.clear();
	m_extractor.str(line);
	if (!(m_extractor >> layer1 >> node1 >> layer2 >> node2))
		throw FileFormatError(io::Str() << "Can't parse multilayer link data (layer1 node1 layer2 node2) from line '" << line << "'");
	(m_extractor >> weight) || (weight = 1.0);
	layer1 -= m_indexOffset;
	node1 -= m_indexOffset;
	layer2 -= m_indexOffset;
	node2 -= m_indexOffset;
}

void MultiplexNetwork::finalizeAndCheckNetwork(bool printSummary)
{
	if (!m_config.isMultiplexNetwork()) {
		return MemNetwork::finalizeAndCheckNetwork(printSummary);
	}
	
	if (!m_networks.empty())
		Log() << " --> Found " << m_numIntraLinksFound << " intra-network links in " << m_networks.size() << " layers.\n";
	if (!m_interLinkLayers.empty())
		Log() << " --> Found " << m_numInterLinksFound << " inter-network links in " << m_interLinkLayers.size() << " layers.\n";
	if (!m_multiplexLinkLayers.empty())
		Log() << " --> Found " << m_numMultiplexLinksFound << " multilayer links in " << m_multiplexLinkLayers.size() << " layers.\n";
	if (m_config.isOriginallyUndirected())
		Log() << "Notice: Multilayer input require directed links, so each undirected link is expanded to two directed opposite links.\n";

	if (!m_interLinkLayers.empty())
	{
//		throw InputDomainError("Need at least two layers of network data for multiplex network.");
		unsigned int maxInterLinkLayer = (--m_interLinkLayers.end())->first + 1;
		if (m_networks.size() < maxInterLinkLayer)
			throw InputDomainError(io::Str() << "No intra-network data for inter-network links at layer " << maxInterLinkLayer << ".");
	}

	if (!m_networks.empty())
	{
		bool printLayerSummary = m_networks.size() <= 10 ||
				(m_networks.size() < 20 && infomath::isBetween(m_config.verbosity, 1, 2)) ||
				(m_networks.size() < 50 && infomath::isBetween(m_config.verbosity, 1, 3));
		// Finalize and check each layer of intra-network links
		for (unsigned int layerIndex = 0; layerIndex < m_networks.size(); ++layerIndex)
		{
			if (printLayerSummary)
				Log() << "Intra-network links on layer " << (layerIndex + 1) << ": " << std::flush;
			m_networks[layerIndex].finalizeAndCheckNetwork(false);
			if (printLayerSummary)
				m_networks[layerIndex].printParsingResult(m_config.verbosity <= 1);
		}

		m_numNodes = adjustForDifferentNumberOfNodes();
	}

	bool simulateInterLayerLinks = m_config.multiplexJSRelaxRate >= 0.0 ||  m_config.multiplexRelaxRate >= 0.0 || m_numInterLinksFound == 0;
	if (simulateInterLayerLinks){
		if(m_config.multiplexJSRelaxRate >= 0.0)
			generateMemoryNetworkWithJensenShannonSimulatedInterLayerLinks();
		else
			generateMemoryNetworkWithSimulatedInterLayerLinks();
	}
	else
		generateMemoryNetworkWithInterLayerLinksFromData();

	addMemoryNetworkFromMultiplexLinks();

	// Dispose intermediate data structures to clear memory
	m_interLinks.clear();
	m_networks.clear();

	MemNetwork::finalizeAndCheckNetwork(printSummary);
}

#ifdef NS_INFOMAP
}
#endif
