1219 lines
34 KiB
Python
1219 lines
34 KiB
Python
#!/usr/bin/env python
|
|
# coding: utf-8
|
|
|
|
# In[1]:
|
|
|
|
|
|
from cadCAD.engine import ExecutionMode, ExecutionContext, Executor
|
|
from cadCAD.configuration import Configuration
|
|
import networkx as nx
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
import scipy.stats as sts
|
|
import pandas as pd
|
|
import seaborn as sns
|
|
import matplotlib.colors as colors
|
|
import matplotlib.cm as cmx
|
|
import matplotlib.animation as animation
|
|
get_ipython().run_line_magic('matplotlib', 'inline')
|
|
|
|
from scipy.stats import expon, gamma
|
|
|
|
|
|
# This notebook uses the differential games framework developed by BlockScience. It is currently in private beta, and building towards a full open source release.
|
|
#
|
|
# **Description:**
|
|
#
|
|
# cadCAD is a Python library that assists in the processes of designing, testing and validating complex systems through simulation. At its core, cadCAD is a differential games engine that supports parameter sweeping and Monte Carlo analyses and can be easily integrated with other scientific computing Python modules and data science workflows.
|
|
#
|
|
# To learn more about cadCAD, follow our [tutorial series](https://github.com/BlockScience/cadCAD-Tutorials/tree/master/01%20Tutorials)
|
|
#
|
|
# **Installing cadCAD:**
|
|
#
|
|
# cadCAD is in private beta. Tokens are issued to participants. Replace `<TOKEN>` in the installation URL below
|
|
# ```bash
|
|
# pip3 install cadCAD --extra-index-url https://<TOKEN>@repo.fury.io/blockscience/
|
|
# ```
|
|
#
|
|
# If you'd like to participate in the beta program, contact cadcad [at] block [dot] science.
|
|
#
|
|
|
|
# In[2]:
|
|
|
|
|
|
#helper functions
|
|
def get_nodes_by_type(g, node_type_selection):
|
|
return [node for node in g.nodes if g.nodes[node]['type']== node_type_selection ]
|
|
|
|
def get_edges_by_type(g, edge_type_selection):
|
|
return [edge for edge in g.edges if g.edges[edge]['type']== edge_type_selection ]
|
|
|
|
|
|
# In[3]:
|
|
|
|
|
|
#THIS policy is one of the main paramters of this system!
|
|
|
|
#maximum share of funds a proposal can take
|
|
beta = .2 #later we should set this to be param so we can sweep it
|
|
# tuning param for the trigger function
|
|
rho = .001
|
|
|
|
def trigger_threshold(requested, funds, supply):
|
|
|
|
share = requested/funds
|
|
if share < beta:
|
|
return rho*supply/(beta-share)**2
|
|
else:
|
|
return np.inf
|
|
|
|
|
|
# Note from Kris, consider: substitutibility of proposals st when a substitute passes, affinity for the others goes away; this will make the process more realistic because proposals will end up never passing.
|
|
#
|
|
# implementation notes:
|
|
# - create substitutability matrix (proposal x proposal)
|
|
# - update accounting when thing pass: change affinities and should affect sentiments
|
|
# - define a new 'type' of proposals for tracking 'dead' ones (no longer candidates = zero staked)
|
|
#
|
|
|
|
# In[4]:
|
|
|
|
|
|
#generate an initial set of 'n' participants
|
|
network = nx.DiGraph()
|
|
n = 100
|
|
for i in range(n):
|
|
network.add_node(i)
|
|
network.nodes[i]['type']="participant"
|
|
|
|
h_rv = expon.rvs(loc=0.0, scale=1000)
|
|
network.nodes[i]['holdings'] = h_rv
|
|
|
|
s_rv = np.random.rand()
|
|
network.nodes[i]['sentiment'] = s_rv
|
|
|
|
participants = get_nodes_by_type(network, 'participant')
|
|
initial_supply = np.sum([ network.nodes[i]['holdings'] for i in participants])
|
|
print(initial_supply)
|
|
|
|
initial_funds = initial_supply
|
|
|
|
|
|
# In[5]:
|
|
|
|
|
|
#generate initial proposals
|
|
m = 7
|
|
for ind in range(m):
|
|
j = n+ind
|
|
network.add_node(j)
|
|
network.nodes[j]['type']="proposal"
|
|
network.nodes[j]['conviction']=0
|
|
network.nodes[j]['status']='candidate'
|
|
network.nodes[j]['age']=0
|
|
|
|
r_rv = gamma.rvs(3,loc=0.001, scale=10000)
|
|
network.node[j]['funds_requested'] = r_rv
|
|
|
|
network.nodes[j]['trigger']= trigger_threshold(r_rv, initial_funds, initial_supply)
|
|
|
|
for i in range(n):
|
|
network.add_edge(i, j)
|
|
|
|
rv = np.random.rand()
|
|
a_rv = 1-4*(1-rv)*rv #polarized distribution
|
|
network.edges[(i, j)]['affinity'] = a_rv
|
|
network.edges[(i,j)]['tokens'] = 0
|
|
network.edges[(i, j)]['conviction'] = 0
|
|
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
total_requested = np.sum([ network.nodes[i]['funds_requested'] for i in proposals])
|
|
print(total_requested/initial_funds)
|
|
|
|
|
|
# In[6]:
|
|
|
|
|
|
network.nodes[get_nodes_by_type(network, 'proposal')[0]]
|
|
|
|
|
|
# In[7]:
|
|
|
|
|
|
network.nodes[get_nodes_by_type(network, 'participant')[0]]
|
|
|
|
|
|
# In[8]:
|
|
|
|
|
|
plt.hist([ network.nodes[i]['holdings'] for i in participants])
|
|
plt.title('Histogram of Participants Token Holdings')
|
|
|
|
|
|
# In[9]:
|
|
|
|
|
|
plt.hist([ network.nodes[i]['funds_requested'] for i in proposals])
|
|
plt.title('Histogram of Proposals Funds Requested')
|
|
|
|
|
|
# In[10]:
|
|
|
|
|
|
plt.hist([ network.edges[e]['affinity'] for e in network.edges])
|
|
plt.title('Histogram of Affinities between Participants and Proposals')
|
|
|
|
|
|
# In[11]:
|
|
|
|
|
|
plt.hist([ network.edges[e]['affinity'] for e in network.edges], weights = [network.nodes[e[0]]['holdings']for e in network.edges],alpha = 1)
|
|
plt.title('Histogram of Affinities between Participants and Proposals weighted by holdings')
|
|
|
|
|
|
# In[12]:
|
|
|
|
|
|
T = 200 #iterations of graph update in our simulation
|
|
#param for conviction accumilation
|
|
alpha = .5 #later we should set this to be param so we can sweep it
|
|
|
|
#sentiment of the outside world which drives grants
|
|
initial_sentiment = .8
|
|
|
|
#sentiment decay rate
|
|
mu =.001 #later we should set this to be param so we can sweep it
|
|
|
|
#minimum periods passed before a proposal can pass
|
|
tmin = 7
|
|
|
|
#how close to a participant's highest affinity proposal that
|
|
#another proposal has to be for them to be happy about its advancement
|
|
sensitivity = .75
|
|
|
|
min_completion_rate = 10
|
|
|
|
|
|
# In[13]:
|
|
|
|
|
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
# Settings of general simulation parameters, unrelated to the system itself
|
|
# `T` is a range with the number of discrete units of time the simulation will run for;
|
|
# `N` is the number of times the simulation will be run (Monte Carlo runs)
|
|
# We'll cover the `M` key in a future article. For now, let's leave it empty
|
|
simulation_parameters = {
|
|
'T': range(T),
|
|
'N': 1,
|
|
'M': {}
|
|
}
|
|
|
|
|
|
# In[14]:
|
|
|
|
|
|
initial_conditions = {'network':network,
|
|
'supply': initial_supply,
|
|
'funds':initial_funds,
|
|
'sentiment': initial_sentiment}
|
|
|
|
|
|
# In[15]:
|
|
|
|
|
|
#functions for partial state update block 1
|
|
|
|
def gen_new_participant(network, new_participant_holdings):
|
|
|
|
i = len([node for node in network.nodes])
|
|
|
|
network.add_node(i)
|
|
network.nodes[i]['type']="participant"
|
|
|
|
s_rv = np.random.rand()
|
|
network.nodes[i]['sentiment'] = s_rv
|
|
network.nodes[i]['holdings']=new_participant_holdings
|
|
|
|
for j in get_nodes_by_type(network, 'proposal'):
|
|
network.add_edge(i, j)
|
|
|
|
rv = np.random.rand()
|
|
a_rv = 1-4*(1-rv)*rv #polarized distribution
|
|
network.edges[(i, j)]['affinity'] = a_rv
|
|
network.edges[(i,j)]['tokens'] = a_rv*network.nodes[i]['holdings']
|
|
network.edges[(i, j)]['conviction'] = 0
|
|
|
|
return network
|
|
|
|
|
|
def gen_new_proposal(network, funds, supply):
|
|
j = len([node for node in network.nodes])
|
|
network.add_node(j)
|
|
network.nodes[j]['type']="proposal"
|
|
|
|
network.nodes[j]['conviction']=0
|
|
network.nodes[j]['status']='candidate'
|
|
network.nodes[j]['age']=0
|
|
|
|
rescale = 10000*funds/initial_funds
|
|
r_rv = gamma.rvs(3,loc=0.001, scale=rescale)
|
|
network.node[j]['funds_requested'] = r_rv
|
|
|
|
network.nodes[j]['trigger']= trigger_threshold(r_rv, funds, supply)
|
|
|
|
participants = get_nodes_by_type(network, 'participant')
|
|
proposing_participant = np.random.choice(participants)
|
|
|
|
for i in participants:
|
|
network.add_edge(i, j)
|
|
if i==proposing_participant:
|
|
network.edges[(i, j)]['affinity']=1
|
|
else:
|
|
rv = np.random.rand()
|
|
a_rv = 1-4*(1-rv)*rv #polarized distribution
|
|
network.edges[(i, j)]['affinity'] = a_rv
|
|
|
|
network.edges[(i, j)]['conviction'] = 0
|
|
network.edges[(i,j)]['tokens'] = 0
|
|
return network
|
|
|
|
|
|
|
|
def driving_process(params, step, sL, s):
|
|
|
|
#placeholder plumbing for random processes
|
|
arrival_rate = 10/s['sentiment']
|
|
rv1 = np.random.rand()
|
|
new_participant = bool(rv1<1/arrival_rate)
|
|
if new_participant:
|
|
h_rv = expon.rvs(loc=0.0, scale=1000)
|
|
new_participant_holdings = h_rv
|
|
else:
|
|
new_participant_holdings = 0
|
|
|
|
network = s['network']
|
|
affinities = [network.edges[e]['affinity'] for e in network.edges ]
|
|
median_affinity = np.median(affinities)
|
|
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
fund_requests = [network.nodes[j]['funds_requested'] for j in proposals if network.nodes[j]['status']=='candidate' ]
|
|
|
|
funds = s['funds']
|
|
total_funds_requested = np.sum(fund_requests)
|
|
|
|
proposal_rate = 10/median_affinity * total_funds_requested/funds
|
|
rv2 = np.random.rand()
|
|
new_proposal = bool(rv2<1/proposal_rate)
|
|
|
|
sentiment = s['sentiment']
|
|
funds = s['funds']
|
|
scale_factor = 1+4000*sentiment**2
|
|
|
|
#this shouldn't happen but expon is throwing domain errors
|
|
if scale_factor > 1:
|
|
funds_arrival = expon.rvs(loc = 0, scale = scale_factor )
|
|
else:
|
|
funds_arrival = 0
|
|
|
|
return({'new_participant':new_participant,
|
|
'new_participant_holdings':new_participant_holdings,
|
|
'new_proposal':new_proposal,
|
|
'funds_arrival':funds_arrival})
|
|
|
|
def update_network(params, step, sL, s, _input):
|
|
|
|
network = s['network']
|
|
funds = s['funds']
|
|
supply = s['supply']
|
|
#placeholder plumbing for new proposals and new participants
|
|
new_participant = _input['new_participant'] #T/F
|
|
new_proposal = _input['new_proposal'] #T/F
|
|
# IF THEN logic to create new nodes // left out for now since always FALSE
|
|
if new_participant:
|
|
new_participant_holdings = _input['new_participant_holdings']
|
|
network = gen_new_participant(network, new_participant_holdings)
|
|
|
|
if new_proposal:
|
|
network= gen_new_proposal(network,funds,supply )
|
|
|
|
#update age of the existing proposals
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
|
|
for j in proposals:
|
|
network.nodes[j]['age'] = network.nodes[j]['age']+1
|
|
if network.nodes[j]['status'] == 'candidate':
|
|
requested = network.nodes[j]['funds_requested']
|
|
network.nodes[j]['trigger'] = trigger_threshold(requested, funds, supply)
|
|
else:
|
|
network.nodes[j]['trigger'] = np.nan
|
|
|
|
key = 'network'
|
|
value = network
|
|
|
|
return (key, value)
|
|
|
|
def increment_funds(params, step, sL, s, _input):
|
|
|
|
funds = s['funds']
|
|
funds_arrival = _input['funds_arrival']
|
|
|
|
#increment funds
|
|
funds = funds + funds_arrival
|
|
|
|
key = 'funds'
|
|
value = funds
|
|
|
|
return (key, value)
|
|
|
|
def increment_supply(params, step, sL, s, _input):
|
|
|
|
supply = s['supply']
|
|
supply_arrival = _input['new_participant_holdings']
|
|
|
|
#increment funds
|
|
supply = supply + supply_arrival
|
|
|
|
key = 'supply'
|
|
value = supply
|
|
|
|
return (key, value)
|
|
|
|
|
|
# In[16]:
|
|
|
|
|
|
#partial state update block 2
|
|
def check_progress(params, step, sL, s):
|
|
|
|
network = s['network']
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
|
|
completed = []
|
|
for j in proposals:
|
|
if network.nodes[j]['status'] == 'active':
|
|
grant_size = network.nodes[j]['funds_requested']
|
|
likelihood = 1.0/(min_completion_rate+np.log(grant_size))
|
|
if np.random.rand() < likelihood:
|
|
completed.append(j)
|
|
|
|
return({'completed':completed})
|
|
|
|
def complete_proposal(params, step, sL, s, _input):
|
|
|
|
network = s['network']
|
|
participants = get_nodes_by_type(network, 'participant')
|
|
|
|
completed = _input['completed']
|
|
for j in completed:
|
|
network.nodes[j]['status']='completed'
|
|
for i in participants:
|
|
force = network.edges[(i,j)]['affinity']
|
|
sentiment = network.node[i]['sentiment']
|
|
network.node[i]['sentiment'] = get_sentimental(sentiment, force, decay=False)
|
|
|
|
key = 'network'
|
|
value = network
|
|
|
|
return (key, value)
|
|
|
|
def update_sentiment_on_completion(params, step, sL, s, _input):
|
|
|
|
network = s['network']
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
completed = _input['completed']
|
|
|
|
grants_outstanding = np.sum([network.nodes[j]['funds_requested'] for j in proposals if network.nodes[j]['status']=='active'])
|
|
|
|
grants_completed = np.sum([network.nodes[j]['funds_requested'] for j in completed])
|
|
|
|
sentiment = s['sentiment']
|
|
|
|
force = grants_completed/grants_outstanding
|
|
if (force >=0) and (force <=1):
|
|
sentiment = get_sentimental(sentiment, force, True)
|
|
else:
|
|
sentiment = get_sentimental(sentiment, 0, True)
|
|
|
|
|
|
key = 'sentiment'
|
|
value = sentiment
|
|
|
|
return (key, value)
|
|
|
|
def get_sentimental(sentiment, force, decay=True):
|
|
sentiment = sentiment*(1-int(decay)*mu) + force
|
|
|
|
if sentiment > 1:
|
|
sentiment = 1
|
|
|
|
return sentiment
|
|
|
|
|
|
# In[17]:
|
|
|
|
|
|
#partial state update block 3
|
|
def trigger_function(params, step, sL, s):
|
|
|
|
network = s['network']
|
|
funds = s['funds']
|
|
supply = s['supply']
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
|
|
accepted = []
|
|
triggers = {}
|
|
for j in proposals:
|
|
if network.nodes[j]['status'] == 'candidate':
|
|
requested = network.nodes[j]['funds_requested']
|
|
age = network.nodes[j]['age']
|
|
threshold = trigger_threshold(requested, funds, supply)
|
|
if age > tmin:
|
|
conviction = network.nodes[j]['conviction']
|
|
if conviction >threshold:
|
|
accepted.append(j)
|
|
else:
|
|
threshold = np.nan
|
|
|
|
triggers[j] = threshold
|
|
|
|
|
|
|
|
return({'accepted':accepted, 'triggers':triggers})
|
|
|
|
def decrement_funds(params, step, sL, s, _input):
|
|
|
|
funds = s['funds']
|
|
network = s['network']
|
|
accepted = _input['accepted']
|
|
|
|
#decrement funds
|
|
for j in accepted:
|
|
funds = funds - network.nodes[j]['funds_requested']
|
|
|
|
key = 'funds'
|
|
value = funds
|
|
|
|
return (key, value)
|
|
|
|
def update_proposals(params, step, sL, s, _input):
|
|
|
|
network = s['network']
|
|
accepted = _input['accepted']
|
|
triggers = _input['triggers']
|
|
participants = get_nodes_by_type(network, 'participant')
|
|
proposals = get_nodes_by_type(network, 'proposals')
|
|
|
|
for j in proposals:
|
|
network.nodes[j]['trigger'] = triggers[j]
|
|
|
|
#bookkeeping conviction and participant sentiment
|
|
for j in accepted:
|
|
network.nodes[j]['status']='active'
|
|
network.nodes[j]['conviction']=np.nan
|
|
#change status to active
|
|
for i in participants:
|
|
|
|
edge = (i,j)
|
|
#reset tokens assigned to other candidates
|
|
network.edges[(i,j)]['tokens']=0
|
|
network.edges[(i,j)]['conviction'] = np.nan
|
|
|
|
#update participants sentiments (positive or negative)
|
|
affinities = [network.edges[(i,p)]['affinity'] for p in proposals if not(p in accepted)]
|
|
if len(affinities)>1:
|
|
max_affinity = np.max(affinities)
|
|
force = network.edges[(i,j)]['affinity']-sensitivity*max_affinity
|
|
else:
|
|
force = 0
|
|
|
|
#based on what their affinities to the accepted proposals
|
|
network.nodes[i]['sentiment'] = get_sentimental(network.nodes[i]['sentiment'], force, False)
|
|
|
|
|
|
key = 'network'
|
|
value = network
|
|
|
|
return (key, value)
|
|
|
|
def update_sentiment_on_release(params, step, sL, s, _input):
|
|
|
|
network = s['network']
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
accepted = _input['accepted']
|
|
|
|
proposals_outstanding = np.sum([network.nodes[j]['funds_requested'] for j in proposals if network.nodes[j]['status']=='candidate'])
|
|
|
|
proposals_accepted = np.sum([network.nodes[j]['funds_requested'] for j in accepted])
|
|
|
|
sentiment = s['sentiment']
|
|
force = proposals_accepted/proposals_outstanding
|
|
if (force >=0) and (force <=1):
|
|
sentiment = get_sentimental(sentiment, force, False)
|
|
else:
|
|
sentiment = get_sentimental(sentiment, 0, False)
|
|
|
|
key = 'sentiment'
|
|
value = sentiment
|
|
|
|
return (key, value)
|
|
|
|
|
|
# In[22]:
|
|
|
|
|
|
def participants_decisions(params, step, sL, s):
|
|
|
|
network = s['network']
|
|
participants = get_nodes_by_type(network, 'participant')
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
candidates = [j for j in proposals if network.nodes[j]['status']=='candidate']
|
|
|
|
gain = .01
|
|
delta_holdings={}
|
|
proposals_supported ={}
|
|
for i in participants:
|
|
force = network.nodes[i]['sentiment']-sensitivity
|
|
delta_holdings[i] = network.nodes[i]['holdings']*gain*force
|
|
|
|
support = []
|
|
for j in candidates:
|
|
affinity = network.edges[(i, j)]['affinity']
|
|
cutoff = sensitivity*np.max([network.edges[(i,p)]['affinity'] for p in candidates])
|
|
if cutoff <.5:
|
|
cutoff = .5
|
|
|
|
if affinity > cutoff:
|
|
support.append(j)
|
|
|
|
proposals_supported[i] = support
|
|
|
|
return({'delta_holdings':delta_holdings, 'proposals_supported':proposals_supported})
|
|
|
|
def update_tokens(params, step, sL, s, _input):
|
|
|
|
network = s['network']
|
|
delta_holdings = _input['delta_holdings']
|
|
proposals = get_nodes_by_type(network, 'proposal')
|
|
proposals_supported = _input['proposals_supported']
|
|
participants = get_nodes_by_type(network, 'participant')
|
|
|
|
for i in participants:
|
|
network.nodes[i]['holdings'] = network.nodes[i]['holdings']+delta_holdings[i]
|
|
supported = proposals_supported[i]
|
|
total_affinity = np.sum([ network.edges[(i, j)]['affinity'] for j in supported])
|
|
for j in proposals:
|
|
if j in supported:
|
|
normalized_affinity = network.edges[(i, j)]['affinity']/total_affinity
|
|
network.edges[(i, j)]['tokens'] = normalized_affinity*network.nodes[i]['holdings']
|
|
else:
|
|
network.edges[(i, j)]['tokens'] = 0
|
|
|
|
prior_conviction = network.edges[(i, j)]['conviction']
|
|
current_tokens = network.edges[(i, j)]['tokens']
|
|
network.edges[(i, j)]['conviction'] =current_tokens+alpha*prior_conviction
|
|
|
|
for j in proposals:
|
|
network.nodes[j]['conviction'] = np.sum([ network.edges[(i, j)]['conviction'] for i in participants])
|
|
|
|
key = 'network'
|
|
value = network
|
|
|
|
return (key, value)
|
|
|
|
def update_supply(params, step, sL, s, _input):
|
|
|
|
supply = s['supply']
|
|
delta_holdings = _input['delta_holdings']
|
|
delta_supply = np.sum([v for v in delta_holdings.values()])
|
|
|
|
supply = supply + delta_supply
|
|
|
|
key = 'supply'
|
|
value = supply
|
|
|
|
return (key, value)
|
|
|
|
|
|
# In[23]:
|
|
|
|
|
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
# The Partial State Update Blocks
|
|
partial_state_update_blocks = [
|
|
{
|
|
'policies': {
|
|
#new proposals or new participants
|
|
'random': driving_process
|
|
},
|
|
'variables': {
|
|
'network': update_network,
|
|
'funds':increment_funds,
|
|
'supply':increment_supply
|
|
}
|
|
},
|
|
{
|
|
'policies': {
|
|
'completion': check_progress #see if any of the funded proposals completes
|
|
},
|
|
'variables': { # The following state variables will be updated simultaneously
|
|
'sentiment': update_sentiment_on_completion, #note completing decays sentiment, completing bumps it
|
|
'network': complete_proposal #book-keeping
|
|
}
|
|
},
|
|
{
|
|
'policies': {
|
|
'release': trigger_function #check each proposal to see if it passes
|
|
},
|
|
'variables': { # The following state variables will be updated simultaneously
|
|
'funds': decrement_funds, #funds expended
|
|
'sentiment': update_sentiment_on_release, #releasing funds can bump sentiment
|
|
'network': update_proposals #reset convictions, and participants sentiments
|
|
#update based on affinities
|
|
}
|
|
},
|
|
{
|
|
'policies': {
|
|
'participants_act': participants_decisions, #high sentiment, high affinity =>buy
|
|
#low sentiment, low affinities => burn
|
|
#assign tokens to top affinities
|
|
},
|
|
'variables': {
|
|
'supply': update_supply,
|
|
'network': update_tokens #update everyones holdings
|
|
#and their conviction for each proposal
|
|
}
|
|
}
|
|
]
|
|
|
|
|
|
# In[24]:
|
|
|
|
|
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
# The configurations above are then packaged into a `Configuration` object
|
|
config = Configuration(initial_state=initial_conditions, #dict containing variable names and initial values
|
|
partial_state_update_blocks=partial_state_update_blocks, #dict containing state update functions
|
|
sim_config=simulation_parameters #dict containing simulation parameters
|
|
)
|
|
|
|
|
|
# In[25]:
|
|
|
|
|
|
exec_mode = ExecutionMode()
|
|
exec_context = ExecutionContext(exec_mode.single_proc)
|
|
executor = Executor(exec_context, [config]) # Pass the configuration object inside an array
|
|
raw_result, tensor = executor.main() # The `main()` method returns a tuple; its first elements contains the raw results
|
|
|
|
|
|
# In[26]:
|
|
|
|
|
|
df = pd.DataFrame(raw_result)
|
|
|
|
|
|
# In[172]:
|
|
|
|
|
|
df.tail(5)
|
|
|
|
|
|
# In[28]:
|
|
|
|
|
|
df.supply.plot()
|
|
|
|
|
|
# In[29]:
|
|
|
|
|
|
df.sentiment.plot()
|
|
|
|
|
|
# In[30]:
|
|
|
|
|
|
df.plot(x='timestep', y='funds')
|
|
|
|
|
|
# In[31]:
|
|
|
|
|
|
def pad(vec, length,fill=True):
|
|
|
|
if fill:
|
|
padded = np.zeros(length,)
|
|
else:
|
|
padded = np.empty(length,)
|
|
padded[:] = np.nan
|
|
|
|
for i in range(len(vec)):
|
|
padded[i]= vec[i]
|
|
|
|
return padded
|
|
|
|
def make2D(key, data, fill=False):
|
|
maxL = data[key].apply(len).max()
|
|
newkey = 'padded_'+key
|
|
data[newkey] = data[key].apply(lambda x: pad(x,maxL,fill))
|
|
reshaped = np.array([a for a in data[newkey].values])
|
|
|
|
return reshaped
|
|
|
|
|
|
# In[32]:
|
|
|
|
|
|
df['conviction'] = df.network.apply(lambda g: np.array([g.nodes[j]['conviction'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='candidate']))
|
|
df['candidate_count'] = df.network.apply(lambda g: len([j for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='candidate']))
|
|
df['candidate_funds'] = df.network.apply(lambda g: np.sum([g.nodes[j]['funds_requested'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='candidate']))
|
|
df['candidate_funds_requested'] = df.network.apply(lambda g: np.array([g.nodes[j]['funds_requested'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='candidate']))
|
|
df['active_count'] = df.network.apply(lambda g: len([j for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='active']))
|
|
df['active_funds'] = df.network.apply(lambda g: np.sum([g.nodes[j]['funds_requested'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='active']))
|
|
df['completed_count'] = df.network.apply(lambda g: len([j for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='completed']))
|
|
df['completed_funds'] = df.network.apply(lambda g: np.sum([g.nodes[j]['funds_requested'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='completed']))
|
|
|
|
|
|
# In[33]:
|
|
|
|
|
|
df['funds_requested'] = df.network.apply(lambda g: np.array([g.nodes[j]['funds_requested'] for j in get_nodes_by_type(g, 'proposal')]))
|
|
df['share_of_funds_requested'] = df.candidate_funds_requested/df.funds
|
|
|
|
df['share_of_funds_requested_all'] = df.funds_requested/df.funds
|
|
|
|
|
|
# In[34]:
|
|
|
|
|
|
df['triggers'] = df.network.apply(lambda g: np.array([g.nodes[j]['trigger'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='candidate' ]))
|
|
df['conviction_share_of_trigger'] = df.conviction/df.triggers
|
|
df['age'] = df.network.apply(lambda g: np.array([g.nodes[j]['age'] for j in get_nodes_by_type(g, 'proposal') if g.nodes[j]['status']=='candidate' ]))
|
|
|
|
|
|
# In[35]:
|
|
|
|
|
|
df['age_all'] = df.network.apply(lambda g: np.array([g.nodes[j]['age'] for j in get_nodes_by_type(g, 'proposal') ]))
|
|
df['conviction_all'] = df.network.apply(lambda g: np.array([g.nodes[j]['conviction'] for j in get_nodes_by_type(g, 'proposal') ]))
|
|
df['triggers_all'] = df.network.apply(lambda g: np.array([g.nodes[j]['trigger'] for j in get_nodes_by_type(g, 'proposal') ]))
|
|
|
|
df['conviction_share_of_trigger_all'] = df.conviction_all/df.triggers_all
|
|
|
|
|
|
# In[36]:
|
|
|
|
|
|
rdf= df[df.substep==4].copy()
|
|
|
|
|
|
# In[37]:
|
|
|
|
|
|
last_net= df.network.values[-1]
|
|
last_props=get_nodes_by_type(last_net, 'proposal')
|
|
M = len(last_props)
|
|
last_parts=get_nodes_by_type(last_net, 'participant')
|
|
N = len(last_parts)
|
|
|
|
|
|
# In[38]:
|
|
|
|
|
|
affinities = np.empty((N,M))
|
|
|
|
|
|
# In[39]:
|
|
|
|
|
|
for i_ind in range(N):
|
|
for j_ind in range(M):
|
|
i = last_parts[i_ind]
|
|
j = last_props[j_ind]
|
|
affinities[i_ind][j_ind] = last_net.edges[(i,j)]['affinity']
|
|
|
|
|
|
# In[40]:
|
|
|
|
|
|
dims = (100, 25)
|
|
fig, ax = plt.subplots(figsize=dims)
|
|
|
|
sns.heatmap(affinities,
|
|
yticklabels=last_parts,
|
|
xticklabels=last_props,
|
|
square=True,
|
|
cbar=True,
|
|
ax=ax)
|
|
|
|
plt.title('affinities between participants and proposals')
|
|
plt.xlabel('proposal_id')
|
|
plt.ylabel('participant_id')
|
|
|
|
|
|
# In[41]:
|
|
|
|
|
|
#working on deduplicating colors
|
|
#
|
|
#last_props=get_nodes_by_type(last_net, 'proposal')
|
|
#M = len(last_props)
|
|
|
|
#cm = plt.get_cmap('gist_rainbow')
|
|
#c= [cm(1.*j/M) for j in range(M)]
|
|
|
|
|
|
# In[168]:
|
|
|
|
|
|
make2D('age_all', rdf)
|
|
|
|
|
|
# In[170]:
|
|
|
|
|
|
plt.plot(rdf.timestep,make2D('age_all', rdf))
|
|
plt.title('check age')
|
|
|
|
|
|
# In[43]:
|
|
|
|
|
|
rdf.plot(x='timestep',y=['candidate_count','active_count','completed_count'])
|
|
|
|
|
|
# In[44]:
|
|
|
|
|
|
rdf.plot(x='timestep',y=['candidate_funds','active_funds','completed_funds'])
|
|
|
|
|
|
# In[45]:
|
|
|
|
|
|
plt.semilogy(rdf.timestep,make2D('conviction_all', rdf))
|
|
plt.title('conviction by proposal')
|
|
plt.xlabel('time $t$')
|
|
plt.ylabel('conviction')
|
|
|
|
|
|
# In[46]:
|
|
|
|
|
|
plt.semilogy(make2D('age_all', rdf),make2D('conviction_all', rdf))
|
|
plt.title('conviction by proposal')
|
|
plt.xlabel('proposal age')
|
|
plt.ylabel('conviction')
|
|
|
|
|
|
# In[47]:
|
|
|
|
|
|
plt.plot(rdf.timestep,make2D('share_of_funds_requested_all', rdf))
|
|
plt.title('share_of_funds_requested by proposal')
|
|
plt.xlabel('time $t$')
|
|
plt.ylabel('share_of_funds_requested')
|
|
|
|
|
|
# In[48]:
|
|
|
|
|
|
plt.semilogy(make2D('age_all', rdf),make2D('share_of_funds_requested_all', rdf))
|
|
plt.title('share_of_funds_requested by proposal')
|
|
plt.xlabel('proposal age')
|
|
plt.ylabel('share_of_funds_requested')
|
|
|
|
|
|
# In[166]:
|
|
|
|
|
|
plt.loglog(make2D('share_of_funds_requested_all', rdf), make2D('conviction_all', rdf), '.')
|
|
plt.ylabel('conviction')
|
|
plt.xlabel('share_of_funds_requested')
|
|
|
|
|
|
# In[50]:
|
|
|
|
|
|
plt.semilogy(make2D('age_all', rdf), make2D('triggers_all', rdf))
|
|
plt.ylabel('triggers')
|
|
plt.xlabel('proposal_age')
|
|
|
|
|
|
# In[51]:
|
|
|
|
|
|
plt.loglog(make2D('conviction_all', rdf), make2D('triggers_all', rdf))
|
|
a = plt.axis()
|
|
plt.loglog(a[:2],a[2:], 'k',alpha=.5 )
|
|
plt.ylabel('triggers')
|
|
plt.xlabel('conviction')
|
|
plt.title('phase: Triggers & Conviction')
|
|
|
|
|
|
# In[174]:
|
|
|
|
|
|
plt.semilogy(rdf.timestep,make2D('conviction_share_of_trigger_all', rdf))
|
|
plt.title('conviction_share_of_trigger')
|
|
plt.xlabel('time $t$')
|
|
plt.ylabel('conviction_share_of_trigger')
|
|
plt.hlines(1,0,T, linestyle='--')
|
|
|
|
|
|
# In[63]:
|
|
|
|
|
|
plt.semilogy(make2D('age_all', rdf), make2D('conviction_share_of_trigger_all', rdf))
|
|
plt.ylabel('triggers')
|
|
plt.xlabel('proposal_age')
|
|
plt.hlines(1,0,T, linestyle='--')
|
|
|
|
|
|
# In[54]:
|
|
|
|
|
|
pos = {}
|
|
for ind in range(N):
|
|
i = last_parts[ind]
|
|
pos[i] = np.array([0, 2*ind-N])
|
|
|
|
for ind in range(M):
|
|
j = last_props[ind]
|
|
pos[j] = np.array([1, 2*N/M *ind-N])
|
|
|
|
#for i in last_parts:
|
|
#for j in last_props:
|
|
|
|
|
|
|
|
# In[55]:
|
|
|
|
|
|
edges = [e for e in last_net.edges]
|
|
max_tok = np.max([last_net.edges[e]['tokens'] for e in edges])
|
|
|
|
E = len(edges)
|
|
|
|
node_color = np.empty((M+N,4))
|
|
node_size = np.empty(M+N)
|
|
|
|
edge_color = np.empty((E,4))
|
|
cm = plt.get_cmap('Reds')
|
|
|
|
cNorm = colors.Normalize(vmin=0, vmax=max_tok)
|
|
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
|
|
|
|
|
|
# In[107]:
|
|
|
|
|
|
size_scale = 1/500
|
|
node_label = {}
|
|
|
|
for j in last_props:
|
|
node_size[j] = last_net.nodes[j]['funds_requested']*size_scale
|
|
if last_net.nodes[j]['status']=="candidate":
|
|
node_color[j] = colors.to_rgba('blue')
|
|
trigger = last_net.nodes[j]['trigger']
|
|
#print(trigger)
|
|
conviction = last_net.nodes[j]['conviction']
|
|
#print(conviction)
|
|
percent_of_trigger = str(int(100*conviction/trigger))+'%'
|
|
#age = last_net.nodes[j]['age']
|
|
node_label[j] = str(percent_of_trigger)
|
|
elif last_net.nodes[j]['status']=="active":
|
|
node_color[j] = colors.to_rgba('orange')
|
|
node_label[j] = ''
|
|
elif last_net.nodes[j]['status']=="completed":
|
|
node_color[j] = colors.to_rgba('green')
|
|
node_label[j] = ''
|
|
|
|
for i in last_parts:
|
|
node_size[i] = last_net.nodes[i]['holdings']*size_scale
|
|
node_color[i] = colors.to_rgba('red')
|
|
node_label[i] = ''
|
|
|
|
included_edges = []
|
|
for ind in range(E):
|
|
e = edges[ind]
|
|
tokens = last_net.edges[e]['tokens']
|
|
if tokens >0:
|
|
included_edges.append(e)
|
|
#print(tokens)
|
|
edge_color[ind] = scalarMap.to_rgba(tokens)
|
|
|
|
iE = len(included_edges)
|
|
included_edge_color = np.empty((iE,4))
|
|
for ind in range(iE):
|
|
e = included_edges[ind]
|
|
tokens = last_net.edges[e]['tokens']
|
|
included_edge_color[ind] = scalarMap.to_rgba(tokens)
|
|
|
|
|
|
# In[108]:
|
|
|
|
|
|
nx.draw(last_net,
|
|
pos=pos,
|
|
node_size = node_size,
|
|
node_color = node_color,
|
|
edge_color = included_edge_color,
|
|
edgelist=included_edges,
|
|
labels = node_label)
|
|
plt.title('Tokens Staked by Partipants to Proposals')
|
|
|
|
|
|
# In[125]:
|
|
|
|
|
|
nets = rdf.network.values
|
|
|
|
|
|
# In[164]:
|
|
|
|
|
|
def snap_plot(nets, size_scale = 1/500, ani = False, dims = (20,20), savefigs=False ):
|
|
|
|
last_net= df.network.values[-1]
|
|
last_props=get_nodes_by_type(last_net, 'proposal')
|
|
M = len(last_props)
|
|
last_parts=get_nodes_by_type(last_net, 'participant')
|
|
N = len(last_parts)
|
|
pos = {}
|
|
|
|
for ind in range(N):
|
|
i = last_parts[ind]
|
|
pos[i] = np.array([0, 2*ind-N])
|
|
|
|
for ind in range(M):
|
|
j = last_props[ind]
|
|
pos[j] = np.array([1, 2*N/M *ind-N])
|
|
|
|
if ani:
|
|
figs = []
|
|
fig, ax = plt.subplots(figsize=dims)
|
|
|
|
if savefigs:
|
|
counter = 0
|
|
length = 10
|
|
import string
|
|
unique_id = ''.join([np.random.choice(list(string.ascii_letters + string.digits)) for _ in range(length)])
|
|
for net in nets:
|
|
edges = [e for e in net.edges]
|
|
max_tok = np.max([net.edges[e]['tokens'] for e in edges])
|
|
|
|
E = len(edges)
|
|
|
|
net_props = get_nodes_by_type(net, 'proposal')
|
|
net_parts = get_nodes_by_type(net, 'participant')
|
|
net_node_label ={}
|
|
|
|
num_nodes = len([node for node in net.nodes])
|
|
|
|
node_color = np.empty((num_nodes,4))
|
|
node_size = np.empty(num_nodes)
|
|
|
|
edge_color = np.empty((E,4))
|
|
cm = plt.get_cmap('Reds')
|
|
|
|
cNorm = colors.Normalize(vmin=0, vmax=max_tok)
|
|
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
|
|
|
|
|
|
|
|
for j in net_props:
|
|
node_size[j] = net.nodes[j]['funds_requested']*size_scale
|
|
if net.nodes[j]['status']=="candidate":
|
|
node_color[j] = colors.to_rgba('blue')
|
|
trigger = net.nodes[j]['trigger']
|
|
conviction = net.nodes[j]['conviction']
|
|
percent_of_trigger = " "+str(int(100*conviction/trigger))+'%'
|
|
net_node_label[j] = str(percent_of_trigger)
|
|
elif net.nodes[j]['status']=="active":
|
|
node_color[j] = colors.to_rgba('orange')
|
|
net_node_label[j] = ''
|
|
elif net.nodes[j]['status']=="completed":
|
|
node_color[j] = colors.to_rgba('green')
|
|
net_node_label[j] = ''
|
|
|
|
for i in net_parts:
|
|
node_size[i] = net.nodes[i]['holdings']*size_scale
|
|
node_color[i] = colors.to_rgba('red')
|
|
net_node_label[i] = ''
|
|
|
|
included_edges = []
|
|
for ind in range(E):
|
|
e = edges[ind]
|
|
tokens = net.edges[e]['tokens']
|
|
if tokens >0:
|
|
included_edges.append(e)
|
|
edge_color[ind] = scalarMap.to_rgba(tokens)
|
|
|
|
iE = len(included_edges)
|
|
included_edge_color = np.empty((iE,4))
|
|
for ind in range(iE):
|
|
e = included_edges[ind]
|
|
tokens = net.edges[e]['tokens']
|
|
included_edge_color[ind] = scalarMap.to_rgba(tokens)
|
|
|
|
nx.draw(net,
|
|
pos=pos,
|
|
node_size = node_size,
|
|
node_color = node_color,
|
|
edge_color = included_edge_color,
|
|
edgelist=included_edges,
|
|
labels = net_node_label)
|
|
plt.title('Tokens Staked by Partipants to Proposals')
|
|
if ani:
|
|
nx.draw(net,
|
|
pos=pos,
|
|
node_size = node_size,
|
|
node_color = node_color,
|
|
edge_color = included_edge_color,
|
|
edgelist=included_edges,
|
|
labels = net_node_label, ax=ax)
|
|
figs.append(fig)
|
|
|
|
else:
|
|
nx.draw(net,
|
|
pos=pos,
|
|
node_size = node_size,
|
|
node_color = node_color,
|
|
edge_color = included_edge_color,
|
|
edgelist=included_edges,
|
|
labels = net_node_label)
|
|
plt.title('Tokens Staked by Partipants to Proposals')
|
|
if savefigs:
|
|
plt.savefig(unique_id+'_fig'+str(counter)+'.png')
|
|
counter = counter+1
|
|
plt.show()
|
|
|
|
if ani:
|
|
False
|
|
#anim = animation.ArtistAnimation(fig, , interval=50, blit=True, repeat_delay=1000)
|
|
#plt.show()
|
|
|
|
|
|
|
|
# In[165]:
|
|
|
|
|
|
snap_plot(nets, ani=False, savefigs=False)
|
|
|
|
|
|
# In[143]:
|
|
|
|
|
|
#totally failing at animating by trying to save a sequence of figures.
|
|
#snap_plot(nets, ani=True)
|
|
|
|
#saving the images to files works so there is almost the option to compile a video from the images
|
|
|
|
|
|
# In[ ]:
|
|
|
|
|
|
|
|
|