---
name: graph_force_layout
kind: function
lang: cpp
domain: viz
version: "1.2.0"
purity: pure
signature: "float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config)"
description: "Layout force-directed con aproximacion Barnes-Hut para grafos grandes, ejecuta un paso de simulacion por llamada"
tags: [graph, layout, force-directed, barnes-hut, physics, gpu]
uses_functions: []
uses_types: ["GraphData_cpp_viz"]
returns: []
returns_optional: false
error_type: ""
imports: []
tested: true
tests: ["should_pause threshold", "should_pause requires consecutive frames", "should_pause emulating low->high->low sequence"]
test_file_path: "cpp/tests/test_graph_should_pause.cpp"
file_path: "cpp/functions/viz/graph_force_layout.cpp"
framework: imgui
params:
  - name: graph
    desc: "Referencia al grafo (GraphData) cuyos nodos se actualizan in-place. Modifica x, y, vx, vy de cada nodo no pinned."
  - name: config
    desc: "Parametros de la simulacion: repulsion (fuerza coulombiana), attraction (spring constant), damping (decay de velocidad), theta (precision Barnes-Hut 0=exacto/1=rapido), gravity (atraccion al centro), max_velocity, iterations."
output: "Energia cinetica total (suma de |v|^2). Cuando cae por debajo de un umbral elegido por el caller, el layout ha convergido y se puede dejar de llamar."
notes: "scaffolding/demo en primitives_gallery"
---

# graph_force_layout

Implementa el algoritmo de layout force-directed clasico (Fruchterman-Reingold / Eades) con aproximacion Barnes-Hut O(n log n) para escalar a grafos de miles de nodos.

## Algoritmo

Cada llamada a `graph_force_layout_step` ejecuta `config.iterations` pasos. Un paso:

1. **Construccion del quadtree** (Barnes-Hut): se calcula el bounding box de las posiciones actuales, se construye un quadtree flat en `quad_pool` (sin allocaciones por nodo). Cada celda acumula centro de masa y masa total.
2. **Repulsion**: para cada nodo se recorre el quadtree. Si el cociente `cell_size / distance < theta`, la celda se trata como una sola masa puntual (multipolo de orden 0). Si no, se desciende a los hijos. Con `theta=0` es O(n²) exacto; con `theta=0.5` es O(n log n).
3. **Atraccion**: para cada arista `(s, t)`, fuerza de Hooke `F = k * dist * weight` en la direccion del arco.
4. **Gravedad**: fuerza proporcional a la distancia al origen, evita que el grafo derive fuera de pantalla.
5. **Integracion**: `v = v * damping + F`, `pos += v`, con clamping de velocidad.
6. Nodos con `pinned = true` no se mueven en ningun paso.

## Funciones auxiliares

```cpp
// Randomizar posiciones para empezar la simulacion
graph_force_layout_reset(graph, 200.0f);

// Layout circular instantaneo (sin iteracion)
graph_layout_circular(graph, 150.0f);

// Layout en grid instantaneo
graph_layout_grid(graph, 25.0f);

// Auto-pause: parar la simulacion cuando la energia se ha estabilizado.
// Pure: el caller mantiene el contador, la funcion solo decide.
//   bool graph_force_layout_should_pause(int low_frames, int min_consecutive);
```

## Ejemplo de uso tipico (loop ImGui)

```cpp
static ForceLayoutConfig cfg;
static bool running = true;
static int  low_frames = 0;
const int   k_min_consecutive = 30;
const float k_threshold_per_node = 0.001f;

if (running) {
    float energy = graph_force_layout_step(my_graph, cfg);
    float per_node = my_graph.node_count > 0
        ? energy / my_graph.node_count : 0.0f;
    if (per_node < k_threshold_per_node) ++low_frames;
    else                                 low_frames = 0;
    if (graph_force_layout_should_pause(low_frames, k_min_consecutive)) {
        running    = false;
        low_frames = 0;
    }
}
```

## Notas de implementacion

- El quadtree usa un pool dinamico (`std::vector<QuadNode>`) que se redimensiona una vez
  por step a `5*N + 1024` celdas. La pila de traversal en `quad_force` es local en pila
  (256 entradas) — thread-safe bajo OpenMP.
- `graph_force_layout_reset` usa `rand()`. Para reproducibilidad llama `srand(seed)` antes.
- Los buffers de fuerza (`fx_buf`, `fy_buf`) se realocan una sola vez cuando el conteo de
  nodos supera la capacidad previa; en el uso normal (tamano fijo) no hay allocaciones
  por frame.

## Notas de version

- **v1.2** (2026-04-29, issue 0049e): el campo `pinned` desaparece del modelo de `GraphNode` y se sustituye por `flags & NF_PINNED`. La logica del integrador, atraccion, gravedad y reset usa el bit equivalente. Sin cambios en la API publica ni en el comportamiento.
- **v1.1** (2026-04-29, issue 0049c): añade el helper puro
  `graph_force_layout_should_pause(low_frames, min_consecutive)` para que las apps
  detecten convergencia sin replicar el contador por todas partes. Sin cambios en
  `graph_force_layout_step` ni en la API existente. Test:
  `cpp/tests/test_graph_should_pause.cpp`.
