Source code for abm.generators
# -*- coding: utf-8 -*-
"""
abm.generators
~~~~~~~~~~~~~~
Objects for distributing node attributes and edges
"""
from random import randint
import numpy as np
from operator import mul
from random import random
NO_VAL = 'no value'
DIFF = 'diff'
ATTR_SCALE = 100.
[docs]class AttributeGenerator(object):
def __init__(self, attributes, scale):
"""
Accepts a dictionary of {attribute: {value: k}}
where attribute like 'color', value like 'blue', k like '35'
sum of all values (k) for each attribute should be <= scale.
Provides a <get_value> method to draw an attribute value according to its probability dist
"""
self.attributes = attributes
self._attr_data = {}
self._setup_attr_data()
def _setup_attr_data(self):
for attribute, value_dist in self.attributes.items():
value_names = value_dist.keys()
value_cumsum = np.cumsum([value_dist[k] for k in value_names])
self._attr_data[attribute] = dict(names=value_names, cumsum=value_cumsum)
[docs] def get_value(self, attribute):
flip = randint(1, int(ATTR_SCALE))
attr_data = self._attr_data[attribute]
matched_value_index = np.searchsorted(attr_data['cumsum'], flip)
if matched_value_index == len(attr_data['names']):
return NO_VAL
return attr_data['names'][matched_value_index]
[docs]class EdgeGenerator(object):
def __init__(self, attributes, edge_probs, density):
"""
Accepts a dictionary of {attribute: {value: k}},
where attribute like 'color', value like 'blue', k like '35'
sum of all values (k) for each attribute should be <= scale;
initial probability vector of liklihoods of setting and edge between two nodes
that are similar or differ on each attribute; and desired network density.
Provides a <set_edge> method to draw an attribute value according to
probs and self.adjustment
"""
self.attributes = attributes
self.edge_probs = edge_probs
self.density = density
self.density_adjuster = 1.0
self._compute_adjuster()
def _compute_adjuster(self):
p_edge_by_attr = []
value_weights = {
attr: {key: value/ATTR_SCALE for key, value in values.items()}
for attr, values in self.attributes.items()
}
for attr in value_weights:
# probability vector over values for this attribute
attr_dist = value_weights[attr]
# first find the probability of different types of dyads (match, unmatch, noval)
p_noval = attr_dist.get(NO_VAL, 0.)
p_dyad_w_no_val_node = 2*p_noval - p_noval**2
p_dyad_w_matched_val = sum(attr_dist[k]**2 for k in attr_dist if k != NO_VAL)
p_dyad_wo_matched_val = 1.0 - p_dyad_w_matched_val - p_dyad_w_no_val_node
# given dyad type probabilities and edge_probs, find expected edge connectivity
p_edge_matched = sum([
attr_dist[k]**2 * self.edge_probs[attr][k]
for k in attr_dist if k != NO_VAL
])
p_edge_wo_match = p_dyad_wo_matched_val * self.edge_probs[attr][DIFF]
p_edge_w_no_val_node = p_dyad_w_no_val_node * self.edge_probs[attr].get(NO_VAL, 0.)
p_edge = p_edge_matched + p_edge_wo_match + p_edge_w_no_val_node
p_edge_by_attr.append(p_edge)
self.density_adjuster = self.density / reduce(mul, p_edge_by_attr, 1)
[docs] def set_edge(self, node1, node2):
p_edge = []
for attr in self.attributes:
if NO_VAL in (node1[attr], node2[attr]):
# if either node is missing this value, use the NOVAL edge prob
p_edge.append(self.edge_probs[attr][NO_VAL])
elif node1[attr] == node2[attr]:
# if nodes match on this attr, use the match edge prob for that value
p_edge.append(self.edge_probs[attr][node1[attr]])
else:
# if nodes do not match on this attr, use the differ edge prob
p_edge.append(self.edge_probs[attr][DIFF])
prob = self.density_adjuster * reduce(mul, p_edge, 1)
return random() <= prob