# lowering { #tvbo.adapters.smallscale.lowering }

`adapters.smallscale.lowering`

Backend-neutral network lowering for small-scale simulators.

The functions here turn a TVB-O ``Network`` (nodes with ``size``, edges with a ``connectivity`` rule, ``Dynamics``/``Coupling``/``Event`` biology) into the two structures every point-neuron backend needs:

* **populations** — nodes grouped by their ``Dynamics``, each a block of
  ``Node.size`` cells, with a stable base index per node so edges can address individual cells; and
* **connections** — the explicit cell-to-cell :class:`ConnectionRecord` set that a
  ``connectivity`` rule (``all_to_all``/``one_to_one``) lowers to, with self-connections filtered and per-connection ``weight``/``delay`` extracted.

Everything here is independent of *how* a backend emits a synapse — that (LEMS XML, Brian2 ``Synapses``, …) stays in the backend adapter. The backend injects its own
*role vocabulary* (which ``Dynamics`` are cells vs current sources vs event
sources) so the same lowering serves NeuroML, Brian2 and the rest unchanged.

## Classes

| Name | Description |
| --- | --- |
| [ConnectionRecord](#tvbo.adapters.smallscale.lowering.ConnectionRecord) | One lowered cell-to-cell connection — the contract every backend consumes. |

### ConnectionRecord { #tvbo.adapters.smallscale.lowering.ConnectionRecord }

```python
adapters.smallscale.lowering.ConnectionRecord()
```

One lowered cell-to-cell connection — the contract every backend consumes.

Produced by connectivity-rule expansion; a plain ``dict`` at runtime so templates and adapters can index it directly. The neutral core is ``from_pop``/``from_idx`` → ``to_pop``/``to_idx`` through ``synapse`` with an optional per-connection ``weight``/``delay``. ``from_rule`` records whether the connection came from a lowered ``connectivity`` rule (vs a single explicit edge). Backends may attach their own keys (e.g. ``conn_class`` for LEMS projection classification) without changing this core.

## Functions

| Name | Description |
| --- | --- |
| [assign_cell_population](#tvbo.adapters.smallscale.lowering.assign_cell_population) | Assign a cell population id and per-node base indices, filling the maps. |
| [classify_node_role](#tvbo.adapters.smallscale.lowering.classify_node_role) | Classify a node group as a cell, current-input, or event-source. |
| [connectivity_pairs](#tvbo.adapters.smallscale.lowering.connectivity_pairs) | Expand a population-level connectivity rule into ``(src_idx, tgt_idx)`` pairs. |
| [expand_edge_connections](#tvbo.adapters.smallscale.lowering.expand_edge_connections) | Yield ``(from_idx, to_idx, from_rule)`` for one synapse edge. |
| [expand_input_targets](#tvbo.adapters.smallscale.lowering.expand_input_targets) | Local target cell indices an input edge fans out to. |
| [group_nodes_by_dynamics](#tvbo.adapters.smallscale.lowering.group_nodes_by_dynamics) | Group nodes by their ``Dynamics`` name, preserving first-encounter order. |
| [merge_params](#tvbo.adapters.smallscale.lowering.merge_params) | Merge parameter dicts with later dicts overriding earlier ones. |
| [node_dynamics_name](#tvbo.adapters.smallscale.lowering.node_dynamics_name) | The ``Dynamics`` name a node runs. |
| [safe_id](#tvbo.adapters.smallscale.lowering.safe_id) | Make a string safe for XML id attribute. |
| [unique_component_id](#tvbo.adapters.smallscale.lowering.unique_component_id) | A component id derived from *name* that no other component already holds. |

### assign_cell_population { #tvbo.adapters.smallscale.lowering.assign_cell_population }

```python
adapters.smallscale.lowering.assign_cell_population(
    dyn_name,
    group_nodes,
    node_pop_map,
    node_size_map,
)
```

Assign a cell population id and per-node base indices, filling the maps.

Each node contributes ``Node.size`` cells laid out contiguously; the running base index lets an edge address an individual cell within the population.
Mutates *node_pop_map* (``node_id -> (pop_id, base)``) and *node_size_map* (``node_id -> size``) in place, and returns ``(pop_id, node_ids, size)``.

### classify_node_role { #tvbo.adapters.smallscale.lowering.classify_node_role }

```python
adapters.smallscale.lowering.classify_node_role(dyn_name, dyn_lib_obj, vocab)
```

Classify a node group as a cell, current-input, or event-source.

The biological type is read from ``Dynamics.iri`` (``neuroml:<type>``); a Dynamics without such an iri is a plain cell named by itself. *vocab* is the backend's role vocabulary — a mapping with ``current_input`` and ``event_source`` keys to sets of type names — so the same lowering serves any backend by swapping the sets.

Returns ``(role, nml_type)`` with role one of ``"cell"``, ``"current_input"``, ``"event_source"``.

### connectivity_pairs { #tvbo.adapters.smallscale.lowering.connectivity_pairs }

```python
adapters.smallscale.lowering.connectivity_pairs(rule, src_size, tgt_size)
```

Expand a population-level connectivity rule into ``(src_idx, tgt_idx)`` pairs.

Given the ``ConnectivityRule`` (or its string value) and the source/target population sizes, yields the local cell-index pairs a projection (or per-cell input list) enumerates.  This is the "allToAll lowering": the user declares one population-to-population Edge and the adapter generates the i x j connection set, so no O(N**2) explicit edges ever appear in the input.

Self-connection filtering (the diagonal of a self-projection) is applied by the caller on the resolved global cell indices, so this helper simply yields the raw pattern.

#### Parameters {.doc-section .doc-section-parameters}

| Name     | Type   | Description                                              | Default    |
|----------|--------|----------------------------------------------------------|------------|
| rule     |        | Connectivity pattern (``all_to_all`` or ``one_to_one``). | _required_ |
| src_size |        | Number of cells in the source population.                | _required_ |
| tgt_size |        | Number of cells in the target population.                | _required_ |

#### Yields {.doc-section .doc-section-yields}

| Name   | Type   | Description                                |
|--------|--------|--------------------------------------------|
|        |        | ``(src_idx, tgt_idx)`` local cell indices. |

### expand_edge_connections { #tvbo.adapters.smallscale.lowering.expand_edge_connections }

```python
adapters.smallscale.lowering.expand_edge_connections(
    edge,
    *,
    src_pop,
    src_base,
    tgt_pop,
    tgt_base,
    src_size,
    tgt_size,
)
```

Yield ``(from_idx, to_idx, from_rule)`` for one synapse edge.

An Edge with a ``connectivity`` rule is a population-to-population projection:
expand it into the individual cell-to-cell connections here, skipping the diagonal of a self-projection when ``allow_self_connections`` is False.
Without a rule the Edge is a single explicit cell-to-cell connection.
``from_rule`` marks whether the connection came from a lowered rule.

### expand_input_targets { #tvbo.adapters.smallscale.lowering.expand_input_targets }

```python
adapters.smallscale.lowering.expand_input_targets(tgt_base, tgt_size, rule)
```

Local target cell indices an input edge fans out to.

A ``connectivity`` rule attaches an independent copy of the input component to every target cell (rule expansion over a size-1 "source"); without a rule the input hits the node's base cell only.

### group_nodes_by_dynamics { #tvbo.adapters.smallscale.lowering.group_nodes_by_dynamics }

```python
adapters.smallscale.lowering.group_nodes_by_dynamics(nodes, default_dyn_name)
```

Group nodes by their ``Dynamics`` name, preserving first-encounter order.

### merge_params { #tvbo.adapters.smallscale.lowering.merge_params }

```python
adapters.smallscale.lowering.merge_params(*param_dicts)
```

Merge parameter dicts with later dicts overriding earlier ones.

The canonical order is dynamics-library → node/edge → per-connection, i.e.
the same precedence as the ``{**dyn, **node, **edge}`` spreads the backends build by hand. Keys are taken verbatim; values are not copied.

### node_dynamics_name { #tvbo.adapters.smallscale.lowering.node_dynamics_name }

```python
adapters.smallscale.lowering.node_dynamics_name(node, default_dyn_name)
```

The ``Dynamics`` name a node runs.

``Node.dynamics`` is a name-reference slot, so it may arrive as a bare name or as a resolved ``Dynamics``; a node that declares none falls back to
*default_dyn_name* — the experiment's top-level dynamics. One rule, shared by
every backend, so they cannot disagree about which model a node runs.

### safe_id { #tvbo.adapters.smallscale.lowering.safe_id }

```python
adapters.smallscale.lowering.safe_id(s)
```

Make a string safe for XML id attribute.

### unique_component_id { #tvbo.adapters.smallscale.lowering.unique_component_id }

```python
adapters.smallscale.lowering.unique_component_id(name, taken, kind='component')
```

A component id derived from *name* that no other component already holds.

Components are named after their Dynamics, so two differently parameterised uses of one Dynamics would collide and the second would be dropped.

#### Parameters {.doc-section .doc-section-parameters}

| Name   | Type   | Description                                           | Default       |
|--------|--------|-------------------------------------------------------|---------------|
| name   |        | the Dynamics name to derive the id from.              | _required_    |
| taken  |        | ids already assigned; the returned id is added to it. | _required_    |
| kind   |        | what is being named, for the disambiguation warning.  | `'component'` |

#### Returns {.doc-section .doc-section-returns}

| Name   | Type   | Description                                                        |
|--------|--------|--------------------------------------------------------------------|
|        |        | ``safe_id(name)``, or that with a numeric suffix when it is taken. |