#!/usr/bin/env python
# coding: utf-8

# In[1]:


import pysake.notebook.params as prm        # parameter reading
import pysake.notebook.dircontent as dc     # directory content display
import pysake.stats.stat_test as st

import os
from os import environ
from IPython.display import display, HTML, Code

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import networkx as nx
import rustworkx as rx
import igraph as ig
import igraph as ig
import numpy as np
from mentsuyu.reader import *
from mentsuyu.dfutils import *
from mentsuyu.nxutils import *
from mentsuyu.cudcolors import OIColor
from itertools import product

import time

from ecmeasure.optecmeasures import ec
from ecmeasure.utils import hamming
from ecmeasure.templates.igraph import IGraphWrapper

from scipy.spatial import ConvexHull
from scipy.cluster.hierarchy import linkage, fcluster
from matplotlib.patches import Polygon

from typing import Callable
from functools import cache


# In[2]:


config = prm.read_parameter_file ( 'params.sh' )

HARDWARE = prm.get_parameter( config, 'HARDWARE', '' )
OS = prm.get_parameter( config, 'OS', '' )
RESDIR = prm.get_parameter( config, 'RESDIR', '' )
TEMPLATEDIR = prm.get_parameter( config, 'TEMPLAEDIR', '' )
SIMDIR = prm.get_parameter( config, 'SIMDIR', '' )
NIMVERS = prm.get_parameter( config, 'NIMVERS', '' )
HASH = prm.get_parameter( config, 'HASH', '' )

DATE = prm.get_parameter( config, 'DATE', '' )
LABEL = prm.get_parameter( config, 'LABEL', '' )
PERFORMER = prm.get_parameter( config, 'PERFORMER', '' )

NBAGENTS = int(prm.get_parameter( config, 'NBAGENTS', '' ))
NBITERATIONS = int(prm.get_parameter( config, 'NBITERATIONS', '' ))
NBFOLLOWS = prm.get_parameter( config, 'NBFOLLOWS', '' )
NBRUNS = prm.get_parameter( config, 'NBRUNS', '' )
EPSILONS = prm.get_parameter( config, 'EPSILONS', '' ).split()
DELTAS = prm.get_parameter( config, 'DELTAS', '' ).split()
REWIRINGS = prm.get_parameter( config, 'REWIRINGS', '' ).split()
SEEDS = prm.get_parameter( config, 'SEEDS', '' ).split()
TWWIDTH = int(prm.get_parameter( config, 'TWWIDTH', '' ))
ALPHA = prm.get_parameter( config, 'ALPHA', '' )
MU = prm.get_parameter( config, 'MU', '' )
PACTIVE = prm.get_parameter( config, 'PACTIVE', '' )
PREWIRE = prm.get_parameter( config, 'PREWIRE', '' )
ATOM = int(prm.get_parameter( config, 'ATOM', '' ))
TOPIC = prm.get_parameter( config, 'TOPIC', '' )
PREHOC = prm.get_parameter( config, 'PREHOC', '' )
COMPONENTS = prm.get_parameter( config, 'COMPONENTS', '' ).split()
THETAS = prm.get_parameter( config, 'THETAS', '' ).split()
REWIRINGS = prm.get_parameter( config, 'REWIRINGS', '' ).split()

CONTROLED = prm.get_parameter( config, 'CONTROLED', '' )
OBSERVED = prm.get_parameter( config, 'OBSERVED', '' )
FIXED = prm.get_parameter( config, 'FIXED', '' )

VARIATIONOF = prm.get_parameter( config, 'VARIATIONOF', '' )
EXPE = prm.get_parameter( config, 'EXPE', '' )
RESULT = prm.get_parameter( config, 'RESULT', '' )
SETTING = prm.get_parameter( config, 'SETTING', '' )
HYPOTHESIS = prm.set_link_labels(prm.get_parameter( config, 'HYPOTHESIS', '' ))
DESIGNER = prm.get_parameter( config, 'DESIGNER', '' )
STATUS = prm.get_parameter( config, 'STATUS', '' )
STATUSDESC = prm.get_parameter( config, 'STATUSDESC', '' )
REF = prm.get_parameter( config, 'REF', '' )


# In[3]:


controled_variables = CONTROLED.split()
observed_variables = OBSERVED.split()
#derived_variables = ['inc','fmeas','conv']
#dependent_variables = observed_variables+derived_variables
#ordered_variables = tdis.orderColumns( dependent_variables )



# # Experiment {{LABEL}}

# In[4]:


display( HTML( prm.htmlResultAndStatus( RESULT, STATUS, STATUSDESC, REF ) ) )


# ## Experiment design

