Skip to content

Commit

Permalink
Merge pull request #37 from nicoloval/stable
Browse files Browse the repository at this point in the history
Stable
  • Loading branch information
EmilianoMarchese authored Mar 31, 2021
2 parents 117f3d6 + 8a08a6a commit 0fb43dc
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 48 deletions.
115 changes: 69 additions & 46 deletions src/NEMtropy/graph_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(
self.relative_error_strength = None
self.full_return = False
self.last_model = None
self.solution_array

# function
self.args = None
Expand Down Expand Up @@ -334,16 +335,16 @@ def _solve_problem(

def _set_solved_problem_cm(self, solution):
if self.full_return:
self.r_xy = solution[0]
self.solution_array = solution[0]
self.comput_time = solution[1]
self.n_steps = solution[2]
self.norm_seq = solution[3]
self.diff_seq = solution[4]
self.alfa_seq = solution[5]
else:
self.r_xy = solution
self.solution_array = solution

self.r_x = self.r_xy
self.r_x = self.solution_array
if self.last_model == "cm":
self.x = self.r_x[self.r_invert_dseq]
elif self.last_model == "cm_exp":
Expand Down Expand Up @@ -779,6 +780,7 @@ def _initialize_problem(self, model, method):
self.hessian_regulariser = sof.matrix_regulariser_function_eigen_based
elif self.regularise == "identity":
self.hessian_regulariser = sof.matrix_regulariser_function


def _solve_problem_crema_undirected(
self,
Expand Down Expand Up @@ -903,28 +905,30 @@ def _set_solved_problem_crema_undirected(self, solution):
else:
self.beta = solution

self.solution_array = self.beta

def _set_solved_problem_ecm(self, solution):
if self.full_return:
self.r_xy = solution[0]
self.solution_array = solution[0]
self.comput_time = solution[1]
self.n_steps = solution[2]
self.norm_seq = solution[3]
self.diff_seq = solution[4]
self.alfa_seq = solution[5]
else:
self.r_xy = solution
self.solution_array = solution

if self.last_model == "ecm":
self.x = self.r_xy[: self.n_nodes]
self.y = self.r_xy[self.n_nodes:]
self.x = self.solution_array[: self.n_nodes]
self.y = self.solution_array[self.n_nodes:]
elif self.last_model == "ecm_exp":
self.x = np.exp(-self.r_xy[:self.n_nodes])
self.y = np.exp(-self.r_xy[self.n_nodes:])
self.x = np.exp(-self.solution_array[:self.n_nodes])
self.y = np.exp(-self.solution_array[self.n_nodes:])

def solve_tool(
self,
model,
method,
method='newton',
initial_guess='random',
adjacency="cm_exp",
method_adjacency="newton",
Expand Down Expand Up @@ -1020,10 +1024,9 @@ def solve_tool(
eps=eps,
)
self._solution_error()
if verbose:
print("\nmin eig = {}".format(self.error))
print("\nsolution error = {}".format(self.error))

def ensemble_sampler(self, n, cpu_n=1, output_dir="sample/", seed=42):
def ensemble_sampler(self, n, cpu_n=1, output_dir="sample/", seed=None):
"""The function sample a given number of graphs in the ensemble
generated from the last model solved. Each grpah is an edgelist
written in the output directory as `.txt` file.
Expand Down Expand Up @@ -1121,6 +1124,12 @@ def ensemble_sampler(self, n, cpu_n=1, output_dir="sample/", seed=42):
else:
raise ValueError("insert a model")

def model_loglikelihood(self):
"""Returns the loglikelihood of the solution of last model executed.
"""
return self.step_fun(self.solution_array)



class DirectedGraph:
"""Directed graph instance can be initialised with
Expand Down Expand Up @@ -1189,7 +1198,7 @@ def __init__(
# reduced solutions
self.r_x = None
self.r_y = None
self.r_xy = None
self.solution_array = None
# Problem (reduced) residuals
self.residuals = None
self.final_result = None
Expand Down Expand Up @@ -1479,73 +1488,73 @@ def _solve_problem(

def _set_solved_problem_dcm(self, solution):
if self.full_return:
self.r_xy = solution[0]
self.solution_array = solution[0]
self.comput_time = solution[1]
self.n_steps = solution[2]
self.norm_seq = solution[3]
self.diff_seq = solution[4]
self.alfa_seq = solution[5]
else:
self.r_xy = solution
self.solution_array = solution

self.r_x = self.r_xy[: self.rnz_n_out]
self.r_y = self.r_xy[self.rnz_n_out:]
self.r_x = self.solution_array[: self.rnz_n_out]
self.r_y = self.solution_array[self.rnz_n_out:]

self.x = self.r_x[self.r_invert_dseq]
self.y = self.r_y[self.r_invert_dseq]

def _set_solved_problem_dcm_exp(self, solution):
if self.full_return:
# conversion from theta to x
self.r_xy = np.exp(-solution[0])
self.solution_array = np.exp(-solution[0])
self.comput_time = solution[1]
self.n_steps = solution[2]
self.norm_seq = solution[3]
self.diff_seq = solution[4]
self.alfa_seq = solution[5]
else:
# conversion from theta to x
self.r_xy = np.exp(-solution)
self.solution_array = np.exp(-solution)

self.r_x = self.r_xy[: self.rnz_n_out]
self.r_y = self.r_xy[self.rnz_n_out:]
self.r_x = self.solution_array[: self.rnz_n_out]
self.r_y = self.solution_array[self.rnz_n_out:]

self.x = self.r_x[self.r_invert_dseq]
self.y = self.r_y[self.r_invert_dseq]

def _set_solved_problem_decm(self, solution):
if self.full_return:
self.r_xy = solution[0]
self.solution_array = solution[0]
self.comput_time = solution[1]
self.n_steps = solution[2]
self.norm_seq = solution[3]
self.diff_seq = solution[4]
self.alfa_seq = solution[5]
else:
self.r_xy = solution
self.solution_array = solution

self.x = self.r_xy[: self.n_nodes]
self.y = self.r_xy[self.n_nodes: 2 * self.n_nodes]
self.b_out = self.r_xy[2 * self.n_nodes: 3 * self.n_nodes]
self.b_in = self.r_xy[3 * self.n_nodes:]
self.x = self.solution_array[: self.n_nodes]
self.y = self.solution_array[self.n_nodes: 2 * self.n_nodes]
self.b_out = self.solution_array[2 * self.n_nodes: 3 * self.n_nodes]
self.b_in = self.solution_array[3 * self.n_nodes:]

def _set_solved_problem_decm_exp(self, solution):
if self.full_return:
# conversion from theta to x
self.r_xy = np.exp(-solution[0])
self.solution_array = np.exp(-solution[0])
self.comput_time = solution[1]
self.n_steps = solution[2]
self.norm_seq = solution[3]
self.diff_seq = solution[4]
self.alfa_seq = solution[5]
else:
# conversion from theta to x
self.r_xy = np.exp(-solution)
self.solution_array = np.exp(-solution)

self.x = self.r_xy[: self.n_nodes]
self.y = self.r_xy[self.n_nodes: 2 * self.n_nodes]
self.b_out = self.r_xy[2 * self.n_nodes: 3 * self.n_nodes]
self.b_in = self.r_xy[3 * self.n_nodes:]
self.x = self.solution_array[: self.n_nodes]
self.y = self.solution_array[self.n_nodes: 2 * self.n_nodes]
self.b_out = self.solution_array[2 * self.n_nodes: 3 * self.n_nodes]
self.b_in = self.solution_array[3 * self.n_nodes:]

def _set_solved_problem(self, solution):
model = self.last_model
Expand Down Expand Up @@ -2377,10 +2386,12 @@ def _set_solved_problem_crema_directed(self, solution):
self.b_out = solution[: self.n_nodes]
self.b_in = solution[self.n_nodes:]

self.solution_array = solution

def solve_tool(
self,
model,
method,
method='newton',
initial_guess='random',
adjacency=None,
method_adjacency='newton',
Expand Down Expand Up @@ -2476,13 +2487,13 @@ def solve_tool(
eps=eps,
)
self._solution_error()
if verbose:
print("\nmin eig = {}".format(self.error))
print("\nsolution error = {}".format(self.error))


def ensemble_sampler(self, n, cpu_n=1, output_dir="sample/", seed=42):
"""The function samples a given number of graphs in the ensemble
generated from the last model solved. Each graph is an edgelist
written in the output directory as a `.txt` file.
def ensemble_sampler(self, n, cpu_n=1, output_dir="sample/", seed=None):
"""The function sample a given number of graphs in the ensemble
generated from the last model solved. Each grpah is an edgelist
written in the output directory as `.txt` file.
The function is parallelised and can run on multiple cpus.
:param n: Number of graphs to sample.
Expand Down Expand Up @@ -2581,6 +2592,11 @@ def ensemble_sampler(self, n, cpu_n=1, output_dir="sample/", seed=42):
else:
raise ValueError("insert a model")

def model_loglikelihood(self):
"""Returns the loglikelihood of the solution of last model executed.
"""
return self.step_fun(self.solution_array)


class BipartiteGraph:
"""Bipartite Graph class for undirected binary bipartite networks.
Expand Down Expand Up @@ -2641,7 +2657,7 @@ def __init__(self, biadjacency=None, adjacency_list=None, edgelist=None, degree_
self.y = None
self.r_x = None
self.r_y = None
self.r_xy = None
self.solution_array = None
self.dict_x = None
self.dict_y = None
self.theta_x = None
Expand Down Expand Up @@ -2682,6 +2698,7 @@ def __init__(self, biadjacency=None, adjacency_list=None, edgelist=None, degree_
self.full_rows_num = None
self.full_rows_num = None
self.solution_converged = None
self.solution_array = None
self.progress_bar = None

def _initialize_graph(self, biadjacency=None, adjacency_list=None, edgelist=None, degree_sequences=None):
Expand Down Expand Up @@ -3136,15 +3153,15 @@ def _set_solved_problem(self, solution):
self.r_theta_xy = solution
self.r_theta_x = self.r_theta_xy[:self.r_n_rows]
self.r_theta_y = self.r_theta_xy[self.r_n_rows:]
self.r_xy = np.exp(- self.r_theta_xy)
self.solution_array = np.exp(- self.r_theta_xy)
self.r_x = np.exp(- self.r_theta_x)
self.r_y = np.exp(- self.r_theta_y)
self.theta_x[self.nonfixed_rows] = self.r_theta_x[self.r_invert_rows_deg]
self.theta_y[self.nonfixed_cols] = self.r_theta_y[self.r_invert_cols_deg]
else:
self.r_xy = solution
self.r_x = self.r_xy[:self.r_n_rows]
self.r_y = self.r_xy[self.r_n_rows:]
self.solution_array = solution
self.r_x = self.solution_array[:self.r_n_rows]
self.r_y = self.solution_array[self.r_n_rows:]
if self.x is None:
self.x = np.zeros(self.n_rows)
if self.y is None:
Expand Down Expand Up @@ -3742,3 +3759,9 @@ def clean_edges(self):
self.rows_deg = None
self.cols_deg = None
self.is_initialized = False


def model_loglikelihood(self):
"""Returns the loglikelihood of the solution of last model executed.
"""
return self.step_fun(self.solution_array)
96 changes: 96 additions & 0 deletions tests/test_ensemble_seed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""This test checks the default setting for the seed is working.
When the seed is not manually set, it should be ranodmly chosen.
"""

import sys
import os
sys.path.append("../")
import NEMtropy.graph_classes as sample
import NEMtropy.matrix_generator as mg
import numpy as np
import unittest # test tool
import random
import networkx as nx


class MyTest(unittest.TestCase):


def setUp(self):
pass


def test_0(self):
"""test with 3 classes of cardinality 1
and no zero degrees
"""
"""
A = np.array(
[
[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 0],
[0, 1, 0, 0],
]
)
e = [(0,1), (0,2), (1,3)]
d = [1,1,2,2]
print(e)
print(d)
"""
N, seed = (10, 42)
A = mg.random_binary_matrix_generator_dense(N, sym=True, seed=seed)
# number of copies to generate

g = sample.UndirectedGraph(A)

g._solve_problem(
model="cm",
method="fixed-point",
max_steps=100,
verbose=False,
linsearch=True,
initial_guess="uniform",
)

x = g.x
# g._solution_error()
err = g.error

# print('\ntest 5: error = {}'.format(g.error))
n = 10
output_dir = "sample/"
# random.seed(100)
g_list = []
for i in range(n):
g.ensemble_sampler(n=1, output_dir=output_dir)
g_list.append(np.loadtxt("sample/0.txt"))

appo = True
old = g_list[0]
for i in range(1, n):
appo = appo*np.all(old == g_list[i])

# debug
"""
print('original dseq',d)
print('original dseq sum ',g.dseq.sum())
print('ensemble average dseq', d_emp)
print('ensemble dseq sum ',np.array([d_emp[key] for key in d_emp.keys()]).sum())
print(d_diff)
print('empirical error', ensemble_error)
print('theoretical error', err)
"""


l = os.listdir(output_dir)
for f in l:
os.remove(output_dir + f)
os.rmdir(output_dir)

# test result
self.assertTrue(not appo)


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 0fb43dc

Please sign in to comment.