Source code for abm.io

# -*- coding: utf-8 -*-
"""
abm.io
======

Reading and writing network configuration files, through
:class:`ConfigReader`.

"""

from abm.generators import NO_VAL, ATTR_SCALE, DIFF

import json
import numpy as np


[docs]class ConfigReader(object): """Interface to reading network settings from a file. Read a json configuration file, `filename`, and validate and augment settings for use in network generation. The final settings are stored in `self.config`. :mod:`abm` configuration files are used for determining the properties of a network, based on which the network is randomly created. :Example: A typical configuration file looks like this: .. code-block:: json { "attributes": { "color": { "blue": 40, "green": 25, "red": 35 }, "region": { "west": 45, "east": 50 } }, "size": 100, "entity_kwargs":{ "policy_duration": 1 }, "edge_probs": { "color": { "blue": 0.2, "diff": 0.1, "green": 0.25, "red": 0.15 }, "region": { "west": 0.2, "east": 0.2, "diff": 0.08 } }, "density": 0.1 } This defines a network with 100 nodes (`size`) and desired edge density of 0.1 (`density`). The keys of the `attributes` dict are used as features to define each node's identity, and the values are the desired proportion of nodes that will have this feature, normalaized to 100. This configuration file defines a network in which 40% of the nodes are of `color` `blue`. The keys of the `edge_probs` dict are the same features as before, but now the values are the probabilities with which an edge joining two nodes with the same value in this feature will be connected. There is also a probability for forming an edge between nodes with different values for this attribute. In the above example, the network will be built so that two nodes with the value 'blue' for the attribute `color` will be joined with 0.2 probability, while two nodes with different `color` values will be joined with 0.1 probability, coming from the value for `diff`. .. note:: Neither the total of the values in `attributes` has to add up to 100, nor the total of the probabilities in `edge_probs` has to add up to 1.0. If they don't, :class:`ConfigReader` fills in the missing data. """ def __init__(self, filename): with open(filename, 'r') as f: self.raw_config = json.load(f) self.attributes, self.edge_probs = ( self.raw_config['attributes'], self.raw_config['edge_probs'] ) self._validate_configs(self.attributes, self.edge_probs) for attr in self.edge_probs: if self._fill_noval_attribute_prob(attr): self._set_noval_edge_prob(attr) self.config = dict(self.raw_config, attributes=self.attributes, edge_probs=self.edge_probs)
[docs] def get_config(self): return self.config
def _validate_configs(self, attributes, edge_probs): """Assert the attribute/edge settings are internally consistent.""" msg = 'Configuration looks wrong.' for attr, inner_dict in attributes.items(): assert sum(inner_dict.values()) <= ATTR_SCALE, msg assert all([isinstance(v, int) for v in inner_dict.values()]), msg assert all([k in edge_probs[attr] for k in inner_dict]), msg for attr, inner_dict in edge_probs.items(): assert DIFF in inner_dict, msg assert all([0 <= v <= 1 for v in inner_dict.values()]), msg def _set_noval_edge_prob(self, attr): """Set probability of edge creation when at least one node has missing data. This probability is defined to be mean( mean(prob for matched values), prob for unmatched values). """ attr_edge_p = self.edge_probs[attr] mean_match_prob = np.mean([ attr_edge_p[k] for k in attr_edge_p if k not in {NO_VAL, DIFF} ]) attr_edge_p[NO_VAL] = np.mean([mean_match_prob, attr_edge_p[DIFF]]) def _fill_noval_attribute_prob(self, attr): """Set probability of getting a NO_VAL for the given attribute.""" attr_dist = self.attributes[attr] observed_scale = sum(attr_dist.values()) if observed_scale < ATTR_SCALE: attr_dist[NO_VAL] = int(ATTR_SCALE - observed_scale) return True else: return False