# 
# Date: **{{DATE}}** 
# <!-- The date of the initial design -->
# 
# Designer: **{{DESIGNER}}**
# 
# Hypotheses: **{{for x in HYPOTHESIS.split('\n'): print(x+'\n')}}**
# 
# **{{NBAGENTS}}** agents; **{{NBITERATIONS}}** games
# 
# <!-- Experimental setting: **{{SETTING}}** -->
# 
# 

# ### Variables

# 
# fixed variables: {{FIXED}}
# 
# controlled variables: {{CONTROLED}}
# 
# dependent variables: {{OBSERVED}}
# 

# #### Values

# In[5]:


for val in CONTROLED.split():
    display(HTML(f'<b>{val}</b>: {globals()[val]}'))


# ### Measures

# Before testing the hypothesis, we define the measures needed to check them.
# Let $A$ be the set of agents.
# Let $\mathcal S^t$ be the set of partitions of the network of agents at time $t$.

# #### eo (Opinion echo chamber)
# 
# This measure counts the number of partitions such that:
# 
# - they are segregated, i.e., $L^t(C)\leq\theta$ where $0<\theta<1$;
# - opinions are homogeneous, i.e., $M_O^t(C)\leq 10^{-4}$;
# - opinions have been reinforced, i.e., $D_O^t(C)$
# 
# where
# 
# $$
# L^t(C)=\frac{|\{(a,a')\in N^t;a\in C\land a'\notin C\}|}{|\{(a,a')\in N^t;a\in C\}|},
# $$
# 
# $$
#  M_O^t(C) = \max_{a,a'\in C}|O_a^t-O_{a'}^t|
# $$
# 
# and
# 
# $$
# D_O^t(C)=\forall s\in [t_C,T),M_O^s(C)\geq M_O^{s+1}(C).
# $$
# 
# Here, $[t_C,T]$ is the maximal time window such that $\forall t\in[t_C,T]$, $C\in\mathcal S^t$ and $T$ is the number of iterations.
# 
# More formally, $eo$ is defined as:
# 
# $$
# eo^t=|\{C\in\mathcal S^t;L^t(C)\leq\theta\land M_O^t(C)\leq 10^{-4}\land D_O^t(C)\}|
# $$
# 

# In[6]:


# Note that this measur is provided by the ecmeasures package


# #### eb (Belief echo chamber)
# 
# This measure counts the number of partitions such that:
# 
# - they are segregated, i.e., $L^t(C)\leq\theta$ where $0<\theta<1$;
# - beliefs are homogeneous, i.e., $M_B^t(C)=0$;
# - opinions have been reinforced, i.e., $D_B^t(C)$
# 
# where
# 
# $$
#  M_B^t(C) = \max_{a,a'\in C}d_B(B_a^t,B_{a'}^t)
# $$
# 
# and
# 
# $$
# D_B^t(C)=\forall s\in [t_C,T),M_B^s(C)\geq M_B^{s+1}(C).
# $$
# 
# Here, $d_B$ is the Hamming distance over the models of two beliefs:
# 
# $$
# d_B(B,B')=|\mathcal M(B)\setminus\mathcal M(B')|+|\mathcal M(B')\setminus\mathcal M(B)|.
# $$
# 
# More formally, $eb$ is defined as: 
# 
# $$
# eb=|\{C\in\mathcal S^t;L^t(C)\leq\theta\land M_B^t(C)=0\land D_B^t(C)\}|
# $$

# In[7]:


## This is also defined by the ecmeasures package


# #### po/pb (Population in opinion/belief echo chambers)
# 
# These measures count how many agents are in opinion/belief echo chambers, respectively.

# #### uo (Unique opinions shared within opinion echo chambers)
# 
# This measure counts how many opinions are shared within opinion echo chambers.
# Let $A_O\subseteq A$ be the set of agents in opinion echo chambers.
# Then, this measure (roughly) returns $|\{O_a;a\in A_O\}|$.
# 
# To avoid applying strict equivalence check, i.e., $=$, toward several float numbers, we apply the hierarchical clustering with the allowed maximal distance $10^{-4}$.

# In[8]:


def uo(oplist, ecs):
    if len(ecs) == 0:
        return 0
    ops = []
    for ec in ecs:
        for a in ec:
            ops.append(oplist[a])
    dendrogram = linkage(np.reshape(ops, (len(ops), 1)), method='ward')
    clusters = fcluster(dendrogram, t=1e-4, criterion='distance')
    return len(set(clusters))


