archgraph-layout 1.0.0__tar.gz → 2.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.
- {archgraph_layout-1.0.0/archgraph_layout.egg-info → archgraph_layout-2.0.0}/PKG-INFO +21 -6
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/README.md +20 -5
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/__init__.py +4 -2
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/cli.py +17 -2
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/model.py +1 -10
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/parser.py +23 -27
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/engine.py +23 -7
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/floorplan.py +40 -19
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/hierarchy.py +37 -11
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/ports.py +370 -354
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/sizing.py +78 -83
- archgraph_layout-2.0.0/archgraph/portlabels.py +73 -0
- archgraph_layout-2.0.0/archgraph/progress.py +143 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/render/html.py +3 -6
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/render/jsonout.py +4 -4
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/render/shapes.py +129 -240
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/render/svg.py +56 -103
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/routing/__init__.py +24 -24
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/routing/grid.py +2 -6
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/routing/router.py +1127 -1112
- archgraph_layout-2.0.0/archgraph/shapegeometry.py +261 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/styles.py +6 -10
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0/archgraph_layout.egg-info}/PKG-INFO +21 -6
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/SOURCES.txt +6 -1
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/docs/xml-reference.md +26 -16
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/automotive.xml +1 -2
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/basic.xml +3 -3
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/datacenter.xml +1 -1
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/fpga_pipeline.xml +1 -1
- archgraph_layout-2.0.0/examples/halcyon7.xml +660 -0
- archgraph_layout-2.0.0/examples/halcyon7_lib.xml +233 -0
- archgraph_layout-2.0.0/examples/halcyon7_styles.xml +71 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/soc.xml +6 -6
- archgraph_layout-1.0.0/archgraph/routing/labels.py +0 -141
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/LICENSE +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/MANIFEST.in +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/__main__.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/geometry.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/__init__.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/congestion.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/detail.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/graphalgo.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/layered.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/placement/vpsc.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/quality.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/render/__init__.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/render/png.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/routing/nudge.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph/textmetrics.py +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/dependency_links.txt +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/entry_points.txt +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/requires.txt +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/top_level.txt +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/accelerator_mesh.xml +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/examples/flat_network.xml +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/pyproject.toml +0 -0
- {archgraph_layout-1.0.0 → archgraph_layout-2.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: archgraph-layout
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Hierarchical architecture diagrams from XML with floorplanning and orthogonal routing
|
|
5
5
|
Author: Ellie Hartung
|
|
6
6
|
License-Expression: MIT
|
|
@@ -96,7 +96,7 @@ XML ──► parse/expand ──► size blocks ──► auto-cluster huge lev
|
|
|
96
96
|
──► hierarchical floorplanning (bottom-up, several passes)
|
|
97
97
|
──► detailed QP placement + edge alignment (bottom-up, then top-down)
|
|
98
98
|
──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
|
|
99
|
-
──► nudging ──►
|
|
99
|
+
──► nudging ──► SVG / HTML / JSON / PNG
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
**Floorplanning (placement).** Each subsystem level is a floorplanning problem over
|
|
@@ -151,19 +151,19 @@ the border of a subsystem that contains neither end is almost forbidden. Specifi
|
|
|
151
151
|
painted over a hop. Crossings closer together than one hop share a single flat bridge.
|
|
152
152
|
Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
|
|
153
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
|
|
155
|
-
|
|
154
|
+
horizontal wire hops sideways on the vertical wire instead. Bit-width markers
|
|
155
|
+
keep clear of crossings.
|
|
156
156
|
|
|
157
157
|
---
|
|
158
158
|
|
|
159
159
|
## Command line
|
|
160
160
|
|
|
161
161
|
```
|
|
162
|
-
archgraph render FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats]
|
|
162
|
+
archgraph render FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats] [--progress]
|
|
163
163
|
[--quality draft|normal|high|max] [--aspect 16:9] [--theme dark]
|
|
164
164
|
[--direction TB] [--seed N] [--set key=value ...]
|
|
165
165
|
archgraph validate FILE.xml # parse, report errors (with line numbers) and warnings
|
|
166
|
-
archgraph stats FILE.xml
|
|
166
|
+
archgraph stats FILE.xml [--progress] # lay out and print quality metrics as JSON
|
|
167
167
|
archgraph expand FILE.xml # print the XML after templates/loops/includes expand
|
|
168
168
|
archgraph FILE.xml -o out.svg # shorthand for render
|
|
169
169
|
```
|
|
@@ -172,6 +172,21 @@ archgraph FILE.xml -o out.svg # shorthand for render
|
|
|
172
172
|
violations, wires through blocks, wire length hugging walls, minimum port escape,
|
|
173
173
|
flow-consistency counts and timings.
|
|
174
174
|
|
|
175
|
+
`--progress` shows elapsed time and compact stage bars for port assignment, routing,
|
|
176
|
+
and congestion refinement, plus placement completion, output writing, and `Done`.
|
|
177
|
+
Bars update in place in a terminal; redirected progress logs retain stage milestones.
|
|
178
|
+
Placement shows completed passes and the current subsystem or optimization step.
|
|
179
|
+
A spinner and elapsed time refresh during long computations; redirected logs receive
|
|
180
|
+
an activity update every five seconds while a stage is running.
|
|
181
|
+
Updates go to stderr, so `stats` JSON on stdout can still be redirected to a file.
|
|
182
|
+
Counts describe the current stage; congestion counts show refinements performed out
|
|
183
|
+
of the configured maximum and can finish early. For example:
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
archgraph render examples/soc.xml -o soc.html --progress
|
|
187
|
+
archgraph stats examples/basic.xml --progress > metrics.json
|
|
188
|
+
```
|
|
189
|
+
|
|
175
190
|
## Python API
|
|
176
191
|
|
|
177
192
|
```python
|
|
@@ -71,7 +71,7 @@ XML ──► parse/expand ──► size blocks ──► auto-cluster huge lev
|
|
|
71
71
|
──► hierarchical floorplanning (bottom-up, several passes)
|
|
72
72
|
──► detailed QP placement + edge alignment (bottom-up, then top-down)
|
|
73
73
|
──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
|
|
74
|
-
──► nudging ──►
|
|
74
|
+
──► nudging ──► SVG / HTML / JSON / PNG
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
**Floorplanning (placement).** Each subsystem level is a floorplanning problem over
|
|
@@ -126,19 +126,19 @@ the border of a subsystem that contains neither end is almost forbidden. Specifi
|
|
|
126
126
|
painted over a hop. Crossings closer together than one hop share a single flat bridge.
|
|
127
127
|
Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
|
|
128
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
|
|
130
|
-
|
|
129
|
+
horizontal wire hops sideways on the vertical wire instead. Bit-width markers
|
|
130
|
+
keep clear of crossings.
|
|
131
131
|
|
|
132
132
|
---
|
|
133
133
|
|
|
134
134
|
## Command line
|
|
135
135
|
|
|
136
136
|
```
|
|
137
|
-
archgraph render FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats]
|
|
137
|
+
archgraph render FILE.xml [-o OUT.svg|.html|.json|.png ...] [--stats] [--progress]
|
|
138
138
|
[--quality draft|normal|high|max] [--aspect 16:9] [--theme dark]
|
|
139
139
|
[--direction TB] [--seed N] [--set key=value ...]
|
|
140
140
|
archgraph validate FILE.xml # parse, report errors (with line numbers) and warnings
|
|
141
|
-
archgraph stats FILE.xml
|
|
141
|
+
archgraph stats FILE.xml [--progress] # lay out and print quality metrics as JSON
|
|
142
142
|
archgraph expand FILE.xml # print the XML after templates/loops/includes expand
|
|
143
143
|
archgraph FILE.xml -o out.svg # shorthand for render
|
|
144
144
|
```
|
|
@@ -147,6 +147,21 @@ archgraph FILE.xml -o out.svg # shorthand for render
|
|
|
147
147
|
violations, wires through blocks, wire length hugging walls, minimum port escape,
|
|
148
148
|
flow-consistency counts and timings.
|
|
149
149
|
|
|
150
|
+
`--progress` shows elapsed time and compact stage bars for port assignment, routing,
|
|
151
|
+
and congestion refinement, plus placement completion, output writing, and `Done`.
|
|
152
|
+
Bars update in place in a terminal; redirected progress logs retain stage milestones.
|
|
153
|
+
Placement shows completed passes and the current subsystem or optimization step.
|
|
154
|
+
A spinner and elapsed time refresh during long computations; redirected logs receive
|
|
155
|
+
an activity update every five seconds while a stage is running.
|
|
156
|
+
Updates go to stderr, so `stats` JSON on stdout can still be redirected to a file.
|
|
157
|
+
Counts describe the current stage; congestion counts show refinements performed out
|
|
158
|
+
of the configured maximum and can finish early. For example:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
archgraph render examples/soc.xml -o soc.html --progress
|
|
162
|
+
archgraph stats examples/basic.xml --progress > metrics.json
|
|
163
|
+
```
|
|
164
|
+
|
|
150
165
|
## Python API
|
|
151
166
|
|
|
152
167
|
```python
|
|
@@ -18,11 +18,12 @@ from typing import Optional
|
|
|
18
18
|
|
|
19
19
|
from .model import ArchGraphError, Graph
|
|
20
20
|
from .parser import parse_file, parse_string
|
|
21
|
+
from .progress import report
|
|
21
22
|
# import subpackages eagerly so the public functions below are not shadowed by them
|
|
22
23
|
from .placement import engine as _engine # noqa: E402
|
|
23
24
|
from . import routing as _routing # noqa: E402
|
|
24
25
|
|
|
25
|
-
__version__ = "
|
|
26
|
+
__version__ = "2.0.0"
|
|
26
27
|
__all__ = ["load", "loads", "layout", "route", "build", "to_svg", "to_html", "to_json", "to_png", "save",
|
|
27
28
|
"ArchGraphError", "Graph", "__version__"]
|
|
28
29
|
|
|
@@ -44,7 +45,7 @@ def layout(g: Graph) -> Graph:
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def route(g: Graph) -> Graph:
|
|
47
|
-
"""Route all connections and buses
|
|
48
|
+
"""Route all connections and buses and nudge them apart."""
|
|
48
49
|
_routing.route(g)
|
|
49
50
|
from .geometry import Rect
|
|
50
51
|
rects = [e.rect for e in g.root.iter_descendants()]
|
|
@@ -55,6 +56,7 @@ def route(g: Graph) -> Graph:
|
|
|
55
56
|
def build(source: str, **overrides) -> Graph:
|
|
56
57
|
"""Parse (file path or XML text), apply setting overrides, lay out and route."""
|
|
57
58
|
t0 = time.time()
|
|
59
|
+
report("Parsing XML")
|
|
58
60
|
if os.path.exists(source) or source.lower().endswith(".xml"):
|
|
59
61
|
g = parse_file(source)
|
|
60
62
|
else:
|
|
@@ -9,6 +9,7 @@ import time
|
|
|
9
9
|
|
|
10
10
|
from . import __version__
|
|
11
11
|
from .model import ArchGraphError
|
|
12
|
+
from .progress import TerminalProgress, report, reporting
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def _common(p):
|
|
@@ -20,6 +21,7 @@ def _common(p):
|
|
|
20
21
|
p.add_argument("--aspect", help="target aspect ratio, e.g. 16:9, 1.5, square")
|
|
21
22
|
p.add_argument("--direction", help="default flow LR | RL | TB | BT")
|
|
22
23
|
p.add_argument("--seed", type=int, help="random seed (layouts are deterministic per seed)")
|
|
24
|
+
p.add_argument("--progress", action="store_true", help="show stage progress bars and elapsed time on stderr")
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
def _overrides(args):
|
|
@@ -61,6 +63,11 @@ def main(argv=None) -> int:
|
|
|
61
63
|
return main(["render"] + list(argv))
|
|
62
64
|
ap.print_help()
|
|
63
65
|
return 1
|
|
66
|
+
with reporting(TerminalProgress() if getattr(args, "progress", False) else None):
|
|
67
|
+
return _run(args)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _run(args) -> int:
|
|
64
71
|
try:
|
|
65
72
|
if args.cmd == "validate":
|
|
66
73
|
from .parser import parse_file
|
|
@@ -89,17 +96,25 @@ def main(argv=None) -> int:
|
|
|
89
96
|
for w in g.warnings:
|
|
90
97
|
print("warning:", w, file=sys.stderr)
|
|
91
98
|
if args.cmd == "stats":
|
|
99
|
+
report("Computing quality metrics")
|
|
92
100
|
print(json.dumps(metrics(g), indent=1))
|
|
101
|
+
report("Done")
|
|
93
102
|
return 0
|
|
94
103
|
outs = args.output or [os.path.splitext(args.input)[0] + ".svg"]
|
|
95
104
|
for o in outs:
|
|
105
|
+
report(f"Writing {o}")
|
|
96
106
|
save(g, o, scale=args.scale)
|
|
97
|
-
|
|
107
|
+
if not args.progress:
|
|
108
|
+
print(f"wrote {o}")
|
|
98
109
|
if args.stats:
|
|
110
|
+
report("Computing quality metrics")
|
|
99
111
|
print(json.dumps(metrics(g), indent=1))
|
|
100
|
-
|
|
112
|
+
report("Done")
|
|
113
|
+
if not args.progress:
|
|
114
|
+
print(f"done in {time.time() - t0:.1f}s", file=sys.stderr)
|
|
101
115
|
return 0
|
|
102
116
|
except ArchGraphError as ex:
|
|
117
|
+
report("Failed")
|
|
103
118
|
print("error:", file=sys.stderr)
|
|
104
119
|
for m in ex.messages:
|
|
105
120
|
print(" " + m, file=sys.stderr)
|
|
@@ -53,7 +53,6 @@ class Settings:
|
|
|
53
53
|
junction_dots: bool = True
|
|
54
54
|
shadows: bool = True
|
|
55
55
|
background_grid: bool = False
|
|
56
|
-
edge_labels: bool = True
|
|
57
56
|
default_shape: str = "box"
|
|
58
57
|
bundle_parallel: bool = False # merge parallel edges between the same endpoints
|
|
59
58
|
margin: float = 24.0 # outer margin of the drawing
|
|
@@ -184,6 +183,7 @@ class Node(Element):
|
|
|
184
183
|
min_h: Optional[float] = None
|
|
185
184
|
virtual: bool = False # internal helper node (not drawn)
|
|
186
185
|
is_note: bool = False
|
|
186
|
+
layout_direction: str = "LR" # inherited flow used by shape geometry
|
|
187
187
|
|
|
188
188
|
kind = "node"
|
|
189
189
|
|
|
@@ -246,9 +246,6 @@ class Edge:
|
|
|
246
246
|
id: str
|
|
247
247
|
src: Endpoint
|
|
248
248
|
dst: Endpoint
|
|
249
|
-
label: str = ""
|
|
250
|
-
src_label: str = ""
|
|
251
|
-
dst_label: str = ""
|
|
252
249
|
classes: List[str] = field(default_factory=list)
|
|
253
250
|
attrs: Dict[str, str] = field(default_factory=dict)
|
|
254
251
|
style: Dict[str, Any] = field(default_factory=dict)
|
|
@@ -267,9 +264,6 @@ class Edge:
|
|
|
267
264
|
line: Optional[int] = None
|
|
268
265
|
# results
|
|
269
266
|
points: List[Point] = field(default_factory=list)
|
|
270
|
-
label_pos: Optional[Tuple[float, float, float, float]] = None # x, y, w, h (centre)
|
|
271
|
-
src_label_pos: Optional[Tuple[float, float]] = None
|
|
272
|
-
dst_label_pos: Optional[Tuple[float, float]] = None
|
|
273
267
|
routed: bool = False
|
|
274
268
|
uid: int = field(default_factory=lambda: next(_counter))
|
|
275
269
|
|
|
@@ -286,14 +280,12 @@ class Edge:
|
|
|
286
280
|
@dataclass(eq=False, repr=False)
|
|
287
281
|
class BusTap:
|
|
288
282
|
endpoint: Endpoint
|
|
289
|
-
label: str = ""
|
|
290
283
|
direction: Optional[str] = None
|
|
291
284
|
|
|
292
285
|
|
|
293
286
|
@dataclass(eq=False, repr=False)
|
|
294
287
|
class Bus:
|
|
295
288
|
id: str
|
|
296
|
-
label: str = ""
|
|
297
289
|
taps: List[BusTap] = field(default_factory=list)
|
|
298
290
|
classes: List[str] = field(default_factory=list)
|
|
299
291
|
attrs: Dict[str, str] = field(default_factory=dict)
|
|
@@ -305,7 +297,6 @@ class Bus:
|
|
|
305
297
|
# results
|
|
306
298
|
segments: List[Tuple[Point, Point]] = field(default_factory=list)
|
|
307
299
|
junctions: List[Point] = field(default_factory=list)
|
|
308
|
-
label_pos: Optional[Tuple[float, float, float, float]] = None
|
|
309
300
|
routed: bool = False
|
|
310
301
|
uid: int = field(default_factory=lambda: next(_counter))
|
|
311
302
|
|
|
@@ -30,7 +30,7 @@ LINE = "__line__"
|
|
|
30
30
|
FILE = "__file__"
|
|
31
31
|
|
|
32
32
|
NODE_TAGS = {"node", "component", "block", "unit", "item", "instance"}
|
|
33
|
-
SHAPE_TAGS = {"
|
|
33
|
+
SHAPE_TAGS = {"memory": "cylinder", "storage": "cylinder", "database": "cylinder",
|
|
34
34
|
"fifo": "fifo", "mux": "mux", "demux": "demux", "register": "register", "processor": "box",
|
|
35
35
|
"sensor": "ellipse", "connector": "connector", "alu": "alu", "adder": "adder", "port-pin": "pin"}
|
|
36
36
|
SUBSYSTEM_TAGS = {"subsystem", "group", "cluster", "module", "domain", "board", "rack", "package", "die",
|
|
@@ -49,7 +49,7 @@ REF_ATTRS = {"from", "to", "src", "dst", "source", "target", "nodes", "path", "a
|
|
|
49
49
|
SHAPE_ALIASES = {"memory": "cylinder", "storage": "cylinder", "database": "cylinder", "db": "cylinder",
|
|
50
50
|
"ram": "cylinder", "disk": "cylinder", "rect": "box", "rectangle": "box", "square": "box",
|
|
51
51
|
"roundbox": "rounded", "round": "rounded", "oval": "ellipse", "buffer": "triangle",
|
|
52
|
-
"amplifier": "triangle", "amp": "triangle", "
|
|
52
|
+
"amplifier": "triangle", "amp": "triangle", "package": "folder",
|
|
53
53
|
"queue": "fifo", "flop": "register", "reg": "register", "flipflop": "register",
|
|
54
54
|
"text": "plaintext", "label": "plaintext", "dsp": "multiplier", "terminal": "pin",
|
|
55
55
|
"io": "pin", "processor": "cpu", "pill": "pill", "monitor": "display", "screen": "display"}
|
|
@@ -62,7 +62,7 @@ def normalize_shape(s: str) -> str:
|
|
|
62
62
|
|
|
63
63
|
KNOWN_SHAPES = {"box", "rect", "rectangle", "rounded", "roundbox", "ellipse", "oval", "circle", "diamond",
|
|
64
64
|
"hexagon", "octagon", "parallelogram", "trapezoid", "cylinder", "note", "folder", "component",
|
|
65
|
-
"
|
|
65
|
+
"cloud", "document", "fifo", "register", "mux", "demux", "alu", "adder", "multiplier",
|
|
66
66
|
"triangle", "invtriangle", "pin", "connector", "stack", "plaintext", "none", "tab", "cpu",
|
|
67
67
|
"pill", "cross", "star", "shield", "gear", "antenna", "battery", "display"}
|
|
68
68
|
|
|
@@ -1086,25 +1086,28 @@ class Builder:
|
|
|
1086
1086
|
e.ports[pid] = p
|
|
1087
1087
|
return Endpoint(element=e, port=p, ref=ref)
|
|
1088
1088
|
|
|
1089
|
-
EDGE_ATTRS = {"from", "to", "src", "dst", "source", "target", "
|
|
1090
|
-
"dst-label", "head-label", "class", "dir", "directed", "arrowhead", "arrowtail", "weight",
|
|
1089
|
+
EDGE_ATTRS = {"from", "to", "src", "dst", "source", "target", "class", "dir", "directed", "arrowhead", "arrowtail", "weight",
|
|
1091
1090
|
"minlen", "constraint", "layout", "straight", "bits", "width-bits", "bus-width", "net",
|
|
1092
1091
|
"tooltip", "id", "nodes", "path", "priority", "lanes"}
|
|
1093
1092
|
|
|
1093
|
+
def _connection_attrs(self, el: ET.Element, attrs: Dict[str, Any]) -> Dict[str, Any]:
|
|
1094
|
+
"""Ignore retired connection labels while still accepting older XML files."""
|
|
1095
|
+
retired = {"label", "src-label", "tail-label", "dst-label", "head-label"}
|
|
1096
|
+
if retired.intersection(attrs) or el.find("label") is not None or (el.text or "").strip():
|
|
1097
|
+
self.graph.warn(f"{_loc(el)}: connection labels are no longer supported; ignored")
|
|
1098
|
+
return {k: v for k, v in attrs.items() if k not in retired}
|
|
1099
|
+
|
|
1094
1100
|
def _make_edges(self, el: ET.Element, extra: Dict[str, Any], sub: Subsystem):
|
|
1095
1101
|
g = self.graph
|
|
1096
1102
|
attrs = {k: v for k, v in el.attrib.items() if not k.startswith("__")}
|
|
1097
1103
|
attrs.update({k: v for k, v in extra.items() if not k.startswith("__")})
|
|
1104
|
+
attrs = self._connection_attrs(el, attrs)
|
|
1098
1105
|
where = _loc(el)
|
|
1099
1106
|
srcs = _split_refs(attrs.get("from") or attrs.get("src") or attrs.get("source"))
|
|
1100
1107
|
dsts = _split_refs(attrs.get("to") or attrs.get("dst") or attrs.get("target"))
|
|
1101
1108
|
if not srcs or not dsts:
|
|
1102
1109
|
self.errors.append(f"{where}: <{el.tag}> needs from= and to=")
|
|
1103
1110
|
return
|
|
1104
|
-
label = attrs.get("label")
|
|
1105
|
-
if label is None:
|
|
1106
|
-
lab_el = el.find("label")
|
|
1107
|
-
label = (lab_el.text or "").strip() if lab_el is not None else (el.text or "").strip()
|
|
1108
1111
|
classes = _split_refs(attrs.get("class"))
|
|
1109
1112
|
raw_style = {}
|
|
1110
1113
|
for k, v in attrs.items():
|
|
@@ -1127,11 +1130,9 @@ class Builder:
|
|
|
1127
1130
|
de = self._endpoint(d, where, eid, "d")
|
|
1128
1131
|
if se is None or de is None:
|
|
1129
1132
|
continue
|
|
1130
|
-
e = Edge(id=eid, src=se, dst=de,
|
|
1133
|
+
e = Edge(id=eid, src=se, dst=de, classes=list(classes), style=dict(style),
|
|
1131
1134
|
line=_line(el))
|
|
1132
1135
|
e.attrs = attrs
|
|
1133
|
-
e.src_label = attrs.get("src-label", attrs.get("tail-label", ""))
|
|
1134
|
-
e.dst_label = attrs.get("dst-label", attrs.get("head-label", ""))
|
|
1135
1136
|
dirv = (attrs.get("dir") or "").lower()
|
|
1136
1137
|
if attrs.get("directed") is not None:
|
|
1137
1138
|
e.directed = st.parse_bool(attrs.get("directed"), True)
|
|
@@ -1165,36 +1166,38 @@ class Builder:
|
|
|
1165
1166
|
g = self.graph
|
|
1166
1167
|
where = _loc(el)
|
|
1167
1168
|
bid = el.get("id") or f"bus{len(g.buses) + 1}"
|
|
1168
|
-
|
|
1169
|
+
attrs = self._connection_attrs(el, el.attrib)
|
|
1170
|
+
bus = Bus(id=bid, line=_line(el))
|
|
1169
1171
|
bus.classes = _split_refs(el.get("class"))
|
|
1170
1172
|
bus.bits = el.get("bits", el.get("width-bits", ""))
|
|
1171
1173
|
bus.orientation = el.get("orientation", "auto")
|
|
1172
1174
|
bus.tooltip = el.get("tooltip", "")
|
|
1173
|
-
raw = {k: v for k, v in
|
|
1174
|
-
if k not in ("id", "
|
|
1175
|
+
raw = {k: v for k, v in attrs.items()
|
|
1176
|
+
if k not in ("id", "class", "bits", "orientation", "taps", "tooltip", "width-bits")
|
|
1175
1177
|
and not k.startswith("__")}
|
|
1176
1178
|
if "style" in raw and raw["style"].lower() not in st.DASH_PATTERNS:
|
|
1177
1179
|
raw.update(st.parse_css_style(raw.pop("style")))
|
|
1178
1180
|
bus.style = st.normalize_style(raw)
|
|
1179
1181
|
k = 0
|
|
1180
|
-
tap_specs: List[Tuple[str,
|
|
1182
|
+
tap_specs: List[Tuple[str, Optional[str], Optional[int]]] = []
|
|
1181
1183
|
for t in _split_refs(el.get("taps")):
|
|
1182
|
-
tap_specs.append((t,
|
|
1184
|
+
tap_specs.append((t, None, _line(el)))
|
|
1183
1185
|
for c in el:
|
|
1184
1186
|
if c.tag in ("tap", "member", "endpoint", "connect", "pin"):
|
|
1187
|
+
self._connection_attrs(c, c.attrib)
|
|
1185
1188
|
ref = c.get("node") or c.get("ref") or c.get("to") or c.get("id")
|
|
1186
1189
|
if not ref:
|
|
1187
1190
|
self.errors.append(f"{_loc(c)}: <tap> needs node=")
|
|
1188
1191
|
continue
|
|
1189
1192
|
if c.get("port") and ":" not in ref:
|
|
1190
1193
|
ref = f"{ref}:{c.get('port')}"
|
|
1191
|
-
tap_specs.append((ref, c.get("
|
|
1192
|
-
for ref,
|
|
1194
|
+
tap_specs.append((ref, c.get("dir", c.get("direction")), _line(c)))
|
|
1195
|
+
for ref, d, ln in tap_specs:
|
|
1193
1196
|
ep = self._endpoint(ref, f"{_loc(el)}" if ln is None else f"{os.path.basename(el.get(FILE, ''))}:{ln}",
|
|
1194
1197
|
f"bus~{bid}~{k}", "t")
|
|
1195
1198
|
k += 1
|
|
1196
1199
|
if ep is not None:
|
|
1197
|
-
bus.taps.append(BusTap(endpoint=ep,
|
|
1200
|
+
bus.taps.append(BusTap(endpoint=ep, direction=d))
|
|
1198
1201
|
if len(bus.taps) < 2:
|
|
1199
1202
|
self.errors.append(f"{where}: bus '{bid}' needs at least two taps")
|
|
1200
1203
|
return
|
|
@@ -1355,11 +1358,6 @@ def _bundle_parallel(g: Graph):
|
|
|
1355
1358
|
continue
|
|
1356
1359
|
first = es[0]
|
|
1357
1360
|
first.multiplicity = sum(x.multiplicity for x in es)
|
|
1358
|
-
labels = [x.label for x in es if x.label]
|
|
1359
|
-
if labels:
|
|
1360
|
-
uniq = list(dict.fromkeys(labels))
|
|
1361
|
-
first.label = ", ".join(uniq[:3]) + (" …" if len(uniq) > 3 else "")
|
|
1362
|
-
first.label = (first.label + " " if first.label else "") + f"×{first.multiplicity}"
|
|
1363
1361
|
for x in es[1:]:
|
|
1364
1362
|
dropped.add(x)
|
|
1365
1363
|
for ep in (x.src, x.dst):
|
|
@@ -1435,8 +1433,6 @@ def _apply_styles(g: Graph):
|
|
|
1435
1433
|
base.update(e.style)
|
|
1436
1434
|
if "color" in base and "stroke" not in e.style:
|
|
1437
1435
|
base["stroke"] = base["color"]
|
|
1438
|
-
if "label-color" not in cls and "label-color" not in e.style and "color" in (cls.keys() | e.style.keys()):
|
|
1439
|
-
base["label-color"] = st.mix(base["color"], theme["text"], 0.35)
|
|
1440
1436
|
if "stroke-width-scale" in e.style:
|
|
1441
1437
|
base["stroke-width"] = base.get("stroke-width", 1.3) * e.style["stroke-width-scale"]
|
|
1442
1438
|
for raw in [st.class_raw(e.classes, g.classes, "edge")]:
|
|
@@ -6,6 +6,7 @@ from typing import Dict
|
|
|
6
6
|
|
|
7
7
|
from ..geometry import Rect
|
|
8
8
|
from ..model import Graph, Subsystem
|
|
9
|
+
from ..progress import report
|
|
9
10
|
from . import sizing
|
|
10
11
|
from .hierarchy import (HierarchicalPlacer, apply_keep_together, auto_cluster, build_layout_edges, lift_hints,
|
|
11
12
|
translate_element)
|
|
@@ -45,6 +46,7 @@ def unify_instances(g: Graph) -> int:
|
|
|
45
46
|
def layout(g: Graph) -> Dict[str, float]:
|
|
46
47
|
t0 = time.time()
|
|
47
48
|
stats: Dict[str, float] = {}
|
|
49
|
+
report("Placement")
|
|
48
50
|
sizing.size_all_nodes(g)
|
|
49
51
|
unify_instances(g)
|
|
50
52
|
apply_keep_together(g)
|
|
@@ -57,10 +59,16 @@ def layout(g: Graph) -> Dict[str, float]:
|
|
|
57
59
|
level_hints = lift_hints(g)
|
|
58
60
|
placer = HierarchicalPlacer(g, ledges, level_hints)
|
|
59
61
|
placer.run()
|
|
62
|
+
report("Placement complete")
|
|
60
63
|
g.extras["placer"] = placer
|
|
61
64
|
stats["place_time"] = time.time() - t0
|
|
62
65
|
stats.update({k: float(v) for k, v in placer.stats.items()})
|
|
63
66
|
finish_ports(g, placer)
|
|
67
|
+
for _ in range(3):
|
|
68
|
+
if not resize_for_ports(g):
|
|
69
|
+
break
|
|
70
|
+
placer.rerun({}, {})
|
|
71
|
+
finish_ports(g, placer)
|
|
64
72
|
stats["layout_time"] = time.time() - t0
|
|
65
73
|
g.layout_stats.update(stats)
|
|
66
74
|
return stats
|
|
@@ -68,6 +76,10 @@ def layout(g: Graph) -> Dict[str, float]:
|
|
|
68
76
|
|
|
69
77
|
def finish_ports(g: Graph, placer):
|
|
70
78
|
# ports, then a port-aware refinement pass, then final sliding
|
|
79
|
+
counted = not g.settings.float_ports or g.settings.routing == "straight"
|
|
80
|
+
total = len(g.all_ports()) if counted else 0
|
|
81
|
+
if counted:
|
|
82
|
+
report("Port assignment", 0, total)
|
|
71
83
|
for p in g.all_ports():
|
|
72
84
|
p.assigned_side = None
|
|
73
85
|
assign_ports(g, slide=False)
|
|
@@ -76,17 +88,15 @@ def finish_ports(g: Graph, placer):
|
|
|
76
88
|
distribute_ports(g)
|
|
77
89
|
slide_ports(g)
|
|
78
90
|
normalize(g)
|
|
91
|
+
if counted:
|
|
92
|
+
report("Port assignment", total, total)
|
|
79
93
|
|
|
80
94
|
|
|
81
95
|
def resize_for_ports(g: Graph) -> int:
|
|
82
|
-
"""Grow blocks
|
|
83
|
-
from collections import Counter
|
|
84
|
-
pitch = max(g.settings.port_spacing, 2.2 * g.settings.edge_spacing)
|
|
96
|
+
"""Grow blocks to fit the labels on their actual assigned sides."""
|
|
85
97
|
grown = 0
|
|
86
98
|
for n in g.nodes():
|
|
87
|
-
|
|
88
|
-
need_h = (max(cnt.get("E", 0), cnt.get("W", 0)) + 1) * pitch
|
|
89
|
-
need_w = (max(cnt.get("N", 0), cnt.get("S", 0)) + 1) * pitch
|
|
99
|
+
need_w, need_h = sizing.size_node(n, g.settings, sizing.flow_of(n.parent, g.settings))
|
|
90
100
|
if need_h > n.h + 1:
|
|
91
101
|
n.h = float(int(need_h + 1.99) // 2 * 2)
|
|
92
102
|
grown += 1
|
|
@@ -95,6 +105,10 @@ def resize_for_ports(g: Graph) -> int:
|
|
|
95
105
|
grown += 1
|
|
96
106
|
if grown:
|
|
97
107
|
unify_instances(g)
|
|
108
|
+
# Subsystem dimensions are calculated by the placer, rather than set directly.
|
|
109
|
+
for sub in g.subsystems():
|
|
110
|
+
w, h = sizing.port_dimensions(sub, g.settings, sizing.flow_of(sub, g.settings))
|
|
111
|
+
grown += int(w > sub.w + 1 or h > sub.h + 1)
|
|
98
112
|
return grown
|
|
99
113
|
|
|
100
114
|
|
|
@@ -110,10 +124,12 @@ def expand_congested(g: Graph, route_fn, max_iters: int = 1) -> int:
|
|
|
110
124
|
grown = resize_for_ports(g)
|
|
111
125
|
if worst < 6.0 and not grown:
|
|
112
126
|
break
|
|
127
|
+
report("Congestion refinement", done, max_iters)
|
|
113
128
|
placer.rerun(margin, pad)
|
|
114
129
|
finish_ports(g, placer)
|
|
115
|
-
route_fn(g)
|
|
116
130
|
done += 1
|
|
131
|
+
report("Congestion refinement", done, max_iters)
|
|
132
|
+
route_fn(g)
|
|
117
133
|
g.layout_stats["spacing_iterations"] = done
|
|
118
134
|
return done
|
|
119
135
|
|
|
@@ -28,6 +28,7 @@ from typing import Dict, Hashable, List, Optional, Sequence, Tuple
|
|
|
28
28
|
import numpy as np
|
|
29
29
|
|
|
30
30
|
from ..geometry import E, N, S, W
|
|
31
|
+
from ..progress import activity
|
|
31
32
|
|
|
32
33
|
FLOW_VEC = {"LR": (1.0, 0.0), "RL": (-1.0, 0.0), "TB": (0.0, 1.0), "BT": (0.0, -1.0)}
|
|
33
34
|
|
|
@@ -316,7 +317,7 @@ class Floorplanner:
|
|
|
316
317
|
qy = cya[eb] + self._offby[er, var_a[eb]] * sgy[eb]
|
|
317
318
|
dx = qx - px
|
|
318
319
|
dy = qy - py
|
|
319
|
-
wl = float(
|
|
320
|
+
wl = float((self._ew * (np.abs(dx) + np.abs(dy))).sum())
|
|
320
321
|
bd["wire"] = wts["wire"] * wl / (self.L0 * self.wsum)
|
|
321
322
|
if self.flow_mult > 0:
|
|
322
323
|
ux, uy = self.flow_vec
|
|
@@ -332,23 +333,24 @@ class Floorplanner:
|
|
|
332
333
|
# perpendicular edges are tolerable, backward edges are expensive
|
|
333
334
|
viol = (0.5 * np.maximum(0.0, delta - np.maximum(along, 0.0)) + 1.5 * np.maximum(0.0, -along))
|
|
334
335
|
viol = viol * self._eflow
|
|
335
|
-
bd["flow"] = wts["flow"] * self.flow_mult * float(
|
|
336
|
+
bd["flow"] = wts["flow"] * self.flow_mult * float((self._ew * viol).sum()) / (self.L0 * self.wsum)
|
|
336
337
|
else:
|
|
337
338
|
bd["flow"] = 0.0
|
|
338
339
|
if wts.get("bend", 0) > 0:
|
|
339
340
|
ha, hb, wa, wb = rha[ea], rha[eb], rwa[ea], rwa[eb]
|
|
340
341
|
sy = np.maximum(0.0, np.abs(dy) - 0.35 * np.minimum(np.minimum(ha, hb), 90.0))
|
|
341
342
|
sx = np.maximum(0.0, np.abs(dx) - 0.35 * np.minimum(np.minimum(wa, wb), 120.0))
|
|
342
|
-
bd["bend"] = wts["bend"] * float(
|
|
343
|
+
bd["bend"] = wts["bend"] * float((self._ew * self._estruct * np.minimum(sx, sy)).sum()) / (self.L0 * self.wsum)
|
|
343
344
|
else:
|
|
344
345
|
bd["bend"] = 0.0
|
|
345
346
|
if self.use_crossings and ne >= 2:
|
|
346
347
|
o1 = (qx[:, None] - px[:, None]) * (py[None, :] - py[:, None]) - (qy[:, None] - py[:, None]) * (px[None, :] - px[:, None])
|
|
347
348
|
o2 = (qx[:, None] - px[:, None]) * (qy[None, :] - py[:, None]) - (qy[:, None] - py[:, None]) * (qx[None, :] - px[:, None])
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
349
|
+
# The reverse orientation tests are the transposes of these
|
|
350
|
+
# same matrices; avoid recomputing two edge-by-edge products.
|
|
351
|
+
opposite = o1 * o2 < -1e-9
|
|
352
|
+
cross = opposite & opposite.T & self._cross_mask
|
|
353
|
+
wcross = float((cross * self._ewpair).sum())
|
|
352
354
|
bd["crossing"] = wts["crossing"] * wcross / max(1.0, math.sqrt(ne))
|
|
353
355
|
else:
|
|
354
356
|
bd["crossing"] = 0.0
|
|
@@ -373,14 +375,12 @@ class Floorplanner:
|
|
|
373
375
|
hit = bbox & ~(allpos | allneg)
|
|
374
376
|
own = self._own
|
|
375
377
|
hit &= ~own
|
|
376
|
-
line_hits =
|
|
378
|
+
line_hits = hit.sum(axis=1).astype(float)
|
|
377
379
|
# orthogonal wires take one of the two L-shaped paths between the pins; a block
|
|
378
380
|
# only gets in the way when it obstructs both of them
|
|
379
381
|
pxc, pyc, qxc, qyc = px[:, None], py[:, None], qx[:, None], qy[:, None]
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
in_x = (lox < rx1[None, :]) & (hix > rx0[None, :])
|
|
383
|
-
in_y = (loy < ry1[None, :]) & (hiy > ry0[None, :])
|
|
382
|
+
in_x = (minx < rx1[None, :]) & (maxx > rx0[None, :])
|
|
383
|
+
in_y = (miny < ry1[None, :]) & (maxy > ry0[None, :])
|
|
384
384
|
|
|
385
385
|
def hseg(yv):
|
|
386
386
|
return in_x & (yv > ry0[None, :]) & (yv < ry1[None, :])
|
|
@@ -389,7 +389,7 @@ class Floorplanner:
|
|
|
389
389
|
return in_y & (xv > rx0[None, :]) & (xv < rx1[None, :])
|
|
390
390
|
l1 = (hseg(pyc) | vseg(qxc)) & ~own # horizontal first, then vertical
|
|
391
391
|
l2 = (vseg(pxc) | hseg(qyc)) & ~own # vertical first, then horizontal
|
|
392
|
-
line_hits = np.minimum(line_hits, np.minimum(
|
|
392
|
+
line_hits = np.minimum(line_hits, np.minimum(l1.sum(axis=1), l2.sum(axis=1)))
|
|
393
393
|
# orthogonal corridor blocking: when two connected blocks face each other (overlap
|
|
394
394
|
# in x and are stacked, or overlap in y and sit side by side) their wires run
|
|
395
395
|
# inside the shared channel; a third block sitting in that channel forces the
|
|
@@ -412,10 +412,10 @@ class Floorplanner:
|
|
|
412
412
|
iy = np.where(own, 0.0, iy)
|
|
413
413
|
span = np.where(vert, kx1 - kx0, ky1 - ky0)
|
|
414
414
|
blocked = np.where(vert[:, None], ix * (iy > 0), iy * (ix > 0))
|
|
415
|
-
frac = np.minimum(1.0,
|
|
415
|
+
frac = np.minimum(1.0, blocked.sum(axis=1) / np.maximum(span, 1.0))
|
|
416
416
|
frac = np.where(vert | horz, frac, 0.0)
|
|
417
417
|
line_hits = np.maximum(line_hits, frac)
|
|
418
|
-
bd["through"] = wts["through"] * float(
|
|
418
|
+
bd["through"] = wts["through"] * float((line_hits * self._ew * self._estruct).sum()) / max(1.0, math.sqrt(ne))
|
|
419
419
|
else:
|
|
420
420
|
bd["through"] = 0.0
|
|
421
421
|
else:
|
|
@@ -748,15 +748,33 @@ class Floorplanner:
|
|
|
748
748
|
def anneal(self, init: FPState, moves: int, accept0: float = 0.75, deadline: Optional[float] = None
|
|
749
749
|
) -> Tuple[FPState, float]:
|
|
750
750
|
n = self.n
|
|
751
|
+
# The objective is unchanged during this anneal. Neighbour moves often
|
|
752
|
+
# revisit exactly the same state, especially for small subsystem levels.
|
|
753
|
+
# Cache only scores, and snapshot the mutable state lists in the key.
|
|
754
|
+
scores = {}
|
|
755
|
+
|
|
756
|
+
def evaluate(st):
|
|
757
|
+
key = (tuple(st.gp), tuple(st.gm), tuple(st.var), tuple(st.fx), tuple(st.fy))
|
|
758
|
+
cost = scores.get(key)
|
|
759
|
+
if cost is not None:
|
|
760
|
+
self.evals += 1
|
|
761
|
+
return cost
|
|
762
|
+
cost = self.evaluate(st)
|
|
763
|
+
# Bound memory only; eviction never skips a candidate or a move.
|
|
764
|
+
if len(scores) >= 8192:
|
|
765
|
+
scores.clear()
|
|
766
|
+
scores[key] = cost
|
|
767
|
+
return cost
|
|
768
|
+
|
|
751
769
|
cur = self.tie(init.copy())
|
|
752
|
-
cur_cost =
|
|
770
|
+
cur_cost = evaluate(cur)
|
|
753
771
|
best, best_cost = cur.copy(), cur_cost
|
|
754
772
|
if n <= 1 or moves <= 0:
|
|
755
773
|
return best, best_cost
|
|
756
774
|
# initial temperature from uphill move statistics
|
|
757
775
|
ups = []
|
|
758
776
|
for _ in range(min(80, 10 + 4 * n)):
|
|
759
|
-
c =
|
|
777
|
+
c = evaluate(self.neighbor(cur))
|
|
760
778
|
if c > cur_cost:
|
|
761
779
|
ups.append(c - cur_cost)
|
|
762
780
|
avg_up = (sum(ups) / len(ups)) if ups else max(1e-3, 0.05 * abs(cur_cost))
|
|
@@ -767,11 +785,12 @@ class Floorplanner:
|
|
|
767
785
|
alpha = (2e-4) ** (1.0 / steps)
|
|
768
786
|
done = 0
|
|
769
787
|
stale = 0
|
|
788
|
+
activity(step=f"0/{moves} moves")
|
|
770
789
|
while done < moves:
|
|
771
790
|
improved = False
|
|
772
791
|
for _ in range(per_T):
|
|
773
792
|
cand = self.neighbor(cur)
|
|
774
|
-
c =
|
|
793
|
+
c = evaluate(cand)
|
|
775
794
|
d = c - cur_cost
|
|
776
795
|
if d <= 0 or self.rng.random() < math.exp(-d / max(T, 1e-12)):
|
|
777
796
|
cur, cur_cost = cand, c
|
|
@@ -780,16 +799,18 @@ class Floorplanner:
|
|
|
780
799
|
improved = True
|
|
781
800
|
done += 1
|
|
782
801
|
T *= alpha
|
|
802
|
+
activity(step=f"{min(done, moves)}/{moves} moves")
|
|
783
803
|
stale = 0 if improved else stale + 1
|
|
784
804
|
if deadline is not None and time.time() > deadline:
|
|
785
805
|
break
|
|
786
806
|
if stale > max(12, steps // 4) and T < T0 * 0.01:
|
|
787
807
|
break
|
|
788
808
|
# greedy polish from best
|
|
809
|
+
activity(step="polishing")
|
|
789
810
|
cur, cur_cost = best.copy(), best_cost
|
|
790
811
|
for _ in range(min(2000, 40 * n)):
|
|
791
812
|
cand = self.neighbor(cur)
|
|
792
|
-
c =
|
|
813
|
+
c = evaluate(cand)
|
|
793
814
|
if c < cur_cost - 1e-12:
|
|
794
815
|
cur, cur_cost = cand, c
|
|
795
816
|
if cur_cost < best_cost:
|