from scipy.stats import expon, gamma import numpy as np import networkx as nx from hatch import TokenBatch from convictionvoting import trigger_threshold from IPython.core.debugger import set_trace from functools import wraps def dump_output(f): @wraps(f) def wrapper(*args, **kwargs): result = f(*args, **kwargs) print(result) return result return wrapper 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 ] def get_proposals(network): return get_nodes_by_type(network, "proposal") def get_participants(network): return get_nodes_by_type(network, "participant") def initial_social_network(network: nx.DiGraph, scale = 1, sigmas=3) -> nx.DiGraph: participants = get_participants(network) for i in participants: for j in participants: if not(j==i): influence_rv = expon.rvs(loc=0.0, scale=scale) if influence_rv > scale+sigmas*scale**2: network.add_edge(i,j) network.edges[(i,j)]['influence'] = influence_rv network.edges[(i,j)]['type'] = 'influence' return network def initial_conflict_network(network: nx.DiGraph, rate = .25) -> nx.DiGraph: proposals = get_proposals(network) for i in proposals: for j in proposals: if not(j==i): conflict_rv = np.random.rand() if conflict_rv < rate : network.add_edge(i,j) network.edges[(i,j)]['conflict'] = 1-conflict_rv network.edges[(i,j)]['type'] = 'conflict' return network def add_proposals_and_relationships_to_network(n: nx.DiGraph, proposals: int, funding_pool: float, token_supply: float) -> nx.DiGraph: participant_count = len(n) for i in range(proposals): j = participant_count + i n.add_node(j, type="proposal", conviction=0, status="candidate", age=0) r_rv = gamma.rvs(3,loc=0.001, scale=10000) n.nodes[j]['funds_requested'] = r_rv n.nodes[j]['trigger']= trigger_threshold(r_rv, funding_pool, token_supply) for i in range(participant_count): n.add_edge(i, j) rv = np.random.rand() a_rv = 1-4*(1-rv)*rv #polarized distribution n.edges[(i, j)]['affinity'] = a_rv n.edges[(i, j)]['tokens'] = 0 n.edges[(i, j)]['conviction'] = 0 n.edges[(i, j)]['type'] = 'support' n = initial_conflict_network(n, rate = .25) n = initial_social_network(n, scale = 1) return n def update_collateral_pool(params, step, sL, s, _input): commons = s["commons"] s["collateral_pool"] = commons._collateral_pool return "collateral_pool", commons._collateral_pool def update_token_supply(params, step, sL, s, _input): commons = s["commons"] s["token_supply"] = commons._token_supply return "token_supply", commons._token_supply def update_funding_pool(params, step, sL, s, _input): commons = s["commons"] s["funding_pool"] = commons._funding_pool return "funding_pool", commons._funding_pool # ========================================================================================================= def gen_new_participant(network, new_participant_tokens): 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_vesting']=None network.nodes[i]['holdings_nonvesting']=TokenBatch(new_participant_tokens, 5, 5) # Connect this new participant to existing proposals. 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_nonvesting'].value network.edges[(i, j)]['conviction'] = 0 network.edges[(i,j)]['type'] = 'support' return network def gen_new_proposal(network, funds, supply, trigger_func, scale_factor = 1.0/100): 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 = funds*scale_factor r_rv = gamma.rvs(3,loc=0.001, scale=rescale) network.nodes[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 network.edges[(i,j)]['type'] = 'support' return network def calc_total_funds_requested(network): proposals = get_proposals(network) fund_requests = [network.nodes[j]['funds_requested'] for j in proposals if network.nodes[j]['status']=='candidate' ] total_funds_requested = np.sum(fund_requests) return total_funds_requested def calc_median_affinity(network): supporters = get_edges_by_type(network, 'support') affinities = [network.edges[e]['affinity'] for e in supporters ] median_affinity = np.median(affinities) return median_affinity @dump_output def gen_new_participants_proposals_funding_randomly(params, step, sL, s): network = s['network'] commons = s['commons'] funds = s['funding_pool'] sentiment = s['sentiment'] def randomly_gen_new_participant(participant_count, sentiment, current_token_supply, commons): arrival_rate = 10/(1+sentiment) rv1 = np.random.rand() new_participant = bool(rv1<1/arrival_rate) if new_participant: # Below line is quite different from Zargham's original, which gave # tokens instead. Here we randomly generate each participant's # post-Hatch investment, in DAI/USD. Here the settings for # expon.rvs() should generate investments of ~0-500 DAI. new_participant_investment = expon.rvs(loc=0.0, scale=100) new_participant_tokens = commons.dai_to_tokens(new_participant_investment) return new_participant, new_participant_investment, new_participant_tokens else: return new_participant, 0, 0 def randomly_gen_new_proposal(total_funds_requested, median_affinity, funding_pool): proposal_rate = 1/median_affinity * (1+total_funds_requested/funding_pool) rv2 = np.random.rand() new_proposal = bool(rv2<1/proposal_rate) return new_proposal def randomly_gen_new_funding(funds, sentiment): """ Each step, more funding comes to the Commons through the exit tribute, because after the hatching phase, all incoming money goes to the collateral reserve, not to the funding pool. """ scale_factor = funds*sentiment**2/10000 if scale_factor <1: scale_factor = 1 #this shouldn't happen but expon is throwing domain errors if sentiment>.4: funds_arrival = expon.rvs(loc = 0, scale = scale_factor ) else: funds_arrival = 0 return funds_arrival new_participant, new_participant_investment, new_participant_tokens = randomly_gen_new_participant(len(get_participants(network)), sentiment, s['token_supply'], commons) new_proposal = randomly_gen_new_proposal(calc_total_funds_requested(network), calc_median_affinity(network), funds) funds_arrival = randomly_gen_new_funding(funds, sentiment) return({'new_participant':new_participant, 'new_participant_investment':new_participant_investment, 'new_participant_tokens': new_participant_tokens, 'new_proposal':new_proposal, 'funds_arrival':funds_arrival}) def add_participants_proposals_to_network(params, step, sL, s, _input): network = s['network'] funds = s['funding_pool'] supply = s['token_supply'] trigger_func = params[0]["trigger_threshold"] new_participant = _input['new_participant'] #T/F new_proposal = _input['new_proposal'] #T/F if new_participant: network = gen_new_participant(network, _input['new_participant_tokens']) 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 new_participants_and_new_funds_commons(params, step, sL, s, _input): commons = s["commons"] if _input['new_participant']: tokens, realized_price = commons.deposit(_input['new_participant_investment']) # print(tokens, realized_price, _input['new_participant_tokens']) if _input['funds_arrival']: commons._funding_pool += _input['funds_arrival'] return "commons", commons # ========================================================================================================= @dump_output def make_active_proposals_complete_or_fail_randomly(params, step, sL, s): network = s['network'] proposals = get_proposals(network) completed = [] failed = [] for j in proposals: if network.nodes[j]['status'] == 'active': grant_size = network.nodes[j]['funds_requested'] base_completion_rate=params[0]['base_completion_rate'] likelihood = 1.0/(base_completion_rate+np.log(grant_size)) base_failure_rate = params[0]['base_failure_rate'] failure_rate = 1.0/(base_failure_rate+np.log(grant_size)) if np.random.rand() < likelihood: completed.append(j) elif np.random.rand() < failure_rate: failed.append(j) return({'completed':completed, 'failed':failed}) def get_sentimental(sentiment, force, decay=0): mu = decay sentiment = sentiment*(1-mu) + force if sentiment > 1: sentiment = 1 return sentiment def sentiment_decays_wo_completed_proposals(params, step, sL, s, _input): network = s['network'] proposals = get_proposals(network) completed = _input['completed'] failed = _input['failed'] 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]) grants_failed = np.sum([network.nodes[j]['funds_requested'] for j in failed]) sentiment = s['sentiment'] if grants_outstanding>0: force = (grants_completed-grants_failed)/grants_outstanding else: force=1 mu = params[0]['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 update_network_w_proposal_status(params, step, sL, s, _input): network = s['network'] participants = get_participants(network) proposals = get_proposals(network) competitors = get_edges_by_type(network, 'conflict') completed = _input['completed'] for j in completed: network.nodes[j]['status']='completed' for c in proposals: if (j,c) in competitors: conflict = network.edges[(j,c)]['conflict'] for i in participants: network.edges[(i,c)]['affinity'] = network.edges[(i,c)]['affinity'] *(1-conflict) for i in participants: force = network.edges[(i,j)]['affinity'] sentiment = network.nodes[i]['sentiment'] network.nodes[i]['sentiment'] = get_sentimental(sentiment, force, decay=0) failed = _input['failed'] for j in failed: network.nodes[j]['status']='failed' for i in participants: force = -network.edges[(i,j)]['affinity'] sentiment = network.nodes[i]['sentiment'] network.nodes[i]['sentiment'] = get_sentimental(sentiment, force, decay=0) key = 'network' value = network return (key, value) # ========================================================================================================= @dump_output def conviction_gathering(params, step, sL, s): def sort_proposals_by_conviction(network, proposals): ordered = sorted(proposals, key=lambda j:network.nodes[j]['conviction'] , reverse=True) return ordered network = s['network'] funding_pool = s['funding_pool'] token_supply = s['token_supply'] proposals = get_proposals(network) min_proposal_age = params[0]['min_proposal_age_days'] trigger_func = params[0]['trigger_threshold'] accepted = [] triggers = {} funds_to_be_released = 0 for j in proposals: if network.nodes[j]['status'] == 'candidate': requested = network.nodes[j]['funds_requested'] age = network.nodes[j]['age'] threshold = trigger_func(requested, funding_pool, token_supply) if age > min_proposal_age: conviction = network.nodes[j]['conviction'] if conviction >threshold: accepted.append(j) funds_to_be_released = funds_to_be_released + requested else: threshold = np.nan triggers[j] = threshold #catch over release and keep the highest conviction results if funds_to_be_released > funding_pool: #print('funds ='+str(funds)) #print(accepted) ordered = sort_proposals_by_conviction(network, accepted) #print(ordered) accepted = [] release = 0 ind = 0 while release + network.nodes[ordered[ind]]['funds_requested'] < funding_pool: accepted.append(ordered[ind]) release= network.nodes[ordered[ind]]['funds_requested'] ind=ind+1 return({'accepted':accepted, 'triggers':triggers}) def decrement_commons_funding_pool(params, step, sL, s, _input): commons = s['commons'] network = s['network'] accepted = _input['accepted'] for j in accepted: commons.spend(network.nodes[j]['funds_requested']) key = 'commons' value = commons return (key, value) def update_sentiment_on_release(params, step, sL, s, _input): network = s['network'] proposals = get_proposals(network) 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 update_proposals(params, step, sL, s, _input): network = s['network'] accepted = _input['accepted'] triggers = _input['triggers'] participants = get_participants(network) proposals = get_proposals(network) sensitivity = params[0]['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)