.. _graph_gen:

================
Graph generation
================

This page gives example on how to generate increasingly complex network
structures.
The example files can be found at: :source:`docs/examples/simple_graphs.py`,
:source:`docs/examples/multi_groups_network.py`,
:source:`docs/examples/basic_nest_network.py`, and
:source:`docs/examples/nest_receptor_ports.py`.


**Content:**

.. contents::
   :local:


Principle
=========

In order to keep the code as generic and easy to maintain as possible, the
generation of graphs or networks is divided in several steps:

* **Structured connectivity:** a simple graph is generated as an assembly of
  nodes and edges, without any biological properties. This allows us to
  implement known graph-theoretical algorithms in a straightforward fashion.
* **Populations:** detailed properties can be implemented, such as inhibitory
  synapses and separation of the neurons into inhibitory and excitatory
  populations -- these can be done while respecting user-defined constraints.
* **Synaptic properties:** eventually, synaptic properties such as
  weight/strength and delays can be added to the network.

Modularity
==========

The library as been designed so that these various operations can be realized
in any order!

Juste to get work on a topological graph/network:
    1) Create graph class
    2) Connect
    3) Set connection weights (optional)
    4) Spatialize (optional)
    5) Set types (optional: to use with NEST)

To work on a really spatially embedded graph/network:
    1) Create spatial graph/network
    2) Connect (can depend on positions)
    3) Set connection weights (optional, can depend on positions)
    4) Set types (optional)

Or to model a complex neural network in NEST:
    1) Create spatial network (with space and neuron types)
    2) Connect (can depend on types and positions)
    3) Set connection weights and types (optional, can depend on types
       and positions)


Setting weights
===============

The weights can be either user-defined or generated by one of the available
distributions (:ref:`distributions`).
User-defined weights are generated via:

* a list of edges
* a list of weights

Pre-defined distributions require the following variables:

* a distribution name ("constant", "gaussian"...)
* a dictionary containing the distribution properties
* an optional attribute for distributions that are correlated to another (e.g.
  the distances between neurons)
* a optional value defining the variance of the Gaussian noise that should be
  applied on the weights

There are several ways of settings the weights of a graph which depend on the
time at which you assign them.

At graph creation
    You can define the weights by entering a ``weights`` argument to the
    constructor; this should be a dictionary containing at least the name of
    the weight distribution: ``{"distrib": "distribution_name"}``.
    If entered, this will be stored as a graph property and used to assign the
    weights whenever new edges are created unless you specifically assign rules
    for those new edges' weights.

At any given time
    You can use the :func:`~nngt.Graph.set_weights` function to set the weights
    of a ``graph`` explicitely by using:

.. code-block:: python

    graph.set_weights(elist=edges_to_weigh, distrib="distrib_of_choice", ...)

For more details on weights, other attributes, and available distributions, see
:ref:`graph-prop`.


Examples
========

.. code-block:: python

    import nngt
    import nngt.generation as ng


Simple generation
-----------------

.. literalinclude:: ../examples/simple_graphs.py
   :lines: 36-58


Networks composed of heterogeneous groups
-----------------------------------------

.. literalinclude:: ../examples/multi_groups_network.py
   :lines: 32-63

.. _nest_net:

Use with NEST
-------------

Generating a network with excitatory and inhibitory neurons:

.. literalinclude:: ../examples/basic_nest_network.py
   :lines: 36-68

Send the network to NEST:

.. literalinclude:: ../examples/basic_nest_network.py
   :lines: 76-99

You can check that connections from neurons that are marked as inhibitory are
automatically assigned a negative sign in NEST:

.. literalinclude:: ../examples/basic_nest_network.py
   :lines: 104-116

Returns: ``NNGT weights: {1.0} versus NEST weights {-1.0}``.


Advanced examples
=================

Receptor ports in NEST
----------------------

Some models, such as multisynaptic neurons, or advanced models incorporating
various neurotransmitters use an additional information, called ``"port"`` to
identify the synapse that will be used by the ``nest.Connect`` method.
These models can also be used with NNGT by telling the
:class:`~nngt.NeuralGroup` which type of port the neuron should try to bind to.

NB: the port is specified in the **source** neuron and declares which synapse
of the **target** neuron is concerned.


.. literalinclude:: ../examples/nest_receptor_ports.py
   :lines: 31-


**Go to other tutorials:**

* :ref:`intro`
* :ref:`graph-prop`
* :ref:`parallelism`
* :ref:`neural_groups`
* :ref:`nest_int`
* :ref:`activ_analysis`
