archgraph-layout 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. archgraph_layout-1.0.0/LICENSE +21 -0
  2. archgraph_layout-1.0.0/MANIFEST.in +13 -0
  3. archgraph_layout-1.0.0/PKG-INFO +245 -0
  4. archgraph_layout-1.0.0/README.md +220 -0
  5. archgraph_layout-1.0.0/archgraph/__init__.py +108 -0
  6. archgraph_layout-1.0.0/archgraph/__main__.py +5 -0
  7. archgraph_layout-1.0.0/archgraph/cli.py +110 -0
  8. archgraph_layout-1.0.0/archgraph/geometry.py +256 -0
  9. archgraph_layout-1.0.0/archgraph/model.py +394 -0
  10. archgraph_layout-1.0.0/archgraph/parser.py +1508 -0
  11. archgraph_layout-1.0.0/archgraph/placement/__init__.py +0 -0
  12. archgraph_layout-1.0.0/archgraph/placement/congestion.py +108 -0
  13. archgraph_layout-1.0.0/archgraph/placement/detail.py +456 -0
  14. archgraph_layout-1.0.0/archgraph/placement/engine.py +133 -0
  15. archgraph_layout-1.0.0/archgraph/placement/floorplan.py +876 -0
  16. archgraph_layout-1.0.0/archgraph/placement/graphalgo.py +345 -0
  17. archgraph_layout-1.0.0/archgraph/placement/hierarchy.py +1086 -0
  18. archgraph_layout-1.0.0/archgraph/placement/layered.py +411 -0
  19. archgraph_layout-1.0.0/archgraph/placement/ports.py +354 -0
  20. archgraph_layout-1.0.0/archgraph/placement/sizing.py +234 -0
  21. archgraph_layout-1.0.0/archgraph/placement/vpsc.py +496 -0
  22. archgraph_layout-1.0.0/archgraph/quality.py +144 -0
  23. archgraph_layout-1.0.0/archgraph/render/__init__.py +0 -0
  24. archgraph_layout-1.0.0/archgraph/render/html.py +154 -0
  25. archgraph_layout-1.0.0/archgraph/render/jsonout.py +43 -0
  26. archgraph_layout-1.0.0/archgraph/render/png.py +35 -0
  27. archgraph_layout-1.0.0/archgraph/render/shapes.py +240 -0
  28. archgraph_layout-1.0.0/archgraph/render/svg.py +1077 -0
  29. archgraph_layout-1.0.0/archgraph/routing/__init__.py +162 -0
  30. archgraph_layout-1.0.0/archgraph/routing/grid.py +267 -0
  31. archgraph_layout-1.0.0/archgraph/routing/labels.py +141 -0
  32. archgraph_layout-1.0.0/archgraph/routing/nudge.py +291 -0
  33. archgraph_layout-1.0.0/archgraph/routing/router.py +1112 -0
  34. archgraph_layout-1.0.0/archgraph/styles.py +405 -0
  35. archgraph_layout-1.0.0/archgraph/textmetrics.py +92 -0
  36. archgraph_layout-1.0.0/archgraph_layout.egg-info/PKG-INFO +245 -0
  37. archgraph_layout-1.0.0/archgraph_layout.egg-info/SOURCES.txt +49 -0
  38. archgraph_layout-1.0.0/archgraph_layout.egg-info/dependency_links.txt +1 -0
  39. archgraph_layout-1.0.0/archgraph_layout.egg-info/entry_points.txt +2 -0
  40. archgraph_layout-1.0.0/archgraph_layout.egg-info/requires.txt +10 -0
  41. archgraph_layout-1.0.0/archgraph_layout.egg-info/top_level.txt +1 -0
  42. archgraph_layout-1.0.0/docs/xml-reference.md +549 -0
  43. archgraph_layout-1.0.0/examples/accelerator_mesh.xml +86 -0
  44. archgraph_layout-1.0.0/examples/automotive.xml +63 -0
  45. archgraph_layout-1.0.0/examples/basic.xml +28 -0
  46. archgraph_layout-1.0.0/examples/datacenter.xml +65 -0
  47. archgraph_layout-1.0.0/examples/flat_network.xml +26 -0
  48. archgraph_layout-1.0.0/examples/fpga_pipeline.xml +61 -0
  49. archgraph_layout-1.0.0/examples/soc.xml +172 -0
  50. archgraph_layout-1.0.0/pyproject.toml +40 -0
  51. archgraph_layout-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ellie Hartung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ include README.md LICENSE pyproject.toml MANIFEST.in