# #### ub (Unique belief shared within belief echo chambers)
# 
# This measure counts how many beliefs are shared within belief echo chambers.
# Let $A_B\subseteq A$ be the set of agents in belief echo chambers.
# Then, this measure returns $|\{B_a;a\in A_B\}|$.
# 

# In[9]:


def ub(bellist, ecs):
    if len(ecs) == 0:
        return 0
    bels = []
    for ec in ecs:
        for a in ec:
            bels.append(bellist[a])
    return len(set(bels))


# ## Experiment

# 
# Date: **{{DATE}}**
# 
# Performer: **{{PERFORMER}}**
# 
# The whole experiment, from scratch, can be executed through:
# 
# 

# In[10]:


display(Code(filename='README.txt'))


# Parameter file: [params.sh](params.sh)
# 

# Hardware: **{{HARDWARE}}**
# 
# OS: **{{OS}}**
# 

# ## Raw Results

# **This experiment is a reanalysis of the existing experiment [[20260210-BROD](https://sake.re/20260210-BROD/)], [[20260610-BROD](https://sake.re/20260610-BROD/)], [[20260615-BROD](https://sake.re/20260615-BROD/)], and [[20260621-BROD](https://sake.re/20260621-BROD/)].
# Hence, no new simulations (or runs of the SOBA simulator) are needed.**
# 
# To perform the following analysis, obtain the raw results of the experiment from these repositories and move all files under the directory called '{{RESDIR}}'.
# 
# Note that files required to perform the analysis are downloaded by executing the script file `utils/unzip.sh` (be careful, this script automatically access to Zenodo. Please do not execute the file frequently).

# ## Analysis

# First, we compute the measures.

# In[11]:


def comp(exp):
    t0 = time.time()
    opdf = readop(exp=exp)
    beldf = readbel(exp=exp)
    tmp = np.genfromtxt(f'{RESDIR}/{exp}/grhist.csv', delimiter=',', dtype=int)
    all_targets = tmp[1:, 1:]
    sources = tmp[0, 1:]

    def readgr(time):
        G = ig.Graph(directed=True)
        G.add_vertices(NBAGENTS)
        G.add_edges(zip(sources, all_targets[time]))
        return IGraphWrapper(G)

    def maxop(comp, time):
        diff = set(comp) - set(opdf.columns)
        if len(diff) > 0:
            print(diff, exp)
        ops = list(opdf.loc[time][list(comp)])
        return max(ops) - min(ops)

    def maxbel(comp, time):
        bels = set(beldf.loc[time][list(comp)])
        res = 0
        for b1 in bels:
            for b2 in bels:
                res = max(res, hamming(b1, b2))
        return res

    t1 = time.time()
    theta = '0.5'
    eoinfo = ec(NBITERATIONS, 'scc', readgr, maxop, float(theta), 1e-4, verbose=True)
    ebinfo = ec(NBITERATIONS, 'scc', readgr, maxbel, float(theta), 0, verbose=True)
    nbcomp = len(readgr(NBITERATIONS).getComponents('scc'))
    t2 = time.time()
    print(f"{exp}: read={t1-t0:.3f}s process={t2-t1:.3f}s", flush=True)

    return exp.split('-') + [
        len(eoinfo.ec), sum([len(c) for c in eoinfo.ec]),
        len(ebinfo.ec), sum([len(c) for c in ebinfo.ec]),
        uo(list(opdf.loc[NBITERATIONS]), eoinfo.ec),
        ub(list(beldf.loc[NBITERATIONS]), ebinfo.ec),
        nbcomp
    ]


MAX_WORKERS=15

from concurrent.futures import ProcessPoolExecutor, as_completed
print(REWIRINGS)

def create_df():
    data = {}
    EXPS = []
    for (eps, delta, seed, rewire) in product(EPSILONS, DELTAS, SEEDS, REWIRINGS):
        EXPS.append( f'{rewire}-{eps}-{delta}-{seed}')

    with ProcessPoolExecutor(max_workers=MAX_WORKERS) as executor:
        future_to_input = {executor.submit(comp, exp): exp for exp in EXPS}
        
        for future in as_completed(future_to_input):
            key = future_to_input[future]
            res = future.result()
            data[key] = res
            print(key)

    df = pd.DataFrame(
        data,
        index=[
            'rewire', 'eps', 'delta', 'seed', 
            'eo', 'po', 
            'eb', 'pb',
            'uo', 'ub',
            'scc'
        ]
    ).transpose()
    return df

if __name__ == '__main__':
    df = load_df(f'analysis/{LABEL}.csv', create_df)


# 
# <font size=1>
#     
# This file can be retrieved from URL [https://sake.re/20260621-BROD-a](https://sake.re/20260621-BROD-a)
#     
# </font>
