Skip to content

Commit

Permalink
Merge pull request #181 from Tensaiz/master
Browse files Browse the repository at this point in the history
🆕 Custom model supporting continuous node states
  • Loading branch information
GiulioRossetti committed Oct 5, 2020
2 parents 74b220d + 73b4ab7 commit f351ca3
Show file tree
Hide file tree
Showing 27 changed files with 2,349 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -8,6 +8,9 @@ __pycache__/
# C extensions
*.so

# IDE
.vscode/

# Distribution / packaging
.Python
env/
Expand Down
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .travis.yml
@@ -1,7 +1,8 @@
language: python
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"

before_install:
- pip install pytest pytest-cov
Expand Down
1 change: 0 additions & 1 deletion README.md
Expand Up @@ -7,7 +7,6 @@
[![Updates](https://pyup.io/repos/github/GiulioRossetti/ndlib/shield.svg)](https://pyup.io/repos/github/GiulioRossetti/ndlib/)
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/GiulioRossetti/ndlib.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/GiulioRossetti/ndlib/context:python)
[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/ndlib_nx/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FGiulioRossetti%2Fndlib.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FGiulioRossetti%2Fndlib?ref=badge_shield)
[![DOI](https://zenodo.org/badge/59556819.svg)](https://zenodo.org/badge/latestdoi/59556819)
[![PyPI download month](https://img.shields.io/pypi/dm/ndlib.svg?color=blue&style=plastic)](https://pypi.python.org/pypi/ndlib/)

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Expand Up @@ -25,7 +25,7 @@ def __getattr__(cls, name):


MOCK_MODULES = ['ipython', 'pygtk', 'gtk', 'gobject', 'argparse', 'matplotlib', 'matplotlib.pyplot', 'numpy', 'pandas', 'dynetx', 'networkx',
'scipy']
'scipy', 'salib', 'pillow', 'pyintergraph', 'python-igraph']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)

html_theme = "sphinx_rtd_theme"
Expand Down
4 changes: 2 additions & 2 deletions docs/custom/compartments/NodeNumericalAttribute.rst
Expand Up @@ -4,9 +4,9 @@ Node Numerical Attribute

Node Numerical Attribute compartments are used to evaluate events attached to numeric edge attributes.

Consider the transition rule **Susceptible->Infected** that requires a that the susceptible node express a specific value
Consider the transition rule **Susceptible->Infected** that requires that the susceptible node expresses a specific value
of an internal numeric attribute, *attr*, to be satisfied (e.g. "Age" == 18).
Such rule can be described by a simple compartment that models Node Numerical Attribute selection. Let's call il *NNA*.
Such a rule can be described by a simple compartment that models Node Numerical Attribute selection. Let's call it *NNA*.

The rule will take as input the *initial* node status (Susceptible), the *final* one (Infected) and the *NNA* compartment.
*NNA* will thus require a probability (*beta*) of activation.
Expand Down
115 changes: 115 additions & 0 deletions docs/custom/compartments/NodeNumericalVariable.rst
@@ -0,0 +1,115 @@
************************
Node Numerical Variable
************************

Node Numerical Variable compartments are used to evaluate events attached to numeric edge attributes or statuses.

Consider the transition rule **Addicted->Not addicted** that requires that the susceptible node satisfies a specific condition
of an internal numeric attribute, *attr*, to be satisfied (e.g. "Self control" *attr* < "Craving" *status*).
Such a rule can be described by a simple compartment that models Node Numerical Attribute and Status selection. Let's call it *NNV*.

The rule will take as input the *initial* node status (Susceptible), the *final* one (Infected) and the *NNV* compartment.
*NNV* will thus require a probability (*beta*) of activation.

During each rule evaluation, given a node *n* and one of its neighbors *m*

- if the actual status of *n* equals the rule *initial*
- if *var(n)* **op** *var(n)* (where var(n) = attr(n) or status(n))
- a random value *b* in [0,1] will be generated
- if *b <= beta*, then *NNV* is considered *satisfied* and the status of *n* changes from *initial* to *final*.

**op** represent a logic operator and can assume one of the following values:
- equality: "=="
- less than: "<"
- greater than: ">"
- equal or less than: "<="
- equal or greater than: ">="
- not equal to: "!="
- within: "IN"

Moreover, *NNV* allows to specify a *triggering* status in order to restrain the compartment evaluation to those nodes that:

1. match the rule *initial* state, and
2. have at least one neighbors in the *triggering* status.

The type of the values that are compared have to be specified in advance, which is done using an enumerated type.
This is done to specify whether the first value to be compared is either a status or an attribute,
the same thing is done for the second value to be compared.
If the value type is not specified, the value to compare the variable to should be a number.

----------
Parameters
----------

================= ================= ======= ========= ===================================
Name Value Type Default Mandatory Description
================= ================= ======= ========= ===================================
variable string None True The name of the variable to compare
variable_type NumericalType None True Numerical type enumerated value
value numeric(*)|string None True Name of the testing value or number
value_type NumericalType None False Numerical type enumerated value
op string None True Logic operator
probability float in [0, 1] 1 False Event probability
triggering_status string None False Trigger
================= ================= ======= ========= ===================================

(*) When *op* equals "IN" the attribute *value* is expected to be a tuple of two elements identifying a closed interval.

-------
Example
-------

In the code below the formulation of a model is shown using NodeNumericalVariable compartments.

The first compartment, *condition*, is used to implement the transition rule *Susceptible->Infected*.
It restrains the rule evaluation to all those nodes having more "Friends" than 18.

The second compartment, *condition2*, is used to implement the transition rule *Infected->Recovered*.
It restrains the rule evaluation to all those nodes where "Age" is less than the amount of "Friends" attributes.

Note that instead of attributes, the states could have been used as well by using *NumericalType.STATUS* instead.
This would only be applicable for numerical states, which can be modelled when using the ``ContinuousModel`` instead of the ``CompositeModel``.


.. code-block:: python
import networkx as nx
import random
import numpy as np
from ndlib.models.CompositeModel import CompositeModel
from ndlib.models.compartments.NodeStochastic import NodeStochastic
from ndlib.models.compartments.enums.NumericalType import NumericalType
from ndlib.models.compartments.NodeNumericalVariable import NodeNumericalVariable
import ndlib.models.ModelConfig as mc
# Network generation
g = nx.erdos_renyi_graph(1000, 0.1)
# Setting edge attribute
attr = {n: {"Age": random.choice(range(0, 100)), "Friends": random.choice(range(0, 100))} for n in g.nodes()}
nx.set_node_attributes(g, attr)
# Composite Model instantiation
model = CompositeModel(g)
# Model statuses
model.add_status("Susceptible")
model.add_status("Infected")
model.add_status("Removed")
# Compartment definition
condition = NodeNumericalVariable('Friends', var_type=NumericalType.ATTRIBUTE, value=18, op='>')
condition2 = NodeNumericalVariable('Age', var_type=NumericalType.ATTRIBUTE, value='Friends', value_type=NumericalType.ATTRIBUTE, op='<')
# Rule definition
model.add_rule("Susceptible", "Infected", condition)
model.add_rule("Infected", "Removed", condition2)
# Model initial status configuration
config = mc.Configuration()
config.add_model_parameter('fraction_infected', 0.5)
# Simulation execution
model.set_initial_status(config)
iterations = model.iteration_bunch(100)

0 comments on commit f351ca3

Please sign in to comment.