Utilitaire pour indexer des informations de la blockchain et dessiner des Plots. Focalisé sur la toile de confiance. http://datajune.coinduf.eu/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
8.2 KiB

module AnimWotmap
"""
structure dedicated to animated wotmap
"""
using ..Config, ..ForceLayout, ..GraphPrinter
using LightGraphs, Statistics, Serialization, CSV, DataFrames, ProgressMeter, JSON3, Logging, Colors, Dates
"""struct used to manage wot along all history"""
mutable struct WotFrame
# global information
N::Int # total all-time number of nodes
all_fixed::Vector{Bool} # fixed nodes are true
all_indegrees::Vector{Int} # indegrees
all_communities::Vector{Int} # communities (represented by color)
all_locs::Array{Float64} # x,y locations of nodes
all_disp::Array{Float64} # current displacement
all_prev_disp::Array{Float64} # previous displacements
all_pseudo::Vector{String} # array of pseudo of size N
intervals::StepRange{Int64,Int64} # range of time intervals beginnings in unix seconds
active::Array{Bool} # npseudo × nstep boolean array of id activity
# information about current state
interval::Int # index of the current interval
g::SimpleDiGraph # current directed graph to plot
ug::SimpleGraph # undirected equivalent of this graph
mass::Vector{Float64} # mass of the nodes of g
charge::Vector{Float64} # charge of the nodes of g
mapping::Vector{Int} # mapping to the 1:N region
n::Int # size of the current graph
fixed::SubArray # view over all_fixed
indegrees::SubArray # view over indegrees of the nodes of g
communities::SubArray # view over all_communities
locs::SubArray # view over all_locs
disp::SubArray # view over all_disp
prev_disp::SubArray # view over all_prev_disp
pseudo::SubArray # view over all_pseudo
end
"""load wotframe"""
function WotFrame()::WotFrame # constructor
pseudo = readlines(joinpath(GLOBAL_PATH, "pseudos.txt")) # load pseudos
info = deserialize(joinpath(CACHE_PATH, "info.jls")) # load info
active_nodes = deserialize(joinpath(CACHE_PATH, "active.jls")) # load activity status
N = size(pseudo,1)
wotframe = WotFrame(
N,
falses(N), # all_fixed
zeros(Int,N), # all_indegrees
collect(1:N), # all_communities
zeros(2,N), # all_locs
zeros(2,N), # all_disp
zeros(2,N), # all_prev_disp
pseudo, # all pseudo
info["start"] : info["step"] : info["stop"], # all time intervals
active_nodes, # actives
0, # current interval index (not defined)
SimpleDiGraph(0), # current directed graph
SimpleGraph(0), # current undirected graph
[], # mass
[], # charge
[], # mapping
0, # size of the current graph
view(falses(1),1,1), # fixed
view(zeros(Int,1),1,1), # indegrees
view(zeros(Int,1),1,1), # communities
view(zeros(1),1,1), # locs
view(zeros(1),1,1), # disp
view(zeros(1),1,1), # prev_disp
view(pseudo,1), # pseudo
)
set_current_interval!(wotframe, 1) # initialize to first interval
return wotframe
end
"""update state to match current interval"""
function set_current_interval!(state::WotFrame, i_num::Int)
# update graph
state.interval = i_num # set current interval number
state.g = loadgraph(joinpath(LG_PATH, Config.graphfile(state.interval))) # load graph (inactive nodes get removed)
# grow g to match N size
add_vertices!(state.g, state.N-nv(state.g))
state.mapping = rem_vertices!(state.g, findall(.!state.active[:,state.interval])) # remove inactive nodes and update mapping
# update views
state.fixed = view(state.all_fixed, state.mapping)
state.indegrees = view(state.all_indegrees, state.mapping)
state.communities = view(state.all_communities, state.mapping)
state.locs = view(state.all_locs, :, state.mapping)
state.disp = view(state.all_disp, :, state.mapping)
state.prev_disp = view(state.all_prev_disp, :, state.mapping)
state.pseudo = view(state.all_pseudo, state.mapping)
# update variables
state.indegrees .= indegree(state.g) # update degrees
state.fixed .= state.indegrees .<1 # fix point without link to avoid it getting too far
state.charge = Array{Float64}(state.indegrees .>=5) # not well connected points get a null charge (they do not repulse), other get a unit charge
state.indegrees[findall(state.indegrees .< 5)] .= 5 # nodes whose certifiers are gone can have indregree <5 → set to 5 (inexact)
state.mass = Array{Float64}(state.indegrees) # update mass
state.n = nv(state.g) # update n
state.ug = SimpleGraph(state.g) # update undirected equivalent
# place new nodes at barycenter of their certifiers (does not work for first interval)
if state.interval != 1
newnodes = state.active[:,state.interval] .& .!state.active[:,state.interval-1]
for newcomer in findall(newnodes)
newc = findfirst(isequal(newcomer), state.mapping) # local index (pseudo[newc] == all_pseudo[newcomer])
certifiers_locs = state.locs[:,inneighbors(state.g, newc)]
if isempty(certifiers_locs) # if certifiers of node have disappeared
println()
@warn "no certifiers for node $(newcomer) at step $(state.interval)"
certifiers_locs = state.locs[:,newc] # take self location
end
state.locs[:,newc] = mean(certifiers_locs, dims=2)
end
end
end
"load cache if exists"
function load_wotframe()::WotFrame
if isfile(WOTFRAME_CACHE_FILE)
@debug "found wotframe cache, loading it"
return deserialize(WOTFRAME_CACHE_FILE)
else
@info "no wotframe cache found, computing from beginning"
wotframe = WotFrame() # initialize wotframe
wotframe.locs[:,:] .= ForceLayout.random_layout(wotframe.n, wotframe.n, seed=1) # initialize with deterministic random layout
# perform first iteration separately
custom_layout!(wotframe, ITER=50) # First layout
return wotframe
end
end
"write CSV"
write_locations(w::WotFrame) = CSV.write(joinpath(COORD_PATH, lpad(string(w.interval),4,'0')*".csv"),
DataFrame(x = w.locs[1,:], y = w.locs[2,:]))
"write graph to json format for use in sigmajs/d3js"
function export_sigmajs(w::WotFrame)
nodejs = [] # lol
edgejs = [] # mdr
for n in vertices(w.g) # only active nodes
push!(nodejs, Dict(
"id" => w.mapping[n], # int describing node
"label" => w.pseudo[n], # pseudo
"x" => w.locs[1,n], # x coordinate
"y" => w.locs[2,n], # y coordinate
"size" => w.indegrees[n], # in_degree after non members removal, clamped to 5 at minimum
))
end
for e in edges(w.g) # certifications except those touching inactive members
push!(edgejs, Dict(
"id" => "$(w.mapping[e.src])-$(w.mapping[e.dst])", # why?
"source" => w.mapping[e.src], # id
"target" => w.mapping[e.dst], # id
))
end
JSON3.write(joinpath(JSON_GRAPH_PATH, lpad(string(w.interval),4,'0')*".json"), Dict("nodes"=>nodejs, "edges"=>edgejs))
end
"run my_layout_0 on state multiple times and then electrostatic once"
function custom_layout!(state::WotFrame; ITER=100)
for i = 1:ITER
ForceLayout.custom_layout_0!(state.ug, state.fixed, state.locs, state.disp, state.prev_disp, state.mass, state.charge)
end
end
"iterate over wot history while persisting state"
function compute_locations()
# load cache or initialize from beginning
wotframe = load_wotframe()
# perform the rest of iterations
@showprogress "Writing coordinates..." for i in wotframe.interval:length(wotframe.intervals)
set_current_interval!(wotframe, i) # load next graph
custom_layout!(wotframe, ITER=1) # iter once
GraphPrinter.printframe(wotframe.g, wotframe.ug, wotframe.locs, wotframe.pseudo, wotframe.indegrees,
repeat([colorant"royalblue"], wotframe.n), Date(unix2datetime(wotframe.intervals[wotframe.interval])), wotframe.interval)
# write_locations(wotframe)
# export_sigmajs(wotframe)
end
# cache result
serialize(WOTFRAME_CACHE_FILE, wotframe)
end
end