2
+ recursive-include archgraph *.py
3
+ include docs/xml-reference.md
4
+ recursive-include examples *.xml
5
+ prune gallery
6
+ prune tests
7
+ prune .git
8
+ prune .pytest_cache
9
+ prune .venv
10
+ prune build
11
+ prune dist
12
+ prune .release-check
13
+ global-exclude __pycache__ *.py[cod] .DS_Store Thumbs.db
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: archgraph-layout
3
+ Version: 1.0.0
4
+ Summary: Hierarchical architecture diagrams from XML with floorplanning and orthogonal routing
5
+ Author: Ellie Hartung
6
+ License-Expression: MIT
7
+ Keywords: architecture,diagrams,xml,layout,floorplanning,svg
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Multimedia :: Graphics
12
+ Classifier: Topic :: Scientific/Engineering :: Visualization
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy>=1.23.5
17
+ Requires-Dist: scipy>=1.9.3
18
+ Provides-Extra: png
19
+ Requires-Dist: playwright>=1.40; extra == "png"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7; extra == "dev"
22
+ Requires-Dist: build>=1.2; extra == "dev"
23
+ Requires-Dist: twine>=6.1; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # archgraph-layout
27
+
28
+ **Hierarchical architecture diagrams from XML** — a Graphviz-style tool built for complex
29
+ hardware and system diagrams. Instead of a layered (Sugiyama) layout that grows very tall
30
+ or very wide, archgraph **floorplans** each subsystem, the way VLSI tools place blocks
31
+ on a die. The result fits a target aspect ratio, groups blocks by subsystem, and keeps
32
+ signal flow readable. Wires are routed orthogonally, with real port handling, nets and
33
+ buses.
34
+
35
+ ```
36
+ python -m archgraph render examples/soc.xml -o soc.svg -o soc.html
37
+ ```
38
+
39
+ * Pure Python (numpy + scipy). Output: **SVG**, an **interactive HTML** viewer
40
+ (pan/zoom/search/highlight), **JSON** layout data, and optionally **PNG** (via Playwright).
41
+ * Deterministic: the same input and seed always give the same drawing.
42
+
43
+ ## Installation
44
+
45
+ Requires Python 3.10 or newer. Install from PyPI after publication:
46
+
47
+ ```sh
48
+ python -m pip install archgraph-layout
49
+ ```
50
+
51
+ The Python import is `archgraph` and the command is `archgraph`:
52
+
53
+ ```sh
54
+ archgraph --version
55
+ archgraph render system.xml -o system.svg -o system.html
56
+ ```
57
+
58
+ For example, save this as `system.xml`:
59
+
60
+ ```xml
61
+ <archgraph title="My system">
62
+ <node id="cpu" label="CPU"/>
63
+ <node id="memory" label="Memory"/>
64
+ <edge from="cpu" to="memory"/>
65
+ </archgraph>
66
+ ```
67
+
68
+ For optional PNG export, install the extra and Chromium:
69
+
70
+ ```sh
71
+ python -m pip install "archgraph-layout[png]"
72
+ python -m playwright install chromium
73
+ ```
74
+
75
+ The source archive includes the XML examples and reference. Generated gallery
76
+ previews and development files are excluded from the published packages.
77
+
78
+ ---
79
+
80
+ ## Contents
81
+
82
+ 1. [How the engine works](#how-the-engine-works)
83
+ 2. [Command line](#command-line)
84
+ 3. [Python API](#python-api)
85
+ 4. [XML reference](docs/xml-reference.md)
86
+ 5. [Tuning](#tuning)
87
+ 6. [Examples](#examples)
88
+ 7. [Testing](#testing)
89
+
90
+ ---
91
+
92
+ ## How the engine works
93
+
94
+ ```
95
+ XML ──► parse/expand ──► size blocks ──► auto-cluster huge levels
96
+ ──► hierarchical floorplanning (bottom-up, several passes)
97
+ ──► detailed QP placement + edge alignment (bottom-up, then top-down)
98
+ ──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
99
+ ──► nudging ──► labels ──► SVG / HTML / JSON / PNG
100
+ ```
101
+
102
+ **Floorplanning (placement).** Each subsystem level is a floorplanning problem over
103
+ its direct children. A child can be a leaf block or a subsystem that was already
104
+ laid out. Topologies are encoded as **sequence pairs** and explored by **simulated
105
+ annealing**. The cost combines:
106
+
107
+ | term | what it measures |
108
+ |---|---|
109
+ | area, aspect | packing density; deviation from the target width/height (steeper at the top level) |
110
+ | wire | weighted Manhattan length between *pins*: the exact deep port positions inside children |
111
+ | flow | edges running against the subsystem's direction (backward edges cost more than sideways ones) |
112
+ | bend | connections whose ends cannot be aligned without a bend |
113
+ | crossing / through | straight-line crossings, and wires passing through other blocks (routing proxies) |
114
+ | terminal | connections leaving the subsystem pull towards the side facing their partner |
115
+ | order | repeated instances (core0, core1, …) read in declaration order |
116
+ | hints | your guidance (see [Layout hints](docs/xml-reference.md#layout-hints)) |
117
+
118
+ Child subsystems contribute several **shape variants** (natural, square, wide, tall).
119
+ The annealer picks one per child, and can also mirror a child perpendicular to its
120
+ flow. Initial states come from a Sugiyama layout (LP layering, barycentre ordering,
121
+ VPSC coordinates, aspect-driven band wrapping), a spectral (Fiedler) grid order, warm
122
+ starts from the previous pass, and random states. Tiny levels (≤ 4 blocks) are solved
123
+ exhaustively. Identical template instances are solved once and drawn identically.
124
+
125
+ **Detailed placement.** The annealer gives each level a topology; a convex QP then
126
+ computes exact coordinates. The QP is solved by gradient projection over a **VPSC**
127
+ separation-constraint solver. It straightens wires, compacts, and enforces non-overlap,
128
+ containment and your hard hints. A greedy **HOLA-style** pass then adds equality
129
+ constraints so that nearly straight connections become exactly straight. A top-down
130
+ pass re-solves every level with the real positions of external partners.
131
+
132
+ **Routing.** All wires are routed orthogonally on a sparse non-uniform grid, using an
133
+ A* search that knows each wire's direction. Costs cover length, bends, crossings,
134
+ channel sharing, corridor overflow, passing other ports, wall proximity (wires prefer
135
+ channel centres), turning close to a wall, and crossing subsystem borders. Crossing
136
+ the border of a subsystem that contains neither end is almost forbidden. Specifics:
137
+
138
+ * Ports have a **straight escape** (about 20px) before the first turn.
139
+ * A first pass routes with **floating ports**, so each automatic port attaches on the side its
140
+ wire arrives from.
141
+ * Edges sharing a declared port form one **net**, drawn as a Steiner tree with junction dots.
142
+ **Buses** become trunk trees.
143
+ * **PathFinder** negotiation rips up and reroutes congested nets.
144
+ * **Congestion-driven spacing.** Gaps that carry more wires than fit comfortably are widened,
145
+ overloaded blocks grow, and the layout is re-compacted (same topology) and rerouted.
146
+ * **Nudging** (VPSC again) spreads overlapping wires into parallel tracks. It orders them to
147
+ avoid crossings, keeps them clear of blocks and titles, and centres bundles in their corridor.
148
+ Every port's escape stub is a keep-out zone, so no wire is squeezed in under an arrowhead.
149
+ * **Crossings** (`crossings="hop"`, the default; or `"gap"` / `"none"`). Hops are drawn in a
150
+ layer above every wire, with a halo in the local background colour, so nothing is ever
151
+ painted over a hop. Crossings closer together than one hop share a single flat bridge.
152
+ Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
153
+ corners and neighbouring parallel wires). A crossing right next to a corner of the
154
+ horizontal wire hops sideways on the vertical wire instead. Bit-width markers and edge
155
+ labels keep clear of crossings.
156
+
157
+ ---
158
+
159
+ ## Command line
160
+
161
+ ```
162
+ archgraph render FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats]
163
+ [--quality draft|normal|high|max] [--aspect 16:9] [--theme dark]
164
+ [--direction TB] [--seed N] [--set key=value ...]
165
+ archgraph validate FILE.xml # parse, report errors (with line numbers) and warnings
166
+ archgraph stats FILE.xml # lay out and print quality metrics as JSON
167
+ archgraph expand FILE.xml # print the XML after templates/loops/includes expand
168
+ archgraph FILE.xml -o out.svg # shorthand for render
169
+ ```
170
+
171
+ `--stats` reports blocks, aspect, crossings, bends, wire length, overlaps, containment
172
+ violations, wires through blocks, wire length hugging walls, minimum port escape,
173
+ flow-consistency counts and timings.
174
+
175
+ ## Python API
176
+
177
+ ```python
178
+ import archgraph
179
+
180
+ g = archgraph.build("system.xml", aspect="4:3", quality="high") # parse + layout + route
181
+ archgraph.save(g, "system.svg") # format by extension: .svg .html .json .png
182
+ svg = archgraph.to_svg(g)
183
+
184
+ g = archgraph.loads(xml_text) # or step by step
185
+ archgraph.layout(g); archgraph.route(g)
186
+ for n in g.nodes(): print(n.id, n.x, n.y, n.w, n.h)
187
+ from archgraph.quality import metrics; print(metrics(g))
188
+ ```
189
+
190
+ ---
191
+
192
+ ## XML reference
193
+
194
+ See the [standalone XML reference](docs/xml-reference.md) for a starter diagram,
195
+ settings and defaults, nodes and ports, edges and buses, layout hints, styles,
196
+ templates, loops, includes, and validation guidance.
197
+
198
+ ---
199
+
200
+ ## Tuning
201
+
202
+ * **Shape**: `aspect` (global) and per-subsystem `aspect`. The top level weights aspect most.
203
+ * **Compactness vs readability**: `<weights area="0.6" wire="2.4" flow="3" crossing="1.4" through="1.4"
204
+ bend="1.6" terminal="1.4" order="1.6" aspect="2.2" hint_hard="10" hint_soft="2.5"/>`.
205
+ * **Effort**: `quality="high"` adds an annealing pass, longer schedules and more spacing iterations.
206
+ * **Room for wires**: `node-spacing`, `subsystem-padding`, `edge-spacing`, `node-margin`.
207
+ * **Big flat graphs**: `max-level-items` and `auto-group="visible"` (shows the clusters found).
208
+ * **Hard vs soft hints**: `left-of`/`above`/`row`/`column`/`order` are hard: the floorplanner
209
+ only considers arrangements that keep them, and a subsystem shape that would break them
210
+ is never offered to the parent. `near`/`place`/`anchor` are soft preferences.
211
+ * **Port sides** are chosen from geometry (facing the partner, flow, blocked sides) and then
212
+ refined by a trial routing pass that may move a port to another side when the wire is
213
+ clearly better there; sides facing away from the partner are strongly discouraged.
214
+ Pin a side explicitly with `side=` on a `<port>` or `from="a@north"`.
215
+
216
+ ## Examples
217
+
218
+ | file | shows |
219
+ |---|---|
220
+ | `examples/basic.xml` | small starter |
221
+ | `examples/soc.xml` | mobile SoC: big.LITTLE templates, NoC hubs, buses, notes, hints, legend, title block |
222
+ | `examples/accelerator_mesh.xml` | 64-tile mesh accelerator, ~200 blocks (nested repeats, grid, uniform instances) |
223
+ | `examples/datacenter.xml` | spine/leaf fabric, racks, storage, dark theme |
224
+ | `examples/automotive.xml` | zonal E/E architecture, CAN/Ethernet buses, TB flow |
225
+ | `examples/fpga_pipeline.xml` | DSP datapath with mux/register/adder shapes, layered algorithm |
226
+ | `examples/flat_network.xml` | 150+ blocks without subsystems: automatic clustering |
227
+
228
+ ## Testing
229
+
230
+ ```
231
+ python -m pip install -e ".[dev]"
232
+ python -m pytest tests -q
233
+ ```
234
+
235
+ The suite covers the VPSC solver (checked against SLSQP and KKT conditions, including
236
+ infeasible systems), the XML language, floorplanning and placement invariants (no
237
+ overlaps, containment, hint satisfaction, aspect), routing invariants (orthogonality,
238
+ connectivity, no wires through blocks, port escape, clearance), rendering, the CLI, and
239
+ end-to-end quality metrics for every example.
240
+
241
+ ## License and author
242
+
243
+ MIT License. Copyright (c) 2026 Ellie Hartung. See [LICENSE](LICENSE).
244
+
245
+ For release instructions, see [Publishing](docs/publishing.md) in the checkout.
@@ -0,0 +1,220 @@
1
+ # archgraph-layout
2
+
3
+ **Hierarchical architecture diagrams from XML** — a Graphviz-style tool built for complex
4
+ hardware and system diagrams. Instead of a layered (Sugiyama) layout that grows very tall
5
+ or very wide, archgraph **floorplans** each subsystem, the way VLSI tools place blocks
6
+ on a die. The result fits a target aspect ratio, groups blocks by subsystem, and keeps
7
+ signal flow readable. Wires are routed orthogonally, with real port handling, nets and
8
+ buses.
9
+
10
+ ```
11
+ python -m archgraph render examples/soc.xml -o soc.svg -o soc.html
12
+ ```
13
+
14
+ * Pure Python (numpy + scipy). Output: **SVG**, an **interactive HTML** viewer
15
+ (pan/zoom/search/highlight), **JSON** layout data, and optionally **PNG** (via Playwright).
16
+ * Deterministic: the same input and seed always give the same drawing.
17
+
18
+ ## Installation
19
+
20
+ Requires Python 3.10 or newer. Install from PyPI after publication:
21
+
22
+ ```sh
23
+ python -m pip install archgraph-layout
24
+ ```
25
+
26
+ The Python import is `archgraph` and the command is `archgraph`:
27
+
28
+ ```sh
29
+ archgraph --version
30
+ archgraph render system.xml -o system.svg -o system.html
31
+ ```
32
+
33
+ For example, save this as `system.xml`:
34
+
35
+ ```xml
36
+ <archgraph title="My system">
37
+ <node id="cpu" label="CPU"/>
38
+ <node id="memory" label="Memory"/>
39
+ <edge from="cpu" to="memory"/>
40
+ </archgraph>
41
+ ```
42
+
43
+ For optional PNG export, install the extra and Chromium:
44
+
45
+ ```sh
46
+ python -m pip install "archgraph-layout[png]"
47
+ python -m playwright install chromium
48
+ ```
49
+
50
+ The source archive includes the XML examples and reference. Generated gallery
51
+ previews and development files are excluded from the published packages.
52
+
53
+ ---
54
+
55
+ ## Contents
56
+
57
+ 1. [How the engine works](#how-the-engine-works)
58
+ 2. [Command line](#command-line)
59
+ 3. [Python API](#python-api)
60
+ 4. [XML reference](docs/xml-reference.md)
61
+ 5. [Tuning](#tuning)
62
+ 6. [Examples](#examples)
63
+ 7. [Testing](#testing)
64
+
65
+ ---
66
+
67
+ ## How the engine works
68
+
69
+ ```
70
+ XML ──► parse/expand ──► size blocks ──► auto-cluster huge levels
71
+ ──► hierarchical floorplanning (bottom-up, several passes)
72
+ ──► detailed QP placement + edge alignment (bottom-up, then top-down)
73
+ ──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
74
+ ──► nudging ──► labels ──► SVG / HTML / JSON / PNG
75
+ ```
76
+
77
+ **Floorplanning (placement).** Each subsystem level is a floorplanning problem over
78
+ its direct children. A child can be a leaf block or a subsystem that was already
79
+ laid out. Topologies are encoded as **sequence pairs** and explored by **simulated
80
+ annealing**. The cost combines:
81
+
82
+ | term | what it measures |
83
+ |---|---|
84
+ | area, aspect | packing density; deviation from the target width/height (steeper at the top level) |
85
+ | wire | weighted Manhattan length between *pins*: the exact deep port positions inside children |
86
+ | flow | edges running against the subsystem's direction (backward edges cost more than sideways ones) |
87
+ | bend | connections whose ends cannot be aligned without a bend |
88
+ | crossing / through | straight-line crossings, and wires passing through other blocks (routing proxies) |
89
+ | terminal | connections leaving the subsystem pull towards the side facing their partner |
90
+ | order | repeated instances (core0, core1, …) read in declaration order |
91
+ | hints | your guidance (see [Layout hints](docs/xml-reference.md#layout-hints)) |
92
+
93
+ Child subsystems contribute several **shape variants** (natural, square, wide, tall).
94
+ The annealer picks one per child, and can also mirror a child perpendicular to its
95
+ flow. Initial states come from a Sugiyama layout (LP layering, barycentre ordering,
96
+ VPSC coordinates, aspect-driven band wrapping), a spectral (Fiedler) grid order, warm
97
+ starts from the previous pass, and random states. Tiny levels (≤ 4 blocks) are solved
98
+ exhaustively. Identical template instances are solved once and drawn identically.
99
+
100
+ **Detailed placement.** The annealer gives each level a topology; a convex QP then
101
+ computes exact coordinates. The QP is solved by gradient projection over a **VPSC**
102
+ separation-constraint solver. It straightens wires, compacts, and enforces non-overlap,
103
+ containment and your hard hints. A greedy **HOLA-style** pass then adds equality
104
+ constraints so that nearly straight connections become exactly straight. A top-down
105
+ pass re-solves every level with the real positions of external partners.
106
+
107
+ **Routing.** All wires are routed orthogonally on a sparse non-uniform grid, using an
108
+ A* search that knows each wire's direction. Costs cover length, bends, crossings,
109
+ channel sharing, corridor overflow, passing other ports, wall proximity (wires prefer
110
+ channel centres), turning close to a wall, and crossing subsystem borders. Crossing
111
+ the border of a subsystem that contains neither end is almost forbidden. Specifics:
112
+
113
+ * Ports have a **straight escape** (about 20px) before the first turn.
114
+ * A first pass routes with **floating ports**, so each automatic port attaches on the side its
115
+ wire arrives from.
116
+ * Edges sharing a declared port form one **net**, drawn as a Steiner tree with junction dots.
117
+ **Buses** become trunk trees.
118
+ * **PathFinder** negotiation rips up and reroutes congested nets.
119
+ * **Congestion-driven spacing.** Gaps that carry more wires than fit comfortably are widened,
120
+ overloaded blocks grow, and the layout is re-compacted (same topology) and rerouted.
121
+ * **Nudging** (VPSC again) spreads overlapping wires into parallel tracks. It orders them to
122
+ avoid crossings, keeps them clear of blocks and titles, and centres bundles in their corridor.
123
+ Every port's escape stub is a keep-out zone, so no wire is squeezed in under an arrowhead.
124
+ * **Crossings** (`crossings="hop"`, the default; or `"gap"` / `"none"`). Hops are drawn in a
125
+ layer above every wire, with a halo in the local background colour, so nothing is ever
126
+ painted over a hop. Crossings closer together than one hop share a single flat bridge.
127
+ Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
128
+ corners and neighbouring parallel wires). A crossing right next to a corner of the
129
+ horizontal wire hops sideways on the vertical wire instead. Bit-width markers and edge
130
+ labels keep clear of crossings.
131
+
132
+ ---
133
+
134
+ ## Command line
135
+
136
+ ```
137
+ archgraph render FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats]
138
+ [--quality draft|normal|high|max] [--aspect 16:9] [--theme dark]
139
+ [--direction TB] [--seed N] [--set key=value ...]
140
+ archgraph validate FILE.xml # parse, report errors (with line numbers) and warnings
141
+ archgraph stats FILE.xml # lay out and print quality metrics as JSON
142
+ archgraph expand FILE.xml # print the XML after templates/loops/includes expand
143
+ archgraph FILE.xml -o out.svg # shorthand for render
144
+ ```
145
+
146
+ `--stats` reports blocks, aspect, crossings, bends, wire length, overlaps, containment
147
+ violations, wires through blocks, wire length hugging walls, minimum port escape,
148
+ flow-consistency counts and timings.
149
+
150
+ ## Python API
151
+
152
+ ```python
153
+ import archgraph
154
+
155
+ g = archgraph.build("system.xml", aspect="4:3", quality="high") # parse + layout + route
156
+ archgraph.save(g, "system.svg") # format by extension: .svg .html .json .png
157
+ svg = archgraph.to_svg(g)
158
+
159
+ g = archgraph.loads(xml_text) # or step by step
160
+ archgraph.layout(g); archgraph.route(g)
161
+ for n in g.nodes(): print(n.id, n.x, n.y, n.w, n.h)
162
+ from archgraph.quality import metrics; print(metrics(g))
163
+ ```
164
+
165
+ ---
166
+
167
+ ## XML reference
168
+
169
+ See the [standalone XML reference](docs/xml-reference.md) for a starter diagram,
170
+ settings and defaults, nodes and ports, edges and buses, layout hints, styles,
171
+ templates, loops, includes, and validation guidance.
172
+
173
+ ---
174
+
175
+ ## Tuning
176
+
177
+ * **Shape**: `aspect` (global) and per-subsystem `aspect`. The top level weights aspect most.
178
+ * **Compactness vs readability**: `<weights area="0.6" wire="2.4" flow="3" crossing="1.4" through="1.4"
179
+ bend="1.6" terminal="1.4" order="1.6" aspect="2.2" hint_hard="10" hint_soft="2.5"/>`.
180
+ * **Effort**: `quality="high"` adds an annealing pass, longer schedules and more spacing iterations.
181
+ * **Room for wires**: `node-spacing`, `subsystem-padding`, `edge-spacing`, `node-margin`.
182
+ * **Big flat graphs**: `max-level-items` and `auto-group="visible"` (shows the clusters found).
183
+ * **Hard vs soft hints**: `left-of`/`above`/`row`/`column`/`order` are hard: the floorplanner
184
+ only considers arrangements that keep them, and a subsystem shape that would break them
185
+ is never offered to the parent. `near`/`place`/`anchor` are soft preferences.
186
+ * **Port sides** are chosen from geometry (facing the partner, flow, blocked sides) and then
187
+ refined by a trial routing pass that may move a port to another side when the wire is
188
+ clearly better there; sides facing away from the partner are strongly discouraged.
189
+ Pin a side explicitly with `side=` on a `<port>` or `from="a@north"`.
190
+
191
+ ## Examples
192
+
193
+ | file | shows |
194
+ |---|---|
195
+ | `examples/basic.xml` | small starter |
196
+ | `examples/soc.xml` | mobile SoC: big.LITTLE templates, NoC hubs, buses, notes, hints, legend, title block |
197
+ | `examples/accelerator_mesh.xml` | 64-tile mesh accelerator, ~200 blocks (nested repeats, grid, uniform instances) |
198
+ | `examples/datacenter.xml` | spine/leaf fabric, racks, storage, dark theme |
199
+ | `examples/automotive.xml` | zonal E/E architecture, CAN/Ethernet buses, TB flow |
200
+ | `examples/fpga_pipeline.xml` | DSP datapath with mux/register/adder shapes, layered algorithm |
201
+ | `examples/flat_network.xml` | 150+ blocks without subsystems: automatic clustering |
202
+
203
+ ## Testing
204
+
205
+ ```
206
+ python -m pip install -e ".[dev]"
207
+ python -m pytest tests -q
208
+ ```
209
+
210
+ The suite covers the VPSC solver (checked against SLSQP and KKT conditions, including
211
+ infeasible systems), the XML language, floorplanning and placement invariants (no
212
+ overlaps, containment, hint satisfaction, aspect), routing invariants (orthogonality,
213
+ connectivity, no wires through blocks, port escape, clearance), rendering, the CLI, and
214
+ end-to-end quality metrics for every example.
215
+
216
+ ## License and author
217
+
218
+ MIT License. Copyright (c) 2026 Ellie Hartung. See [LICENSE](LICENSE).
219
+
220
+ For release instructions, see [Publishing](docs/publishing.md) in the checkout.
@@ -0,0 +1,108 @@
1
+ """archgraph — hierarchical architecture diagrams with floorplanning layout and orthogonal routing.
2
+
3
+ Typical use::
4
+
5
+ import archgraph
6
+ g = archgraph.build("system.xml") # parse + layout + route
7
+ open("system.svg", "w").write(archgraph.to_svg(g))
8
+
9
+ or from the command line::
10
+
11
+ archgraph render system.xml -o system.svg
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import time
17
+ from typing import Optional
18
+
19
+ from .model import ArchGraphError, Graph
20
+ from .parser import parse_file, parse_string
21
+ # import subpackages eagerly so the public functions below are not shadowed by them
22
+ from .placement import engine as _engine # noqa: E402
23
+ from . import routing as _routing # noqa: E402
24
+
25
+ __version__ = "1.0.0"
26
+ __all__ = ["load", "loads", "layout", "route", "build", "to_svg", "to_html", "to_json", "to_png", "save",
27
+ "ArchGraphError", "Graph", "__version__"]
28
+
29
+
30
+ def load(path: str) -> Graph:
31
+ """Parse an XML file into a :class:`Graph` (no layout yet)."""
32
+ return parse_file(path)
33
+
34
+
35
+ def loads(text: str, base_dir: Optional[str] = None) -> Graph:
36
+ """Parse XML text into a :class:`Graph`."""
37
+ return parse_string(text, base_dir=base_dir)
38
+
39
+
40
+ def layout(g: Graph) -> Graph:
41
+ """Place every block and subsystem (and assign ports)."""
42
+ _engine.layout(g)
43
+ return g
44
+
45
+
46
+ def route(g: Graph) -> Graph:
47
+ """Route all connections and buses, nudge them apart and place labels."""
48
+ _routing.route(g)
49
+ from .geometry import Rect
50
+ rects = [e.rect for e in g.root.iter_descendants()]
51
+ g.bbox = Rect.bounding(rects)
52
+ return g
53
+
54
+
55
+ def build(source: str, **overrides) -> Graph:
56
+ """Parse (file path or XML text), apply setting overrides, lay out and route."""
57
+ t0 = time.time()
58
+ if os.path.exists(source) or source.lower().endswith(".xml"):
59
+ g = parse_file(source)
60
+ else:
61
+ g = parse_string(source)
62
+ from .parser import apply_setting
63
+ errs = []
64
+ for k, v in overrides.items():
65
+ if v is not None:
66
+ apply_setting(g.settings, k, str(v), errs, "override")
67
+ if errs:
68
+ raise ArchGraphError(errs)
69
+ layout(g)
70
+ route(g)
71
+ if g.settings.routing == "orthogonal" and g.settings.num_spacing_passes() > 0:
72
+ _engine.expand_congested(g, route, g.settings.num_spacing_passes())
73
+ g.layout_stats["total_time"] = time.time() - t0
74
+ return g
75
+
76
+
77
+ def to_svg(g: Graph) -> str:
78
+ from .render.svg import render_svg
79
+ return render_svg(g)
80
+
81
+
82
+ def to_html(g: Graph) -> str:
83
+ from .render.html import render_html
84
+ return render_html(g, to_svg(g))
85
+
86
+
87
+ def to_json(g: Graph) -> str:
88
+ from .render.jsonout import render_json
89
+ return render_json(g)
90
+
91
+
92
+ def to_png(g: Graph, path: str, scale: float = 2.0) -> None:
93
+ from .render.png import render_png
94
+ render_png(to_svg(g), path, scale)
95
+
96
+
97
+ def save(g: Graph, path: str, fmt: Optional[str] = None, scale: float = 2.0) -> str:
98
+ """Write the diagram; the format follows the extension unless ``fmt`` is given."""
99
+ fmt = (fmt or os.path.splitext(path)[1].lstrip(".") or "svg").lower()
100
+ if fmt == "png":
101
+ to_png(g, path, scale)
102
+ return path
103
+ text = {"svg": to_svg, "html": to_html, "htm": to_html, "json": to_json}.get(fmt)
104
+ if text is None:
105
+ raise ArchGraphError(f"unsupported output format '{fmt}' (svg, html, json, png)")
106
+ with open(path, "w", encoding="utf-8") as fh:
107
+ fh.write(text(g))
108
+ return path
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())