├── hypergraph ├── __init__.py ├── tests │ ├── __init__.py │ ├── test_utils.py │ ├── test_notebooks.py │ ├── tests.py │ ├── test_diffusion.py │ └── test_generators.py ├── diffusion_engine.py ├── generators.py ├── converters.py ├── entropy.py ├── analytical.py ├── markov_diffusion.py ├── utils.py ├── notebook_utils.py ├── diffusion_convergence_analytic.py └── hypergraph_models.py ├── nose.cfg ├── circle.yml ├── packages_requirements.txt ├── hypergraph_model.png ├── .gitignore ├── requirements.txt ├── setup.py ├── examples ├── show_hypergraph.py └── compare_hypergraphs_with_cliques.py ├── README.md ├── notes.md └── notebooks ├── hypergraphs_introduction.ipynb ├── Pykov.ipynb ├── diffusion_simulations.ipynb └── clique_comparison.ipynb /hypergraph/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hypergraph/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'att' 2 | -------------------------------------------------------------------------------- /nose.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | verbosity=3 3 | with-doctest=1 4 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | python: 3 | version: 3.4.1 4 | -------------------------------------------------------------------------------- /packages_requirements.txt: -------------------------------------------------------------------------------- 1 | python3-numpy python3-scipy python3-matplotlib 2 | -------------------------------------------------------------------------------- /hypergraph_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilonajulczuk/hypergraph/HEAD/hypergraph_model.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | build 4 | dist 5 | .buildinfo 6 | .coverage 7 | *.swo 8 | *.html 9 | *.swp 10 | py3 11 | .ipynb_checkpoints 12 | tags 13 | visualization 14 | hypergraph/plots 15 | -------------------------------------------------------------------------------- /hypergraph/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestConnectedHypergraph(unittest.TestCase): 5 | def test_if_classifies_correctly(self): 6 | # TODO implement this test :) 7 | self.assertTrue(True) 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | decorator==3.4.0 2 | mpmath==0.18 3 | networkx==1.8.1 4 | nose==1.3.0 5 | python-dateutil==2.2 6 | pytz==2013.9 7 | simplejson==3.3.3 8 | six==1.5.2 9 | urwid==1.1.2 10 | virtualenv==1.11.3 11 | ipython==2.1.0 12 | pyzmq==14.3.0 13 | tornado==3.2.1 14 | Jinja2==2.7.2 15 | -------------------------------------------------------------------------------- /hypergraph/tests/test_notebooks.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from IPython.nbformat.current import reads 4 | 5 | from hypergraph.notebook_utils import run_notebook 6 | 7 | 8 | def test_notebooks(): 9 | notebooks_path = 'notebooks' 10 | paths = os.listdir(notebooks_path) 11 | notebooks_filenames = [notebooks_path + '/' + name 12 | for name in paths if name.endswith('.ipynb')] 13 | 14 | for ipynb in notebooks_filenames: 15 | print("testing %s" % ipynb) 16 | with open(ipynb) as f: 17 | nb = reads(f.read(), 'json') 18 | run_notebook(nb) 19 | 20 | 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | from setuptools import setup 4 | 5 | def read(fname): 6 | """Utility function to read the README file.""" 7 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 8 | 9 | setup( 10 | name = "hypergraph", 11 | version = "0.0.1", 12 | author = "Justyna Ilczuk", 13 | author_email = "justyna.ilczuk@gmail.com", 14 | description = ("Library for hypegraphs in python"), 15 | license = "BSD", 16 | keywords = "hypergraph diffusion", 17 | url = "http://atte.ro", 18 | packages=['hypergraph'], 19 | long_description=read('README.md'), 20 | classifiers=[ 21 | "Development Status :: 3 - Alpha", 22 | "Topic :: Utilities", 23 | "License :: OSI Approved :: BSD License", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /hypergraph/tests/tests.py: -------------------------------------------------------------------------------- 1 | import nose.tools as nt 2 | 3 | from hypergraph.markov_diffusion import create_markov_matrix_model_nodes 4 | import hypergraph.generators as generators 5 | from hypergraph.diffusion_engine import DiffusionEngine 6 | 7 | 8 | def test_diffusion_engine(): 9 | hyper_graph = generators.generic_hypergraph(10, ((3, 2), (4, 3), (5, 3))) 10 | t_max = 1000 11 | number_of_walkers = 1 12 | offset = 10 13 | 14 | markov_matrix = create_markov_matrix_model_nodes(hyper_graph) 15 | 16 | chosen_states = [] 17 | for x in range(10, t_max, offset): 18 | engine = DiffusionEngine(markov_matrix) 19 | frequencies, states = engine.simulate(offset) 20 | 21 | chosen_states += states[0] 22 | 23 | nt.assert_equals(len(chosen_states), t_max / number_of_walkers - 10) 24 | 25 | 26 | -------------------------------------------------------------------------------- /hypergraph/tests/test_diffusion.py: -------------------------------------------------------------------------------- 1 | import nose.tools as nt 2 | from hypergraph.diffusion_engine import ( 3 | next_value, probabilities_to_distribution 4 | ) 5 | 6 | from collections import Counter 7 | 8 | 9 | def test_random_variable(): 10 | probs = [0.2, 0.3, 0.5] 11 | 12 | distribution = probabilities_to_distribution(probs) 13 | 14 | number_of_tries = 100000 15 | values_count = Counter() 16 | for _ in range(number_of_tries): 17 | value = next_value(distribution) 18 | values_count[value] += 1 19 | 20 | expected_frequencies = {i: probability for i, probability in enumerate(probs)} 21 | 22 | actual_frequencies = {} 23 | for value, count in values_count.items(): 24 | actual_frequencies[value] = count / number_of_tries 25 | 26 | for x in range(len(probs)): 27 | nt.assert_almost_equals(actual_frequencies[x], expected_frequencies[x], places=2) 28 | -------------------------------------------------------------------------------- /examples/show_hypergraph.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import networkx as nx 3 | from matplotlib import pyplot as plt 4 | 5 | from hypergraph.generators import uniform_hypergraph 6 | from hypergraph.converters import (convert_to_nx_bipartite_graph, 7 | convert_to_custom_hyper_G) 8 | from hypergraph import utils 9 | 10 | 11 | def show_different_hypergraphs(n=10, k=3, parts=10): 12 | for fraction in np.linspace(0, 1, parts)[1:]: 13 | plt.figure(figsize=(6, 6)) 14 | g = uniform_hypergraph(n=n, k=k, 15 | number_of_edges=int(n * fraction)) 16 | G = convert_to_nx_bipartite_graph(g.nodes(), g.hyper_edges()) 17 | utils.plot_bipartite_graph(G, *utils.hypergraph_to_bipartite_parts(G)) 18 | hyper_G = convert_to_custom_hyper_G(g.nodes(), g.hyper_edges()) 19 | 20 | plt.figure(figsize=(6, 6)) 21 | nx.draw(hyper_G, 22 | node_size=3000, cmap=plt.cm.Blues, alpha=0.6) 23 | 24 | plt.show() 25 | 26 | if __name__ == '__main__': 27 | show_different_hypergraphs(parts=3) 28 | -------------------------------------------------------------------------------- /hypergraph/tests/test_generators.py: -------------------------------------------------------------------------------- 1 | import nose.tools as nt 2 | from hypergraph import generators 3 | 4 | 5 | def test_uniform_generator(): 6 | number_of_nodes = 10 7 | number_of_edges = 5 8 | cardinality = 3 9 | hu = generators.uniform_hypergraph(number_of_nodes, number_of_edges, 10 | cardinality) 11 | nt.assert_true(hu) 12 | nt.assert_equals(len(hu.nodes()), number_of_nodes) 13 | nt.assert_equals(len(hu.hyper_edges()), number_of_edges) 14 | nt.assert_true(all(len(e) == cardinality for e in hu.hyper_edges())) 15 | 16 | 17 | def test_impossible_number_of_edges_in_uniform_generator(): 18 | number_of_nodes = 5 19 | number_of_edges = 5 20 | cardinality = 5 21 | 22 | nt.assert_raises(ValueError, generators.uniform_hypergraph, number_of_nodes, number_of_edges, cardinality) 23 | 24 | 25 | def test_generator(): 26 | number_of_nodes = 10 27 | edges_params = ((2, 3), (3, 4), (4, 8)) 28 | hypergraph = generators.generic_hypergraph(number_of_nodes, edges_params) 29 | number_of_edges = sum(edge_count for _, edge_count in edges_params) 30 | nt.assert_equals(number_of_edges, len(hypergraph.hyper_edges())) 31 | 32 | -------------------------------------------------------------------------------- /hypergraph/diffusion_engine.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from collections import Counter 4 | 5 | 6 | class DiffusionEngine(): 7 | """DiffusionEngine to simulate diffusion based on Markov Chains 8 | represent by transition matrix called `markov_matrix`. 9 | 10 | Uses python futures to compute states concurrently. 11 | """ 12 | def __init__(self, markov_matrix): 13 | self.markov_matrix = markov_matrix 14 | self.available_steps = range(len(markov_matrix)) 15 | 16 | def simulate(self, t_max, current_state=0): 17 | all_states_per_iteration = [] 18 | all_states = [] 19 | c = Counter() 20 | states = simulate(self.markov_matrix, 21 | current_state, 22 | t_max) 23 | all_states += states 24 | all_states_per_iteration.append(states) 25 | for state in all_states: 26 | c[state] += 1 27 | return c.most_common(), all_states_per_iteration 28 | 29 | def __str__(self): 30 | return """DiffusionEngine with transitions: %s""" % self.markov_matrix 31 | 32 | 33 | def probabilities_to_distribution(discrete_probabilities): 34 | return [sum(discrete_probabilities[:i]) for i in range(1, len(discrete_probabilities) + 1)] 35 | 36 | 37 | def simulate(markov_matrix, current_state, t_max): 38 | state_to_distribution_function = {} 39 | for i, probabilities in enumerate(markov_matrix): 40 | state_to_distribution_function[i] = probabilities_to_distribution(probabilities) 41 | 42 | states = [] 43 | state = current_state 44 | for _ in range(t_max): 45 | state = next_value(state_to_distribution_function[state]) 46 | states.append(state) 47 | 48 | return states 49 | 50 | 51 | def next_value(distribution_function): 52 | prob = random.random() 53 | return distribution_function.index([x for x in distribution_function if x > prob][0]) 54 | -------------------------------------------------------------------------------- /hypergraph/generators.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | from scipy.misc import comb 4 | from hypergraph.hypergraph_models import HyperGraph 5 | from hypergraph.utils import is_connected 6 | 7 | 8 | def random_combinations(set_of_values, cardinality, count): 9 | combinations = set() 10 | if count > comb(len(set_of_values), cardinality): 11 | raise ValueError('There are less that {count} combinations in {set_of_values} of length {cardinality}'.format( 12 | count=count, cardinality=cardinality, set_of_values=set_of_values 13 | )) 14 | 15 | while len(combinations) < count: 16 | sample = list(set(random.sample(set_of_values, cardinality))) 17 | sample.sort() 18 | # sorted and without duplicates 19 | if len(sample) == cardinality: 20 | combinations.add(tuple(sample)) 21 | return combinations 22 | 23 | 24 | def uniform_hypergraph(n=6, number_of_edges=None, k=3, has_to_be_connected=True): 25 | """Generate hypergraph with constant cardinality of edges. 26 | 27 | :n - number of nodes, 28 | :number_of_edges - how many hyper edges will be created, 29 | :k - cardinality of hyper edges. 30 | """ 31 | nodes = range(1, n + 1) 32 | 33 | if number_of_edges is None: 34 | number_of_edges = int(math.sqrt(n)) 35 | 36 | connected = False 37 | 38 | hypergraph = None 39 | while not connected and has_to_be_connected: 40 | hyper_edges = random_combinations(nodes, k, number_of_edges) 41 | hypergraph = HyperGraph() 42 | hypergraph.add_nodes_from(nodes) 43 | hypergraph.add_edges_from(hyper_edges) 44 | connected = is_connected(hypergraph) 45 | 46 | return hypergraph 47 | 48 | 49 | def generic_hypergraph(number_of_nodes, edges_params, has_to_be_connected=True): 50 | nodes = range(1, number_of_nodes + 1) 51 | 52 | connected = False 53 | hypergraph = None 54 | while not connected and has_to_be_connected: 55 | hyper_edges = [] 56 | for cardinality, count in edges_params: 57 | edges_subset = random_combinations(nodes, cardinality, count) 58 | hyper_edges += edges_subset 59 | 60 | hypergraph = HyperGraph() 61 | 62 | hypergraph.add_nodes_from(nodes) 63 | hypergraph.add_edges_from(hyper_edges) 64 | connected = is_connected(hypergraph) 65 | return hypergraph 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /hypergraph/converters.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import itertools 3 | import pykov 4 | 5 | 6 | def convert_to_nx_bipartite_graph(nodes, hyper_edges): 7 | """Convert collections of nodes and hyper_edges to bipartite graph. 8 | 9 | Parameters: 10 | nodes: collection of nodes 11 | hyper_edges: collection of hyperedges (subsets of nodes) 12 | 13 | Returns: 14 | networkx bipartite graph in which every hyper_edge is connected 15 | to nodes it links as a hyper_edge 16 | """ 17 | G = nx.Graph() 18 | G.add_nodes_from(nodes) 19 | hyper_edges = [tuple(edge) for edge in hyper_edges] 20 | G.add_nodes_from(hyper_edges) 21 | for e in hyper_edges: 22 | for n in e: 23 | G.add_edge(n, e) 24 | return G 25 | 26 | 27 | def convert_to_custom_hyper_G(nodes, hyper_edges): 28 | """Convert nodes and hyperedges to custom hypergraph represtentation. 29 | 30 | This custom representations is: 31 | nodes are hyper_edges 32 | edges are implicit connections between hyper_edges - hyper_edges 33 | are connected if they have nonempty intersection 34 | 35 | """ 36 | nodes = [node for node in hyper_edges if isinstance(node, tuple)] 37 | edges = [(tuple(node_1), tuple(node_2)) for node_1, node_2 38 | in itertools.combinations(hyper_edges, 2) 39 | if set(node_1) & set(node_2)] 40 | hyper_G = nx.Graph() 41 | hyper_G.add_nodes_from(nodes) 42 | hyper_G.add_edges_from(edges) 43 | return hyper_G 44 | 45 | 46 | def convert_to_clique_graph(nodes, hyper_edges): 47 | """Convert nodes and hyperedges to clique graph. 48 | 49 | In practice clique graph is a graph in which every hyper_edge 50 | is translated to clique. 51 | 52 | Parameters: 53 | nodes: collection of nodes (list, tuple or set) 54 | hyper_edges: collection of subsets of nodes 55 | """ 56 | graph = nx.Graph() 57 | graph.add_nodes_from(nodes) 58 | edges = [] 59 | for hyper_edge in hyper_edges: 60 | for node_1, node_2 in itertools.combinations(hyper_edge, 2): 61 | edges.append((node_1, node_2)) 62 | graph.add_edges_from(edges) 63 | return graph 64 | 65 | 66 | def transition_matrix_to_pykov_chain(matrix): 67 | chain = pykov.Chain() 68 | 69 | for i, row in enumerate(matrix): 70 | for j, column in enumerate(row): 71 | chain[(i, j)] = column 72 | return chain -------------------------------------------------------------------------------- /hypergraph/entropy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Used definition of entropy: 3 | S = - k_{\mathrm{B}}\sum_i p_i \ln p_i 4 | 5 | """ 6 | 7 | import numpy as np 8 | 9 | from collections import Counter 10 | from itertools import chain 11 | from hypergraph import utils 12 | from matplotlib import pyplot as plt 13 | 14 | 15 | from hypergraph.markov_diffusion import (create_markov_matrix, 16 | count_nodes) 17 | from hypergraph.diffusion_engine import DiffusionEngine 18 | 19 | 20 | def entropy(pis): 21 | """Compute entropy given list of stochastic probabilities""" 22 | return -np.dot(np.log(pis), pis) 23 | 24 | 25 | def compute_states_per_time(HG, t_max, t_per_walker): 26 | 27 | # todo use markov_matrix model nodes instead 28 | markov_matrix = create_markov_matrix(HG.hyper_edges()) 29 | 30 | engine = DiffusionEngine(markov_matrix, t_per_walker=t_per_walker) 31 | 32 | most_common, states = engine.simulate(t_max) 33 | plt.show() 34 | 35 | states_per_time = list(zip(*states)) 36 | 37 | return states_per_time 38 | 39 | 40 | def entropy_value(states, nodes, edges): 41 | """Compute entropy values from states, nodes and edges. 42 | 43 | It recomputes edge occurrences to node occurrences. 44 | It's too much coupled with bipartite model. 45 | #TODO - decouple it 46 | 47 | """ 48 | cum_states = chain(*states) 49 | most_common = Counter(cum_states).most_common() 50 | most_common_nodes = count_nodes(nodes, edges, most_common) 51 | frequencies = (utils.get_names_and_occurrences(most_common_nodes)[1]) 52 | return entropy(frequencies) 53 | 54 | 55 | def compare_entropy(HG, t_max=10000, t_per_walker=100, title=None, 56 | filename=None): 57 | """Simulate diffusion and show how entropy changes in time""" 58 | 59 | nodes = HG.nodes() 60 | edges = HG.hyper_edges() 61 | 62 | states_per_time = compute_states_per_time(HG, t_max, t_per_walker) 63 | state_indices = list(range(len(states_per_time))[4:]) 64 | 65 | ys = [entropy_value(states_per_time[:i], nodes, edges) 66 | for i in state_indices] 67 | 68 | plt.plot(state_indices, ys) 69 | if title: 70 | plt.title(title) 71 | if filename: 72 | plt.savefig(filename) 73 | 74 | 75 | if __name__ == '__main__': 76 | k = 3 77 | f = 1.6 78 | 79 | for n in range(10, 30, 5): 80 | HG = utils.create_graph(n, k, f) 81 | filename = 'entropy_h_%s_%s_%s.png' % (n, k, f) 82 | compare_entropy(HG, filename=filename) 83 | 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stories in Ready](https://badge.waffle.io/atterothegreatest/hypergraph.png?label=ready&title=Ready)](https://waffle.io/atterothegreatest/hypergraph) 2 | #Hypergraphs are fun! 3 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/atteroTheGreatest/hypergraph?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Code is written in python 3. 6 | 7 | I use tools such as 8 | 9 | - [IPython](http://ipython.org/) 10 | - [numpy](http://www.numpy.org/) 11 | - [networkx](http://networkx.github.io/) 12 | 13 | 14 | ##Installation 15 | 16 | - clone the library 17 | - create virtualenv 18 | - install requirements 19 | - run setup.py 20 | 21 | ``` 22 | $ python setup.py install 23 | ``` 24 | 25 | You can also use [automated provisioning](https://github.com/atteroTheGreatest/hypergraph-provisioning) 26 | which would set up 27 | a virtual machine with all dependencies installed and ready to 28 | use interactive examples in IPython notebook server. 29 | 30 | 31 | I write about first two on my 32 | [blog](http://blog.atte.ro/posts/python-for-science.html)... 33 | 34 | IPython has a powerful notebook which is amazing for sharing code 35 | and insights and for interactive development. 36 | 37 | ##Links to notebooks: 38 | 39 | ###How can we represent hypergraphs in code? 40 | A very good representation is bipartite graph of nodes and hyperedges. 41 | Another representation is a graph with hyperedges as nodes connected if 42 | hyperedges have common nodes. Is it a good representation for our 43 | diffusion problem? Can we use it as a reference model? 44 | 45 | - [hypergraphs 1](http://nbviewer.ipython.org/github/atteroTheGreatest/hypergraph/blob/master/notebooks/hypergraphs_1.ipynb?create=1) 46 | 47 | 48 | ###How different are hypergraphs from graphs with cliques? 49 | Cliques are sets of nodes in which every node is connected to every other node. 50 | They are a bit similar to hypergraphs, but how the nodes are connected 51 | is conceptually different from hypergraphs. 52 | 53 | ###How are hypergraphs different from graphs with cliques in diffusion simulation with markov chain? 54 | 55 | - [hypergraphs vs graphs of cliques - diffusion](http://nbviewer.ipython.org/github/atteroTheGreatest/hypergraph/blob/master/hypergraph/clique_comparison.ipynb?create=1) 56 | 57 | ###Hypergraph traversal with random walk 58 | 59 | I developed two models. One is node based, second is edge based. 60 | 61 | You can find an interactive notebook here: 62 | 63 | - [models, numerical solutions, interactive comparison](http://nbviewer.ipython.org/github/atteroTheGreatest/hypergraph/blob/master/notebooks/Modelling%20diffusion%20on%20hypergraph.ipynb?create=1) 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/compare_hypergraphs_with_cliques.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from hypergraph.generators import uniform_hypergraph 3 | from hypergraph import converters 4 | from hypergraph import utils 5 | from hypergraph.diffusion_engine import DiffusionEngine 6 | from hypergraph.markov_diffusion import (create_markov_matrix, 7 | count_nodes) 8 | 9 | # TODO change create_markov_matrix to create_markov_matrix model node 10 | 11 | 12 | def compare_hypergraph_with_cliques(number_of_nodes, 13 | cardinality, fraction, t_max, 14 | plot_representations=False): 15 | """Create hypergraph and clique, run diffusion and compare""" 16 | HG = uniform_hypergraph( 17 | n=number_of_nodes, 18 | k=cardinality, 19 | number_of_edges=int( 20 | number_of_nodes * 21 | fraction)) 22 | 23 | nodes = HG.nodes() 24 | hyperedges = HG.hyper_edges() 25 | 26 | all_nodes = [] 27 | for hyperedge in hyperedges: 28 | all_nodes += hyperedge 29 | 30 | if plot_representations: 31 | utils.plot_different_representations(nodes, hyperedges) 32 | 33 | markov_matrix = create_markov_matrix(hyperedges) 34 | print(markov_matrix) 35 | engine = DiffusionEngine(markov_matrix) 36 | most_common, states = engine.simulate(t_max) 37 | 38 | plt.figure(figsize=(12, 10)) 39 | utils.plot_hyperedges_frequencies(most_common, hyperedges, 40 | 'Occurrences of hyperedges' 41 | ' in a hypergraph') 42 | 43 | most_common_nodes = count_nodes(nodes, hyperedges, most_common) 44 | 45 | plt.figure(figsize=(12, 10)) 46 | utils.plot_nodes_frequencies(most_common_nodes, 'Nodes in a hypergraph') 47 | 48 | clique_graph = converters.convert_to_clique_graph(nodes, hyperedges) 49 | clique_markov_matrix = create_markov_matrix(clique_graph.edges()) 50 | 51 | print("clique markov matrix") 52 | print(clique_markov_matrix) 53 | 54 | engine = DiffusionEngine(markov_matrix) 55 | most_common, states = engine.simulate(t_max) 56 | 57 | plt.figure(figsize=(12, 10)) 58 | utils.plot_hyperedges_frequencies(most_common, clique_graph.edges(), 59 | 'Occurrences of edges in a graph') 60 | 61 | most_common_nodes = count_nodes(clique_graph.nodes(), clique_graph.edges(), 62 | most_common) 63 | plt.figure(figsize=(12, 10)) 64 | utils.plot_nodes_frequencies(most_common_nodes, 'Nodes in a graph') 65 | 66 | 67 | def demo(): 68 | n = 20 69 | k = 3 70 | fraction = 2.0 / 3 71 | 72 | for simulation_time in [100]: 73 | compare_hypergraph_with_cliques(n, k, fraction, simulation_time) 74 | 75 | 76 | if __name__ == '__main__': 77 | demo() 78 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | 2 | ##Hypergraph representation 3 | 4 | It would be extremely nice to have a HyperGraph class similar 5 | to other network classes in networkx. 6 | 7 | However, I still don't have a really good idea of representing 8 | hypergraphs. Probably the best way would be to store nodes and 9 | hyperedges and add other info as some kind of surplus? 10 | 11 | My current idea is to inherit after networkx Graph class and provide 12 | similar interface. Basically what it does now is representing hypergraph 13 | as clique on nodes adjacency level (I suspect that multigraph approach 14 | would be better) and as graph of hyper edges. 15 | 16 | I need to write more methods and think about this multigraph approach 17 | because it sounds pretty intersting! 18 | 19 | ##Networkx tutorial thoughts 20 | 21 | It was a very simple tutorial! 22 | 23 | 24 | ##Hypergraphs and matroids paper 25 | 26 | What is incidence matrix? 27 | 28 | Oh, this paper wasn't really good. But it used the book: 29 | Berge-Holland hypergraphs and it looks promising 30 | 31 | I have this idea of writing a series of blogposts about 32 | hypergraphs using this book as reference material to understand 33 | it better and promote hypergraphs as a whole. Cool! 34 | 35 | ##Materials about markov chains from UW 36 | 37 | It looks really promising! 38 | 39 | Równanie Chapmana-Kołmogorowa - I have no idea, but when I was checking it 40 | with octave it didn't hold! 41 | 42 | 43 | ##Diffusion on markov chains 44 | 45 | I really have this specific uncertainty about it. The best way to be sure 46 | is to check it. I'm reading now the ipython notebook on markov chains. 47 | 48 | ##Time to get wiser? 49 | 50 | I downloaded a book about markov chains. 51 | 52 | ![markov chains](home/att/random_walk_mc.jpeg) 53 | 54 | The article/lecture was helpful! It has a theorem with choosing the one element 55 | and powering matrix, yay. 56 | 57 | I'll check it with my case. It prooves this theorem with 58 | degree of the node and it's stationary distribution pi! 59 | 60 | It should be the case with my hyperedges "degree", 61 | it's easy to check. 62 | 63 | 64 | It's again a few days/weeks later 65 | 66 | ##21-04-2014 Tests in hypergraph repository 67 | 68 | I think that I should think more about what I do here. There is a lack 69 | of design. It's a bit too much bottom up work. 70 | 71 | I have some distinct modules and notebooks. And this is awesome. 72 | 73 | I would like to work more on those notebooks to make them tell 74 | complete stories. It's important for my research. I don't want to realize 75 | that, "well, I haven't thought about all those corner cases, and it was 76 | a premature generalization". It's important to think! 77 | 78 | To ensure good design and that my software works - I want to introduce 79 | testing to the system. 80 | 81 | Tests don't have to be too complicated. I think that python nose 82 | would be good enough. 83 | 84 | But what do I want to test? Probably it would be awesome to have every module 85 | tested. I also have those "demo modules" and notebooks. And they should 86 | work well. 87 | 88 | 89 | -------------------------------------------------------------------------------- /hypergraph/analytical.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from collections import Counter 3 | 4 | 5 | def prediction(graph, model="hypergraph", plot_results=False): 6 | """Predict diffusion on given graph using `model`. 7 | 8 | Parameters: 9 | graph: networkx graph or hypergraph 10 | model: name of model used to compute prediction, currently 11 | available models are: 12 | - hypergraph 13 | - clique 14 | plot_results: should results be plotted in function? 15 | 16 | Returns: 17 | list of numbers representing probability of being in given node 18 | """ 19 | 20 | computations = { 21 | "hypergraph_nodes": { 22 | "prediction": analytical_hypergraph_nodes, 23 | "title": "Analytical prediction of diffusion on hypergraph", 24 | }, 25 | "hypergraph_edges": { 26 | "prediction": analytical_hypergraph_edges, 27 | "title": "Analytical prediction of diffusion on hypergraph using edges", 28 | }, 29 | "clique": { 30 | "prediction": analytical_clique_diffusion, 31 | "title": "Analytical prediction of diffusion on clique", 32 | } 33 | } 34 | 35 | if model in computations: 36 | xs, ys = computations[model]['prediction'](graph) 37 | title = computations[model]['title'] 38 | else: 39 | title = None 40 | xs, ys = [], [] 41 | if plot_results: 42 | plt.bar(xs, ys) 43 | plt.title(title) 44 | 45 | return ys 46 | 47 | 48 | def analytical_clique_diffusion(graph): 49 | """Predict probabilities of being in a node on graph diffusion 50 | 51 | Parameters: 52 | graph: networkx graph, should have `degree` method implemented 53 | 54 | Returns: 55 | tuple of lists: nodes, probabilities 56 | """ 57 | xs, ys = zip(*graph.degree().items()) 58 | sum_of_ys = sum(ys) 59 | ys = [float(y) / sum_of_ys for y in ys] 60 | return xs, ys 61 | 62 | 63 | def analytical_hypergraph_nodes(hyper_graph): 64 | """Predict probabilities of being in a node in a hypergraph 65 | 66 | Parameters: 67 | hyper_graph: instance of `hypergraph`, should have `nodes` and `hyper_edges` 68 | methods implemented 69 | 70 | Predict using bipartite model from perspective of nodes. 71 | """ 72 | nodes = hyper_graph.nodes() 73 | hyper_edges = hyper_graph.hyper_edges() 74 | all_nodes = [] 75 | for hyperedge in hyper_edges: 76 | all_nodes += hyperedge 77 | 78 | c = Counter(all_nodes) 79 | for node in nodes: 80 | if node not in all_nodes: 81 | c[node] = 0 82 | 83 | xs, ys = zip(*c.items()) 84 | 85 | sum_of_ys = sum(ys) 86 | ys = [y / sum_of_ys for y in ys] 87 | 88 | return xs, ys 89 | 90 | 91 | def analytical_hypergraph_edges(hyper_graph): 92 | """Predict probabilities for edge model. 93 | 94 | :param hyper_graph to predict diffusion on : 95 | :return: list of probabilities of ergodic state 96 | """ 97 | phis = [] 98 | all_phis = 0 99 | for edge in hyper_graph.hyper_edges(): 100 | edge_cardinality = len(edge) 101 | phis.append(edge_cardinality) 102 | all_phis += edge_cardinality 103 | 104 | pis = [phi / all_phis for phi in phis] 105 | xs = range(len(pis)) 106 | return xs, pis -------------------------------------------------------------------------------- /hypergraph/markov_diffusion.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import defaultdict 3 | 4 | from collections import Counter 5 | 6 | 7 | def create_markov_matrix(edges, count_itself=False): 8 | """Create transition matrix of Markov chain 9 | 10 | DEPRECATED 11 | 12 | Parameters: 13 | edges: collection of edges 14 | count_itself: flag determining if there should be loops 15 | 16 | Algorithm works as follows. Every edge is compared 17 | with other edges. If they have nonempty intersection, 18 | appropriate cell in adjacency matrix gets cardinality 19 | of this intersection. 20 | 21 | If `count_itself` is True, diagonal of adjacency 22 | matrix has number of nodes in hyperedge that doesn't 23 | belong to any intersection (leftovers). 24 | """ 25 | a_matrix = np.zeros((len(edges), len(edges))) 26 | 27 | for i, edge_1 in enumerate(edges): 28 | not_in_other_edges = set(edge_1) 29 | for j, edge_2 in enumerate(edges): 30 | if i != j: 31 | a_matrix[i][j] = len(set(edge_1) & set(edge_2)) 32 | not_in_other_edges -= set(edge_2) 33 | 34 | if count_itself: 35 | a_matrix[i][i] = len(not_in_other_edges) 36 | 37 | for i, edge_1 in enumerate(edges): 38 | a_matrix[i] /= float(sum(a_matrix[i])) 39 | 40 | return a_matrix 41 | 42 | 43 | def populate_node_hyperedges(hyper_graph): 44 | """Create a dictionary with nodes V as keys and hyperedges E that E contains V as values""" 45 | node_hyperedges = defaultdict(list) 46 | for hyper_edge in hyper_graph.hyper_edges(): 47 | for node in hyper_edge: 48 | node_hyperedges[node].append(hyper_edge) 49 | return node_hyperedges 50 | 51 | 52 | def create_markov_matrix_model_nodes(hyper_graph): 53 | 54 | # create N x N matrix with zeroes 55 | number_of_nodes = len(hyper_graph.nodes()) 56 | markov_matrix = np.zeros((number_of_nodes, number_of_nodes)) 57 | 58 | node_hyper_edges = populate_node_hyperedges(hyper_graph) 59 | 60 | # fill transition matrix with all possible ways to get from V_i to V_j 61 | for node in hyper_graph.nodes(): 62 | for hyper_edge in node_hyper_edges[node]: 63 | for node2 in hyper_edge: 64 | markov_matrix[node - 1, node2 - 1] += 1 / len(hyper_edge) / len(node_hyper_edges[node]) 65 | 66 | return markov_matrix 67 | 68 | 69 | def create_markov_matrix_model_hyper_edges(hyper_graph): 70 | 71 | number_of_edges = len(hyper_graph.hyper_edges()) 72 | node_edges = np.zeros((len(hyper_graph.nodes()), number_of_edges)) 73 | 74 | for node in hyper_graph.nodes(): 75 | node_edges[node - 1] = np.array([node in edge for edge in hyper_graph.hyper_edges()]) 76 | 77 | markov_matrix = np.zeros((number_of_edges, number_of_edges)) 78 | 79 | for i, edge in enumerate(hyper_graph.hyper_edges()): 80 | for node in edge: 81 | node_index = node - 1 82 | for j, contained in enumerate(node_edges[node_index]): 83 | markov_matrix[i, j] += contained / len(edge) / sum([edge for edge in node_edges[node_index] if edge]) 84 | 85 | return markov_matrix 86 | 87 | 88 | def count_nodes(nodes, edges, occurrences): 89 | """Given nodes, edges and occurrences of edges, 90 | return recalculated occurrences of nodes. 91 | 92 | If node is in edge, it get one more occurrence. 93 | """ 94 | c = Counter() 95 | for index, count in occurrences: 96 | for edge in edges[index]: 97 | c[edge] += count 98 | for node in nodes: 99 | if node not in c: 100 | c[node] = 0 101 | return c -------------------------------------------------------------------------------- /hypergraph/utils.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | 3 | import networkx as nx 4 | import numpy as np 5 | from hypergraph import converters 6 | 7 | 8 | def plot_bipartite_graph(G, group_1, group_2): 9 | """Draw graph as bipartite graph (U, V, E). 10 | 11 | Parameters: 12 | G: a networkx graph 13 | group_1: U 14 | group_2: V 15 | """ 16 | pos = {x: (0, float(i % 20) * 2) for i, x in enumerate(group_1)} 17 | pos.update({node: (18.3, 0 + float(i % 20) * 2) for i, 18 | node in enumerate(group_2)}) 19 | 20 | plt.figure() 21 | nx.draw(G, pos, node_color='m', node_size=800, 22 | with_labels=True, width=1.3, alpha=0.4) 23 | 24 | 25 | def hypergraph_to_bipartite_parts(G): 26 | """Convert hypergraph to two parts: nodes and edges. 27 | """ 28 | group_1 = (node for node in G.nodes() if not isinstance(node, tuple)) 29 | group_2 = (node for node in G.nodes() if isinstance(node, tuple)) 30 | return group_1, group_2 31 | 32 | 33 | def plot_transition_matrix(matrix): 34 | conf_arr = matrix 35 | 36 | norm_conf = [] 37 | for i in conf_arr: 38 | a = 0 39 | tmp_arr = [] 40 | a = sum(i, 0) 41 | for j in i: 42 | tmp_arr.append(float(j) / float(a)) 43 | norm_conf.append(tmp_arr) 44 | 45 | fig = plt.figure(figsize=(12, 10)) 46 | plt.clf() 47 | ax = fig.add_subplot(111) 48 | ax.set_aspect(1) 49 | ax.imshow(np.array(norm_conf), cmap=plt.cm.gray, 50 | interpolation='nearest') 51 | 52 | width = len(conf_arr) 53 | height = len(conf_arr[0]) 54 | 55 | for x in range(width): 56 | for y in range(height): 57 | ax.annotate("%.2f" % (conf_arr[x][y]), xy=(y, x), 58 | horizontalalignment='center', 59 | verticalalignment='center') 60 | 61 | index = range(1, len(matrix) + 1) 62 | plt.xticks(range(width), index) 63 | plt.yticks(range(height), index) 64 | 65 | 66 | def plot_different_representations(nodes, hyperedges): 67 | print("Drawing different representations of hypergraph") 68 | 69 | print("Bipartite graph") 70 | nx_bipartite = converters.convert_to_nx_bipartite_graph(nodes, hyperedges) 71 | plot_bipartite_graph(nx_bipartite, 72 | *hypergraph_to_bipartite_parts(nx_bipartite)) 73 | 74 | print("Graph of hypereges as nodes") 75 | custom_hyper_g = converters.convert_to_custom_hyper_G(nodes, hyperedges) 76 | plt.figure() 77 | nx.draw(custom_hyper_g) 78 | 79 | print("Clique graph") 80 | 81 | clique_graph = converters.convert_to_clique_graph(nodes, hyperedges) 82 | plt.figure() 83 | nx.draw(clique_graph) 84 | 85 | 86 | 87 | def plot_hyperedges_frequencies(most_common, hyperedges, title, normed=True): 88 | hyperedges_indexes, occurrences = zip(*most_common) 89 | if normed: 90 | occurrences /= np.sum(occurrences) 91 | plt.bar(hyperedges_indexes, occurrences) 92 | plt.title(title) 93 | plt.xticks(range(len(hyperedges)), hyperedges) 94 | 95 | 96 | def get_names_and_occurrences(most_common_nodes, normed=True): 97 | names_of_nodes = list(most_common_nodes.keys()) 98 | node_occurrences = list(most_common_nodes.values()) 99 | 100 | if normed: 101 | all_occurrences = sum(node_occurrences) 102 | node_occurrences = [float(oc) / all_occurrences 103 | for oc in node_occurrences] 104 | return names_of_nodes, node_occurrences 105 | 106 | 107 | def plot_nodes_frequencies(most_common_nodes, title, normed=True): 108 | names, node_occurrences = get_names_and_occurrences(most_common_nodes, 109 | normed) 110 | plt.bar(names, 111 | node_occurrences, 112 | alpha=0.7, color='magenta') 113 | 114 | plt.title(title) 115 | plt.show() 116 | return node_occurrences 117 | 118 | 119 | def is_connected(HG): 120 | nodes = HG.nodes() 121 | hyper_edges = HG.hyper_edges() 122 | 123 | nodes_in_hyperedges = set() 124 | for hyper_edge in hyper_edges: 125 | nodes_in_hyperedges |= hyper_edge 126 | 127 | is_every_node_in_edge = not bool(set(nodes) - nodes_in_hyperedges) 128 | 129 | return nx.is_connected(HG) and is_every_node_in_edge 130 | -------------------------------------------------------------------------------- /hypergraph/notebook_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | simple example script for running and testing notebooks. 3 | 4 | Each cell is submitted to the kernel, and the outputs are compared with those stored in the notebook. 5 | """ 6 | 7 | import os 8 | import sys 9 | import re 10 | 11 | from collections import defaultdict 12 | from queue import Empty 13 | 14 | from IPython.kernel import KernelManager 15 | from IPython.nbformat.current import NotebookNode 16 | 17 | 18 | def sanitize(s): 19 | """sanitize a string for comparison. 20 | 21 | fix universal newlines, strip trailing newlines, and normalize likely random values (memory addresses and UUIDs) 22 | """ 23 | if not isinstance(s, str): 24 | return s 25 | # normalize newline: 26 | s = s.replace('\r\n', '\n') 27 | 28 | # ignore trailing newlines (but not space) 29 | s = s.rstrip('\n') 30 | 31 | # normalize hex addresses: 32 | s = re.sub(r'0x[a-f0-9]+', '0xFFFFFFFF', s) 33 | 34 | # normalize UUIDs: 35 | s = re.sub(r'[a-f0-9]{8}(\-[a-f0-9]{4}){3}\-[a-f0-9]{12}', 'U-U-I-D', s) 36 | 37 | return s 38 | 39 | 40 | def consolidate_outputs(outputs): 41 | """consolidate outputs into a summary dict (incomplete)""" 42 | data = defaultdict(list) 43 | data['stdout'] = '' 44 | data['stderr'] = '' 45 | 46 | for out in outputs: 47 | if out.type == 'stream': 48 | data[out.stream] += out.text 49 | elif out.type == 'pyerr': 50 | data['pyerr'] = dict(ename=out.ename, evalue=out.evalue) 51 | else: 52 | for key in ('png', 'svg', 'latex', 'html', 'javascript', 'text', 'jpeg',): 53 | if key in out: 54 | data[key].append(out[key]) 55 | return data 56 | 57 | 58 | def compare_outputs(test, ref, skip_compare=('png', 'traceback', 'latex', 'prompt_number')): 59 | for key in ref: 60 | if key not in test: 61 | print("missing key: %s != %s" % (test.keys(), ref.keys())) 62 | return False 63 | elif key not in skip_compare and sanitize(test[key]) != sanitize(ref[key]): 64 | return False 65 | return True 66 | 67 | 68 | def run_cell(shell, iopub, cell): 69 | shell.execute(cell.input) 70 | # wait for finish, maximum 20s 71 | shell.get_msg(timeout=20) 72 | outs = [] 73 | 74 | while True: 75 | try: 76 | msg = iopub.get_msg(timeout=0.2) 77 | except Empty: 78 | break 79 | msg_type = msg['msg_type'] 80 | if msg_type in ('status', 'pyin'): 81 | continue 82 | elif msg_type == 'clear_output': 83 | outs = [] 84 | continue 85 | 86 | content = msg['content'] 87 | out = NotebookNode(output_type=msg_type) 88 | 89 | if msg_type == 'stream': 90 | out.stream = content['name'] 91 | out.text = content['data'] 92 | elif msg_type in ('display_data', 'pyout'): 93 | out['metadata'] = content['metadata'] 94 | for mime, data in content['data'].items(): 95 | attr = mime.split('/')[-1].lower() 96 | # this gets most right, but fix svg+html, plain 97 | attr = attr.replace('+xml', '').replace('plain', 'text') 98 | setattr(out, attr, data) 99 | if msg_type == 'pyout': 100 | out.prompt_number = content['execution_count'] 101 | elif msg_type == 'pyerr': 102 | out.ename = content['ename'] 103 | out.evalue = content['evalue'] 104 | out.traceback = content['traceback'] 105 | elif msg_type not in ('comm_msg', 'comm_open'): 106 | print("unhandled iopub msg:", msg_type) 107 | 108 | outs.append(out) 109 | return outs 110 | 111 | 112 | def run_notebook(nb): 113 | km = KernelManager() 114 | km.start_kernel(extra_arguments=['--pylab=inline'], stderr=open(os.devnull, 'w')) 115 | kc = km.client() 116 | kc.start_channels() 117 | iopub = kc.iopub_channel 118 | shell = kc.shell_channel 119 | 120 | # run %pylab inline, because some notebooks assume this 121 | # even though they shouldn't 122 | shell.execute("pass") 123 | shell.get_msg() 124 | while True: 125 | try: 126 | iopub.get_msg(timeout=1) 127 | except Empty: 128 | break 129 | 130 | successes = 0 131 | failures = 0 132 | errors = 0 133 | for ws in nb.worksheets: 134 | for cell in ws.cells: 135 | if cell.cell_type != 'code': 136 | continue 137 | try: 138 | run_cell(shell, iopub, cell) 139 | except Exception as e: 140 | print("failed to run cell:", repr(e)) 141 | print(cell.input) 142 | errors += 1 143 | continue 144 | 145 | failed = False 146 | 147 | if failed: 148 | failures += 1 149 | else: 150 | successes += 1 151 | sys.stdout.write('.') 152 | 153 | print("tested notebook %s" % nb.metadata.name) 154 | print(" %3i cells successfully replicated" % successes) 155 | if failures: 156 | print(" %3i cells mismatched output" % failures) 157 | if errors: 158 | print(" %3i cells failed to complete" % errors) 159 | kc.stop_channels() 160 | km.shutdown_kernel() 161 | del km 162 | -------------------------------------------------------------------------------- /hypergraph/diffusion_convergence_analytic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import stats 3 | from matplotlib import pyplot as plt 4 | from hypergraph import converters 5 | from hypergraph import analytical 6 | from hypergraph.diffusion_engine import DiffusionEngine 7 | from hypergraph.markov_diffusion import (create_markov_matrix, 8 | count_nodes) 9 | from hypergraph import utils 10 | 11 | 12 | def diffusion_on_hypergraph(hypergraph, markov_matrix, 13 | t_max, plot_results=False): 14 | """Simulate numerically diffusion on a hypergraph, 15 | 16 | Diffusion is simulated using Markov Chains. 17 | 18 | """ 19 | nodes = hypergraph.nodes() 20 | hyper_edges = hypergraph.hyper_edges() 21 | 22 | most_common, most_common_nodes, states = simulate_diffusion( 23 | nodes, hyper_edges, markov_matrix, t_max) 24 | 25 | if plot_results: 26 | return plot_diffusion_results(most_common, most_common_nodes, 27 | hyper_edges, "hypergraph") 28 | else: 29 | return utils.get_names_and_occurrences(most_common_nodes)[1] 30 | 31 | 32 | def plot_diffusion_results(most_common, most_common_nodes, edges, name): 33 | """Plot results of diffusion. 34 | 35 | Parameters: 36 | most_common: indexes of hyperedges and their frequences 37 | most_common_nodes: indexes of nodes and their frequences 38 | edges: names of edge 39 | name: name of object on which diffusion was simulated, used 40 | plot title 41 | 42 | Returns: 43 | probabilities of being in a node 44 | 45 | """ 46 | plt.figure(figsize=(8, 4)) 47 | utils.plot_hyperedges_frequencies(most_common, edges, 48 | ('Occurrences of hyperedges in' 49 | ' a {}').format(name), 50 | normed=True) 51 | 52 | plt.figure(figsize=(8, 4)) 53 | return utils.plot_nodes_frequencies(most_common_nodes, 54 | 'Nodes in a {}'.format(name), 55 | normed=True) 56 | 57 | 58 | def simulate_diffusion(nodes, edges, markov_matrix, t_max): 59 | """Simulate diffusion using diffusion engine. 60 | 61 | Returns: 62 | most_common: hyper_edges to their probabilities of occurrences 63 | most_common_nodes: similar to above, but with nodes 64 | states: list of states which were visited by workers 65 | """ 66 | engine = DiffusionEngine(markov_matrix, t_per_walker=100) 67 | most_common, states = engine.simulate(t_max) 68 | most_common_nodes = count_nodes(nodes, edges, most_common) 69 | return most_common, most_common_nodes, states 70 | 71 | 72 | def diffusion_on_clique(hypergraph, t_max, plot_results=False): 73 | """Simulate diffusion on corresponding clique graph""" 74 | nodes = hypergraph.nodes() 75 | hyper_edges = hypergraph.hyper_edges() 76 | clique_graph = converters.convert_to_clique_graph(nodes, hyper_edges) 77 | 78 | # TODO change to markov matrix model nodes 79 | markov_matrix = create_markov_matrix(clique_graph.edges(), 80 | count_itself=False) 81 | edges = clique_graph.edges() 82 | 83 | most_common, most_common_nodes, states = simulate_diffusion( 84 | nodes, edges, markov_matrix, t_max) 85 | if plot_results: 86 | return plot_diffusion_results(most_common, most_common_nodes, 87 | hyper_edges, "clique") 88 | else: 89 | return utils.get_names_and_occurrences(most_common_nodes)[1] 90 | 91 | 92 | def correct_zero(value): 93 | """Add small amount if value is 0. 94 | 95 | Used as workaround of scipy.chisquare bug. 96 | """ 97 | if value == 0: 98 | value += 0.00001 99 | return value 100 | 101 | 102 | def compare_to_theory(experimental, *theoretical_models): 103 | """Use chisquare test to evaluate models""" 104 | 105 | # correcting 0 is a workaround of scipy.chisquare bug 106 | # this bug gives nan in test if there is zero in data 107 | experimental = [correct_zero(node) for node in experimental] 108 | 109 | for theoretical in theoretical_models: 110 | theoretical = [correct_zero(node) for node in theoretical] 111 | 112 | return stats.chisquare(experimental, 113 | f_exp=theoretical_models, 114 | axis=1) 115 | 116 | 117 | def comparing_pipeline(t_max=10000): 118 | """Generate various hypergraphs, run diffusion and compare models 119 | """ 120 | 121 | # TODO rewrite to use new hypergraph models with differenty created transition matrices 122 | for n in range(20, 31, 5): 123 | for k in range(3, 4): 124 | for f in range(90, 91, 10): 125 | f = float(f) / 100 126 | # number_of_nodes, cardinality, fraction_of_hyperedges 127 | print(n, k, f) 128 | 129 | HG = utils.create_graph(n, k, f) 130 | number_of_nodes = analytical.prediction(HG) 131 | number_of_nodes_clique = analytical.prediction(HG, 132 | model="clique") 133 | 134 | # TODO change to matrix model nodes 135 | markov_matrix_loops = create_markov_matrix(HG.hyper_edges(), 136 | count_itself=True) 137 | markov_matrix = create_markov_matrix(HG.hyper_edges(), 138 | count_itself=False) 139 | 140 | simulated_n_o_n = diffusion_on_hypergraph(HG, markov_matrix, 141 | t_max) 142 | simulated_n_o_n_i = diffusion_on_hypergraph(HG, 143 | markov_matrix_loops, 144 | t_max) 145 | 146 | simulated_n_o_n_c = diffusion_on_clique(HG, t_max=t_max) 147 | 148 | plt.figure(figsize=(12, 10)) 149 | 150 | width = 0.15 151 | plt.bar(HG.nodes(), simulated_n_o_n, 152 | width=width, color='crimson', 153 | label='Simulated markov hypergraph') 154 | 155 | plt.bar(np.array(HG.nodes()) + width, simulated_n_o_n_i, width, 156 | color='burlywood', 157 | label='Simulated markov hypergraph with loops') 158 | 159 | plt.bar(np.array(HG.nodes()) + 2 * width, number_of_nodes, 160 | width, 161 | label='Analytical diffusion model on hypergraph', 162 | color="#65df25") 163 | 164 | plt.bar(np.array(HG.nodes()) + 3 * width, 165 | simulated_n_o_n_c, width, 166 | label='Simulated clique graph') 167 | 168 | plt.bar(np.array(HG.nodes()) + 4 * width, 169 | number_of_nodes_clique, width, 170 | label='Analytical diffusion model on clique', 171 | color="#dcab11") 172 | 173 | plt.legend(loc=0) 174 | plt.savefig("next_diffusion_%s_%s_%s.png" % (n, k, f)) 175 | 176 | 177 | if __name__ == '__main__': 178 | comparing_pipeline() 179 | -------------------------------------------------------------------------------- /hypergraph/hypergraph_models.py: -------------------------------------------------------------------------------- 1 | """HyperGraph implementation based on networkx Graph""" 2 | import networkx as nx 3 | 4 | 5 | class HyperGraph(nx.Graph): 6 | """HyperGraph representing hypergraph. """ 7 | 8 | def __init__(self, *args, **kwargs): 9 | """@todo: to be defined1. """ 10 | super().__init__(*args, **kwargs) 11 | self.hyperedge = [] 12 | self.hadj = {} 13 | 14 | def add_edge(self, edge, attr_dict=None, **attr): 15 | """Add an hyperedge between nodes. 16 | 17 | Parameters 18 | ---------- 19 | edge : list of nodes 20 | Nodes can be, for example, strings or numbers. 21 | Nodes must be hashable (and not None) Python objects. 22 | key : hashable identifier, optional (default=lowest unused integer) 23 | Used to distinguish multiedges between a pair of nodes. 24 | attr_dict : dictionary, optional (default= no attributes) 25 | Dictionary of edge attributes. Key/value pairs will 26 | update existing data associated with the edge. 27 | attr : keyword arguments, optional 28 | Edge data (or labels or objects) can be assigned using 29 | keyword arguments. 30 | 31 | See Also 32 | -------- 33 | add_edges_from : add a collection of edges 34 | 35 | Notes 36 | ----- 37 | To replace/update edge data, use the optional key argument 38 | to identify a unique edge. Otherwise a new edge will be created. 39 | 40 | Examples 41 | -------- 42 | The following all add the edge e=(1,2) to graph G: 43 | 44 | >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 45 | >>> e = (1, 2, 4) 46 | >>> G.add_edge(*e) # single edge as tuple of two nodes 47 | >>> G.add_edges_from( [(1,2), (1, 2, 3)] ) # add edges from iterable 48 | 49 | Associate data to edges using keywords: 50 | 51 | >>> G.add_edge((1, 2), weight=3) 52 | >>> G.add_edge((1, 2), weight=4) # update data for key=0 53 | >>> G.add_edge((1, 3, 4), weight=7, capacity=15, length=342.7) 54 | """ 55 | 56 | edge = set(edge) 57 | # set up attribute dict 58 | if attr_dict is None: 59 | attr_dict = attr 60 | else: 61 | try: 62 | attr_dict.update(attr) 63 | except AttributeError: 64 | raise nx.NetworkXError( 65 | "The attr_dict argument must be a dictionary.") 66 | # add nodes 67 | for node in edge: 68 | if node not in self.adj: 69 | self.adj[node] = {} 70 | self.node[node] = {} 71 | for v in set(edge) - set([node]): 72 | datadict = self.adj[node].get(v, {}) 73 | datadict.update(attr_dict) 74 | self.adj[node][v] = datadict 75 | self.adj[v][node] = datadict 76 | 77 | new_edge_index = len(self.hyperedge) 78 | self.hyperedge.append(set(edge)) 79 | self.hadj[new_edge_index] = {} 80 | 81 | for i, old_edge in enumerate(self.hyperedge): 82 | if i != new_edge_index: 83 | self.hadj[i][new_edge_index] = len(edge & old_edge) 84 | self.hadj[new_edge_index][i] = self.hadj[i][new_edge_index] 85 | 86 | def add_edges_from(self, ebunch, attr_dict=None, **attr): 87 | """Add all the edges in ebunch. 88 | 89 | Parameters 90 | ---------- 91 | ebunch : container of edges 92 | Each edge given in the container will be added to the 93 | graph. The edges can be arbitrary long collection of nodes. 94 | 95 | attr_dict : dictionary, optional (default= no attributes) 96 | Dictionary of edge attributes. Key/value pairs will 97 | update existing data associated with each edge. 98 | attr : keyword arguments, optional 99 | Edge data (or labels or objects) can be assigned using 100 | keyword arguments. 101 | 102 | 103 | See Also 104 | -------- 105 | add_edge : add a single edge 106 | 107 | Notes 108 | ----- 109 | Adding the same edge twice has no effect but any edge data 110 | will be updated when each duplicate edge is added. 111 | 112 | Examples 113 | -------- 114 | >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 115 | >>> G.add_edges_from([(0,1),(1,2)]) # using a list of edge tuples 116 | >>> e = zip(range(0,3),range(1,4)) 117 | >>> G.add_edges_from(e) # Add the path graph 0-1-2-3 118 | 119 | Associate data to edges 120 | 121 | >>> G.add_edges_from([(1,2),(2,3)], weight=3) 122 | >>> G.add_edges_from([(3,4),(1,4)], label='WN2898') 123 | """ 124 | # set up attribute dict 125 | if attr_dict is None: 126 | attr_dict = attr 127 | else: 128 | try: 129 | attr_dict.update(attr) 130 | except AttributeError: 131 | raise nx.NetworkXError( 132 | "The attr_dict argument must be a dictionary.") 133 | # process ebunch 134 | for e in ebunch: 135 | self.add_edge(e, attr_dict=attr_dict) 136 | 137 | def has_edge(self, edge): 138 | """Return True if the graph has an edge between with nodes. 139 | 140 | Parameters 141 | ---------- 142 | edge : collection of nodes 143 | Nodes can be, for example, strings or numbers. 144 | 145 | Returns 146 | ------- 147 | edge_ind : bool 148 | True if edge is in the graph, False otherwise. 149 | 150 | Examples 151 | -------- 152 | Can be called either using arbitrary long collection of nodes. 153 | 154 | >>> G = nx.HyperGraph() 155 | >>> G.add_path([0, 1, 2, 3]) 156 | >>> G.has_edge(0, 1, 2, 3) 157 | True 158 | >>> e = (0, 1, 2, 3) 159 | >>> G.has_edge(*e) # e is a 2-tuple (u,v) 160 | True 161 | 162 | The following syntax are equivalent: 163 | 164 | >>> G.has_edge(0,1) 165 | True 166 | >>> 1 in G[0] # though this gives KeyError if 0 not in G 167 | True 168 | 169 | """ 170 | return set(edge) in self.hyperedge 171 | 172 | def hyper_edges(self): 173 | """Hyper_edges of a hypegraph""" 174 | return self.hyperedge 175 | 176 | def hyper_edges_iter(self): 177 | """Return iterator of hyper_edges""" 178 | for edge in self.hyperedge: 179 | yield edge 180 | 181 | def remove_edge(self, e): 182 | """Remove the edge with nodes as in e. 183 | 184 | Parameters 185 | ---------- 186 | e: collection of nodes 187 | Remove the edge between nodes in e. 188 | 189 | Raises 190 | ------ 191 | NetworkXError 192 | If there isn't such edge as e. 193 | 194 | See Also 195 | -------- 196 | remove_edges_from : remove a collection of edges 197 | 198 | Examples 199 | -------- 200 | >>> HG = HyperGraph() # or DiGraph, etc 201 | >>> HG.add_nodes([0,1,2,3]) 202 | >>> HG.add_edge((0, 1, 2)) 203 | >>> e = (0, 1, 2) 204 | >>> HG.remove_edge(*e) # unpacks e from an edge tuple 205 | >>> e = (2,3,{'weight':7}) # an edge with attribute data 206 | """ 207 | try: 208 | self.hyperedge.remove(set(e)) 209 | except ValueError: 210 | raise nx.NetworkXError("The edge %s is not in the hypergraph" % (e)) 211 | 212 | def remove_edges_from(self, ebunch): 213 | """Remove all edges specified in ebunch. 214 | 215 | Parameters 216 | ---------- 217 | ebunch: list or container of edge tuples 218 | Each edge given in the list or container will be removed 219 | from the graph. 220 | 221 | See Also 222 | -------- 223 | remove_edge : remove a single edge 224 | 225 | Notes 226 | ----- 227 | Will fail silently if an edge in ebunch is not in the graph. 228 | 229 | Examples 230 | -------- 231 | >>> HG = HyperGraph() # or DiGraph, etc 232 | >>> HG.add_nodes([0,1,2,3]) 233 | >>> HG.add_edge((0, 1, 2)) 234 | >>> HG.add_edge((1, 3)) 235 | >>> e = (0, 1, 2) 236 | >>> ebunch=[(0, 1, 2),(1, 3)] 237 | >>> HG.remove_edges_from(ebunch) 238 | """ 239 | for e in ebunch: 240 | self.remove_edge(e) 241 | 242 | 243 | -------------------------------------------------------------------------------- /notebooks/hypergraphs_introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:29d65cce19e210672a104eb88b05fb73094bc0b826259bb4697abfb94f26e756" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "In the next few paragraphs I want to write about my thoughts and observation about hypergraphs." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "Few days ago I decided to read a wikipedia article about hypergraphs and it was very fruitful actually.\n", 23 | "\n", 24 | "I found out that most of my observations can be summed up by this article if read carefully.\n", 25 | "\n", 26 | "https://en.wikipedia.org/wiki/Hypergraph" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Hypergraphs definition\n", 34 | "\n", 35 | "A hypergraph is a generalization of a graph in which an edge can connect any number of vertices. Formally, a hypergraph H is a pair H = (X, E) where X is a set of elements called nodes of vertices, and E is a set of non-empty subsets of X called hyperedges or edges.\n", 36 | "\n", 37 | "E is a subset of powerset of X minus empty set.\n", 38 | "\n" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "In a graph, edges are pairs of nodes, in hypergraphs hyperedges can contain an arbitrary number of nodes.\n", 46 | "\n", 47 | "Hypergraphs that have hyperedges of the same cardinality (number of vertices in an edge) are called k-uniform hypergraphs where k is the number of nodes per hyperedge." 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "\n", 55 | "An example of a hypergraph, with \n", 56 | "\n", 57 | "\\begin{equation}\n", 58 | "X = \\{v_1, v_2, v_3, v_4, v_5, v_6, v_7\\}\n", 59 | "\\end{equation}\n", 60 | "\n", 61 | "and \n", 62 | "\n", 63 | "\\begin{equation}\n", 64 | "E = \\{e_1,e_2,e_3,e_4\\} = \\{\\{v_1, v_2, v_3\\}, \\{v_2,v_3\\}, \\{v_3,v_5,v_6\\}, \\{v_4\\}\\}\n", 65 | "\\end{equation}" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/Hypergraph-wikipedia.svg/262px-Hypergraph-wikipedia.svg.png)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "Hypergraphs can be viewed as incidence structures. In particular, there is a bipartite \"incidence graph\" or \"Levi graph\" corresponding to every hypergraph, and conversely, most, but not all, bipartite graphs can be regarded as incidence graphs of hypergraphs." 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "But what is this incidence structure?\n", 87 | "\n", 88 | "https://en.wikipedia.org/wiki/Incidence_structure\n", 89 | "\n", 90 | "Spodoba\u0142 mi si\u0119 fano plane i jego levi graph. Bardzo fajnie to wygl\u0105da, ale przez to, \u017ce przyk\u0142ad by\u0142 tylko jeden i to do tego specyficzny, to nie wyrobi\u0142am sobie zbyt dobrej opinii na temat tego jak to w\u0142a\u015bciwie zgeneralizowa\u0107 sobie wewn\u0119trznie." 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | ">The 2-section (or clique graph, representing graph, primal graph, Gaifman graph) of a hypergraph is the graph with the same vertices of the hypergraph, and edges between all pairs of vertices contained in the same hyperedge." 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | ">#Bipartite graph model\n", 105 | ">A hypergraph H may be represented by a bipartite graph BG as follows: the sets X and E are the partitions of BG, and (x1, e1) are connected with an edge if and only if vertex x1 is contained in edge e1 in H. Conversely, any bipartite graph with fixed parts and no unconnected nodes in the second part represents some hypergraph in the manner described above. This bipartite graph is also called incidence graph." 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "There is no singular notion of cycles and acyclicity for hypegraphs.\n", 113 | "\n", 114 | "They're different from each other and in some ways counter intuitive.\n", 115 | "\n", 116 | "Maybe I should explore them further for analysis of diffusion?" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "# What to write next?\n", 124 | "\n", 125 | "1. How to express hypergraphs (incidence structures, bipartite graphs) with examples\n", 126 | "\n", 127 | "Let's imagine that I have some hypergraph.\n", 128 | "\n", 129 | "3. What is diffusion\n", 130 | "\n", 131 | "https://en.wikipedia.org/wiki/Diffusion\n", 132 | "\n", 133 | "I haven't yet probably studied what diffusion is. I've read about in my statistical phisics book, but it didn't describe whole picture. At least in my opinion.\n", 134 | "\n", 135 | ">Diffusion is the net movement of a substance (e.g., an atom, ion or molecule) from a region of high concentration to a region of low concentration. This is also referred to as the movement of a substance down a concentration gradient. A gradient is the change in the value of a quantity (e.g., concentration, pressure, temperature) with the change in another variable (e.g., distance). For example, a change in concentration over a distance is called a concentration gradient, a change in pressure over a distance is called a pressure gradient, and a change in temperature over a distance is a called a temperature gradient.\n", 136 | "\n", 137 | ">The word diffusion is derived from the Latin word, \"diffundere\", which means \"to spread out\" (if a substance is \u201cspreading out\u201d, it is moving from an area of high concentration to an area of low concentration). A distinguishing feature of diffusion is that it results in mixing or mass transport, without requiring bulk motion (bulk flow). Thus, diffusion should not be confused with convection, or advection, which are other transport phenomena that utilize bulk motion to move particles from one place to another.\n", 138 | "\n", 139 | "\n", 140 | "Okay, movement of the substance. But what substance exactly?\n", 141 | "\n", 142 | ">There are two ways to introduce the notion of diffusion: either a phenomenological approach starting with Fick's laws of diffusion and their mathematical consequences, or a physical and atomistic one, by considering the random walk of the diffusing particles.[1]\n", 143 | "\n", 144 | ">In the phenomenological approach, diffusion is the movement of a substance from a region of high concentration to a region of low concentration without bulk motion. According to Fick's laws, the diffusion flux is proportional to the negative gradient of concentrations. It goes from regions of higher concentration to regions of lower concentration. Some time later, various generalizations of Fick's laws were developed in the frame of thermodynamics and non-equilibrium thermodynamics.[2]\n", 145 | "\n", 146 | ">From the atomistic point of view, diffusion is considered as a result of the random walk of the diffusing particles. In molecular diffusion, the moving molecules are self-propelled by thermal energy. Random walk of small particles in suspension in a fluid was discovered in 1827 by Robert Brown. The theory of the Brownian motion and the atomistic backgrounds of diffusion were developed by Albert Einstein.[3] The concept of diffusion is typically applied to any subject matter involving random walks in ensembles of individuals." 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "4. How I implement my walks and why\n", 154 | "\n", 155 | "So, my walks are implemented as random walks on markov chain. Does it make sense?\n", 156 | "\n", 157 | "I was looking at my resources on that matter:\n", 158 | "\n", 159 | "Spectral Graph Theory and Applications\n", 160 | " WS 2011/2012\n", 161 | "Lecture 5: Random Walks and Markov Chain\n", 162 | "Lecturer: Thomas Sauerwald & He Sun\n", 163 | "\n", 164 | "And this Thomas Sauerwald is a very interesting folk: http://www.cl.cam.ac.uk/~tms41/\n", 165 | "\n", 166 | "He Sun is also cool:\n", 167 | "http://people.mpi-inf.mpg.de/~hsun\n", 168 | "\n", 169 | "This can be relevant:\n", 170 | "http://resources.mpi-inf.mpg.de/departments/d1/teaching/ws11/SGT/index.html\n", 171 | "\n", 172 | "This is the particular lecture:\n", 173 | "\n", 174 | "http://resources.mpi-inf.mpg.de/departments/d1/teaching/ws11/SGT/Lecture5.pdf\n", 175 | "\n", 176 | "What is in this lecture:\n", 177 | "\n", 178 | "- How to define markov chains.\n", 179 | "\n", 180 | "- How to walk on a markov chain.\n", 181 | "\n", 182 | "- How they converge.\n", 183 | "\n", 184 | "- How to analytically compute results.\n", 185 | "\n", 186 | "\n", 187 | "and, oh, there is more:\n", 188 | "\n", 189 | "http://resources.mpi-inf.mpg.de/departments/d1/teaching/ws11/SGT/Lecture6.pdf\n", 190 | "\n", 191 | "http://resources.mpi-inf.mpg.de/departments/d1/teaching/ws11/SGT/Lecture7.pdf\n", 192 | "\n", 193 | "Haven't read yet.\n", 194 | "\n", 195 | "A very important special case is the Markov chain that corresponds to a random walk on\n", 196 | "an undirected, unweighted graph. Here, the random walk picks each step a neighbor chosen\n", 197 | "uniformly at random and moves to that neighbor. Hence, the transition matrix is \n", 198 | "\n", 199 | "\n", 200 | "\n", 201 | "\n", 202 | "5. What are my current results\n", 203 | "6. Maybe something about those relevant papers?" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "collapsed": false, 209 | "input": [], 210 | "language": "python", 211 | "metadata": {}, 212 | "outputs": [] 213 | } 214 | ], 215 | "metadata": {} 216 | } 217 | ] 218 | } -------------------------------------------------------------------------------- /notebooks/Pykov.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:25d5e2f06395ff6dda482f8e546b880517bb26be941ce99e5ac3ece7976bf49b" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "## Purpose of the notebook\n", 16 | "explore pykov\n" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "collapsed": false, 22 | "input": [ 23 | "%matplotlib inline\n", 24 | "import pykov\n", 25 | "\n", 26 | "T = pykov.Chain({('A','B'): .3, ('A','A'): .7, ('B','A'): 1.})\n", 27 | "T.steady()" 28 | ], 29 | "language": "python", 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "metadata": {}, 34 | "output_type": "pyout", 35 | "prompt_number": 4, 36 | "text": [ 37 | "{'B': 0.23076923076923075, 'A': 0.76923076923076938}" 38 | ] 39 | } 40 | ], 41 | "prompt_number": 4 42 | }, 43 | { 44 | "cell_type": "code", 45 | "collapsed": false, 46 | "input": [ 47 | "import numpy as np\n", 48 | "\n", 49 | "from functools import partial\n", 50 | "from matplotlib import pyplot as plt\n", 51 | "\n", 52 | "from numpy import linalg as LA\n", 53 | "\n", 54 | "from hypergraph import generators\n", 55 | "from hypergraph.analytical import prediction\n", 56 | "from hypergraph import utils\n", 57 | "from hypergraph.markov_diffusion import create_markov_matrix_model_nodes\n", 58 | "from hypergraph.markov_diffusion import create_markov_matrix_model_hyper_edges\n", 59 | "\n", 60 | "\n", 61 | "def hypergraph_analytical_edges(HG):\n", 62 | " phis = []\n", 63 | " number_of_nodes = len(HG.nodes())\n", 64 | " all_phis = 0\n", 65 | " for edge in HG.hyper_edges():\n", 66 | " phis.append(len(edge))\n", 67 | " all_phis += len(edge)\n", 68 | " \n", 69 | " pis = [phi / all_phis for phi in phis]\n", 70 | " return pis\n", 71 | "\n", 72 | "\n", 73 | "\n", 74 | "all_models = {\n", 75 | " \"node\": {\n", 76 | " \"analytical\": partial(prediction, model='hypergraph_nodes'),\n", 77 | " \"numerical\": create_markov_matrix_model_nodes,\n", 78 | " \"name\": \"node\",\n", 79 | " },\n", 80 | " \"hyperedges\": {\n", 81 | " \"analytical\": partial(prediction, model='hypergraph_edges'),\n", 82 | " \"numerical\": create_markov_matrix_model_hyper_edges,\n", 83 | " \"name\": \"hyperedges\",\n", 84 | " }\n", 85 | "}\n", 86 | "\n", 87 | "\n", 88 | "def show_models(HG):\n", 89 | " model = all_models['node']\n", 90 | "\n", 91 | " def show_model(model):\n", 92 | " markov_matrix = model[\"numerical\"](HG)\n", 93 | " print(markov_matrix)\n", 94 | " show_model(model)\n", 95 | "\n", 96 | "\n", 97 | "def transition_matrix_to_pykov_chain(matrix):\n", 98 | " chain = pykov.Chain()\n", 99 | " \n", 100 | " for i, row in enumerate(matrix):\n", 101 | " for j, column in enumerate(row):\n", 102 | " chain[(i, j)] = column\n", 103 | " return chain\n", 104 | "\n" 105 | ], 106 | "language": "python", 107 | "metadata": {}, 108 | "outputs": [], 109 | "prompt_number": 13 110 | }, 111 | { 112 | "cell_type": "code", 113 | "collapsed": false, 114 | "input": [ 115 | "HG = generators.generic_hypergraph(9, ((3, 5), (3, 3)))\n", 116 | "show_models(HG)" 117 | ], 118 | "language": "python", 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "output_type": "stream", 123 | "stream": "stdout", 124 | "text": [ 125 | "[[ 0.33333333 0.11111111 0.11111111 0.22222222 0. 0.\n", 126 | " 0.11111111 0. 0.11111111]\n", 127 | " [ 0.11111111 0.33333333 0.22222222 0.11111111 0. 0.\n", 128 | " 0.11111111 0.11111111 0. ]\n", 129 | " [ 0.08333333 0.16666667 0.33333333 0.25 0. 0.08333333\n", 130 | " 0. 0.08333333 0. ]\n", 131 | " [ 0.16666667 0.08333333 0.25 0.33333333 0. 0.08333333\n", 132 | " 0. 0. 0.08333333]\n", 133 | " [ 0. 0. 0. 0. 0.33333333 0.16666667\n", 134 | " 0.16666667 0.16666667 0.16666667]\n", 135 | " [ 0. 0. 0.16666667 0.16666667 0.16666667 0.33333333\n", 136 | " 0. 0. 0.16666667]\n", 137 | " [ 0.16666667 0.16666667 0. 0. 0.16666667 0.\n", 138 | " 0.33333333 0.16666667 0. ]\n", 139 | " [ 0. 0.16666667 0.16666667 0. 0.16666667 0.\n", 140 | " 0.16666667 0.33333333 0. ]\n", 141 | " [ 0.16666667 0. 0. 0.16666667 0.16666667 0.16666667\n", 142 | " 0. 0. 0.33333333]]\n" 143 | ] 144 | } 145 | ], 146 | "prompt_number": 14 147 | }, 148 | { 149 | "cell_type": "code", 150 | "collapsed": false, 151 | "input": [ 152 | "def plot_stuff(xs, ys, ys_prediction, freqs_matrix):\n", 153 | " width = 0.2\n", 154 | " plt.figure(figsize=(10, 8))\n", 155 | " plt.bar(xs, ys, width=width, color='crimson', label='Steady state for chains')\n", 156 | "\n", 157 | " print('prediction', ys_prediction)\n", 158 | " plt.bar(np.array(xs) + 1 * width, ys_prediction, width=width, color='#dcccdd', label='Analytical')\n", 159 | " plt.bar(np.array(xs) + 2 * width, freqs_matrix, width=width, label='Traversal matrix to N')\n", 160 | " plt.legend(loc=0)\n", 161 | " " 162 | ], 163 | "language": "python", 164 | "metadata": {}, 165 | "outputs": [], 166 | "prompt_number": 19 167 | }, 168 | { 169 | "cell_type": "code", 170 | "collapsed": false, 171 | "input": [ 172 | "mm = create_markov_matrix_model_nodes(HG)\n", 173 | "print('Matrix from model nodes')\n", 174 | "print(mm)\n", 175 | "\n", 176 | "chain = transition_matrix_to_pykov_chain(mm)\n", 177 | "print('\\nChain')\n", 178 | "print(chain)" 179 | ], 180 | "language": "python", 181 | "metadata": {}, 182 | "outputs": [ 183 | { 184 | "output_type": "stream", 185 | "stream": "stdout", 186 | "text": [ 187 | "Matrix from model nodes\n", 188 | "[[ 0.33333333 0.11111111 0.11111111 0.22222222 0. 0.\n", 189 | " 0.11111111 0. 0.11111111]\n", 190 | " [ 0.11111111 0.33333333 0.22222222 0.11111111 0. 0.\n", 191 | " 0.11111111 0.11111111 0. ]\n", 192 | " [ 0.08333333 0.16666667 0.33333333 0.25 0. 0.08333333\n", 193 | " 0. 0.08333333 0. ]\n", 194 | " [ 0.16666667 0.08333333 0.25 0.33333333 0. 0.08333333\n", 195 | " 0. 0. 0.08333333]\n", 196 | " [ 0. 0. 0. 0. 0.33333333 0.16666667\n", 197 | " 0.16666667 0.16666667 0.16666667]\n", 198 | " [ 0. 0. 0.16666667 0.16666667 0.16666667 0.33333333\n", 199 | " 0. 0. 0.16666667]\n", 200 | " [ 0.16666667 0.16666667 0. 0. 0.16666667 0.\n", 201 | " 0.33333333 0.16666667 0. ]\n", 202 | " [ 0. 0.16666667 0.16666667 0. 0.16666667 0.\n", 203 | " 0.16666667 0.33333333 0. ]\n", 204 | " [ 0.16666667 0. 0. 0.16666667 0.16666667 0.16666667\n", 205 | " 0. 0. 0.33333333]]\n", 206 | "\n", 207 | "Chain\n", 208 | "{(4, 7): 0.16666666666666666, (1, 3): 0.1111111111111111, (6, 6): 0.33333333333333331, (3, 0): 0.16666666666666666, (8, 0): 0.16666666666666666, (5, 4): 0.16666666666666666, (2, 1): 0.16666666666666666, (1, 6): 0.1111111111111111, (2, 5): 0.083333333333333329, (8, 5): 0.16666666666666666, (0, 3): 0.22222222222222221, (5, 8): 0.16666666666666666, (1, 2): 0.22222222222222221, (3, 8): 0.083333333333333329, (6, 7): 0.16666666666666666, (5, 5): 0.33333333333333331, (2, 0): 0.083333333333333329, (7, 6): 0.16666666666666666, (4, 8): 0.16666666666666666, (4, 4): 0.33333333333333331, (3, 3): 0.33333333333333331, (7, 2): 0.16666666666666666, (2, 2): 0.33333333333333331, (6, 4): 0.16666666666666666, (5, 3): 0.16666666666666666, (1, 1): 0.33333333333333331, (0, 1): 0.1111111111111111, (3, 2): 0.25, (0, 0): 0.33333333333333331, (7, 1): 0.16666666666666666, (4, 5): 0.16666666666666666, (6, 0): 0.16666666666666666, (7, 7): 0.33333333333333331, (2, 3): 0.25, (1, 0): 0.1111111111111111, (0, 8): 0.1111111111111111, (3, 5): 0.083333333333333329, (2, 7): 0.083333333333333329, (8, 3): 0.16666666666666666, (4, 6): 0.16666666666666666, (6, 1): 0.16666666666666666, (3, 1): 0.083333333333333329, (7, 4): 0.16666666666666666, (0, 6): 0.1111111111111111, (8, 8): 0.33333333333333331, (1, 7): 0.1111111111111111, (5, 2): 0.16666666666666666, (0, 2): 0.1111111111111111, (8, 4): 0.16666666666666666}\n" 209 | ] 210 | } 211 | ], 212 | "prompt_number": 20 213 | }, 214 | { 215 | "cell_type": "code", 216 | "collapsed": false, 217 | "input": [ 218 | "chain_transposed = pykov.Chain(chain)\n", 219 | "xs, ys = zip(*chain_transposed.steady().items())\n", 220 | "print(xs, ys)\n", 221 | "print(chain_transposed.succ(1))\n", 222 | "print('steady pykov', chain_transposed.steady())\n", 223 | "\n", 224 | "freqs_matrix = LA.matrix_power(mm, 40)[0]\n", 225 | "model = all_models['node'] \n", 226 | " \n", 227 | "xs = list(map(int, xs))\n", 228 | "ys_prediction = model[\"analytical\"](HG)\n", 229 | "\n", 230 | "plot_stuff(xs, ys, ys_prediction, freqs_matrix)" 231 | ], 232 | "language": "python", 233 | "metadata": {}, 234 | "outputs": [ 235 | { 236 | "output_type": "stream", 237 | "stream": "stdout", 238 | "text": [ 239 | "(0, 1, 2, 3, 4, 5, 6, 7, 8) (0.12499999999999994, 0.12499999999999993, 0.16666666666666657, 0.16666666666666666, 0.083333333333333356, 0.08333333333333337, 0.083333333333333287, 0.083333333333333287, 0.083333333333333523)\n", 240 | "{0: 0.1111111111111111, 1: 0.33333333333333331, 2: 0.22222222222222221, 3: 0.1111111111111111, 6: 0.1111111111111111, 7: 0.1111111111111111}\n", 241 | "steady pykov {0: 0.12499999999999994, 1: 0.12499999999999993, 2: 0.16666666666666657, 3: 0.16666666666666666, 4: 0.083333333333333356, 5: 0.08333333333333337, 6: 0.083333333333333287, 7: 0.083333333333333287, 8: 0.083333333333333523}\n", 242 | "prediction" 243 | ] 244 | }, 245 | { 246 | "output_type": "stream", 247 | "stream": "stdout", 248 | "text": [ 249 | " [0.125, 0.125, 0.16666666666666666, 0.16666666666666666, 0.08333333333333333, 0.08333333333333333, 0.08333333333333333, 0.08333333333333333, 0.08333333333333333]\n" 250 | ] 251 | }, 252 | { 253 | "metadata": {}, 254 | "output_type": "display_data", 255 | "png": "iVBORw0KGgoAAAANSUhEUgAAAloAAAHhCAYAAAC2vhDBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xt4VNW9//F3mIABA8FDEZCLCTdBKiiiYsspgrFSi0XE\nxaVKbY+tgOAR1IKitFovRahKBbVUrEe8gUvR+muh+qCnSuRSJV5AsEdCMMEIImi4VSQkvz8S0hCH\nzA5kZ4d8P6/n8ZE9s/bM+mRv8Zu9vrMHREREREREREREREREREREREREREREREREREREREREREQk\nREmJBjjnBgGzgBgwz3t/T6XnuwGPAWcAt3jv763w3M3AFUAxsAb4mfd+X81NX0RERKTualDVk865\nGDAHGAScCoxyznWvNGw7cC3wu0r7pgO/AHp770+jtFAbWTPTFhEREan7qiy0gLOBDd77Td77/cAC\nYEjFAd77bd77t4H9lfbdWfZYE+dcMtAE+KRmpi0iIiJS9yUqtNoC+RW2N5c9lpD3fgdwL5AHFABf\neu+XHskkRURERI5FyQmeLznSF3bOdQImAulAIeCdc5d775+KN/7ll18uicViR/p2IiIiIrXp9czM\nzPMSDUpUaH0CtK+w3Z7Sq1pB9AGWe++3AzjnFgHfAeIWWrFYjN69ewd8aREREZHoZGdn9w8yLtHS\n4dtAF+dcunOuETACeOkwYyt/gvFDoK9zrrFzLgnIBNYFmZQlWVlZUU8hEspti3Lboty2WM0dVJVX\ntLz3Rc65CcDLlH5q8FHv/Xrn3Jiy5+c651oDbwHNgGLn3HXAqd7795xz8ykt1oqBbOCPIWYRERER\nqVMS3kertixdurRES4ciIiJyLMjOziYzMzNhHZVo6VBEREREjlCiZngJWVZWFv369Yt6GrVOuW1R\nbju2b9/O1q1bad68edRTqXWFhYWkpaVFPY1aV59zH3fccbRo0eKoXkOFloiI1Ijdu3cDcOqpp0Y8\nk2icdNJJUU8hEvU59/bt29m9ezepqalH/Brq0RIRkRrxySefcNJJJ5GUVGf+1yJyVEpKSigoKKBt\n22/eq109WiIiUquSkpJUZEm9UhPntAqtiFm9/4hy26LcImKVCi0RERGRkKjQipi1TyQdpNy2KLdY\nNn36dMaOHRv1NI7a+PHj6dixIxdccEHo73PXXXcd0b73338/1113XQ3P6OjoU4ciIhKaA3lbKP50\nW2iv36BNS2IdWicct3LlSn7961/zz3/+k1gsRteuXbn77rs544wzePrpp3nyySdZvHhxKHMMo2/t\n4osvZvjw4YwePTrQ+OnTp7Np0yb+8Ic/HNH7rVixgtdff51169aRkpJyRK9RHUf6M5s0aVINz+To\nqdCKmMX77IByW6PcdhV/uo1do28J7fWbPnFXwkJr586djBw5kvvuu4+hQ4eyb98+VqxYwXHHHRfa\nvMJW2x86yM/Pp0OHDkdUZBUVFZGcbLfc0NKhiIjUazk5OSQlJXHppZeSlJRESkoKAwYM4NRTT+Wf\n//wnN954I2+99RYdOnSgY8eOAOzbt49p06bRs2dPunXrxg033MBXX30FlN6gc+TIkXTt2pWOHTsy\natQoCgoKyt/v448/ZvDgwXTo0IFLL72UHTt2lD83YsQIHnnkkUPm169fv7hX07766ivGjBlD586d\nycjIIDMzk23btnHnnXeyYsUKpkyZQocOHbjpppsAuOmmmzjttNM4+eSTGThwICtXrgRg6dKlzJo1\nixdeeIEOHTrQv39/oLQAvfbaazn11FPp0aMHd911F8XFxd+YxxNPPMHEiRPLf0b33HMPAI8//jh9\n+vShU6dOXH755WzZsqV8nxYtWvDoo4/Sp08fzj777LjHZeXKlVx44YVkZGRw2mmnsWDBgvLnvvzy\nS0aOHEmHDh244IIL2LRpU/lzh8sJhy7T5uXl0aJFCxYsWEDPnj3p0qUL9913X/nY1atXM3DgQE4+\n+WS6devGrbfeGneeR0uFVsSs/rar3LYot0Spc+fOxGIxxo8fz9KlS/nyyy/LnzvllFO49957Oeus\ns8jLy2Pjxo0A3H777eTm5rJs2TLefvttPv30U2bOnAlAcXExV1xxBe+//z7vv/8+KSkpTJkypfw1\nf/GLX3DGGWeQk5PDL3/5S5555pnyK1CjRo3i2WefLR+7du1atmzZwve///1vzHvBggXs2rWLtWvX\nsnHjRu677z5SUlK49dZbOffcc5kxYwZ5eXlMnz4dgDPPPJNly5aRm5vLsGHD+NnPfsbXX39NZmYm\nkyZN4tJLLyUvL4/XX38dKO2FatSoEatXr+b111/nf//3f5k/f/435jF69OhDfkZTpkzhjTfe4M47\n7+Sxxx5j/fr1tG/fnp///OeH7Ld48WJeffVVVqxY8Y3XzM/PZ/jw4YwZM4YNGzbwxhtv8O1vf7v8\n+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\nbluUW+JRoSUiIiISEhVaEbO6tm21V8lqbqvnuXLbotwSjwotERERkZCo0IqY1bVtq71KVnNbPc+V\n2xbllnhvyQJmAAAgAElEQVRUaImIiIiERIVWxKyubVvtVbKa2+p5rty2KLfEo0JLREREJCQqtCJm\ndW3baq+S1dxWz3PltkW5JZ7kRAOcc4OAWUAMmOe9v6fS892Ax4AzgFu89/dWej4GvA1s9t5fXFMT\nFxEREanrqryiVVYkzQEGAacCo5xz3SsN2w5cC/zuMC9zHbAOKDm6qdZPVte2rfYqWc1t9TxXbluU\nW+JJtHR4NrDBe7/Je78fWAAMqTjAe7/Ne/82sL/yzs65dsBFwDwgqWamLCIiInJsSFRotQXyK2xv\nLnssqPuBXwLF1ZyXGVbXtq32KlnNbfU8V25blFviSVRoHfFyn3NuMPCZ9/4ddDVLREREDEpUaH0C\ntK+w3Z7Sq1pBfAf4kXMuF3gGGOicm1/VDhWr4qysLBPbB9e268p8amv7SBQdKKre+KLqja8orPx1\ntUersLAw9ONdl84//fcd/n/fdWk+Ot463mHnr0qVV5qcc8nAP4HzgQLgH8Ao7/36OGNvA3ZV/tRh\n2XP9gRur+tTh0qVLS3r37h144nJs279qDbtG31KtfVKXzWf3nuAXWfMaxZj8t5xqvcfMizrT66Sm\n1dqnOqzmFhGpb7Kzs8nMzEy4YlflFS3vfREwAXiZ0k8OLvTer3fOjXHOjQFwzrV2zuUDk4BbnXN5\nzrnUOC+nTx3GUZ2quD6x2qtkNbfV81y5bVFuiSc50QDv/RJgSaXH5lb48xYOXV6M9xqvA68f4RxF\nREREjkm6M3zErN5/pK72KoXNam6r57ly26LcEo8KLREREZGQqNCKmNW1bau9SlZzWz3PldsW5ZZ4\nVGiJiIiIhESFVsSsrm1b7VWymtvqea7ctii3xKNCS0RERCQkKrQiZnVt22qvktXcVs9z5bZFuSUe\nFVoiIiIiIVGhFTGra9tWe5Ws5rZ6niu3Lcot8ajQEhEREQmJCq2IWV3bttqrZDW31fNcuW1RbolH\nhZaIiIhISFRoRczq2rbVXiWrua2e58pti3JLPCq0REREREKiQitiVte2rfYqWc1t9TxXbluUW+JR\noSUiIiISEhVaEbO6tm21V8lqbqvnuXLbotwSjwotERERkZCo0IqY1bVtq71KVnNbPc+V2xbllnhU\n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\nAEYAow4ztvKbVWdfERERkXqnyh4t730RMAF4GVgHLPTer3fOjXHOjQFwzrV2zuUDk4BbnXN5zrnU\nw+0bZphjkdWeHeW2xWoPh3LbotwST8L7aHnvlwBLKj02t8KftwDtg+4rIiIiYoXuDB8xq/dVUm5b\nrN5nR7ltUW6JR4WWiIiISEhUaEXMas+OcttitYdDuW1RbolHhZaIiIhISFRoRcxqz45y22K1h0O5\nbVFuiUeFloiIiEhIVGhFzGrPjnLbYrWHQ7ltUW6JR4WWiIiISEhUaEXMas+OcttitYdDuW1RbolH\nhZaIiIhISFRoRcxqz45y22K1h0O5bVFuiUeFloiIiEhIVGhFzGrPjnLbYrWHQ7ltUW6JR4WWiIiI\nSEhUaEXMas+OcttitYdDuW1RbolHhZaIiIhISFRoRcxqz45y22K1h0O5bVFuiUeFloiIiEhIVGhF\nzGrPjnLbYrWHQ7ltUW6JR4WWiIiISEhUaEXMas+OcttitYdDuW1RbolHhZaIiIhISFRoRcxqz45y\n22K1h0O5bVFuiUeFloiIiEhIVGhFzGrPjnLbYrWHQ7ltUW6JR4WWiIiISEhUaEXMas+OcttitYdD\nuW1RbolHhZaIiIhISFRoRcxqz45y22K1h0O5bVFuiUeFloiIiEhIVGhFzGrPjnLbYrWHQ7ltUW6J\nR4WWiIiISEhUaEXMas+OcttitYdDuW1RbolHhZaIiIhISFRoRcxqz45y22K1h0O5bVFuiUeFloiI\niEhIkqOegHVpaWnsinoSEVBuW87t0Jn9q9ZUa5+krumUpKQEHr9t3wG27d1frfc4MbURbZodV619\nqkO5g1PuYJQ7uLBzB6VCS0RCV/zpNnaNvqVa+6Qum8/uL3YHHv9ZoxiT/5ZTrfeYeVHnUP8iVu7g\nlDsY5Q4u7NxBaekwYlZ7dpTbFuW2RbltsZo7KBVaIiIiIiFJuHTonBsEzAJiwDzv/T1xxjwA/ADY\nC/zUe/9O2eM3A1cAxcAa4Gfe+301N/1jn9WeHeW2RbltUW5brOYOqsorWs65GDAHGAScCoxyznWv\nNOYioLP3vgtwNfBw2ePpwC+A3t770ygt1EbWdAARERGRuirR0uHZwAbv/Sbv/X5gATCk0pgfAY8D\neO9XAc2dc62AncB+oIlzLhloAnxSk5OvD6yubSu3Lcpti3LbYjV3UIkKrbZAfoXtzWWPJRzjvd8B\n3AvkAQXAl977pUc3XREREZFjR6JCqyTg6yRVfsA51wmYCKQDJwGpzrnLqzW7BIoOFFVvfFH1xleU\nlZV1yN1va2q7rn73XWFhYSh5j4aOd3h0vHW8dbx1vI90+0jUh+MdVKJm+E+A9hW221N6xaqqMe3K\nHjsPWO693w7gnFsEfAd4KvDsEkiOJRO8FoTk5CO/bVjlL82sqe3q3uSttqSlpdGr+7/nXNP5j6R5\nUsc7PDreOt4V6XjreFdn2+rxzs7ODvT+iWb+NtClrLG9ABgBjKo05iVgArDAOdeX0iXCrc65fwLT\nnHONga+ATOAfgWZlSGFhocl7bCi3Lcpti3LbYjV3UFX+bLz3RZQWUS8D64CF3vv1zrkxzrkxZWMW\nAxudcxuAucA1ZY+/C8yntFh7v+wl/xhKChEREZE6KOG1OO/9EmBJpcfmVtqecJh9ZwAzjmaC9Z3V\n+48oty3KbYty22I1d1C62iciIiISEhVaEbN6/xHltkW5bVFuW6zmDkqFloiIiEhIVGhFrK7edyVs\nym2Lctui3LZYzR2UCi0RERGRkKjQipjVtW3ltkW5bVFuW6zmDkqFloiIiEhIVGhFzOratnLboty2\nKLctVnMHpUJLREREJCQqtCJmdW1buW1RbluU2xaruYNSoSUiIiISEhVaEbO6tq3ctii3Lcpti9Xc\nQanQEhEREQmJCq2IWV3bVm5blNsW5bbFau6gVGiJiIiIhESFVsSsrm0rty3KbYty22I1d1AqtERE\nRERCokIrYlbXtpXbFuW2RbltsZo7KBVaIiIiIiFRoRUxq2vbym2Lctui3LZYzR2UCi0RERGRkKjQ\nipjVtW3ltkW5bVFuW6zmDkqFloiIiEhIVGhFzOratnLboty2KLctVnMHpUJLREREJCQqtCJmdW1b\nuW1RbluU2xaruYNSoSUiIiISEhVaEbO6tq3ctii3Lcpti9XcQanQEhEREQmJCq2IWV3bVm5blNsW\n5bbFau6gVGiJiIiIhESFVsSsrm0rty3KbYty22I1d1AqtERERERCokIrYlbXtpXbFuW2RbltsZo7\nKBVaIiIiIiFRoRUxq2vbym2Lctui3LZYzR2UCi0RERGRkKjQipjVtW3ltkW5bVFuW6zmDkqFloiI\niEhIVGhFzOratnLboty2KLctVnMHpUJLREREJCTJiQY45wYBs4AYMM97f0+cMQ8APwD2Aj/13r9T\n9nhzYB7QAygB/st7v7Lmpn/sKywsNFntKrctym2LcttiNXdQVf5snHMxYA4wCDgVGOWc615pzEVA\nZ+99F+Bq4OEKT/8eWOy97w70BNbX4NxFRERE6rREV7TOBjZ47zcBOOcWAEM4tGD6EfA4gPd+lXOu\nuXOuFfAV8J/e+yvLnisC9NGEStLS0tgV9SQioNy2KLctym2L1dxBJSq02gL5FbY3A+cEGNMOOABs\nc849BvQCVgPXee/3HtWMRURERI4RiZZVSwK+TlKc/ZKB3sBD3vvewB7gpupNr2pFB4qqN76oeuMr\nysrKIisrq8a36+r9RwoLC0PJe1BBQUG156TjHR4dbx1vHW8d7yPdtnq8g0p0ResToH2F7faUXrGq\naky7sseSgM3e+7fKHn+OGi60kmPJBK8FITk5Ye//YfXr1y+U7c9fCX6walNaWhq9uv97zjWd//jj\nj6/2nHS8w6PjreNdkY63jnd1tq0e7+zs7EDvn+iK1ttAF+dcunOuETACeKnSmJeAnwA45/oCX3rv\nt3rvtwD5zrmuZeMygQ8CzcoQq/cfUW5blNsW5bbFau6gqiy0yhrYJwAvA+uAhd779c65Mc65MWVj\nFgMbnXMbgLnANRVe4lrgKefce5R+6vDuEDKIiIiI1EkJr8V575cASyo9NrfS9oTD7PsecNbRTLC+\ns3r/EeW2RbltUW5brOYOSj8bERERkZCo0IqY1bVt5bZFuW1Rblus5g5KhZaIiIhISFRoRayu3ncl\nbMpti3Lboty2WM0dlAotERERkZCo0IqY1bVt5bZFuW1Rblus5g5KhZaIiIhISFRoRczq2rZy26Lc\ntii3LVZzB6VCS0RERCQkKrQiZnVtW7ltUW5blNsWq7mDUqElIiIiEhIVWhGzurat3LYoty3KbYvV\n3EGp0BIREREJiQqtiFld21ZuW5TbFuW2xWruoFRoiYiIiIREhVbErK5tK7ctym2LcttiNXdQKrRE\nREREQqJCK2JW17aV2xbltkW5bbGaOygVWiIiIiIhUaEVMatr28pti3Lboty2WM0dlAotERERkZCo\n0IqY1bVt5bZFuW1Rblus5g5KhZaIiIhISFRoRczq2rZy26Lctii3LVZzB6VCS0RERCQkKrQiZnVt\nW7ltUW5blNsWq7mDUqElIiIiEhIVWhGzurat3LYoty3KbYvV3EGp0BIREREJiQqtiFld21ZuW5Tb\nFuW2xWruoFRoiYiIiIREhVbErK5tK7ctym2LcttiNXdQKrREREREQqJCK2JW17aV2xbltkW5bbGa\nOygVWiIiIiIhUaEVMatr28pti3Lboty2WM0dlAotERERkZCo0IqY1bVt5bZFuW1Rblus5g5KhZaI\niIhISFRoRczq2rZy26Lctii3LVZzB5WcaIBzbhAwC4gB87z398QZ8wDwA2Av8FPv/TsVnosBbwOb\nvfcX19TERUREROq6Kq9olRVJc4BBwKnAKOdc90pjLgI6e++7AFcDD1d6meuAdUBJTU26PrG6tq3c\ntii3Lcpti9XcQSVaOjwb2OC93+S93w8sAIZUGvMj4HEA7/0qoLlzrhWAc64dcBEwD0iqyYmLiIiI\n1HWJCq22QH6F7c1ljwUdcz/wS6D4KOZYr1ld21ZuW5TbFuW2xWruoBIVWkGX+ypfrUpyzg0GPivr\n19LVLBERETEnUaH1CdC+wnZ7Sq9YVTWmXdlj3wF+5JzLBZ4BBjrn5h/ddA9VdKCoeuOLqje+oqys\nLLKysmp8u66ubRcWFoaS92joeIdHx1vHW8dbx/tIt49EfTjeQSX61OHbQBfnXDpQAIwARlUa8xIw\nAVjgnOsLfOm93wJMLfsH51x/4Ebv/U8CzyyA5Fgy1emxT05O+CHLw+rXr18o2/tXrTniOYUpLS2N\nXt3/Peeazp+Wlsauas5Jxzs8Ot463hXpeOt4V2fb6vHOzs4O9P5VXtHy3hdRWkS9TOknBxd679c7\n58Y458aUjVkMbHTObQDmAtcc5uX0qcM4rK5tK7ctym2LcttiNXdQCUtE7/0SYEmlx+ZW2p6Q4DVe\nB14/kgmKiIiIHKt0Z/iI1dU1/bApty3KbYty22I1d1AqtERERERCokIrYlbXtpXbFuW2RbltsZo7\nKBVaIiIiIiFRoRUxq2vbym2Lctui3LZYzR2UCi0RERGRkKjQipjVtW3ltkW5bVFuW6zmDkqFloiI\niEhIVGhFzOratnLboty2KLctVnMHpUJLREREJCQqtCJmdW1buW1RbluU2xaruYNSoSUiIiISEhVa\nEbO6tq3ctii3Lcpti9XcQanQEhEREQmJCq2IWV3bVm5blNsW5bbFau6gVGiJiIiIhESFVsSsrm0r\nty3KbYty22I1d1AqtERERERCokIrYlbXtpXbFuW2RbltsZo7KBVaIiIiIiFRoRUxq2vbym2Lctui\n3LZYzR2UCi0RERGRkKjQipjVtW3ltkW5bVFuW6zmDkqFloiIiEhIVGhFzOratnLboty2KLctVnMH\npUJLREREJCQqtCJmdW1buW1RbluU2xaruYNSoSUiIiISEhVaEbO6tq3ctii3Lcpti9XcQanQEhER\nEQmJCq2IWV3bVm5blNsW5bbFau6gVGiJiIiIhESFVsSsrm0rty3KbYty22I1d1AqtERERERCokIr\nYlbXtpXbFuW2RbltsZo7KBVaIiIiIiFRoRUxq2vbym2Lctui3LZYzR2UCi0RERGRkKjQipjVtW3l\ntkW5bVFuW6zmDkqFloiIiEhIVGhFzOratnLboty2KLctVnMHlRxkkHNuEDALiAHzvPf3xBnzAPAD\nYC/wU+/9O8659sB84ESgBPij9/6Bmpq8iIiISF2W8IqWcy4GzAEGAacCo5xz3SuNuQjo7L3vAlwN\nPFz21H5gkve+B9AXGF95X+usrm0rty3KbYty22I1d1BBlg7PBjZ47zd57/cDC4Ahlcb8CHgcwHu/\nCmjunGvlvd/ivX+37PHdwHrgpBqbvYiIiEgdFqTQagvkV9jeXPZYojHtKg5wzqUDZwCrqj3Leszq\n2rZy26Lctii3LVZzBxWk0CoJ+FpJh9vPOZcKPAdcV3Zlq0YUHSiq3vii6o2vKCsri6ysrNC265rC\nwsJQ8x7JpWYd7/DoeOt463jreB/pto531YI0w38CtK+w3Z7SK1ZVjWlX9hjOuYbA88CT3vsXA88s\ngORYMsHrQEhODtT7H1e/fv1C2f78law6+dHPtLQ0enX/95xrOv+R0PEOj463jndFOt463tXZPhL1\n4XhnZ2cHev8gM38b6FK29FcAjABGVRrzEjABWOCc6wt86b3f6pxLAh4F1nnvZwWakYiIiEg9kbD4\n9t4XUVpEvQysAxZ679c758Y458aUjVkMbHTObQDmAteU7f5d4ApggHPunbJ/BoUR5FhldW1buW1R\nbluU2xaruYMKdC3Oe78EWFLpsbmVtifE2S8L3RRVREREjFIRFDGr9x9RbluU2xbltsVq7qBUaImI\niIiERIVWxKyubSu3Lcpti3LbYjV3UCq0REREREKiQitiVte2ldsW5bZFuW2xmjsoFVoiIiIiIVGh\nFTGra9vKbYty26LctljNHZQKLREREZGQqNCKmNW1beW2RbltUW5brOYOSoWWiIiISEhUaEXM6tq2\nctui3LYoty1WcwelQktEREQkJCq0ImZ1bVu5bVFuW5TbFqu5g1KhJSIiIhISFVoRs7q2rdy2KLct\nym2L1dxBqdASERERCYkKrYhZXdtWbluU2xbltsVq7qBUaImIiIiERIVWxKyubSu3Lcpti3LbYjV3\nUCq0REREREKiQitiVte2ldsW5bZFuW2xmjsoFVoiIiIiIVGhFTGra9vKbYty26LctljNHZQKLRER\nEZGQqNCKmNW1beW2RbltUW5brOYOSoWWiIiISEhUaEXM6tq2ctui3LYoty1WcwelQktEREQkJCq0\nImZ1bVu5bVFuW5TbFqu5g1KhJSIiIhISFVoRs7q2rdy2KLctym2L1dxBqdASERERCYkKrYhZXdtW\nbluU2xbltsVq7qBUaImIiIiERIVWxKyubSu3Lcpti3LbYjV3UCq0REREREKiQitiVte2ldsW5bZF\nuW2xmjsoFVoiIiIiIVGhFTGra9vKbYty26LctljNHZQKLREREZGQqNCKmNW1beW2RbltUW5brOYO\nKjnRAOfcIGAWEAPmee/viTPmAeAHwF7gp977d4LuKyIiIlJfVXlFyzkXA+YAg4BTgVHOue6VxlwE\ndPbedwGuBh4Ouq/YXdtWbluU2xbltsVq7qASLR2eDWzw3m/y3u8HFgBDKo35EfA4gPd+FdDcOdc6\n4L4iIiIi9VaiQqstkF9he3PZY0HGnBRgX/Osrm0rty3KbYty22I1d1BJVT3pnBsGDPLe/6Js+wrg\nHO/9tRXG/D9guvf+zbLtpcAUID3RvhUtXbr070D/ow0kIiIiUgtez8zMPC/RoETN8J8A7Stst6f0\nylRVY9qVjWkYYN9yQSYrIiIicixJVGi9DXRxzqUDBcAIYFSlMS8BE4AFzrm+wJfe+63Oue0B9hUR\nERGpt6rs0fLeF1FaRL0MrAMWeu/XO+fGOOfGlI1ZDGx0zm0A5gLXVLVvaElERERERERERERERERE\nRERERETqnipv71BbLH5Vj3PuT8APgc+896dFPZ/a4pxrD8wHTgRKgD967x+Idlbhc86lAK8DxwGN\ngD9772+Odla1p+ybIt4GNnvvL456PrXBObcJ2AkcAPZ778+Odka1wznXHJgH9KD0v/H/8t6vjHZW\n4XLOnULpTbkP6ghMM/J3283AFUAxsAb4mfd+X7SzCp9z7jrg55TWUY94739/uLGRf6m04a/qeYzS\nzNbsByZ573sAfYHxFo639/4rYID3/nSgJzDAOdcv4mnVpuso/VBMSdQTqUUlwHne+zOsFFllfg8s\n9rSezx0AAANjSURBVN53p/Rcr/cfgvLe/7PsOJ8BnEnp9/6+EPG0Qld2V4FfAL3LLhjEgJGRTqoW\nOOe+TWmRdRbQCxjsnOt0uPGRF1oY/aoe7/0y4Iuo51HbvPdbvPfvlv15N6V/CZ8U7axqh/d+b9kf\nG1H6F9KOCKdTa5xz7YCLKL3KUSeuotciU3mdc2nAf3rv/wSlnz733lu7bXgmkOO9z0848ti3k9Jf\nnps455KBJpTeW7O+6was8t5/5b0/QOlqxaWHG5zoPlq1Id5X+JwT0VykFpX9NnQGsCriqdQK51wD\nIBvoBDzsvV8X8ZRqy/3AL4FmUU+klpUAS51zB4C53vtHop5QLcgAtjnnHqP0N/3VwHUVfsmwYCTw\ndNSTqA3e+x3OuXuBPOBfwMve+6URT6s2rAXucs79B/AVpW1A/zjc4LpwRcvSUoKUcc6lAs9R+pfw\n7qjnUxu898VlS4ftgO85586LeEqhc84NprQP8R2MXd0Bvlu2lPQDSpfI/zPqCdWCZKA38JD3vjew\nB7gp2inVHudcI+BiwEc9l9pQtlw2kdKv3DsJSHXOXR7ppGqB9/5D4B7gFWAJ8A6lPWpx1YVCK8jX\n/Eg94pxrCDwPPOm9fzHq+dS2sqWUvwJ9op5LLfgO8CPnXC7wDDDQOTc/4jnVCu/9p2X/3kZpv46F\nPq3NlH7g4a2y7ecoLbys+AGwuuyYW9AHWO693152k/JFlP43X+957//kve/jve8PfAn883Bj60Kh\nVf41P2W/DYyg9Gt9pB5yziUBjwLrvPezop5PbXHOfavs01g45xoDF1D6W1C95r2f6r1v773PoHRJ\n5TXv/U+inlfYnHNNnHNNy/58PPB9Sj+RVa9577cA+c65rmUPZQIfRDil2jaK0l8orPgQ6Ouca1z2\nd3smpR96qfeccyeW/bsDMJQqlosjL7SsflWPc+4ZYDnQ1TmX75z7WdRzqiXfpfSjwAOcc++U/WPh\n05dtgNecc+9S2pP2/7z3r0Y8pyhYaRVoBSyrcLz/4r1/JeI51ZZrgaecc+9R+qnDuyOeT60oK6gz\nKb2qY4L3/j1Kb9fzNvB+2cN/jG5Gteo559wHlF4YusZ7vzPqCYmIiIiIiIiIiIiIiIiIiIiIiIiI\niIiIiIiIiIiIiIiIiIiIiIjUpP8PHI7oqi0KsHgAAAAASUVORK5CYII=\n", 256 | "text": [ 257 | "" 258 | ] 259 | } 260 | ], 261 | "prompt_number": 21 262 | } 263 | ], 264 | "metadata": {} 265 | } 266 | ] 267 | } -------------------------------------------------------------------------------- /notebooks/diffusion_simulations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:8b9d561809b5f72e6052b67df2b0285504ac55f128b3c22c9c09823c27d7e681" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "import numpy as np\n", 16 | "\n", 17 | "from functools import partial\n", 18 | "from matplotlib import pyplot as plt\n", 19 | "\n", 20 | "from numpy import linalg as LA\n", 21 | "\n", 22 | "from hypergraph import generators\n", 23 | "from hypergraph.analytical import prediction\n", 24 | "from hypergraph.diffusion_engine import DiffusionEngine\n", 25 | "from hypergraph import utils\n", 26 | "from hypergraph.markov_diffusion import create_markov_matrix_model_nodes\n", 27 | "from hypergraph.markov_diffusion import create_markov_matrix_model_hyper_edges\n", 28 | "\n", 29 | "\n", 30 | "# Define model's definitions\n", 31 | "ALL_MODELS = {\n", 32 | " \"node\": {\n", 33 | " \"analytical\": partial(prediction, model='hypergraph_nodes'),\n", 34 | " \"numerical\": create_markov_matrix_model_nodes,\n", 35 | " \"name\": \"node\",\n", 36 | " },\n", 37 | " \"hyperedges\": {\n", 38 | " \"analytical\": partial(prediction, model='hypergraph_edges'),\n", 39 | " \"numerical\": create_markov_matrix_model_hyper_edges,\n", 40 | " \"name\": \"hyperedges\",\n", 41 | " }\n", 42 | "}\n", 43 | "\n", 44 | "# Constants for atomistic simulation\n", 45 | "t_max = 100000\n", 46 | "number_of_walkers = 1\n" 47 | ], 48 | "language": "python", 49 | "metadata": {}, 50 | "outputs": [], 51 | "prompt_number": 2 52 | }, 53 | { 54 | "cell_type": "code", 55 | "collapsed": false, 56 | "input": [ 57 | "%matplotlib inline\n", 58 | "from hypergraph.hypergraph_models import HyperGraph\n", 59 | "from hypergraph.generators import generic_hypergraph" 60 | ], 61 | "language": "python", 62 | "metadata": {}, 63 | "outputs": [], 64 | "prompt_number": 3 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "How it works:\n", 71 | "\n", 72 | " - generate random hypergraphs of given properties (another way of generating hypergraphs?)\n", 73 | " - have function which evaluates model so that, stationary distribution is available\n", 74 | " - saves hypergraph and results from three? (what about pykov?) ways of computing stationary distributions\n", 75 | " to later compare them\n", 76 | " - another function for comparing results later with nice graphs and pictures!\n" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "collapsed": false, 82 | "input": [ 83 | "import json\n", 84 | "import pykov\n", 85 | "import os\n", 86 | "\n", 87 | "\n", 88 | "def generate_hypergraph(generator_function, hypergraph_properties):\n", 89 | " HG = generator_function(**hypergraph_properties)\n", 90 | " return HG\n", 91 | "\n", 92 | "\n", 93 | "def transition_matrix_to_pykov_chain(matrix):\n", 94 | " chain = pykov.Chain()\n", 95 | " \n", 96 | " for i, row in enumerate(matrix):\n", 97 | " for j, column in enumerate(row):\n", 98 | " chain[(i, j)] = column\n", 99 | " return chain" 100 | ], 101 | "language": "python", 102 | "metadata": {}, 103 | "outputs": [] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "collapsed": false, 108 | "input": [ 109 | "from collections import Counter\n", 110 | "\n", 111 | "\n", 112 | "# for nodes' model\n", 113 | "\n", 114 | "def compute_atomistic_results_nodes(HG):\n", 115 | " markov_matrix = create_markov_matrix_model_nodes(HG)\n", 116 | " chain = transition_matrix_to_pykov_chain(mm)\n", 117 | " pykov_chain = pykov.Chain(chain)\n", 118 | " all_states = []\n", 119 | " for x in range(100):\n", 120 | " states = pykov_chain.walk(1000)\n", 121 | " all_states += states\n", 122 | " \n", 123 | " freqs = Counter(all_states)\n", 124 | " for x in range(len(mm)):\n", 125 | " if x not in freqs:\n", 126 | " freqs = 0\n", 127 | " else:\n", 128 | " freqs[x] /= 100\n", 129 | " xs, ys = zip(*freq.items()) \n", 130 | " return xs, ys\n", 131 | "\n", 132 | "\n", 133 | "def compute_matrix_power_results_nodes(HG):\n", 134 | " markov_matrix = create_markov_matrix_model_nodes(HG)\n", 135 | "\n", 136 | " freqs_matrix = LA.matrix_power(markov_matrix, 40)[0]\n", 137 | " ys = freqs_matrix\n", 138 | " xs = range(len(ys))\n", 139 | " return xs, ys\n", 140 | "\n", 141 | "\n", 142 | "def compute_pykov_results_nodes(HG):\n", 143 | " mm = create_markov_matrix_model_nodes(HG)\n", 144 | " chain = transition_matrix_to_pykov_chain(mm)\n", 145 | " chain_transposed = pykov.Chain(chain)\n", 146 | " xs, ys = zip(*chain_transposed.steady().items())\n", 147 | " return xs, ys\n", 148 | "\n", 149 | "\n", 150 | "def compute_analytical_prediction_nodes(HG):\n", 151 | " ys = prediction(model='hypergraph_nodes', graph=HG)\n", 152 | " xs = range(len(ys))\n", 153 | " return xs, ys\n", 154 | "\n", 155 | "\n", 156 | "# for hyper edges' model\n", 157 | "def compute_atomistic_results_edges(HG):\n", 158 | " markov_matrix = create_markov_matrix_model_hyper_edges(HG)\n", 159 | " t_per_walker = int(t_max / number_of_walkers)\n", 160 | " engine = DiffusionEngine(markov_matrix, t_per_walker=t_per_walker)\n", 161 | "\n", 162 | " frequencies, states = engine.simulate(t_max)\n", 163 | "\n", 164 | " frequencies = [(node, frequency) for node, frequency in frequencies]\n", 165 | " frequencies.sort(key=lambda x: x[0])\n", 166 | " xs, ys = zip(*frequencies)\n", 167 | "\n", 168 | " ys = np.array(ys, dtype='float')\n", 169 | " ys /= sum(ys)\n", 170 | " return xs, ys\n", 171 | "\n", 172 | "\n", 173 | "def compute_matrix_power_results_edges(HG):\n", 174 | " markov_matrix = create_markov_matrix_model_hyper_edges(HG)\n", 175 | "\n", 176 | " freqs_matrix = LA.matrix_power(markov_matrix, 40)[0]\n", 177 | " ys = freqs_matrix\n", 178 | " xs = range(len(ys))\n", 179 | " return xs, ys\n", 180 | "\n", 181 | "\n", 182 | "def compute_pykov_results_edges(HG):\n", 183 | " mm = create_markov_matrix_model_hyper_edges(HG)\n", 184 | " chain = transition_matrix_to_pykov_chain(mm)\n", 185 | " pykov_chain = pykov.Chain(chain)\n", 186 | " xs, ys = zip(*pykov_chain.steady().items())\n", 187 | " return xs, ys\n", 188 | "\n", 189 | "\n", 190 | "def compute_analytical_prediction_edges(HG):\n", 191 | " ys = prediction(model='hypergraph_edges', graph=HG)\n", 192 | " xs = range(len(ys))\n", 193 | " return xs, ys\n" 194 | ], 195 | "language": "python", 196 | "metadata": {}, 197 | "outputs": [], 198 | "prompt_number": 4 199 | }, 200 | { 201 | "cell_type": "code", 202 | "collapsed": false, 203 | "input": [ 204 | "\n", 205 | "def compute_stationary_distributions(HG, name_to_computation_functions_mapping):\n", 206 | " results = {}\n", 207 | " for name, computation_function in name_to_computation_functions_mapping.items():\n", 208 | " xs, pies = computation_function(HG)\n", 209 | " results[name] = pies\n", 210 | " return results\n", 211 | "\n", 212 | "\n", 213 | "def serialize(HG):\n", 214 | " edges = [list(edge) for edge in HG.hyper_edges()]\n", 215 | " return json.dumps(edges)\n", 216 | "\n", 217 | "\n", 218 | "def save_result_distribution(filename, result_distribution):\n", 219 | " with open(filename, 'w') as f:\n", 220 | " for value in result_distribution:\n", 221 | " f.write(\"%s\\n\" % value)\n", 222 | "\n", 223 | "\n", 224 | "def save_hypergraph_values(filename, hg_description):\n", 225 | " with open(filename, 'w') as f:\n", 226 | " f.write(hg_description)\n", 227 | "\n", 228 | "\n", 229 | "def save_results_to_files(HG, results, counter, directory_name=None):\n", 230 | " base_filename = '%s_{name}.csv' % counter\n", 231 | " if directory_name:\n", 232 | " if not os.path.exists(directory_name):\n", 233 | " os.mkdir(directory_name)\n", 234 | " base_filename = directory_name + '/' + base_filename\n", 235 | " \n", 236 | " for name, result_distribution in results.items():\n", 237 | " filename = base_filename.format(name=name)\n", 238 | " save_result_distribution(filename, result_distribution)\n", 239 | " \n", 240 | " hg_description = serialize(HG)\n", 241 | " filename = base_filename.format(name='hypergraph')\n", 242 | " save_hypergraph_values(filename, hg_description)" 243 | ], 244 | "language": "python", 245 | "metadata": {}, 246 | "outputs": [], 247 | "prompt_number": 5 248 | }, 249 | { 250 | "cell_type": "code", 251 | "collapsed": false, 252 | "input": [ 253 | "nodes_mapping = {\n", 254 | " 'analytical_nodes': compute_analytical_prediction_nodes,\n", 255 | " 'atomistic_nodes': compute_atomistic_results_nodes,\n", 256 | " 'matrix_power_nodes': compute_matrix_power_results_nodes,\n", 257 | " 'pykov_nodes': compute_pykov_results_nodes,\n", 258 | "}\n", 259 | "\n", 260 | "edges_mapping = {\n", 261 | " 'analytical_edges': compute_analytical_prediction_edges,\n", 262 | " 'atomistic_edges': compute_atomistic_results_edges,\n", 263 | " 'matrix_power_edges': compute_matrix_power_results_edges,\n", 264 | " 'pykov_edges': compute_pykov_results_edges,\n", 265 | "}\n", 266 | "\n", 267 | "\n", 268 | "def execute_pipeline(generator_function, hypergraph_properties, directory_name, name_to_computation_functions_mapping, n=10): \n", 269 | " for counter in range(n):\n", 270 | " HG = generate_hypergraph(generator_function, hypergraph_properties)\n", 271 | " results = compute_stationary_distributions(HG, name_to_computation_functions_mapping)\n", 272 | " save_results_to_files(HG, results, counter, directory_name=directory_name)\n", 273 | " print(\"%s/%s\" % (counter + 1, n))\n", 274 | " print('done')" 275 | ], 276 | "language": "python", 277 | "metadata": {}, 278 | "outputs": [], 279 | "prompt_number": 3 280 | }, 281 | { 282 | "cell_type": "code", 283 | "collapsed": false, 284 | "input": [ 285 | "for number_of_nodes in range(50, 90, 10):\n", 286 | " print(number_of_nodes)\n", 287 | " generator_function = generic_hypergraph\n", 288 | " hypergraph_properties = {\n", 289 | " 'number_of_nodes': number_of_nodes,\n", 290 | " 'edges_params': ((2, 20), (3, 30), (4, 20), (5, 15), (6, 10))\n", 291 | " }\n", 292 | " \n", 293 | " print('Nodes models')\n", 294 | " directory_name = 'hypergraph_nodes_%s' % number_of_nodes\n", 295 | " execute_pipeline(generator_function, hypergraph_properties, directory_name, nodes_mapping)\n", 296 | " \n", 297 | " print('\\nEdges models')\n", 298 | " directory_name = 'hypergraph_edges_%s' % number_of_nodes\n", 299 | " execute_pipeline(generator_function, hypergraph_properties, directory_name, edges_mapping)" 300 | ], 301 | "language": "python", 302 | "metadata": {}, 303 | "outputs": [ 304 | { 305 | "output_type": "stream", 306 | "stream": "stdout", 307 | "text": [ 308 | "50\n", 309 | "Nodes models\n", 310 | "1/10" 311 | ] 312 | }, 313 | { 314 | "output_type": "stream", 315 | "stream": "stdout", 316 | "text": [ 317 | "\n", 318 | "2/10" 319 | ] 320 | }, 321 | { 322 | "output_type": "stream", 323 | "stream": "stdout", 324 | "text": [ 325 | "\n", 326 | "3/10" 327 | ] 328 | }, 329 | { 330 | "output_type": "stream", 331 | "stream": "stdout", 332 | "text": [ 333 | "\n", 334 | "4/10" 335 | ] 336 | }, 337 | { 338 | "output_type": "stream", 339 | "stream": "stdout", 340 | "text": [ 341 | "\n", 342 | "5/10" 343 | ] 344 | }, 345 | { 346 | "output_type": "stream", 347 | "stream": "stdout", 348 | "text": [ 349 | "\n", 350 | "6/10" 351 | ] 352 | }, 353 | { 354 | "output_type": "stream", 355 | "stream": "stdout", 356 | "text": [ 357 | "\n", 358 | "7/10" 359 | ] 360 | }, 361 | { 362 | "output_type": "stream", 363 | "stream": "stdout", 364 | "text": [ 365 | "\n", 366 | "8/10" 367 | ] 368 | }, 369 | { 370 | "output_type": "stream", 371 | "stream": "stdout", 372 | "text": [ 373 | "\n", 374 | "9/10" 375 | ] 376 | }, 377 | { 378 | "output_type": "stream", 379 | "stream": "stdout", 380 | "text": [ 381 | "\n", 382 | "10/10" 383 | ] 384 | }, 385 | { 386 | "output_type": "stream", 387 | "stream": "stdout", 388 | "text": [ 389 | "\n", 390 | "done\n", 391 | "\n", 392 | "Edges models\n", 393 | "1/10" 394 | ] 395 | }, 396 | { 397 | "output_type": "stream", 398 | "stream": "stdout", 399 | "text": [ 400 | "\n", 401 | "2/10" 402 | ] 403 | }, 404 | { 405 | "output_type": "stream", 406 | "stream": "stdout", 407 | "text": [ 408 | "\n", 409 | "3/10" 410 | ] 411 | }, 412 | { 413 | "output_type": "stream", 414 | "stream": "stdout", 415 | "text": [ 416 | "\n", 417 | "4/10" 418 | ] 419 | }, 420 | { 421 | "output_type": "stream", 422 | "stream": "stdout", 423 | "text": [ 424 | "\n", 425 | "5/10" 426 | ] 427 | }, 428 | { 429 | "output_type": "stream", 430 | "stream": "stdout", 431 | "text": [ 432 | "\n", 433 | "6/10" 434 | ] 435 | }, 436 | { 437 | "output_type": "stream", 438 | "stream": "stdout", 439 | "text": [ 440 | "\n", 441 | "7/10" 442 | ] 443 | }, 444 | { 445 | "output_type": "stream", 446 | "stream": "stdout", 447 | "text": [ 448 | "\n", 449 | "8/10" 450 | ] 451 | }, 452 | { 453 | "output_type": "stream", 454 | "stream": "stdout", 455 | "text": [ 456 | "\n", 457 | "9/10" 458 | ] 459 | }, 460 | { 461 | "output_type": "stream", 462 | "stream": "stdout", 463 | "text": [ 464 | "\n", 465 | "10/10" 466 | ] 467 | }, 468 | { 469 | "output_type": "stream", 470 | "stream": "stdout", 471 | "text": [ 472 | "\n", 473 | "done\n", 474 | "60\n", 475 | "Nodes models\n", 476 | "1/10" 477 | ] 478 | }, 479 | { 480 | "output_type": "stream", 481 | "stream": "stdout", 482 | "text": [ 483 | "\n", 484 | "2/10" 485 | ] 486 | }, 487 | { 488 | "output_type": "stream", 489 | "stream": "stdout", 490 | "text": [ 491 | "\n", 492 | "3/10" 493 | ] 494 | }, 495 | { 496 | "output_type": "stream", 497 | "stream": "stdout", 498 | "text": [ 499 | "\n", 500 | "4/10" 501 | ] 502 | }, 503 | { 504 | "output_type": "stream", 505 | "stream": "stdout", 506 | "text": [ 507 | "\n", 508 | "5/10" 509 | ] 510 | }, 511 | { 512 | "output_type": "stream", 513 | "stream": "stdout", 514 | "text": [ 515 | "\n", 516 | "6/10" 517 | ] 518 | }, 519 | { 520 | "output_type": "stream", 521 | "stream": "stdout", 522 | "text": [ 523 | "\n", 524 | "7/10" 525 | ] 526 | }, 527 | { 528 | "output_type": "stream", 529 | "stream": "stdout", 530 | "text": [ 531 | "\n", 532 | "8/10" 533 | ] 534 | }, 535 | { 536 | "output_type": "stream", 537 | "stream": "stdout", 538 | "text": [ 539 | "\n", 540 | "9/10" 541 | ] 542 | }, 543 | { 544 | "output_type": "stream", 545 | "stream": "stdout", 546 | "text": [ 547 | "\n", 548 | "10/10" 549 | ] 550 | }, 551 | { 552 | "output_type": "stream", 553 | "stream": "stdout", 554 | "text": [ 555 | "\n", 556 | "done\n", 557 | "\n", 558 | "Edges models\n", 559 | "1/10" 560 | ] 561 | }, 562 | { 563 | "output_type": "stream", 564 | "stream": "stdout", 565 | "text": [ 566 | "\n", 567 | "2/10" 568 | ] 569 | }, 570 | { 571 | "output_type": "stream", 572 | "stream": "stdout", 573 | "text": [ 574 | "\n", 575 | "3/10" 576 | ] 577 | }, 578 | { 579 | "output_type": "stream", 580 | "stream": "stdout", 581 | "text": [ 582 | "\n", 583 | "4/10" 584 | ] 585 | }, 586 | { 587 | "output_type": "stream", 588 | "stream": "stdout", 589 | "text": [ 590 | "\n", 591 | "5/10" 592 | ] 593 | }, 594 | { 595 | "output_type": "stream", 596 | "stream": "stdout", 597 | "text": [ 598 | "\n", 599 | "6/10" 600 | ] 601 | }, 602 | { 603 | "output_type": "stream", 604 | "stream": "stdout", 605 | "text": [ 606 | "\n", 607 | "7/10" 608 | ] 609 | }, 610 | { 611 | "output_type": "stream", 612 | "stream": "stdout", 613 | "text": [ 614 | "\n", 615 | "8/10" 616 | ] 617 | }, 618 | { 619 | "output_type": "stream", 620 | "stream": "stdout", 621 | "text": [ 622 | "\n", 623 | "9/10" 624 | ] 625 | }, 626 | { 627 | "output_type": "stream", 628 | "stream": "stdout", 629 | "text": [ 630 | "\n", 631 | "10/10" 632 | ] 633 | }, 634 | { 635 | "output_type": "stream", 636 | "stream": "stdout", 637 | "text": [ 638 | "\n", 639 | "done\n", 640 | "70\n", 641 | "Nodes models\n", 642 | "1/10" 643 | ] 644 | }, 645 | { 646 | "output_type": "stream", 647 | "stream": "stdout", 648 | "text": [ 649 | "\n", 650 | "2/10" 651 | ] 652 | }, 653 | { 654 | "output_type": "stream", 655 | "stream": "stdout", 656 | "text": [ 657 | "\n", 658 | "3/10" 659 | ] 660 | }, 661 | { 662 | "output_type": "stream", 663 | "stream": "stdout", 664 | "text": [ 665 | "\n", 666 | "4/10" 667 | ] 668 | }, 669 | { 670 | "output_type": "stream", 671 | "stream": "stdout", 672 | "text": [ 673 | "\n", 674 | "5/10" 675 | ] 676 | }, 677 | { 678 | "output_type": "stream", 679 | "stream": "stdout", 680 | "text": [ 681 | "\n", 682 | "6/10" 683 | ] 684 | }, 685 | { 686 | "output_type": "stream", 687 | "stream": "stdout", 688 | "text": [ 689 | "\n", 690 | "7/10" 691 | ] 692 | }, 693 | { 694 | "output_type": "stream", 695 | "stream": "stdout", 696 | "text": [ 697 | "\n", 698 | "8/10" 699 | ] 700 | }, 701 | { 702 | "output_type": "stream", 703 | "stream": "stdout", 704 | "text": [ 705 | "\n", 706 | "9/10" 707 | ] 708 | }, 709 | { 710 | "output_type": "stream", 711 | "stream": "stdout", 712 | "text": [ 713 | "\n", 714 | "10/10" 715 | ] 716 | }, 717 | { 718 | "output_type": "stream", 719 | "stream": "stdout", 720 | "text": [ 721 | "\n", 722 | "done\n", 723 | "\n", 724 | "Edges models\n", 725 | "1/10" 726 | ] 727 | }, 728 | { 729 | "output_type": "stream", 730 | "stream": "stdout", 731 | "text": [ 732 | "\n", 733 | "2/10" 734 | ] 735 | }, 736 | { 737 | "output_type": "stream", 738 | "stream": "stdout", 739 | "text": [ 740 | "\n", 741 | "3/10" 742 | ] 743 | }, 744 | { 745 | "output_type": "stream", 746 | "stream": "stdout", 747 | "text": [ 748 | "\n", 749 | "4/10" 750 | ] 751 | }, 752 | { 753 | "output_type": "stream", 754 | "stream": "stdout", 755 | "text": [ 756 | "\n", 757 | "5/10" 758 | ] 759 | }, 760 | { 761 | "output_type": "stream", 762 | "stream": "stdout", 763 | "text": [ 764 | "\n", 765 | "6/10" 766 | ] 767 | }, 768 | { 769 | "output_type": "stream", 770 | "stream": "stdout", 771 | "text": [ 772 | "\n", 773 | "7/10" 774 | ] 775 | }, 776 | { 777 | "output_type": "stream", 778 | "stream": "stdout", 779 | "text": [ 780 | "\n", 781 | "8/10" 782 | ] 783 | }, 784 | { 785 | "output_type": "stream", 786 | "stream": "stdout", 787 | "text": [ 788 | "\n", 789 | "9/10" 790 | ] 791 | }, 792 | { 793 | "output_type": "stream", 794 | "stream": "stdout", 795 | "text": [ 796 | "\n", 797 | "10/10" 798 | ] 799 | }, 800 | { 801 | "output_type": "stream", 802 | "stream": "stdout", 803 | "text": [ 804 | "\n", 805 | "done\n", 806 | "80\n", 807 | "Nodes models\n", 808 | "1/10" 809 | ] 810 | }, 811 | { 812 | "output_type": "stream", 813 | "stream": "stdout", 814 | "text": [ 815 | "\n", 816 | "2/10" 817 | ] 818 | }, 819 | { 820 | "output_type": "stream", 821 | "stream": "stdout", 822 | "text": [ 823 | "\n", 824 | "3/10" 825 | ] 826 | }, 827 | { 828 | "output_type": "stream", 829 | "stream": "stdout", 830 | "text": [ 831 | "\n", 832 | "4/10" 833 | ] 834 | }, 835 | { 836 | "output_type": "stream", 837 | "stream": "stdout", 838 | "text": [ 839 | "\n", 840 | "5/10" 841 | ] 842 | }, 843 | { 844 | "output_type": "stream", 845 | "stream": "stdout", 846 | "text": [ 847 | "\n", 848 | "6/10" 849 | ] 850 | }, 851 | { 852 | "output_type": "stream", 853 | "stream": "stdout", 854 | "text": [ 855 | "\n", 856 | "7/10" 857 | ] 858 | }, 859 | { 860 | "output_type": "stream", 861 | "stream": "stdout", 862 | "text": [ 863 | "\n", 864 | "8/10" 865 | ] 866 | }, 867 | { 868 | "output_type": "stream", 869 | "stream": "stdout", 870 | "text": [ 871 | "\n", 872 | "9/10" 873 | ] 874 | }, 875 | { 876 | "output_type": "stream", 877 | "stream": "stdout", 878 | "text": [ 879 | "\n", 880 | "10/10" 881 | ] 882 | }, 883 | { 884 | "output_type": "stream", 885 | "stream": "stdout", 886 | "text": [ 887 | "\n", 888 | "done\n", 889 | "\n", 890 | "Edges models\n", 891 | "1/10" 892 | ] 893 | }, 894 | { 895 | "output_type": "stream", 896 | "stream": "stdout", 897 | "text": [ 898 | "\n", 899 | "2/10" 900 | ] 901 | }, 902 | { 903 | "output_type": "stream", 904 | "stream": "stdout", 905 | "text": [ 906 | "\n", 907 | "3/10" 908 | ] 909 | }, 910 | { 911 | "output_type": "stream", 912 | "stream": "stdout", 913 | "text": [ 914 | "\n", 915 | "4/10" 916 | ] 917 | }, 918 | { 919 | "output_type": "stream", 920 | "stream": "stdout", 921 | "text": [ 922 | "\n", 923 | "5/10" 924 | ] 925 | }, 926 | { 927 | "output_type": "stream", 928 | "stream": "stdout", 929 | "text": [ 930 | "\n", 931 | "6/10" 932 | ] 933 | }, 934 | { 935 | "output_type": "stream", 936 | "stream": "stdout", 937 | "text": [ 938 | "\n", 939 | "7/10" 940 | ] 941 | }, 942 | { 943 | "output_type": "stream", 944 | "stream": "stdout", 945 | "text": [ 946 | "\n", 947 | "8/10" 948 | ] 949 | }, 950 | { 951 | "output_type": "stream", 952 | "stream": "stdout", 953 | "text": [ 954 | "\n", 955 | "9/10" 956 | ] 957 | }, 958 | { 959 | "output_type": "stream", 960 | "stream": "stdout", 961 | "text": [ 962 | "\n", 963 | "10/10" 964 | ] 965 | }, 966 | { 967 | "output_type": "stream", 968 | "stream": "stdout", 969 | "text": [ 970 | "\n", 971 | "done\n" 972 | ] 973 | } 974 | ], 975 | "prompt_number": 50 976 | }, 977 | { 978 | "cell_type": "code", 979 | "collapsed": false, 980 | "input": [ 981 | "# show serialized form of hypergraph (possible to recreate it later)\n", 982 | "!cat hypergraph_nodes_50/0_hypergraph.csv" 983 | ], 984 | "language": "python", 985 | "metadata": {}, 986 | "outputs": [ 987 | { 988 | "output_type": "stream", 989 | "stream": "stdout", 990 | "text": [ 991 | "[[21, 46], [9, 5], [17, 50], [28, 37], [42, 36], [14, 7], [25, 18], [27, 21], [40, 11], [28, 23], [18, 37], [42, 35], [18, 34], [19, 12], [22, 39], [10, 20], [10, 27], [18, 50], [20, 14], [2, 26], [2, 21, 14], [1, 34, 14], [24, 26, 50], [16, 13, 45], [11, 12, 43], [49, 5, 31], [2, 34, 30], [2, 5, 14], [16, 41, 39], [49, 18, 34], [32, 48, 27], [26, 19, 23], [8, 25, 18], [16, 3, 43], [31, 38, 6], [32, 40, 18], [25, 20, 22], [16, 17, 19], [25, 3, 19], [8, 9, 23], [16, 27, 36], [41, 27, 15], [40, 2, 37], [41, 12, 45], [27, 43, 21], [32, 10, 21], [33, 20, 44], [18, 43, 15], [16, 49, 44], [40, 48, 3], [2, 44, 22, 38], [24, 15, 42, 39], [48, 42, 27, 22], [19, 28, 5, 30], [8, 29, 6, 30], [16, 26, 43, 7], [25, 2, 4, 23], [48, 3, 44, 14], [7, 10, 29, 39], [17, 5, 38, 31], [1, 37, 22, 15], [25, 20, 45, 7], [8, 50, 11, 21], [24, 9, 42, 13], [16, 9, 3, 15], [1, 26, 19, 7], [25, 41, 27, 45], [24, 32, 50, 45], [16, 41, 30, 7], [9, 12, 36, 30], [24, 25, 29, 38, 31], [16, 17, 32, 5, 6], [9, 34, 41, 50, 15], [16, 17, 25, 20, 5], [40, 10, 35, 50, 29], [40, 49, 11, 21, 45], [32, 41, 7, 13, 47], [15, 25, 2, 36, 39], [3, 11, 29, 35, 15], [6, 14, 18, 22, 23], [17, 42, 3, 36, 39], [32, 49, 35, 37, 38], [40, 17, 2, 11, 44], [8, 33, 50, 13, 22], [24, 48, 43, 21, 14], [49, 2, 24, 42, 14, 47], [2, 23, 8, 40, 27, 44], [47, 7, 25, 29, 14, 15], [17, 49, 36, 7, 12, 14], [2, 36, 5, 20, 28, 14], [1, 4, 5, 21, 28, 14], [48, 2, 34, 5, 23, 41], [48, 23, 10, 42, 26, 13], [33, 17, 19, 25, 42, 12], [33, 18, 50, 21, 24, 41]]" 992 | ] 993 | } 994 | ], 995 | "prompt_number": 42 996 | }, 997 | { 998 | "cell_type": "markdown", 999 | "metadata": {}, 1000 | "source": [ 1001 | "This way I generated a lot of data for a pretty complex hypergraph. However, how do I analyze it?\n", 1002 | "It would be nice to load data from disc and make some basic statistics.\n", 1003 | "I know that atomistic is the most divergent, huh. However, rest should be fine.\n", 1004 | "\n", 1005 | "If I for example get to compare pykov (steady state distributions of Markov Chain based on transition matrix generated from the hypergraph) with model nodes, how big differences will be?" 1006 | ] 1007 | }, 1008 | { 1009 | "cell_type": "code", 1010 | "collapsed": false, 1011 | "input": [ 1012 | "# read example of results with numpy\n", 1013 | "pykov_results = np.loadtxt('hypergraph_nodes_50/0_pykov_nodes.csv')" 1014 | ], 1015 | "language": "python", 1016 | "metadata": {}, 1017 | "outputs": [], 1018 | "prompt_number": 43 1019 | }, 1020 | { 1021 | "cell_type": "code", 1022 | "collapsed": false, 1023 | "input": [ 1024 | "analytical_results = np.loadtxt('hypergraph_nodes_50/0_analytical_nodes.csv')" 1025 | ], 1026 | "language": "python", 1027 | "metadata": {}, 1028 | "outputs": [], 1029 | "prompt_number": 44 1030 | }, 1031 | { 1032 | "cell_type": "code", 1033 | "collapsed": false, 1034 | "input": [ 1035 | "# compare arrays of results by computing their difference\n", 1036 | "pykov_results - analytical_results" 1037 | ], 1038 | "language": "python", 1039 | "metadata": {}, 1040 | "outputs": [ 1041 | { 1042 | "metadata": {}, 1043 | "output_type": "pyout", 1044 | "prompt_number": 45, 1045 | "text": [ 1046 | "array([ 4.92748203e-14, 1.01446629e-14, 3.62349040e-14,\n", 1047 | " 4.63778321e-15, 2.31897834e-14, 4.92748203e-14,\n", 1048 | " -3.91284227e-14, -2.60867716e-14, -2.60867716e-14,\n", 1049 | " -2.60867716e-14, -2.60867716e-14, -2.60867716e-14,\n", 1050 | " 1.15948917e-14, 1.01446629e-14, -3.91284227e-14,\n", 1051 | " -1.44884105e-14, -3.91284227e-14, 2.31897834e-14,\n", 1052 | " 3.62349040e-14, 3.62349040e-14, 2.31897834e-14,\n", 1053 | " 3.62349040e-14, -1.45022883e-15, -1.45022883e-15,\n", 1054 | " 4.78228568e-14, -2.60867716e-14, -3.91284227e-14,\n", 1055 | " 1.15948917e-14, -2.60867716e-14, 1.15948917e-14,\n", 1056 | " 4.92748203e-14, 3.62349040e-14, 4.92748203e-14,\n", 1057 | " -2.60867716e-14, 4.92748203e-14, 3.62349040e-14,\n", 1058 | " 1.15948917e-14, 1.15948917e-14, -2.60867716e-14,\n", 1059 | " -1.45022883e-15, -3.91284227e-14, -3.91284227e-14,\n", 1060 | " -2.60867716e-14, -2.60867716e-14, -2.60867716e-14,\n", 1061 | " 2.31889161e-15, -3.04443970e-15, 3.62349040e-14,\n", 1062 | " 3.62349040e-14, -3.91284227e-14])" 1063 | ] 1064 | } 1065 | ], 1066 | "prompt_number": 45 1067 | }, 1068 | { 1069 | "cell_type": "code", 1070 | "collapsed": false, 1071 | "input": [ 1072 | "def compare_results(base_directory, suffix_one, suffix_two):\n", 1073 | " \"\"\"Compute differences between two different methods for computing the same result\"\"\"\n", 1074 | " filenames = os.listdir(base_directory)\n", 1075 | " first_filenames = [filename for filename in filenames if filename.endswith(suffix_one)]\n", 1076 | " first_filenames.sort()\n", 1077 | " second_filenames = [filename for filename in filenames if filename.endswith(suffix_two)]\n", 1078 | " second_filenames.sort()\n", 1079 | " differences = []\n", 1080 | " for first, second in zip(first_filenames, second_filenames):\n", 1081 | " difference = np.loadtxt(base_directory + '/' + first) - np.loadtxt(base_directory + '/' + second)\n", 1082 | " differences.append(difference)\n", 1083 | " return differences" 1084 | ], 1085 | "language": "python", 1086 | "metadata": {}, 1087 | "outputs": [], 1088 | "prompt_number": 46 1089 | }, 1090 | { 1091 | "cell_type": "code", 1092 | "collapsed": false, 1093 | "input": [ 1094 | "# compare all the sets of sizes\n", 1095 | "def compare_sets(base_directory_template, suffix_one, suffix_two):\n", 1096 | " directories = (base_directory_template % number_of_nodes for number_of_nodes in range(50, 90, 10))\n", 1097 | " for directory in directories:\n", 1098 | " print(directory)\n", 1099 | " differences = compare_results(directory, suffix_one, suffix_two)\n", 1100 | " print('average difference', np.average(np.abs(differences)))\n", 1101 | " print('variance of differences', np.var(differences))\n", 1102 | " print('-' * 80)\n", 1103 | " print()\n", 1104 | "compare_sets('hypergraph_nodes_%s', 'pykov_nodes.csv', 'analytical_nodes.csv')" 1105 | ], 1106 | "language": "python", 1107 | "metadata": {}, 1108 | "outputs": [ 1109 | { 1110 | "output_type": "stream", 1111 | "stream": "stdout", 1112 | "text": [ 1113 | "hypergraph_nodes_50\n", 1114 | "average difference 2.48766320043e-14\n", 1115 | "variance of differences 8.226043135e-28\n", 1116 | "--------------------------------------------------------------------------------\n", 1117 | "\n", 1118 | "hypergraph_nodes_60\n", 1119 | "average difference 2.36902345252e-14\n", 1120 | "variance of differences 7.83562261469e-28\n", 1121 | "--------------------------------------------------------------------------------\n", 1122 | "\n", 1123 | "hypergraph_nodes_70\n", 1124 | "average difference 2.19816315447e-14\n", 1125 | "variance of differences 6.67990066372e-28\n", 1126 | "--------------------------------------------------------------------------------\n", 1127 | "\n", 1128 | "hypergraph_nodes_80\n", 1129 | "average difference 2.07090556726e-14\n", 1130 | "variance of differences 6.26384928982e-28\n", 1131 | "--------------------------------------------------------------------------------\n", 1132 | "\n" 1133 | ] 1134 | } 1135 | ], 1136 | "prompt_number": 47 1137 | }, 1138 | { 1139 | "cell_type": "code", 1140 | "collapsed": false, 1141 | "input": [ 1142 | "compare_sets('hypergraph_nodes_%s', 'atomistic_nodes.csv', 'analytical_nodes.csv')" 1143 | ], 1144 | "language": "python", 1145 | "metadata": {}, 1146 | "outputs": [ 1147 | { 1148 | "output_type": "stream", 1149 | "stream": "stdout", 1150 | "text": [ 1151 | "hypergraph_nodes_50\n", 1152 | "average difference 0.000474616811594\n", 1153 | "variance of differences 3.63764214325e-07\n", 1154 | "--------------------------------------------------------------------------------\n", 1155 | "\n", 1156 | "hypergraph_nodes_60\n", 1157 | "average difference 0.000444607246377\n", 1158 | "variance of differences 3.26531986978e-07\n", 1159 | "--------------------------------------------------------------------------------\n", 1160 | "\n", 1161 | "hypergraph_nodes_70\n", 1162 | "average difference 0.000407904347826\n", 1163 | "variance of differences 2.66943587062e-07\n", 1164 | "--------------------------------------------------------------------------------\n", 1165 | "\n", 1166 | "hypergraph_nodes_80\n", 1167 | "average difference 0.000356900724638\n", 1168 | "variance of differences 2.21134558129e-07\n", 1169 | "--------------------------------------------------------------------------------\n", 1170 | "\n" 1171 | ] 1172 | } 1173 | ], 1174 | "prompt_number": 48 1175 | }, 1176 | { 1177 | "cell_type": "code", 1178 | "collapsed": false, 1179 | "input": [ 1180 | "compare_sets('hypergraph_edges_%s', 'matrix_power_edges.csv', 'analytical_edges.csv')" 1181 | ], 1182 | "language": "python", 1183 | "metadata": {}, 1184 | "outputs": [ 1185 | { 1186 | "output_type": "stream", 1187 | "stream": "stdout", 1188 | "text": [ 1189 | "hypergraph_edges_50\n", 1190 | "average difference 8.44996189011e-11\n", 1191 | "variance of differences 5.54932472914e-20\n", 1192 | "--------------------------------------------------------------------------------\n", 1193 | "\n", 1194 | "hypergraph_edges_60\n", 1195 | "average difference 2.99177497468e-09\n", 1196 | "variance of differences 1.80022375173e-16\n", 1197 | "--------------------------------------------------------------------------------\n", 1198 | "\n", 1199 | "hypergraph_edges_70\n", 1200 | "average difference" 1201 | ] 1202 | }, 1203 | { 1204 | "output_type": "stream", 1205 | "stream": "stdout", 1206 | "text": [ 1207 | " 8.39051564135e-09\n", 1208 | "variance of differences 5.36273893841e-16\n", 1209 | "--------------------------------------------------------------------------------\n", 1210 | "\n", 1211 | "hypergraph_edges_80\n", 1212 | "average difference 3.22182446427e-06\n", 1213 | "variance of differences 6.41941085925e-10\n", 1214 | "--------------------------------------------------------------------------------\n", 1215 | "\n" 1216 | ] 1217 | } 1218 | ], 1219 | "prompt_number": 51 1220 | }, 1221 | { 1222 | "cell_type": "markdown", 1223 | "metadata": {}, 1224 | "source": [ 1225 | "## Summing up:\n", 1226 | "\n", 1227 | "- differences are really small between pykov (steady states distributions of markov chain and analytical solutions" 1228 | ] 1229 | }, 1230 | { 1231 | "cell_type": "code", 1232 | "collapsed": false, 1233 | "input": [], 1234 | "language": "python", 1235 | "metadata": {}, 1236 | "outputs": [] 1237 | } 1238 | ], 1239 | "metadata": {} 1240 | } 1241 | ] 1242 | } -------------------------------------------------------------------------------- /notebooks/clique_comparison.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:0f2644bf9c98ff91e4c0e932f18d1b0890bbe42744e0359439a5366f5f5bcf9d" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "%matplotlib inline" 16 | ], 17 | "language": "python", 18 | "metadata": {}, 19 | "outputs": [], 20 | "prompt_number": 1 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "##todo\n", 27 | "\n", 28 | "- Is this form of code the most recent one?\n", 29 | "- How can I make this more educational?\n", 30 | "- Write something about setup\n", 31 | "- Write more about conclusions\n", 32 | "- Divide code into smaller snippets and comment it a lot" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "collapsed": false, 38 | "input": [ 39 | "%matplotlib inline\n", 40 | "\n", 41 | "import numpy as np\n", 42 | "import networkx as nx\n", 43 | "from collections import Counter\n", 44 | "from matplotlib import pyplot as plt\n", 45 | "from numpy.random import random_sample\n", 46 | "from hypergraph.generators import uniform_hypergraph\n", 47 | "from hypergraph import converters\n", 48 | "from hypergraph import utils\n", 49 | "\n", 50 | "\n", 51 | "def create_markov_matrix(edges):\n", 52 | " a_matrix = np.zeros((len(edges), len(edges)))\n", 53 | "\n", 54 | " for i, edge_1 in enumerate(edges):\n", 55 | " for j, edge_2 in enumerate(edges):\n", 56 | " if i != j:\n", 57 | " a_matrix[i][j] = len(set(edge_1) & set(edge_2))\n", 58 | "\n", 59 | " for i, edge_1 in enumerate(edges):\n", 60 | " a_matrix[i] = a_matrix[i] / float(sum(a_matrix[i]))\n", 61 | "\n", 62 | " return a_matrix\n", 63 | "\n", 64 | "\n", 65 | "def weighted_values(values, probabilities, size):\n", 66 | " bins = np.add.accumulate(probabilities)\n", 67 | " bins[-1] = max(1, bins[-1])\n", 68 | " index = np.digitize(random_sample(size), bins)\n", 69 | " return values[index]\n", 70 | "\n", 71 | "\n", 72 | "def step_diffusion(current_state, markov_matrix):\n", 73 | " values_size = len(markov_matrix)\n", 74 | " values = range(values_size)\n", 75 | " probs = markov_matrix[current_state]\n", 76 | " next_state = weighted_values(values, probs, 1)\n", 77 | " return next_state\n", 78 | "\n", 79 | "\n", 80 | "def simulate(current_state, markov_matrix, t_max):\n", 81 | " c = Counter()\n", 82 | " states = []\n", 83 | " for _ in range(t_max):\n", 84 | " current_state = step_diffusion(current_state, markov_matrix)\n", 85 | " visited_hyperedge = current_state\n", 86 | " c[visited_hyperedge] += 1\n", 87 | " states.append(visited_hyperedge)\n", 88 | " return c.most_common(), states\n", 89 | "\n", 90 | "\n", 91 | "def count_nodes(nodes, edges, occurences):\n", 92 | " c = Counter()\n", 93 | " for index, count in occurences:\n", 94 | " for edge in edges[index]:\n", 95 | " c[edge] += count\n", 96 | " for node in nodes:\n", 97 | " if node not in c:\n", 98 | " c[node] = 0\n", 99 | " return c\n", 100 | "\n", 101 | "def plot_nodes_frequencies(most_common_nodes, title):\n", 102 | " plt.bar(list(most_common_nodes.keys()),\n", 103 | " list(most_common_nodes.values()),\n", 104 | " alpha=0.7, color='magenta')\n", 105 | "\n", 106 | " plt.title(title)\n", 107 | " plt.show()\n", 108 | "\n", 109 | "\n", 110 | "def plot_different_representations(nodes, hyperedges):\n", 111 | " print(\"Drawing different representations of hypergraph\")\n", 112 | "\n", 113 | " print(\"Bipartite graph\")\n", 114 | " nx_bipartite = converters.convert_to_nx_bipartite_graph(nodes, hyperedges)\n", 115 | " utils.draw_bipartite_graph(nx_bipartite,\n", 116 | " *utils.hypergraph_to_bipartite_parts(nx_bipartite))\n", 117 | "\n", 118 | " print(\"Graph of hypereges as nodes\")\n", 119 | " custom_hyper_g = converters.convert_to_custom_hyper_G(nodes, hyperedges)\n", 120 | " plt.figure()\n", 121 | " nx.draw(custom_hyper_g)\n", 122 | "\n", 123 | " print(\"Clique graph\")\n", 124 | "\n", 125 | " clique_graph = converters.convert_to_clique_graph(nodes, hyperedges)\n", 126 | " plt.figure()\n", 127 | " nx.draw(clique_graph)\n", 128 | "\n", 129 | "def plot_hyperedges_frequencies(most_common, hyperedges, title):\n", 130 | " hyperedges_indexes, occurences = zip(*most_common)\n", 131 | " plt.bar(hyperedges_indexes, occurences)\n", 132 | " plt.title(title)\n", 133 | " plt.xticks(range(len(hyperedges)), hyperedges)\n", 134 | "\n", 135 | "\n", 136 | "def compare_hypergraph_with_cliques(number_of_nodes,\n", 137 | " cardinality, fraction, t_max, plot_representations=False):\n", 138 | " HG = uniform_hypergraph(\n", 139 | " n=number_of_nodes,\n", 140 | " k=cardinality,\n", 141 | " number_of_edges=int(\n", 142 | " number_of_nodes *\n", 143 | " fraction))\n", 144 | "\n", 145 | " nodes = HG.nodes()\n", 146 | " hyperedges = HG.hyper_edges()\n", 147 | "\n", 148 | " all_nodes = []\n", 149 | " for hyperedge in hyperedges:\n", 150 | " all_nodes += hyperedge\n", 151 | " c = Counter(all_nodes)\n", 152 | " xs, ys = zip(*c.items())\n", 153 | " #plt.bar(xs, ys)\n", 154 | "\n", 155 | " if plot_representations:\n", 156 | " plot_different_representations(nodes, hyperedges)\n", 157 | "\n", 158 | " markov_matrix = create_markov_matrix(hyperedges)\n", 159 | " print(markov_matrix)\n", 160 | " current_state = 1\n", 161 | " most_common, states = simulate(current_state, markov_matrix, t_max)\n", 162 | "\n", 163 | " plt.figure(figsize=(8, 4))\n", 164 | " plot_hyperedges_frequencies(most_common, hyperedges,\n", 165 | " 'Ocurrences of hyperedges in a hypergraph')\n", 166 | "\n", 167 | " most_common_nodes = count_nodes(nodes, hyperedges, most_common)\n", 168 | "\n", 169 | " plt.figure(figsize=(8, 4))\n", 170 | " plot_nodes_frequencies(most_common_nodes, 'Nodes in a hypergraph')\n", 171 | "\n", 172 | " clique_graph = converters.convert_to_clique_graph(nodes, hyperedges)\n", 173 | " clique_markov_matrix = create_markov_matrix(clique_graph.edges())\n", 174 | "\n", 175 | " print(\"clique markov matrix\")\n", 176 | " print(clique_markov_matrix)\n", 177 | " most_common, states = simulate(current_state, clique_markov_matrix, t_max)\n", 178 | "\n", 179 | " plt.figure(figsize=(8, 4))\n", 180 | " plot_hyperedges_frequencies(most_common, clique_graph.edges(),\n", 181 | " 'Ocurrences of edges in a graph')\n", 182 | "\n", 183 | " most_common_nodes = count_nodes(clique_graph.nodes(), clique_graph.edges(),\n", 184 | " most_common)\n", 185 | " plt.figure(figsize=(12, 10))\n", 186 | " plot_nodes_frequencies(most_common_nodes, 'Nodes in a graph')\n", 187 | "\n", 188 | "\n", 189 | "def demo():\n", 190 | " n = 10\n", 191 | " k = 3\n", 192 | " fraction = 2.0 / 3\n", 193 | "\n", 194 | " for simulation_time in [100]:\n", 195 | " compare_hypergraph_with_cliques(n, k, fraction, simulation_time)\n", 196 | "\n", 197 | "\n", 198 | "if __name__ == '__main__':\n", 199 | " demo()\n", 200 | "\n", 201 | "\n" 202 | ], 203 | "language": "python", 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "output_type": "stream", 208 | "stream": "stdout", 209 | "text": [ 210 | "[[ 0. 0.14285714 0.28571429 0.28571429 0.28571429]\n", 211 | " [ 0.2 0. 0.4 0. 0.4 ]\n", 212 | " [ 0.28571429 0.28571429 0. 0.14285714 0.28571429]\n", 213 | " [ 0.5 0. 0.25 0. 0.25 ]\n", 214 | " [ 0.28571429 0.28571429 0.28571429 0.14285714 0. ]]\n" 215 | ] 216 | }, 217 | { 218 | "metadata": {}, 219 | "output_type": "display_data", 220 | "png": "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\nkUEsSVJGBrEkSRkZxJIkZWQQS5KUkUEsSVJGBrEkSRkZxJIkZWQQS5KUkUEsSVJGBrEkSRkZxJIk\nZWQQS5KUkUEsSVJGBrEkSRkZxJIkZWQQS5KUUTdBvBC4CrgZuAk4IQ2fD1wJ3ApcAcztZgUlSRpl\nlS7eu1N6/ByYBfwUeClwHHA3cBpwEjAPeG/De2tQ62LRo6xCrWZthkGlUsHtuB235WHhttxOb7bj\nqHHzzO3mjPgOIoQB7gd+AewCvBhYloYvI8JZkiQ10atrxIuAA4HrgB2BdWn4uvRakiQ1Mb0H85gF\nfA14B7CxYVyNlu0dSwvPx9JDkqThV61WqVarHU3bzTVigBnAN4HLgDPSsBVEqt4B7Ex06Nq74X1e\nI27J62rDwutqk3FbHhZuy+0M9jXiCnAOcAvjIQxwCXBsen4scHEXy5AkaaR1c0b8bOAHwI2MH0qd\nDPwYuAjYDVgFHAX8oeG9nhG35FnEsPAsYjJuy8PCbbmd8s+Iu22a3loGcUvuvIaFO6/JuC0PC7fl\ndga7aVqSJHXJIJYkKSODWJKkjAxiSZIyMoglScrIIJYkKSODWJKkjAxiSZIyMoglScrIIJYkKSOD\nWJKkjAxiSZIyMoglScrIIJYkKSODWJKkjAxiSZIyMoglScrIIJYkKSODWJKkjAxiSZIyMoglScrI\nIJYkKSODWJKkjAxiSZIyMoglScrIIJYkKSODWJKkjAxiSZIyMoglScrIIJYkKSODWJKkjKbnXgGV\nY86c+WzceG/u1RhYs2fPY8OG9blXQ5KoZFpuDWqZFj3oKtRq3demUqlgjdvpvs7WeDK92ZZVPrfl\ndnq5T26euTZNS5KUkUEsSVJG3QTxucA6YHlh2FLgd8D16bG4i/lLkjTyugni89g8aGvA6cCB6XF5\nF/OXJGnkdRPEVwPNuuXm6gAmSdLQKeMa8fHADcA5wNwS5i9J0sjodRCfBTwOOABYC3yix/OXJGmk\n9PoHPe4sPD8buLT1pEsLz8fSQ5Kk4VetVqlWqx1N2+313EVE2O6XXu9MnAkDvAt4GnBMk/f5gx4t\n+YMe/eEPepTPH/QYFm7L7ZT/gx7dnBFfCBwK7ACsAU4hTmsPIL7R24C3dDF/SZJGnj9xOXA8I+4P\nz4jL5xnxsHBbbsefuJQkaaQZxJIkZWQQS5KUkUEsSVJGBrEkSRn1+gc9JKmn5syZz8aNzX7WXrNn\n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\nSPw/uPHp4R/w0DEAAAAASUVORK5CYII=\n", 221 | "text": [ 222 | "" 223 | ] 224 | }, 225 | { 226 | "metadata": {}, 227 | "output_type": "display_data", 228 | "png": "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\nTkSuQmCC\n", 229 | "text": [ 230 | "" 231 | ] 232 | }, 233 | { 234 | "output_type": "stream", 235 | "stream": "stdout", 236 | "text": [ 237 | "clique markov matrix\n", 238 | "[[ 0. 0.14285714 0.14285714 0.14285714 0.14285714 0.\n", 239 | " 0.14285714 0. 0.14285714 0.14285714]\n", 240 | " [ 0.2 0. 0.2 0.2 0. 0. 0.2\n", 241 | " 0.2 0. 0. ]\n", 242 | " [ 0.2 0.2 0. 0.2 0. 0.2 0.\n", 243 | " 0. 0.2 0. ]\n", 244 | " [ 0.2 0.2 0.2 0. 0. 0. 0.\n", 245 | " 0.2 0. 0.2 ]\n", 246 | " [ 0.2 0. 0. 0. 0. 0.2 0.2\n", 247 | " 0. 0.2 0.2 ]\n", 248 | " [ 0. 0. 0.33333333 0. 0.33333333 0. 0.\n", 249 | " 0. 0.33333333 0. ]\n", 250 | " [ 0.16666667 0.16666667 0. 0. 0.16666667 0. 0.\n", 251 | " 0.16666667 0.16666667 0.16666667]\n", 252 | " [ 0. 0.25 0. 0.25 0. 0. 0.25\n", 253 | " 0. 0. 0.25 ]\n", 254 | " [ 0.16666667 0. 0.16666667 0. 0.16666667 0.16666667\n", 255 | " 0.16666667 0. 0. 0.16666667]\n", 256 | " [ 0.16666667 0. 0. 0.16666667 0.16666667 0.\n", 257 | " 0.16666667 0.16666667 0.16666667 0. ]]\n" 258 | ] 259 | }, 260 | { 261 | "metadata": {}, 262 | "output_type": "display_data", 263 | "png": "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\nTkSuQmCC\n", 264 | "text": [ 265 | "" 266 | ] 267 | }, 268 | { 269 | "metadata": {}, 270 | "output_type": "display_data", 271 | "png": "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\nut5xznnPaXku37w6f697QvVlCd/tcHH1pavze1qWllhLfWYuWH3cXf1W9fg1znI++GfVS6pXrnuQ\n88A7q4vWPcR55NrqO1bnd1cPWuMs55P7VO9r2THDqbus+uPujN2XVlevbZpz3xe29Ob9W3bE3Ng9\nHFjhTN+y+ER+s/rQWXrsaW5t2TtZdahlz8VnrW+c88LHVh/v1/Kf5PY1znKu++zq66uf6syOEsOd\nPI/b40EtO2FetNr+RMteSs7cFS0vbn/PyW7IcX205Y2/Lmj5heyC6pa1TnRue1TLX3L/svrr6jXV\nN5/oxmcrfDk7LmvZk/76Nc9xrrtPyy8TB1uWkbx1veOc036s+v6WQxly5g5Xv1H9bvXsNc9yrnt4\n9YHqp6s3VD/ZnX/t4cx8a8uf5zk9t1f/vnp39afVh1v+33N63tzyS+5FLf/Hv6Flp8y97rIsddhO\ne1p+GD593YOcRx7UstRhY81znKu+sfovq/MbWeO7HS5ZfXxoyy9nT1jjLOe6r2jZq/aVq+0fr354\nfeOcN+7X8gvFQ9c9yDnsES07XB7cssf3l6tvX+tE577vaGmk11Q/0bJT5rjs8T03fEr1i9XPVa9Y\n8yznk49Uv9ryA5JT99XV01rWpV5XPan62bVOdO573+rjB1p+GD52jbOc6967Ov3Oavvl1WPWN855\n48rq91q+Rjk9X1G9rrqtZQnOL7V8P+X0vajleX1iyx70t69jiMuyx3c77GqJiRP+9sIpeUjLK72r\nHlC9tvq69Y1z3nhi9vieqQuqT1udf2D1v6unrG+c88Jrq8tX5/d313cW5fT8Ql6Idaa+pOXP8w9o\n+Rl/bfVda53o3PeZq4+f0/JaqE+/twe4rmXdyl+1LH5/5r09wHnk8S3rJ2/qzsPIPPUe78E9+aKW\n9X43tRw26vvXO85544k5qsOZenjL1+VNLT8UvSnQmfuSlj2+v9+yV81RHc7MA6sPducvaJy+H+jO\nw5ld2/KXXU7fa1uez5uqr13zLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxk/x96+R6rEjGbOwAA\nAABJRU5ErkJggg==\n", 272 | "text": [ 273 | "" 274 | ] 275 | } 276 | ], 277 | "prompt_number": 2 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "#How can we represent hypergraphs in code?\n", 284 | "\n", 285 | "A very good representation is bipartite graph of nodes and hyperedges.\n", 286 | "Another representation is a graph with hyperedges as nodes connected if hyperedges have common nodes.\n", 287 | "Is it a good representation for our diffusion problem? Can we use it as a reference model?\n", 288 | "\n", 289 | "#How different are hypergraphs from graphs with cliques?\n", 290 | "\n", 291 | "Cliques are sets of nodes in which every node is connected to every other node.\n", 292 | "They are a bit similar to hypergraphs, but how the nodes are connected is conceptually different from\n", 293 | "hypergraphs.\n", 294 | "\n", 295 | "How is it different in diffusion simulation with markov chain?" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "collapsed": false, 301 | "input": [], 302 | "language": "python", 303 | "metadata": {}, 304 | "outputs": [] 305 | } 306 | ], 307 | "metadata": {} 308 | } 309 | ] 310 | } --------------------------------------------------------------------------------