diff --git a/conviction_system_logic2.py b/conviction_system_logic2.py new file mode 100644 index 0000000..74ed220 --- /dev/null +++ b/conviction_system_logic2.py @@ -0,0 +1,495 @@ +import numpy as np +from conviction_helpers import get_nodes_by_type, get_edges_by_type, social_affinity_booster +#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 + 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.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 + network.edges[(i,j)]['type'] = 'support' + + 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) + supporters = get_edges_by_type(s['network'], 'support') + 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 supporters ] + 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 = funds*sentiment**2/10000 + + #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({'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'] + #print(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 = [] + failed = [] + 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)) + + base_failure_rate = params['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: + completed.append(j) + + return({'completed':completed, 'failed':failed}) + + +#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') + proposals = get_nodes_by_type(network, 'proposal') + 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.node[i]['sentiment'] + network.node[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.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'] + 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['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'] + trigger_func = params['trigger_func'] + + 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_func(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}) + +#functions for partial state update block 3 + +#state updates +##--- + +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) + + +#functions for partial state update block 4 + +#Decision processes: trigger function policy +##--- +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: + + engagement_rate = .3*network.nodes[i]['sentiment'] + if np.random.rand() cutoff: + support.append(j) + + proposals_supported[i] = support + else: + delta_holdings[i] = 0 + proposals_supported[i] = [j for j in candidates if network.edges[(i,j)]['tokens']>0 ] + + return({'delta_holdings':delta_holdings, 'proposals_supported':proposals_supported}) + +#functions for partial state update block 4 + +#state updates +##--- + +def update_tokens(params, step, sL, s, _input): + + network = s['network'] + delta_holdings = _input['delta_holdings'] + proposals = get_nodes_by_type(network, 'proposal') + candidates = [j for j in proposals if network.nodes[j]['status']=='candidate'] + proposals_supported = _input['proposals_supported'] + participants = get_nodes_by_type(network, 'participant') + alpha = params['alpha'] + min_support = params['min_supp'] + + 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 candidates: + 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 candidates: + network.nodes[j]['conviction'] = np.sum([ network.edges[(i, j)]['conviction'] for i in participants]) + total_tokens = np.sum([network.edges[(i, j)]['tokens'] for i in participants ]) + if total_tokens < min_support: + network.nodes[j]['status'] = 'killed' + + 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) \ No newline at end of file