Metadata-Version: 2.4
Name: archgraph-layout
Version: 2.0.2
Summary: Hierarchical architecture diagrams from XML with floorplanning and orthogonal routing
Author: Ellie Hartung
License-Expression: MIT
Keywords: architecture,diagrams,xml,layout,floorplanning,svg
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23.5
Requires-Dist: scipy>=1.9.3
Provides-Extra: png
Requires-Dist: playwright>=1.40; extra == "png"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=6.1; extra == "dev"
Dynamic: license-file

# archgraph-layout

**Hierarchical architecture diagrams from XML** — a Graphviz-style tool built for complex
hardware and system diagrams. Instead of a layered (Sugiyama) layout that grows very tall
or very wide, archgraph **floorplans** each subsystem, the way VLSI tools place blocks
on a die. The result fits a target aspect ratio, groups blocks by subsystem, and keeps
signal flow readable. Wires are routed orthogonally, with real port handling, nets and
buses.

```
python -m archgraph render examples/soc.xml -o soc.svg -o soc.html
```

* Pure Python (numpy + scipy). Output: **SVG**, an **interactive HTML** viewer
  (pan/zoom/search/highlight), **JSON** layout data, and optionally **PNG** (via Playwright).
* Deterministic: the same input and seed always give the same drawing.

## Installation

Requires Python 3.10 or newer. Install from PyPI after publication:

```sh
python -m pip install archgraph-layout
```

The Python import is `archgraph` and the command is `archgraph`:

```sh
archgraph --version
archgraph render system.xml -o system.svg -o system.html
```

For example, save this as `system.xml`:

```xml
<archgraph title="My system">
  <node id="cpu" label="CPU"/>
  <node id="memory" label="Memory"/>
  <edge from="cpu" to="memory"/>
</archgraph>
```

For optional PNG export, install the extra and Chromium:

```sh
python -m pip install "archgraph-layout[png]"
python -m playwright install chromium
```

The source archive includes the XML examples and reference. Generated gallery
previews and development files are excluded from the published packages.

---

## Contents

