Create conviction_system_logic_sim.py
running version with params; made with Josh help
This commit is contained in:
parent
0977da9052
commit
dbfc657d29
|
|
@ -0,0 +1,547 @@
|
|||
import numpy as np
|
||||
|
||||
from cadCAD.configuration.utils import config_sim
|
||||
from conviction_helpers import *
|
||||
#import networkx as nx
|
||||
from scipy.stats import expon, gamma
|
||||
|
||||
|
||||
#functions for partial state update block 1
|
||||
|
||||
#Driving processes: arrival of participants, proposals and funds
|
||||
##-----------------------------------------
|
||||
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
|
||||
|
||||
|
||||
scale_factor = 1000
|
||||
|
||||
def gen_new_proposal(network, funds, supply, trigger_func):
|
||||
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 = scale_factor*funds
|
||||
r_rv = gamma.rvs(3,loc=0.001, scale=rescale)
|
||||
network.node[j]['funds_requested'] = r_rv
|
||||
|
||||
network.nodes[j]['trigger']= trigger_func(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})
|
||||
|
||||
|
||||
#Mechanisms for updating the state based on driving processes
|
||||
##---
|
||||
def update_network(params, step, sL, s, _input):
|
||||
|
||||
network = s['network']
|
||||
funds = s['funds']
|
||||
supply = s['supply']
|
||||
trigger_func = params['trigger_func']
|
||||
|
||||
new_participant = _input['new_participant'] #T/F
|
||||
new_proposal = _input['new_proposal'] #T/F
|
||||
|
||||
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,trigger_func )
|
||||
|
||||
#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_func(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)
|
||||
|
||||
#functions for partial state update block 2
|
||||
|
||||
#Driving processes: completion of previously funded proposals
|
||||
##-----------------------------------------
|
||||
|
||||
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']
|
||||
base_completion_rate=params['base_completion_rate']
|
||||
likelihood = 1.0/(base_completion_rate+np.log(grant_size))
|
||||
if np.random.rand() < likelihood:
|
||||
completed.append(j)
|
||||
|
||||
return({'completed':completed})
|
||||
|
||||
|
||||
#Mechanisms for updating the state based on check progress
|
||||
##---
|
||||
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=0)
|
||||
|
||||
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
|
||||
mu = params['sentiment_decay']
|
||||
if (force >=0) and (force <=1):
|
||||
sentiment = get_sentimental(sentiment, force, mu)
|
||||
else:
|
||||
sentiment = get_sentimental(sentiment, 0, mu)
|
||||
|
||||
|
||||
key = 'sentiment'
|
||||
value = sentiment
|
||||
|
||||
return (key, value)
|
||||
|
||||
def get_sentimental(sentiment, force, decay=0):
|
||||
mu = decay
|
||||
sentiment = sentiment*(1-mu) + force
|
||||
|
||||
if sentiment > 1:
|
||||
sentiment = 1
|
||||
|
||||
return sentiment
|
||||
|
||||
#functions for partial state update block 3
|
||||
|
||||
#Decision processes: trigger function policy
|
||||
##-----------------------------------------
|
||||
|
||||
def trigger_function(params, step, sL, s):
|
||||
|
||||
network = s['network']
|
||||
funds = s['funds']
|
||||
supply = s['supply']
|
||||
proposals = get_nodes_by_type(network, 'proposal')
|
||||
tmin = params['tmin']
|
||||
|
||||
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')
|
||||
sensitivity = params['sensitivity']
|
||||
|
||||
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:
|
||||
|
||||
#operating on 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)
|
||||
|
||||
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']
|
||||
sensitivity = params['sensitivity']
|
||||
|
||||
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')
|
||||
alpha = params['alpha']
|
||||
|
||||
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)
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# 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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
n= 25 #initial participants
|
||||
m= 3 #initial proposals
|
||||
|
||||
initial_sentiment = .5
|
||||
|
||||
network, initial_funds, initial_supply, total_requested = initialize_network(n,m,total_funds_given_total_supply,trigger_threshold)
|
||||
|
||||
initial_conditions = {'network':network,
|
||||
'supply': initial_supply,
|
||||
'funds':initial_funds,
|
||||
'sentiment': initial_sentiment}
|
||||
|
||||
#power of 1 token forever
|
||||
# conviction_capactity = [2]
|
||||
# alpha = [1-1/cc for cc in conviction_capactity]
|
||||
# print(alpha)
|
||||
|
||||
params={
|
||||
'sensitivity': [.75],
|
||||
'tmin': [7], #unit days; minimum periods passed before a proposal can pass
|
||||
'sentiment_decay': [.001], #termed mu in the state update function
|
||||
'alpha': [0.5, 0.9],
|
||||
'base_completion_rate': [10],
|
||||
'trigger_func': [trigger_threshold]
|
||||
}
|
||||
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# 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)
|
||||
time_periods_per_run = 250
|
||||
monte_carlo_runs = 1
|
||||
|
||||
simulation_parameters = config_sim({
|
||||
'T': range(time_periods_per_run),
|
||||
'N': monte_carlo_runs,
|
||||
'M': params
|
||||
})
|
||||
|
||||
|
||||
from cadCAD.configuration import append_configs
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# The configurations above are then packaged into a `Configuration` object
|
||||
append_configs(
|
||||
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_configs=simulation_parameters #dict containing simulation parameters
|
||||
)
|
||||
|
||||
from cadCAD.engine import ExecutionMode, ExecutionContext, Executor
|
||||
from cadCAD import configs
|
||||
|
||||
exec_mode = ExecutionMode()
|
||||
multi_proc_ctx = ExecutionContext(context=exec_mode.multi_proc)
|
||||
run = Executor(exec_context=multi_proc_ctx, configs=configs)
|
||||
raw_result, tensor = run.execute()
|
||||
|
||||
#import pandas as pd
|
||||
#df = pd.DataFrame(raw_result)
|
||||
# exec_mode = ExecutionMode()
|
||||
# exec_context = ExecutionContext(context=exec_mode.multi_proc)
|
||||
# # run = Executor(exec_context=exec_context, configs=configs)
|
||||
# executor = Executor(exec_context, configs) # Pass the configuration object inside an array
|
||||
# raw_result, tensor = executor.execute() # The `main()` method returns a tuple; its first elements contains the raw results
|
||||
Loading…
Reference in New Issue