1. [How the engine works](#how-the-engine-works)
2. [Command line](#command-line)
3. [Python API](#python-api)
4. [XML reference](docs/xml-reference.md)
5. [Tuning](#tuning)
6. [Examples](#examples)
7. [Testing](#testing)

---

## How the engine works

```
XML ──► parse/expand ──► size blocks ──► auto-cluster huge levels
    ──► hierarchical floorplanning (bottom-up, several passes)
    ──► detailed QP placement + edge alignment (bottom-up, then top-down)
    ──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
    ──► nudging ──► SVG / HTML / JSON / PNG
```

**Floorplanning (placement).** Each subsystem level is a floorplanning problem over
its direct children. A child can be a leaf block or a subsystem that was already
laid out. Topologies are encoded as **sequence pairs** and explored by **simulated
annealing**. The cost combines:

| term | what it measures |
|---|---|
| area, aspect | packing density; deviation from the target width/height (steeper at the top level) |
| wire | weighted Manhattan length between *pins*: the exact deep port positions inside children |
| flow | edges running against the subsystem's direction (backward edges cost more than sideways ones) |
| bend | connections whose ends cannot be aligned without a bend |
| crossing / through | straight-line crossings, and wires passing through other blocks (routing proxies) |
| terminal | connections leaving the subsystem pull towards the side facing their partner |
| order | repeated instances (core0, core1, …) read in declaration order |
| hints | your guidance (see [Layout hints](docs/xml-reference.md#layout-hints)) |

Child subsystems contribute several **shape variants** (natural, square, wide, tall).
The annealer picks one per child, and can also mirror a child perpendicular to its
flow. Initial states come from a Sugiyama layout (LP layering, barycentre ordering,
VPSC coordinates, aspect-driven band wrapping), a spectral (Fiedler) grid order, warm
starts from the previous pass, and random states. Tiny levels (≤ 4 blocks) are solved
exhaustively. Identical template instances are solved once and drawn identically.

**Detailed placement.** The annealer gives each level a topology; a convex QP then
computes exact coordinates. The QP is solved by gradient projection over a **VPSC**
separation-constraint solver. It straightens wires, compacts, and enforces non-overlap,
containment and your hard hints. A greedy **HOLA-style** pass then adds equality
constraints so that nearly straight connections become exactly straight. A top-down
pass re-solves every level with the real positions of external partners.

**Routing.** All wires are routed orthogonally on a sparse non-uniform grid, using an
A* search that knows each wire's direction. Costs cover length, bends, crossings,
channel sharing, corridor overflow, passing other ports, wall proximity (wires prefer
channel centres), turning close to a wall, and crossing subsystem borders. Crossing
the border of a subsystem that contains neither end is almost forbidden. Specifics:

* Ports have a **straight escape** (about 20px) before the first turn.
* A first pass routes with **floating ports**, so each automatic port attaches on the side its
  wire arrives from.
* Edges sharing a declared port form one **net**, drawn as a Steiner tree with junction dots.
  **Buses** become trunk trees.
* **PathFinder** negotiation rips up and reroutes congested nets.
* **Congestion-driven spacing.** Gaps that carry more wires than fit comfortably are widened,
  overloaded blocks grow, and the layout is re-compacted (same topology) and rerouted.
* **Nudging** (VPSC again) spreads overlapping wires into parallel tracks. It orders them to
  avoid crossings, keeps them clear of blocks and titles, and centres bundles in their corridor.
  Every port's escape stub is a keep-out zone, so no wire is squeezed in under an arrowhead.
* **Crossings** (`crossings="hop"`, the default; or `"gap"` / `"none"`). Hops are drawn in a
  layer above every wire, with a halo in the local background colour, so nothing is ever
  painted over a hop. Crossings closer together than one hop share a single flat bridge.
  Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
  corners and neighbouring parallel wires). A crossing right next to a corner of the
  horizontal wire hops sideways on the vertical wire instead. Bit-width markers
  keep clear of crossings.

---

## Command line

```
archgraph render  FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats] [--progress]
                  [--quality draft|normal|high|max] [--aspect 16:9] [--theme dark]
                  [--direction TB] [--seed N] [--set key=value ...]
archgraph validate FILE.xml        # parse, report errors (with line numbers) and warnings
archgraph stats    FILE.xml [--progress] # lay out and print quality metrics as JSON
archgraph expand   FILE.xml        # print the XML after templates/loops/includes expand
archgraph FILE.xml -o out.svg      # shorthand for render
```

`--stats` reports blocks, aspect, crossings, bends, wire length, overlaps, containment
violations, wires through blocks, wire length hugging walls, minimum port escape,
flow-consistency counts and timings.

`--progress` shows elapsed time and compact stage bars for port assignment, routing,
and congestion refinement, plus placement completion, output writing, and `Done`.
Bars update in place in a terminal; redirected progress logs retain stage milestones.
Placement shows completed passes and the current subsystem or optimization step.
A spinner and elapsed time refresh during long computations; redirected logs receive
an activity update every five seconds while a stage is running.
Updates go to stderr, so `stats` JSON on stdout can still be redirected to a file.
Counts describe the current stage; congestion counts show refinements performed out
of the configured maximum and can finish early. For example:

```sh
archgraph render examples/soc.xml -o soc.html --progress
archgraph stats examples/basic.xml --progress > metrics.json
```

## Python API

```python
import archgraph

g = archgraph.build("system.xml", aspect="4:3", quality="high")  # parse + layout + route
archgraph.save(g, "system.svg")        # format by extension: .svg .html .json .png
svg = archgraph.to_svg(g)

g = archgraph.loads(xml_text)            # or step by step
archgraph.layout(g); archgraph.route(g)
for n in g.nodes(): print(n.id, n.x, n.y, n.w, n.h)
from archgraph.quality import metrics; print(metrics(g))
```

---

## XML reference

See the [standalone XML reference](docs/xml-reference.md) for a starter diagram,
settings and defaults, nodes and ports, edges and buses, layout hints, styles,
templates, loops, includes, and validation guidance.

---

## Tuning

* **Shape**: `aspect` (global) and per-subsystem `aspect`. The top level weights aspect most.
* **Compactness vs readability**: `<weights area="0.6" wire="2.4" flow="3" crossing="1.4" through="1.4"
  bend="1.6" terminal="1.4" order="1.6" aspect="2.2" hint_hard="10" hint_soft="2.5"/>`.
* **Effort**: `quality="high"` adds an annealing pass, longer schedules and more spacing iterations.
* **Room for wires**: `node-spacing`, `subsystem-padding`, `edge-spacing`, `node-margin`.
* **Big flat graphs**: `max-level-items` and `auto-group="visible"` (shows the clusters found).
* **Hard vs soft hints**: `left-of`/`above`/`row`/`column`/`order` are hard: the floorplanner
  only considers arrangements that keep them, and a subsystem shape that would break them
  is never offered to the parent. `near`/`place`/`anchor` are soft preferences.
* **Port sides** are chosen from geometry (facing the partner, flow, blocked sides) and then
  refined by a trial routing pass that may move a port to another side when the wire is
  clearly better there; sides facing away from the partner are strongly discouraged.
  Pin a side explicitly with `side=` on a `<port>` or `from="a@north"`.

## Examples

| file | shows |
|---|---|
| `examples/basic.xml` | small starter |
| `examples/soc.xml` | mobile SoC: big.LITTLE templates, NoC hubs, buses, notes, hints, legend, title block |
| `examples/accelerator_mesh.xml` | 64-tile mesh accelerator, ~200 blocks (nested repeats, grid, uniform instances) |
| `examples/datacenter.xml` | spine/leaf fabric, racks, storage, dark theme |
| `examples/automotive.xml` | zonal E/E architecture, CAN/Ethernet buses, TB flow |
| `examples/fpga_pipeline.xml` | DSP datapath with mux/register/adder shapes, layered algorithm |
| `examples/flat_network.xml` | 150+ blocks without subsystems: automatic clustering |

## Testing

```
python -m pip install -e ".[dev]"
python -m pytest tests -q
```

The suite covers the VPSC solver (checked against SLSQP and KKT conditions, including
infeasible systems), the XML language, floorplanning and placement invariants (no
overlaps, containment, hint satisfaction, aspect), routing invariants (orthogonality,
connectivity, no wires through blocks, port escape, clearance), rendering, the CLI, and
end-to-end quality metrics for every example.

## License and author

MIT License. Copyright (c) 2026 Ellie Hartung. See [LICENSE](LICENSE).

For release instructions, see [Publishing](docs/publishing.md) in the checkout.
