Remove ELK and baseline Rust solver performance

This commit is contained in:
Meta Vens 2026-05-19 20:33:49 +01:00
commit 1b3a07f09a
17 changed files with 6219 additions and 886 deletions

View file

@ -6,10 +6,10 @@ The live package is `web/epiphany-graph-viewer`. It is what clients and agents
should reach for when they want to render `architecture`, `dataflow`, and typed
cross-links in a UI.
The older .NET generator, MSAGL SVG renderer, MCP server, and ELK probe still exist
as support tools for note exports and layout experiments. They are not competing
client surfaces. Do not make consumers choose between engines like this repo is a
sad little trade show booth.
The older .NET generator, MSAGL SVG renderer, and MCP server still exist as support
tools for note exports and legacy inspection. They are not competing client surfaces.
Do not make consumers choose between engines like this repo is a sad little trade
show booth.
## React Viewer
@ -211,10 +211,6 @@ That keeps structurally separated note graphs from vanishing just because they n
- `msagl`: the stock MSAGL SVG, useful as the plain baseline
- `rich`: a custom SVG renderer that consumes MSAGL graph geometry and semantic metadata, then draws something with more visual hierarchy and less beige despair
## ELK Probe
There is also a small web-side `elkjs` probe under `web/elk-probe/`. It inspects which algorithms and options are actually present in the installed `elkjs` build and can relayout existing `source-tree.json` / `control-flow.json` outputs without crossing the C# boundary.
## Support Tool: Epiphany Graph Viewer Demo
The web-side viewer package lives under `web/epiphany-graph-viewer/`.

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="460" viewBox="0 0 920 460">
<rect width="100%" height="100%" fill="#071019" />
<text x="74" y="32" fill="#e5eef8" font-family="Arial, sans-serif" font-size="22" font-weight="700">Rust Solver Convergence Loss By Iterations</text>
<line x1="74" y1="390" x2="884" y2="390" stroke="#64748b" />
<line x1="74" y1="58" x2="74" y2="390" stroke="#64748b" />
<text x="479" y="438" fill="#cbd5e1" font-family="Arial, sans-serif" font-size="14" text-anchor="middle">iterations</text>
<text x="22" y="224" fill="#cbd5e1" font-family="Arial, sans-serif" font-size="14" text-anchor="middle" transform="rotate(-90 22 224)">normalized loss</text>
<text x="74" y="412" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12">32</text>
<text x="884" y="412" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">224</text>
<text x="64" y="390" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">0</text>
<text x="64" y="62" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">0.2308</text>
<path d="M 74.00 93.57 L 344.00 154.87 L 884.00 214.72" fill="none" stroke="#f472b6" stroke-width="3" /><circle cx="74.00" cy="93.57" r="4" fill="#f472b6" /><circle cx="344.00" cy="154.87" r="4" fill="#f472b6" /><circle cx="884.00" cy="214.72" r="4" fill="#f472b6" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="460" viewBox="0 0 920 460">
<rect width="100%" height="100%" fill="#071019" />
<text x="74" y="32" fill="#e5eef8" font-family="Arial, sans-serif" font-size="22" font-weight="700">Rust Solver P95 Latency By Graph Size</text>
<line x1="74" y1="390" x2="884" y2="390" stroke="#64748b" />
<line x1="74" y1="58" x2="74" y2="390" stroke="#64748b" />
<text x="479" y="438" fill="#cbd5e1" font-family="Arial, sans-serif" font-size="14" text-anchor="middle">nodes</text>
<text x="22" y="224" fill="#cbd5e1" font-family="Arial, sans-serif" font-size="14" text-anchor="middle" transform="rotate(-90 22 224)">p95 ms</text>
<text x="74" y="412" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12">24</text>
<text x="884" y="412" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">1024</text>
<text x="64" y="390" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">0</text>
<text x="64" y="62" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">212.6332</text>
<path d="M 74.00 389.84 L 106.40 389.19 L 158.24 386.69 L 261.92 371.72 L 469.28 333.64 L 884.00 93.57" fill="none" stroke="#22d3ee" stroke-width="3" /><circle cx="74.00" cy="389.84" r="4" fill="#22d3ee" /><circle cx="106.40" cy="389.19" r="4" fill="#22d3ee" /><circle cx="158.24" cy="386.69" r="4" fill="#22d3ee" /><circle cx="261.92" cy="371.72" r="4" fill="#22d3ee" /><circle cx="469.28" cy="333.64" r="4" fill="#22d3ee" /><circle cx="884.00" cy="93.57" r="4" fill="#22d3ee" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,271 @@
family,nodeCount,edgeCount,profileId,iterations,rankGap,nodeGap,edgeLength,latencyMs,p95LatencyMs,loss,edgeStress,referenceIterations
chain,24,23,fast-flat,32,96,76,130,0.2488,0.2488,0.2791,0.254,360
chain,24,23,fast-flat,96,96,76,130,0.5909,0.5909,0.2788,0.2526,360
chain,24,23,fast-flat,224,96,76,130,1.1465,1.1465,0.2788,0.2525,360
chain,24,23,balanced-hierarchy,32,136,92,170,0.1267,0.1267,0.1465,0.1953,360
chain,24,23,balanced-hierarchy,96,136,92,170,0.2492,0.2492,0.1463,0.1945,360
chain,24,23,balanced-hierarchy,224,136,92,170,0.5487,0.5487,0.1463,0.1945,360
chain,24,23,quality-spacious,32,180,118,230,0.1107,0.1107,0.0003,0.2131,360
chain,24,23,quality-spacious,96,180,118,230,0.2452,0.2452,0,0.2123,360
chain,24,23,quality-spacious,224,180,118,230,0.5297,0.5297,0,0.2123,360
chain,64,63,fast-flat,32,96,76,130,0.7609,0.7609,0.273,0.2587,360
chain,64,63,fast-flat,96,96,76,130,1.6521,1.6521,0.273,0.2582,360
chain,64,63,fast-flat,224,96,76,130,5.399,5.399,0.273,0.2582,360
chain,64,63,balanced-hierarchy,32,136,92,170,0.5904,0.5904,0.1431,0.1983,360
chain,64,63,balanced-hierarchy,96,136,92,170,2.5069,2.5069,0.143,0.198,360
chain,64,63,balanced-hierarchy,224,136,92,170,4.2243,4.2243,0.143,0.198,360
chain,64,63,quality-spacious,32,180,118,230,2.9015,2.9015,0.0001,0.2158,360
chain,64,63,quality-spacious,96,180,118,230,1.7075,1.7075,0,0.2155,360
chain,64,63,quality-spacious,224,180,118,230,3.5914,3.5914,0,0.2155,360
chain,128,127,fast-flat,32,96,76,130,2.8866,2.8866,0.2712,0.2602,360
chain,128,127,fast-flat,96,96,76,130,13.7612,13.7612,0.2712,0.2599,360
chain,128,127,fast-flat,224,96,76,130,46.0941,46.0941,0.2712,0.2599,360
chain,128,127,balanced-hierarchy,32,136,92,170,5.6582,5.6582,0.1421,0.1992,360
chain,128,127,balanced-hierarchy,96,136,92,170,38.0374,38.0374,0.1421,0.199,360
chain,128,127,balanced-hierarchy,224,136,92,170,35.1158,35.1158,0.1421,0.199,360
chain,128,127,quality-spacious,32,180,118,230,30.4521,30.4521,0,0.2166,360
chain,128,127,quality-spacious,96,180,118,230,49.3048,49.3048,0,0.2165,360
chain,128,127,quality-spacious,224,180,118,230,37.4902,37.4902,0,0.2165,360
chain,256,255,fast-flat,32,96,76,130,14.7115,14.7115,0.2703,0.2608,360
chain,256,255,fast-flat,96,96,76,130,83.9211,83.9211,0.2703,0.2607,360
chain,256,255,fast-flat,224,96,76,130,195.3656,195.3656,0.2703,0.2607,360
chain,256,255,balanced-hierarchy,32,136,92,170,18.5755,18.5755,0.1416,0.1996,360
chain,256,255,balanced-hierarchy,96,136,92,170,24.3222,24.3222,0.1416,0.1995,360
chain,256,255,balanced-hierarchy,224,136,92,170,192.6467,192.6467,0.1416,0.1995,360
chain,256,255,quality-spacious,32,180,118,230,32.1363,32.1363,0,0.217,360
chain,256,255,quality-spacious,96,180,118,230,59.932,59.932,0,0.2169,360
chain,256,255,quality-spacious,224,180,118,230,112.9374,112.9374,0,0.2169,360
chain,512,511,fast-flat,32,96,76,130,131.2833,131.2833,0.2699,0.2612,280
chain,512,511,fast-flat,96,96,76,130,354.3509,354.3509,0.2699,0.2611,280
chain,512,511,fast-flat,224,96,76,130,458.5302,458.5302,0.2699,0.2611,280
chain,512,511,balanced-hierarchy,32,136,92,170,55.493,55.493,0.1414,0.1998,280
chain,512,511,balanced-hierarchy,96,136,92,170,184.8303,184.8303,0.1414,0.1998,280
chain,512,511,balanced-hierarchy,224,136,92,170,632.1172,632.1172,0.1414,0.1997,280
chain,512,511,quality-spacious,32,180,118,230,44.7984,44.7984,0,0.2172,280
chain,512,511,quality-spacious,96,180,118,230,140.1735,140.1735,0,0.2172,280
chain,512,511,quality-spacious,224,180,118,230,564.6244,564.6244,0,0.2172,280
chain,1024,1023,fast-flat,32,96,76,130,263.6441,263.6441,0.2697,0.2614,220
chain,1024,1023,fast-flat,96,96,76,130,1133.1209,1133.1209,0.2697,0.2613,220
chain,1024,1023,fast-flat,224,96,76,130,1582.2375,1582.2375,0.2697,0.2613,220
chain,1024,1023,balanced-hierarchy,32,136,92,170,207.5773,207.5773,0.1412,0.1999,220
chain,1024,1023,balanced-hierarchy,96,136,92,170,991.8572,991.8572,0.1412,0.1999,220
chain,1024,1023,balanced-hierarchy,224,136,92,170,2407.9727,2407.9727,0.1412,0.1999,220
chain,1024,1023,quality-spacious,32,180,118,230,221.886,221.886,0,0.2173,220
chain,1024,1023,quality-spacious,96,180,118,230,728.9886,728.9886,0,0.2173,220
chain,1024,1023,quality-spacious,224,180,118,230,2212.4852,2212.4852,0,0.2173,220
layered-dag,24,34,fast-flat,32,96,76,130,0.1299,0.1299,0.3017,0.1345,360
layered-dag,24,34,fast-flat,96,96,76,130,0.2868,0.2868,0.2756,0.1621,360
layered-dag,24,34,fast-flat,224,96,76,130,1.0623,1.0623,0.2529,0.1792,360
layered-dag,24,34,balanced-hierarchy,32,136,92,170,0.1949,0.1949,0.2016,0.1023,360
layered-dag,24,34,balanced-hierarchy,96,136,92,170,0.3345,0.3345,0.1768,0.1165,360
layered-dag,24,34,balanced-hierarchy,224,136,92,170,0.6188,0.6188,0.149,0.1318,360
layered-dag,24,34,quality-spacious,32,180,118,230,0.114,0.114,0.092,0.1122,360
layered-dag,24,34,quality-spacious,96,180,118,230,0.2849,0.2849,0.0692,0.106,360
layered-dag,24,34,quality-spacious,224,180,118,230,0.7908,0.7908,0.0328,0.1169,360
layered-dag,64,105,fast-flat,32,96,76,130,2.2729,2.2729,0.291,0.1266,360
layered-dag,64,105,fast-flat,96,96,76,130,93.8521,93.8521,0.2708,0.1509,360
layered-dag,64,105,fast-flat,224,96,76,130,7.0965,7.0965,0.25,0.1829,360
layered-dag,64,105,balanced-hierarchy,32,136,92,170,0.5319,0.5319,0.1864,0.0994,360
layered-dag,64,105,balanced-hierarchy,96,136,92,170,1.5299,1.5299,0.1684,0.1086,360
layered-dag,64,105,balanced-hierarchy,224,136,92,170,6.9493,6.9493,0.1457,0.1267,360
layered-dag,64,105,quality-spacious,32,180,118,230,0.6223,0.6223,0.0719,0.1225,360
layered-dag,64,105,quality-spacious,96,180,118,230,1.5497,1.5497,0.0552,0.1139,360
layered-dag,64,105,quality-spacious,224,180,118,230,81.4445,81.4445,0.0262,0.1108,360
layered-dag,128,223,fast-flat,32,96,76,130,2.2184,2.2184,0.2708,0.1296,360
layered-dag,128,223,fast-flat,96,96,76,130,9.1519,9.1519,0.2566,0.1477,360
layered-dag,128,223,fast-flat,224,96,76,130,112.1169,112.1169,0.2407,0.1831,360
layered-dag,128,223,balanced-hierarchy,32,136,92,170,3.6201,3.6201,0.1661,0.1019,360
layered-dag,128,223,balanced-hierarchy,96,136,92,170,7.4542,7.4542,0.1539,0.1062,360
layered-dag,128,223,balanced-hierarchy,224,136,92,170,99.9972,99.9972,0.1375,0.1247,360
layered-dag,128,223,quality-spacious,32,180,118,230,3.806,3.806,0.0533,0.1273,360
layered-dag,128,223,quality-spacious,96,180,118,230,10.8485,10.8485,0.0413,0.1196,360
layered-dag,128,223,quality-spacious,224,180,118,230,32.5006,32.5006,0.02,0.114,360
layered-dag,256,465,fast-flat,32,96,76,130,17.6227,17.6227,0.2708,0.1327,360
layered-dag,256,465,fast-flat,96,96,76,130,107.2987,107.2987,0.2593,0.1362,360
layered-dag,256,465,fast-flat,224,96,76,130,219.4445,219.4445,0.2449,0.1659,360
layered-dag,256,465,balanced-hierarchy,32,136,92,170,92.2619,92.2619,0.1623,0.1038,360
layered-dag,256,465,balanced-hierarchy,96,136,92,170,35.3534,35.3534,0.1529,0.1023,360
layered-dag,256,465,balanced-hierarchy,224,136,92,170,341.3665,341.3665,0.1393,0.1147,360
layered-dag,256,465,quality-spacious,32,180,118,230,22.812,22.812,0.0419,0.1304,360
layered-dag,256,465,quality-spacious,96,180,118,230,84.4691,84.4691,0.0329,0.1243,360
layered-dag,256,465,quality-spacious,224,180,118,230,157.2866,157.2866,0.0162,0.1184,360
layered-dag,512,956,fast-flat,32,96,76,130,171.0027,171.0027,0.2519,0.1371,280
layered-dag,512,956,fast-flat,96,96,76,130,440.7858,440.7858,0.2439,0.1353,280
layered-dag,512,956,fast-flat,224,96,76,130,894.2877,894.2877,0.2333,0.1559,280
layered-dag,512,956,balanced-hierarchy,32,136,92,170,101.5174,101.5174,0.1453,0.1062,280
layered-dag,512,956,balanced-hierarchy,96,136,92,170,301.8864,301.8864,0.1391,0.1034,280
layered-dag,512,956,balanced-hierarchy,224,136,92,170,681.1257,681.1257,0.1299,0.1093,280
layered-dag,512,956,quality-spacious,32,180,118,230,112.146,112.146,0.0231,0.1328,280
layered-dag,512,956,quality-spacious,96,180,118,230,274.3755,274.3755,0.0168,0.1278,280
layered-dag,512,956,quality-spacious,224,180,118,230,487.8526,487.8526,0.0049,0.1226,280
layered-dag,1024,1953,fast-flat,32,96,76,130,265.6747,265.6747,0.2579,0.1416,220
layered-dag,1024,1953,fast-flat,96,96,76,130,1034.2477,1034.2477,0.2521,0.1369,220
layered-dag,1024,1953,fast-flat,224,96,76,130,2487.4745,2487.4745,0.244,0.1456,220
layered-dag,1024,1953,balanced-hierarchy,32,136,92,170,341.3757,341.3757,0.1448,0.1084,220
layered-dag,1024,1953,balanced-hierarchy,96,136,92,170,557.8116,557.8116,0.1405,0.1051,220
layered-dag,1024,1953,balanced-hierarchy,224,136,92,170,1428.6917,1428.6917,0.1338,0.1065,220
layered-dag,1024,1953,quality-spacious,32,180,118,230,259.2804,259.2804,0.0134,0.1349,220
layered-dag,1024,1953,quality-spacious,96,180,118,230,679.6793,679.6793,0.0087,0.131,220
layered-dag,1024,1953,quality-spacious,224,180,118,230,1677.3771,1677.3771,0.0003,0.1265,220
binary-tree,24,23,fast-flat,32,96,76,130,0.0994,0.0994,0.2315,0.2278,360
binary-tree,24,23,fast-flat,96,96,76,130,0.3308,0.3308,0.2102,0.1893,360
binary-tree,24,23,fast-flat,224,96,76,130,0.6879,0.6879,0.1871,0.1854,360
binary-tree,24,23,balanced-hierarchy,32,136,92,170,0.1173,0.1173,0.1641,0.1949,360
binary-tree,24,23,balanced-hierarchy,96,136,92,170,0.2445,0.2445,0.1411,0.1605,360
binary-tree,24,23,balanced-hierarchy,224,136,92,170,0.6525,0.6525,0.1158,0.1338,360
binary-tree,24,23,quality-spacious,32,180,118,230,0.1089,0.1089,0.1231,0.1869,360
binary-tree,24,23,quality-spacious,96,180,118,230,0.302,0.302,0.0851,0.1552,360
binary-tree,24,23,quality-spacious,224,180,118,230,1.4987,1.4987,0.0361,0.1217,360
binary-tree,64,63,fast-flat,32,96,76,130,0.5334,0.5334,0.2169,1.4057,360
binary-tree,64,63,fast-flat,96,96,76,130,1.5077,1.5077,0.1861,0.7332,360
binary-tree,64,63,fast-flat,224,96,76,130,47.4958,47.4958,0.1748,0.3761,360
binary-tree,64,63,balanced-hierarchy,32,136,92,170,0.5165,0.5165,0.1994,1.3141,360
binary-tree,64,63,balanced-hierarchy,96,136,92,170,2.4395,2.4395,0.133,0.6676,360
binary-tree,64,63,balanced-hierarchy,224,136,92,170,6.9034,6.9034,0.1084,0.3358,360
binary-tree,64,63,quality-spacious,32,180,118,230,0.6857,0.6857,0.2604,1.3039,360
binary-tree,64,63,quality-spacious,96,180,118,230,4.0524,4.0524,0.1344,0.6388,360
binary-tree,64,63,quality-spacious,224,180,118,230,4.8413,4.8413,0.0435,0.3092,360
binary-tree,128,127,fast-flat,32,96,76,130,3.3911,3.3911,0.2462,3.8579,360
binary-tree,128,127,fast-flat,96,96,76,130,8.1517,8.1517,0.1893,1.93,360
binary-tree,128,127,fast-flat,224,96,76,130,21.4976,21.4976,0.1745,0.7793,360
binary-tree,128,127,balanced-hierarchy,32,136,92,170,4.0164,4.0164,0.2648,3.7141,360
binary-tree,128,127,balanced-hierarchy,96,136,92,170,66.2096,66.2096,0.1642,1.9737,360
binary-tree,128,127,balanced-hierarchy,224,136,92,170,20.7004,20.7004,0.1075,0.7572,360
binary-tree,128,127,quality-spacious,32,180,118,230,2.358,2.358,0.3624,3.6958,360
binary-tree,128,127,quality-spacious,96,180,118,230,12.2222,12.2222,0.2339,2.1938,360
binary-tree,128,127,quality-spacious,224,180,118,230,21.1299,21.1299,0.0693,0.8203,360
binary-tree,256,255,fast-flat,32,96,76,130,16.9305,16.9305,0.1864,9.7142,360
binary-tree,256,255,fast-flat,96,96,76,130,110.6497,110.6497,0.1505,6.5718,360
binary-tree,256,255,fast-flat,224,96,76,130,161.4961,161.4961,0.1504,2.7779,360
binary-tree,256,255,balanced-hierarchy,32,136,92,170,14.3811,14.3811,0.1986,9.2064,360
binary-tree,256,255,balanced-hierarchy,96,136,92,170,57.5473,57.5473,0.1411,6.6632,360
binary-tree,256,255,balanced-hierarchy,224,136,92,170,106.9944,106.9944,0.0831,3.181,360
binary-tree,256,255,quality-spacious,32,180,118,230,37.3606,37.3606,0.2771,8.9666,360
binary-tree,256,255,quality-spacious,96,180,118,230,77.6774,77.6774,0.2118,6.9749,360
binary-tree,256,255,quality-spacious,224,180,118,230,156.5553,156.5553,0.0971,3.9117,360
binary-tree,512,511,fast-flat,32,96,76,130,122.1017,122.1017,0.1328,21.9711,280
binary-tree,512,511,fast-flat,96,96,76,130,187.3812,187.3812,0.1354,18.1953,280
binary-tree,512,511,fast-flat,224,96,76,130,571.7195,571.7195,0.1568,11.8925,280
binary-tree,512,511,balanced-hierarchy,32,136,92,170,53.5575,53.5575,0.0927,20.5908,280
binary-tree,512,511,balanced-hierarchy,96,136,92,170,148.5947,148.5947,0.0829,17.6309,280
binary-tree,512,511,balanced-hierarchy,224,136,92,170,398.2566,398.2566,0.0925,12.4995,280
binary-tree,512,511,quality-spacious,32,180,118,230,36.0988,36.0988,0.1106,19.794,280
binary-tree,512,511,quality-spacious,96,180,118,230,181.5769,181.5769,0.0809,17.5481,280
binary-tree,512,511,quality-spacious,224,180,118,230,528.5091,528.5091,0.0239,13.5086,280
binary-tree,1024,1023,fast-flat,32,96,76,130,210.2692,210.2692,0.1369,46.7937,220
binary-tree,1024,1023,fast-flat,96,96,76,130,610.4159,610.4159,0.1432,42.6892,220
binary-tree,1024,1023,fast-flat,224,96,76,130,2249.5615,2249.5615,0.1586,35.0941,220
binary-tree,1024,1023,balanced-hierarchy,32,136,92,170,271.652,271.652,0.0798,43.5894,220
binary-tree,1024,1023,balanced-hierarchy,96,136,92,170,1021.4519,1021.4519,0.0841,40.4131,220
binary-tree,1024,1023,balanced-hierarchy,224,136,92,170,3800.4651,3800.4651,0.0984,34.4479,220
binary-tree,1024,1023,quality-spacious,32,180,118,230,332.3558,332.3558,0.042,41.6159,220
binary-tree,1024,1023,quality-spacious,96,180,118,230,1071.4013,1071.4013,0.0276,39.238,220
binary-tree,1024,1023,quality-spacious,224,180,118,230,2616.4795,2616.4795,0.0009,34.7045,220
hub-spoke,24,29,fast-flat,32,96,76,130,0.1137,0.1137,0.1814,0.8226,360
hub-spoke,24,29,fast-flat,96,96,76,130,0.4694,0.4694,0.1492,0.4697,360
hub-spoke,24,29,fast-flat,224,96,76,130,1.098,1.098,0.1585,0.4307,360
hub-spoke,24,29,balanced-hierarchy,32,136,92,170,0.2271,0.2271,0.2224,0.8247,360
hub-spoke,24,29,balanced-hierarchy,96,136,92,170,0.4943,0.4943,0.0881,0.4002,360
hub-spoke,24,29,balanced-hierarchy,224,136,92,170,0.8968,0.8968,0.0954,0.3494,360
hub-spoke,24,29,quality-spacious,32,180,118,230,0.882,0.882,0.4151,0.9344,360
hub-spoke,24,29,quality-spacious,96,180,118,230,0.3203,0.3203,0.1192,0.3564,360
hub-spoke,24,29,quality-spacious,224,180,118,230,16.0776,16.0776,0.0234,0.2954,360
hub-spoke,64,78,fast-flat,32,96,76,130,0.6576,0.6576,0.8482,5.8995,360
hub-spoke,64,78,fast-flat,96,96,76,130,2.0299,2.0299,0.3601,2.2776,360
hub-spoke,64,78,fast-flat,224,96,76,130,6.3586,6.3586,0.1055,0.7324,360
hub-spoke,64,78,balanced-hierarchy,32,136,92,170,0.99,0.99,1.1505,5.8157,360
hub-spoke,64,78,balanced-hierarchy,96,136,92,170,11.8964,11.8964,0.6505,2.653,360
hub-spoke,64,78,balanced-hierarchy,224,136,92,170,5.8031,5.8031,0.0686,0.6729,360
hub-spoke,64,78,quality-spacious,32,180,118,230,1.5412,1.5412,1.6506,5.9046,360
hub-spoke,64,78,quality-spacious,96,180,118,230,6.3829,6.3829,1.1423,3.252,360
hub-spoke,64,78,quality-spacious,224,180,118,230,37.8104,37.8104,0.3026,0.8043,360
hub-spoke,128,162,fast-flat,32,96,76,130,2.4179,2.4179,0.3497,16.0025,360
hub-spoke,128,162,fast-flat,96,96,76,130,23.8242,23.8242,0.2072,10.6282,360
hub-spoke,128,162,fast-flat,224,96,76,130,68.7851,68.7851,0.1036,3.654,360
hub-spoke,128,162,balanced-hierarchy,32,136,92,170,5.8344,5.8344,0.4665,15.2361,360
hub-spoke,128,162,balanced-hierarchy,96,136,92,170,10.0863,10.0863,0.3271,10.9589,360
hub-spoke,128,162,balanced-hierarchy,224,136,92,170,22.1724,22.1724,0.0739,4.7465,360
hub-spoke,128,162,quality-spacious,32,180,118,230,3.3698,3.3698,0.6685,14.894,360
hub-spoke,128,162,quality-spacious,96,180,118,230,36.8697,36.8697,0.5377,11.6044,360
hub-spoke,128,162,quality-spacious,224,180,118,230,44.4775,44.4775,0.2847,6.3452,360
hub-spoke,256,328,fast-flat,32,96,76,130,33.5236,33.5236,0.1669,36.4441,360
hub-spoke,256,328,fast-flat,96,96,76,130,88.0661,88.0661,0.1327,30.3648,360
hub-spoke,256,328,fast-flat,224,96,76,130,116.2219,116.2219,0.1471,19.9784,360
hub-spoke,256,328,balanced-hierarchy,32,136,92,170,33.1609,33.1609,0.1822,34.19,360
hub-spoke,256,328,balanced-hierarchy,96,136,92,170,36.7797,36.7797,0.1303,29.4519,360
hub-spoke,256,328,balanced-hierarchy,224,136,92,170,122.1986,122.1986,0.0724,21.1357,360
hub-spoke,256,328,quality-spacious,32,180,118,230,54.9571,54.9571,0.2674,32.8891,360
hub-spoke,256,328,quality-spacious,96,180,118,230,72.35,72.35,0.2149,29.3306,360
hub-spoke,256,328,quality-spacious,224,180,118,230,184.0544,184.0544,0.1114,22.8709,360
hub-spoke,512,664,fast-flat,32,96,76,130,96.2749,96.2749,0.1496,78.9376,280
hub-spoke,512,664,fast-flat,96,96,76,130,304.5229,304.5229,0.1523,72.5247,280
hub-spoke,512,664,fast-flat,224,96,76,130,566.0394,566.0394,0.1742,60.5199,280
hub-spoke,512,664,balanced-hierarchy,32,136,92,170,88.6981,88.6981,0.0964,73.5417,280
hub-spoke,512,664,balanced-hierarchy,96,136,92,170,269.6925,269.6925,0.0906,68.597,280
hub-spoke,512,664,balanced-hierarchy,224,136,92,170,715.2272,715.2272,0.103,59.2111,280
hub-spoke,512,664,quality-spacious,32,180,118,230,107.2466,107.2466,0.0946,70.2089,280
hub-spoke,512,664,quality-spacious,96,180,118,230,385.9852,385.9852,0.0702,66.5199,280
hub-spoke,512,664,quality-spacious,224,180,118,230,666.3786,666.3786,0.0213,59.4487,280
hub-spoke,1024,1338,fast-flat,32,96,76,130,333.8144,333.8144,0.1596,164.564,220
hub-spoke,1024,1338,fast-flat,96,96,76,130,710.1158,710.1158,0.1637,157.9969,220
hub-spoke,1024,1338,fast-flat,224,96,76,130,2184.0934,2184.0934,0.1755,145.2562,220
hub-spoke,1024,1338,balanced-hierarchy,32,136,92,170,416.9324,416.9324,0.0953,152.8116,220
hub-spoke,1024,1338,balanced-hierarchy,96,136,92,170,1319.5015,1319.5015,0.0981,147.7718,220
hub-spoke,1024,1338,balanced-hierarchy,224,136,92,170,2762.4865,2762.4865,0.1087,137.9328,220
hub-spoke,1024,1338,quality-spacious,32,180,118,230,569.0331,569.0331,0.035,145.3619,220
hub-spoke,1024,1338,quality-spacious,96,180,118,230,1520.0452,1520.0452,0.0231,141.6236,220
hub-spoke,1024,1338,quality-spacious,224,180,118,230,2587.3624,2587.3624,0.0008,134.2803,220
clustered-modules,24,42,fast-flat,32,96,76,130,0.1194,0.1194,0.2896,0.3399,360
clustered-modules,24,42,fast-flat,96,96,76,130,0.2845,0.2845,0.2659,0.296,360
clustered-modules,24,42,fast-flat,224,96,76,130,0.5939,0.5939,0.2521,0.2863,360
clustered-modules,24,42,balanced-hierarchy,32,136,92,170,0.1046,0.1046,0.2055,0.3352,360
clustered-modules,24,42,balanced-hierarchy,96,136,92,170,0.2615,0.2615,0.1716,0.291,360
clustered-modules,24,42,balanced-hierarchy,224,136,92,170,0.7611,0.7611,0.145,0.2739,360
clustered-modules,24,42,quality-spacious,32,180,118,230,0.1697,0.1697,0.1496,0.3424,360
clustered-modules,24,42,quality-spacious,96,180,118,230,0.296,0.296,0.0954,0.2844,360
clustered-modules,24,42,quality-spacious,224,180,118,230,1.4378,1.4378,0.0376,0.2652,360
clustered-modules,64,120,fast-flat,32,96,76,130,0.6757,0.6757,0.2325,0.3366,360
clustered-modules,64,120,fast-flat,96,96,76,130,5.7168,5.7168,0.2231,0.3168,360
clustered-modules,64,120,fast-flat,224,96,76,130,12.3975,12.3975,0.2147,0.3104,360
clustered-modules,64,120,balanced-hierarchy,32,136,92,170,7.0531,7.0531,0.1446,0.3242,360
clustered-modules,64,120,balanced-hierarchy,96,136,92,170,3.4456,3.4456,0.133,0.2895,360
clustered-modules,64,120,balanced-hierarchy,224,136,92,170,6.3231,6.3231,0.1198,0.2798,360
clustered-modules,64,120,quality-spacious,32,180,118,230,1.0192,1.0192,0.0823,0.331,360
clustered-modules,64,120,quality-spacious,96,180,118,230,15.0834,15.0834,0.0585,0.2839,360
clustered-modules,64,120,quality-spacious,224,180,118,230,4.7643,4.7643,0.0268,0.2703,360
clustered-modules,128,244,fast-flat,32,96,76,130,2.7995,2.7995,0.2106,0.3718,360
clustered-modules,128,244,fast-flat,96,96,76,130,23.0144,23.0144,0.2056,0.3566,360
clustered-modules,128,244,fast-flat,224,96,76,130,25.9841,25.9841,0.1997,0.3588,360
clustered-modules,128,244,balanced-hierarchy,32,136,92,170,2.1172,2.1172,0.1217,0.3577,360
clustered-modules,128,244,balanced-hierarchy,96,136,92,170,6.8458,6.8458,0.1175,0.3296,360
clustered-modules,128,244,balanced-hierarchy,224,136,92,170,43.6508,43.6508,0.1095,0.326,360
clustered-modules,128,244,quality-spacious,32,180,118,230,5.6318,5.6318,0.0631,0.3736,360
clustered-modules,128,244,quality-spacious,96,180,118,230,24.4066,24.4066,0.0413,0.324,360
clustered-modules,128,244,quality-spacious,224,180,118,230,86.6985,86.6985,0.0186,0.3121,360
clustered-modules,256,496,fast-flat,32,96,76,130,30.7665,30.7665,0.2075,0.4111,360
clustered-modules,256,496,fast-flat,96,96,76,130,86.1928,86.1928,0.2057,0.4007,360
clustered-modules,256,496,fast-flat,224,96,76,130,121.0846,121.0846,0.202,0.3988,360
clustered-modules,256,496,balanced-hierarchy,32,136,92,170,15.2616,15.2616,0.1129,0.4058,360
clustered-modules,256,496,balanced-hierarchy,96,136,92,170,71.2521,71.2521,0.1135,0.3776,360
clustered-modules,256,496,balanced-hierarchy,224,136,92,170,135.0564,135.0564,0.1091,0.3705,360
clustered-modules,256,496,quality-spacious,32,180,118,230,11.7053,11.7053,0.0559,0.4309,360
clustered-modules,256,496,quality-spacious,96,180,118,230,50.246,50.246,0.0306,0.3761,360
clustered-modules,256,496,quality-spacious,224,180,118,230,133.2498,133.2498,0.0145,0.3616,360
clustered-modules,512,1000,fast-flat,32,96,76,130,172.2378,172.2378,0.2014,0.4756,280
clustered-modules,512,1000,fast-flat,96,96,76,130,288.0959,288.0959,0.2022,0.4434,280
clustered-modules,512,1000,fast-flat,224,96,76,130,502.8001,502.8001,0.1996,0.4416,280
clustered-modules,512,1000,balanced-hierarchy,32,136,92,170,95.3391,95.3391,0.107,0.4747,280
clustered-modules,512,1000,balanced-hierarchy,96,136,92,170,369.7732,369.7732,0.1089,0.4271,280
clustered-modules,512,1000,balanced-hierarchy,224,136,92,170,689.7119,689.7119,0.1069,0.4151,280
clustered-modules,512,1000,quality-spacious,32,180,118,230,141.8101,141.8101,0.0517,0.4906,280
clustered-modules,512,1000,quality-spacious,96,180,118,230,262.9639,262.9639,0.0188,0.4305,280
clustered-modules,512,1000,quality-spacious,224,180,118,230,456.8799,456.8799,0.0047,0.4081,280
clustered-modules,1024,2016,fast-flat,32,96,76,130,370.416,370.416,0.2011,0.5064,220
clustered-modules,1024,2016,fast-flat,96,96,76,130,1037.4821,1037.4821,0.2025,0.4666,220
clustered-modules,1024,2016,fast-flat,224,96,76,130,1788.4993,1788.4993,0.2006,0.47,220
clustered-modules,1024,2016,balanced-hierarchy,32,136,92,170,189.8511,189.8511,0.1064,0.5016,220
clustered-modules,1024,2016,balanced-hierarchy,96,136,92,170,660.9775,660.9775,0.1077,0.454,220
clustered-modules,1024,2016,balanced-hierarchy,224,136,92,170,1581.5562,1581.5562,0.1071,0.4426,220
clustered-modules,1024,2016,quality-spacious,32,180,118,230,246.5,246.5,0.0437,0.5135,220
clustered-modules,1024,2016,quality-spacious,96,180,118,230,596.422,596.422,0.0164,0.4659,220
clustered-modules,1024,2016,quality-spacious,224,180,118,230,2067.5246,2067.5246,0.0003,0.437,220
1 family nodeCount edgeCount profileId iterations rankGap nodeGap edgeLength latencyMs p95LatencyMs loss edgeStress referenceIterations
2 chain 24 23 fast-flat 32 96 76 130 0.2488 0.2488 0.2791 0.254 360
3 chain 24 23 fast-flat 96 96 76 130 0.5909 0.5909 0.2788 0.2526 360
4 chain 24 23 fast-flat 224 96 76 130 1.1465 1.1465 0.2788 0.2525 360
5 chain 24 23 balanced-hierarchy 32 136 92 170 0.1267 0.1267 0.1465 0.1953 360
6 chain 24 23 balanced-hierarchy 96 136 92 170 0.2492 0.2492 0.1463 0.1945 360
7 chain 24 23 balanced-hierarchy 224 136 92 170 0.5487 0.5487 0.1463 0.1945 360
8 chain 24 23 quality-spacious 32 180 118 230 0.1107 0.1107 0.0003 0.2131 360
9 chain 24 23 quality-spacious 96 180 118 230 0.2452 0.2452 0 0.2123 360
10 chain 24 23 quality-spacious 224 180 118 230 0.5297 0.5297 0 0.2123 360
11 chain 64 63 fast-flat 32 96 76 130 0.7609 0.7609 0.273 0.2587 360
12 chain 64 63 fast-flat 96 96 76 130 1.6521 1.6521 0.273 0.2582 360
13 chain 64 63 fast-flat 224 96 76 130 5.399 5.399 0.273 0.2582 360
14 chain 64 63 balanced-hierarchy 32 136 92 170 0.5904 0.5904 0.1431 0.1983 360
15 chain 64 63 balanced-hierarchy 96 136 92 170 2.5069 2.5069 0.143 0.198 360
16 chain 64 63 balanced-hierarchy 224 136 92 170 4.2243 4.2243 0.143 0.198 360
17 chain 64 63 quality-spacious 32 180 118 230 2.9015 2.9015 0.0001 0.2158 360
18 chain 64 63 quality-spacious 96 180 118 230 1.7075 1.7075 0 0.2155 360
19 chain 64 63 quality-spacious 224 180 118 230 3.5914 3.5914 0 0.2155 360
20 chain 128 127 fast-flat 32 96 76 130 2.8866 2.8866 0.2712 0.2602 360
21 chain 128 127 fast-flat 96 96 76 130 13.7612 13.7612 0.2712 0.2599 360
22 chain 128 127 fast-flat 224 96 76 130 46.0941 46.0941 0.2712 0.2599 360
23 chain 128 127 balanced-hierarchy 32 136 92 170 5.6582 5.6582 0.1421 0.1992 360
24 chain 128 127 balanced-hierarchy 96 136 92 170 38.0374 38.0374 0.1421 0.199 360
25 chain 128 127 balanced-hierarchy 224 136 92 170 35.1158 35.1158 0.1421 0.199 360
26 chain 128 127 quality-spacious 32 180 118 230 30.4521 30.4521 0 0.2166 360
27 chain 128 127 quality-spacious 96 180 118 230 49.3048 49.3048 0 0.2165 360
28 chain 128 127 quality-spacious 224 180 118 230 37.4902 37.4902 0 0.2165 360
29 chain 256 255 fast-flat 32 96 76 130 14.7115 14.7115 0.2703 0.2608 360
30 chain 256 255 fast-flat 96 96 76 130 83.9211 83.9211 0.2703 0.2607 360
31 chain 256 255 fast-flat 224 96 76 130 195.3656 195.3656 0.2703 0.2607 360
32 chain 256 255 balanced-hierarchy 32 136 92 170 18.5755 18.5755 0.1416 0.1996 360
33 chain 256 255 balanced-hierarchy 96 136 92 170 24.3222 24.3222 0.1416 0.1995 360
34 chain 256 255 balanced-hierarchy 224 136 92 170 192.6467 192.6467 0.1416 0.1995 360
35 chain 256 255 quality-spacious 32 180 118 230 32.1363 32.1363 0 0.217 360
36 chain 256 255 quality-spacious 96 180 118 230 59.932 59.932 0 0.2169 360
37 chain 256 255 quality-spacious 224 180 118 230 112.9374 112.9374 0 0.2169 360
38 chain 512 511 fast-flat 32 96 76 130 131.2833 131.2833 0.2699 0.2612 280
39 chain 512 511 fast-flat 96 96 76 130 354.3509 354.3509 0.2699 0.2611 280
40 chain 512 511 fast-flat 224 96 76 130 458.5302 458.5302 0.2699 0.2611 280
41 chain 512 511 balanced-hierarchy 32 136 92 170 55.493 55.493 0.1414 0.1998 280
42 chain 512 511 balanced-hierarchy 96 136 92 170 184.8303 184.8303 0.1414 0.1998 280
43 chain 512 511 balanced-hierarchy 224 136 92 170 632.1172 632.1172 0.1414 0.1997 280
44 chain 512 511 quality-spacious 32 180 118 230 44.7984 44.7984 0 0.2172 280
45 chain 512 511 quality-spacious 96 180 118 230 140.1735 140.1735 0 0.2172 280
46 chain 512 511 quality-spacious 224 180 118 230 564.6244 564.6244 0 0.2172 280
47 chain 1024 1023 fast-flat 32 96 76 130 263.6441 263.6441 0.2697 0.2614 220
48 chain 1024 1023 fast-flat 96 96 76 130 1133.1209 1133.1209 0.2697 0.2613 220
49 chain 1024 1023 fast-flat 224 96 76 130 1582.2375 1582.2375 0.2697 0.2613 220
50 chain 1024 1023 balanced-hierarchy 32 136 92 170 207.5773 207.5773 0.1412 0.1999 220
51 chain 1024 1023 balanced-hierarchy 96 136 92 170 991.8572 991.8572 0.1412 0.1999 220
52 chain 1024 1023 balanced-hierarchy 224 136 92 170 2407.9727 2407.9727 0.1412 0.1999 220
53 chain 1024 1023 quality-spacious 32 180 118 230 221.886 221.886 0 0.2173 220
54 chain 1024 1023 quality-spacious 96 180 118 230 728.9886 728.9886 0 0.2173 220
55 chain 1024 1023 quality-spacious 224 180 118 230 2212.4852 2212.4852 0 0.2173 220
56 layered-dag 24 34 fast-flat 32 96 76 130 0.1299 0.1299 0.3017 0.1345 360
57 layered-dag 24 34 fast-flat 96 96 76 130 0.2868 0.2868 0.2756 0.1621 360
58 layered-dag 24 34 fast-flat 224 96 76 130 1.0623 1.0623 0.2529 0.1792 360
59 layered-dag 24 34 balanced-hierarchy 32 136 92 170 0.1949 0.1949 0.2016 0.1023 360
60 layered-dag 24 34 balanced-hierarchy 96 136 92 170 0.3345 0.3345 0.1768 0.1165 360
61 layered-dag 24 34 balanced-hierarchy 224 136 92 170 0.6188 0.6188 0.149 0.1318 360
62 layered-dag 24 34 quality-spacious 32 180 118 230 0.114 0.114 0.092 0.1122 360
63 layered-dag 24 34 quality-spacious 96 180 118 230 0.2849 0.2849 0.0692 0.106 360
64 layered-dag 24 34 quality-spacious 224 180 118 230 0.7908 0.7908 0.0328 0.1169 360
65 layered-dag 64 105 fast-flat 32 96 76 130 2.2729 2.2729 0.291 0.1266 360
66 layered-dag 64 105 fast-flat 96 96 76 130 93.8521 93.8521 0.2708 0.1509 360
67 layered-dag 64 105 fast-flat 224 96 76 130 7.0965 7.0965 0.25 0.1829 360
68 layered-dag 64 105 balanced-hierarchy 32 136 92 170 0.5319 0.5319 0.1864 0.0994 360
69 layered-dag 64 105 balanced-hierarchy 96 136 92 170 1.5299 1.5299 0.1684 0.1086 360
70 layered-dag 64 105 balanced-hierarchy 224 136 92 170 6.9493 6.9493 0.1457 0.1267 360
71 layered-dag 64 105 quality-spacious 32 180 118 230 0.6223 0.6223 0.0719 0.1225 360
72 layered-dag 64 105 quality-spacious 96 180 118 230 1.5497 1.5497 0.0552 0.1139 360
73 layered-dag 64 105 quality-spacious 224 180 118 230 81.4445 81.4445 0.0262 0.1108 360
74 layered-dag 128 223 fast-flat 32 96 76 130 2.2184 2.2184 0.2708 0.1296 360
75 layered-dag 128 223 fast-flat 96 96 76 130 9.1519 9.1519 0.2566 0.1477 360
76 layered-dag 128 223 fast-flat 224 96 76 130 112.1169 112.1169 0.2407 0.1831 360
77 layered-dag 128 223 balanced-hierarchy 32 136 92 170 3.6201 3.6201 0.1661 0.1019 360
78 layered-dag 128 223 balanced-hierarchy 96 136 92 170 7.4542 7.4542 0.1539 0.1062 360
79 layered-dag 128 223 balanced-hierarchy 224 136 92 170 99.9972 99.9972 0.1375 0.1247 360
80 layered-dag 128 223 quality-spacious 32 180 118 230 3.806 3.806 0.0533 0.1273 360
81 layered-dag 128 223 quality-spacious 96 180 118 230 10.8485 10.8485 0.0413 0.1196 360
82 layered-dag 128 223 quality-spacious 224 180 118 230 32.5006 32.5006 0.02 0.114 360
83 layered-dag 256 465 fast-flat 32 96 76 130 17.6227 17.6227 0.2708 0.1327 360
84 layered-dag 256 465 fast-flat 96 96 76 130 107.2987 107.2987 0.2593 0.1362 360
85 layered-dag 256 465 fast-flat 224 96 76 130 219.4445 219.4445 0.2449 0.1659 360
86 layered-dag 256 465 balanced-hierarchy 32 136 92 170 92.2619 92.2619 0.1623 0.1038 360
87 layered-dag 256 465 balanced-hierarchy 96 136 92 170 35.3534 35.3534 0.1529 0.1023 360
88 layered-dag 256 465 balanced-hierarchy 224 136 92 170 341.3665 341.3665 0.1393 0.1147 360
89 layered-dag 256 465 quality-spacious 32 180 118 230 22.812 22.812 0.0419 0.1304 360
90 layered-dag 256 465 quality-spacious 96 180 118 230 84.4691 84.4691 0.0329 0.1243 360
91 layered-dag 256 465 quality-spacious 224 180 118 230 157.2866 157.2866 0.0162 0.1184 360
92 layered-dag 512 956 fast-flat 32 96 76 130 171.0027 171.0027 0.2519 0.1371 280
93 layered-dag 512 956 fast-flat 96 96 76 130 440.7858 440.7858 0.2439 0.1353 280
94 layered-dag 512 956 fast-flat 224 96 76 130 894.2877 894.2877 0.2333 0.1559 280
95 layered-dag 512 956 balanced-hierarchy 32 136 92 170 101.5174 101.5174 0.1453 0.1062 280
96 layered-dag 512 956 balanced-hierarchy 96 136 92 170 301.8864 301.8864 0.1391 0.1034 280
97 layered-dag 512 956 balanced-hierarchy 224 136 92 170 681.1257 681.1257 0.1299 0.1093 280
98 layered-dag 512 956 quality-spacious 32 180 118 230 112.146 112.146 0.0231 0.1328 280
99 layered-dag 512 956 quality-spacious 96 180 118 230 274.3755 274.3755 0.0168 0.1278 280
100 layered-dag 512 956 quality-spacious 224 180 118 230 487.8526 487.8526 0.0049 0.1226 280
101 layered-dag 1024 1953 fast-flat 32 96 76 130 265.6747 265.6747 0.2579 0.1416 220
102 layered-dag 1024 1953 fast-flat 96 96 76 130 1034.2477 1034.2477 0.2521 0.1369 220
103 layered-dag 1024 1953 fast-flat 224 96 76 130 2487.4745 2487.4745 0.244 0.1456 220
104 layered-dag 1024 1953 balanced-hierarchy 32 136 92 170 341.3757 341.3757 0.1448 0.1084 220
105 layered-dag 1024 1953 balanced-hierarchy 96 136 92 170 557.8116 557.8116 0.1405 0.1051 220
106 layered-dag 1024 1953 balanced-hierarchy 224 136 92 170 1428.6917 1428.6917 0.1338 0.1065 220
107 layered-dag 1024 1953 quality-spacious 32 180 118 230 259.2804 259.2804 0.0134 0.1349 220
108 layered-dag 1024 1953 quality-spacious 96 180 118 230 679.6793 679.6793 0.0087 0.131 220
109 layered-dag 1024 1953 quality-spacious 224 180 118 230 1677.3771 1677.3771 0.0003 0.1265 220
110 binary-tree 24 23 fast-flat 32 96 76 130 0.0994 0.0994 0.2315 0.2278 360
111 binary-tree 24 23 fast-flat 96 96 76 130 0.3308 0.3308 0.2102 0.1893 360
112 binary-tree 24 23 fast-flat 224 96 76 130 0.6879 0.6879 0.1871 0.1854 360
113 binary-tree 24 23 balanced-hierarchy 32 136 92 170 0.1173 0.1173 0.1641 0.1949 360
114 binary-tree 24 23 balanced-hierarchy 96 136 92 170 0.2445 0.2445 0.1411 0.1605 360
115 binary-tree 24 23 balanced-hierarchy 224 136 92 170 0.6525 0.6525 0.1158 0.1338 360
116 binary-tree 24 23 quality-spacious 32 180 118 230 0.1089 0.1089 0.1231 0.1869 360
117 binary-tree 24 23 quality-spacious 96 180 118 230 0.302 0.302 0.0851 0.1552 360
118 binary-tree 24 23 quality-spacious 224 180 118 230 1.4987 1.4987 0.0361 0.1217 360
119 binary-tree 64 63 fast-flat 32 96 76 130 0.5334 0.5334 0.2169 1.4057 360
120 binary-tree 64 63 fast-flat 96 96 76 130 1.5077 1.5077 0.1861 0.7332 360
121 binary-tree 64 63 fast-flat 224 96 76 130 47.4958 47.4958 0.1748 0.3761 360
122 binary-tree 64 63 balanced-hierarchy 32 136 92 170 0.5165 0.5165 0.1994 1.3141 360
123 binary-tree 64 63 balanced-hierarchy 96 136 92 170 2.4395 2.4395 0.133 0.6676 360
124 binary-tree 64 63 balanced-hierarchy 224 136 92 170 6.9034 6.9034 0.1084 0.3358 360
125 binary-tree 64 63 quality-spacious 32 180 118 230 0.6857 0.6857 0.2604 1.3039 360
126 binary-tree 64 63 quality-spacious 96 180 118 230 4.0524 4.0524 0.1344 0.6388 360
127 binary-tree 64 63 quality-spacious 224 180 118 230 4.8413 4.8413 0.0435 0.3092 360
128 binary-tree 128 127 fast-flat 32 96 76 130 3.3911 3.3911 0.2462 3.8579 360
129 binary-tree 128 127 fast-flat 96 96 76 130 8.1517 8.1517 0.1893 1.93 360
130 binary-tree 128 127 fast-flat 224 96 76 130 21.4976 21.4976 0.1745 0.7793 360
131 binary-tree 128 127 balanced-hierarchy 32 136 92 170 4.0164 4.0164 0.2648 3.7141 360
132 binary-tree 128 127 balanced-hierarchy 96 136 92 170 66.2096 66.2096 0.1642 1.9737 360
133 binary-tree 128 127 balanced-hierarchy 224 136 92 170 20.7004 20.7004 0.1075 0.7572 360
134 binary-tree 128 127 quality-spacious 32 180 118 230 2.358 2.358 0.3624 3.6958 360
135 binary-tree 128 127 quality-spacious 96 180 118 230 12.2222 12.2222 0.2339 2.1938 360
136 binary-tree 128 127 quality-spacious 224 180 118 230 21.1299 21.1299 0.0693 0.8203 360
137 binary-tree 256 255 fast-flat 32 96 76 130 16.9305 16.9305 0.1864 9.7142 360
138 binary-tree 256 255 fast-flat 96 96 76 130 110.6497 110.6497 0.1505 6.5718 360
139 binary-tree 256 255 fast-flat 224 96 76 130 161.4961 161.4961 0.1504 2.7779 360
140 binary-tree 256 255 balanced-hierarchy 32 136 92 170 14.3811 14.3811 0.1986 9.2064 360
141 binary-tree 256 255 balanced-hierarchy 96 136 92 170 57.5473 57.5473 0.1411 6.6632 360
142 binary-tree 256 255 balanced-hierarchy 224 136 92 170 106.9944 106.9944 0.0831 3.181 360
143 binary-tree 256 255 quality-spacious 32 180 118 230 37.3606 37.3606 0.2771 8.9666 360
144 binary-tree 256 255 quality-spacious 96 180 118 230 77.6774 77.6774 0.2118 6.9749 360
145 binary-tree 256 255 quality-spacious 224 180 118 230 156.5553 156.5553 0.0971 3.9117 360
146 binary-tree 512 511 fast-flat 32 96 76 130 122.1017 122.1017 0.1328 21.9711 280
147 binary-tree 512 511 fast-flat 96 96 76 130 187.3812 187.3812 0.1354 18.1953 280
148 binary-tree 512 511 fast-flat 224 96 76 130 571.7195 571.7195 0.1568 11.8925 280
149 binary-tree 512 511 balanced-hierarchy 32 136 92 170 53.5575 53.5575 0.0927 20.5908 280
150 binary-tree 512 511 balanced-hierarchy 96 136 92 170 148.5947 148.5947 0.0829 17.6309 280
151 binary-tree 512 511 balanced-hierarchy 224 136 92 170 398.2566 398.2566 0.0925 12.4995 280
152 binary-tree 512 511 quality-spacious 32 180 118 230 36.0988 36.0988 0.1106 19.794 280
153 binary-tree 512 511 quality-spacious 96 180 118 230 181.5769 181.5769 0.0809 17.5481 280
154 binary-tree 512 511 quality-spacious 224 180 118 230 528.5091 528.5091 0.0239 13.5086 280
155 binary-tree 1024 1023 fast-flat 32 96 76 130 210.2692 210.2692 0.1369 46.7937 220
156 binary-tree 1024 1023 fast-flat 96 96 76 130 610.4159 610.4159 0.1432 42.6892 220
157 binary-tree 1024 1023 fast-flat 224 96 76 130 2249.5615 2249.5615 0.1586 35.0941 220
158 binary-tree 1024 1023 balanced-hierarchy 32 136 92 170 271.652 271.652 0.0798 43.5894 220
159 binary-tree 1024 1023 balanced-hierarchy 96 136 92 170 1021.4519 1021.4519 0.0841 40.4131 220
160 binary-tree 1024 1023 balanced-hierarchy 224 136 92 170 3800.4651 3800.4651 0.0984 34.4479 220
161 binary-tree 1024 1023 quality-spacious 32 180 118 230 332.3558 332.3558 0.042 41.6159 220
162 binary-tree 1024 1023 quality-spacious 96 180 118 230 1071.4013 1071.4013 0.0276 39.238 220
163 binary-tree 1024 1023 quality-spacious 224 180 118 230 2616.4795 2616.4795 0.0009 34.7045 220
164 hub-spoke 24 29 fast-flat 32 96 76 130 0.1137 0.1137 0.1814 0.8226 360
165 hub-spoke 24 29 fast-flat 96 96 76 130 0.4694 0.4694 0.1492 0.4697 360
166 hub-spoke 24 29 fast-flat 224 96 76 130 1.098 1.098 0.1585 0.4307 360
167 hub-spoke 24 29 balanced-hierarchy 32 136 92 170 0.2271 0.2271 0.2224 0.8247 360
168 hub-spoke 24 29 balanced-hierarchy 96 136 92 170 0.4943 0.4943 0.0881 0.4002 360
169 hub-spoke 24 29 balanced-hierarchy 224 136 92 170 0.8968 0.8968 0.0954 0.3494 360
170 hub-spoke 24 29 quality-spacious 32 180 118 230 0.882 0.882 0.4151 0.9344 360
171 hub-spoke 24 29 quality-spacious 96 180 118 230 0.3203 0.3203 0.1192 0.3564 360
172 hub-spoke 24 29 quality-spacious 224 180 118 230 16.0776 16.0776 0.0234 0.2954 360
173 hub-spoke 64 78 fast-flat 32 96 76 130 0.6576 0.6576 0.8482 5.8995 360
174 hub-spoke 64 78 fast-flat 96 96 76 130 2.0299 2.0299 0.3601 2.2776 360
175 hub-spoke 64 78 fast-flat 224 96 76 130 6.3586 6.3586 0.1055 0.7324 360
176 hub-spoke 64 78 balanced-hierarchy 32 136 92 170 0.99 0.99 1.1505 5.8157 360
177 hub-spoke 64 78 balanced-hierarchy 96 136 92 170 11.8964 11.8964 0.6505 2.653 360
178 hub-spoke 64 78 balanced-hierarchy 224 136 92 170 5.8031 5.8031 0.0686 0.6729 360
179 hub-spoke 64 78 quality-spacious 32 180 118 230 1.5412 1.5412 1.6506 5.9046 360
180 hub-spoke 64 78 quality-spacious 96 180 118 230 6.3829 6.3829 1.1423 3.252 360
181 hub-spoke 64 78 quality-spacious 224 180 118 230 37.8104 37.8104 0.3026 0.8043 360
182 hub-spoke 128 162 fast-flat 32 96 76 130 2.4179 2.4179 0.3497 16.0025 360
183 hub-spoke 128 162 fast-flat 96 96 76 130 23.8242 23.8242 0.2072 10.6282 360
184 hub-spoke 128 162 fast-flat 224 96 76 130 68.7851 68.7851 0.1036 3.654 360
185 hub-spoke 128 162 balanced-hierarchy 32 136 92 170 5.8344 5.8344 0.4665 15.2361 360
186 hub-spoke 128 162 balanced-hierarchy 96 136 92 170 10.0863 10.0863 0.3271 10.9589 360
187 hub-spoke 128 162 balanced-hierarchy 224 136 92 170 22.1724 22.1724 0.0739 4.7465 360
188 hub-spoke 128 162 quality-spacious 32 180 118 230 3.3698 3.3698 0.6685 14.894 360
189 hub-spoke 128 162 quality-spacious 96 180 118 230 36.8697 36.8697 0.5377 11.6044 360
190 hub-spoke 128 162 quality-spacious 224 180 118 230 44.4775 44.4775 0.2847 6.3452 360
191 hub-spoke 256 328 fast-flat 32 96 76 130 33.5236 33.5236 0.1669 36.4441 360
192 hub-spoke 256 328 fast-flat 96 96 76 130 88.0661 88.0661 0.1327 30.3648 360
193 hub-spoke 256 328 fast-flat 224 96 76 130 116.2219 116.2219 0.1471 19.9784 360
194 hub-spoke 256 328 balanced-hierarchy 32 136 92 170 33.1609 33.1609 0.1822 34.19 360
195 hub-spoke 256 328 balanced-hierarchy 96 136 92 170 36.7797 36.7797 0.1303 29.4519 360
196 hub-spoke 256 328 balanced-hierarchy 224 136 92 170 122.1986 122.1986 0.0724 21.1357 360
197 hub-spoke 256 328 quality-spacious 32 180 118 230 54.9571 54.9571 0.2674 32.8891 360
198 hub-spoke 256 328 quality-spacious 96 180 118 230 72.35 72.35 0.2149 29.3306 360
199 hub-spoke 256 328 quality-spacious 224 180 118 230 184.0544 184.0544 0.1114 22.8709 360
200 hub-spoke 512 664 fast-flat 32 96 76 130 96.2749 96.2749 0.1496 78.9376 280
201 hub-spoke 512 664 fast-flat 96 96 76 130 304.5229 304.5229 0.1523 72.5247 280
202 hub-spoke 512 664 fast-flat 224 96 76 130 566.0394 566.0394 0.1742 60.5199 280
203 hub-spoke 512 664 balanced-hierarchy 32 136 92 170 88.6981 88.6981 0.0964 73.5417 280
204 hub-spoke 512 664 balanced-hierarchy 96 136 92 170 269.6925 269.6925 0.0906 68.597 280
205 hub-spoke 512 664 balanced-hierarchy 224 136 92 170 715.2272 715.2272 0.103 59.2111 280
206 hub-spoke 512 664 quality-spacious 32 180 118 230 107.2466 107.2466 0.0946 70.2089 280
207 hub-spoke 512 664 quality-spacious 96 180 118 230 385.9852 385.9852 0.0702 66.5199 280
208 hub-spoke 512 664 quality-spacious 224 180 118 230 666.3786 666.3786 0.0213 59.4487 280
209 hub-spoke 1024 1338 fast-flat 32 96 76 130 333.8144 333.8144 0.1596 164.564 220
210 hub-spoke 1024 1338 fast-flat 96 96 76 130 710.1158 710.1158 0.1637 157.9969 220
211 hub-spoke 1024 1338 fast-flat 224 96 76 130 2184.0934 2184.0934 0.1755 145.2562 220
212 hub-spoke 1024 1338 balanced-hierarchy 32 136 92 170 416.9324 416.9324 0.0953 152.8116 220
213 hub-spoke 1024 1338 balanced-hierarchy 96 136 92 170 1319.5015 1319.5015 0.0981 147.7718 220
214 hub-spoke 1024 1338 balanced-hierarchy 224 136 92 170 2762.4865 2762.4865 0.1087 137.9328 220
215 hub-spoke 1024 1338 quality-spacious 32 180 118 230 569.0331 569.0331 0.035 145.3619 220
216 hub-spoke 1024 1338 quality-spacious 96 180 118 230 1520.0452 1520.0452 0.0231 141.6236 220
217 hub-spoke 1024 1338 quality-spacious 224 180 118 230 2587.3624 2587.3624 0.0008 134.2803 220
218 clustered-modules 24 42 fast-flat 32 96 76 130 0.1194 0.1194 0.2896 0.3399 360
219 clustered-modules 24 42 fast-flat 96 96 76 130 0.2845 0.2845 0.2659 0.296 360
220 clustered-modules 24 42 fast-flat 224 96 76 130 0.5939 0.5939 0.2521 0.2863 360
221 clustered-modules 24 42 balanced-hierarchy 32 136 92 170 0.1046 0.1046 0.2055 0.3352 360
222 clustered-modules 24 42 balanced-hierarchy 96 136 92 170 0.2615 0.2615 0.1716 0.291 360
223 clustered-modules 24 42 balanced-hierarchy 224 136 92 170 0.7611 0.7611 0.145 0.2739 360
224 clustered-modules 24 42 quality-spacious 32 180 118 230 0.1697 0.1697 0.1496 0.3424 360
225 clustered-modules 24 42 quality-spacious 96 180 118 230 0.296 0.296 0.0954 0.2844 360
226 clustered-modules 24 42 quality-spacious 224 180 118 230 1.4378 1.4378 0.0376 0.2652 360
227 clustered-modules 64 120 fast-flat 32 96 76 130 0.6757 0.6757 0.2325 0.3366 360
228 clustered-modules 64 120 fast-flat 96 96 76 130 5.7168 5.7168 0.2231 0.3168 360
229 clustered-modules 64 120 fast-flat 224 96 76 130 12.3975 12.3975 0.2147 0.3104 360
230 clustered-modules 64 120 balanced-hierarchy 32 136 92 170 7.0531 7.0531 0.1446 0.3242 360
231 clustered-modules 64 120 balanced-hierarchy 96 136 92 170 3.4456 3.4456 0.133 0.2895 360
232 clustered-modules 64 120 balanced-hierarchy 224 136 92 170 6.3231 6.3231 0.1198 0.2798 360
233 clustered-modules 64 120 quality-spacious 32 180 118 230 1.0192 1.0192 0.0823 0.331 360
234 clustered-modules 64 120 quality-spacious 96 180 118 230 15.0834 15.0834 0.0585 0.2839 360
235 clustered-modules 64 120 quality-spacious 224 180 118 230 4.7643 4.7643 0.0268 0.2703 360
236 clustered-modules 128 244 fast-flat 32 96 76 130 2.7995 2.7995 0.2106 0.3718 360
237 clustered-modules 128 244 fast-flat 96 96 76 130 23.0144 23.0144 0.2056 0.3566 360
238 clustered-modules 128 244 fast-flat 224 96 76 130 25.9841 25.9841 0.1997 0.3588 360
239 clustered-modules 128 244 balanced-hierarchy 32 136 92 170 2.1172 2.1172 0.1217 0.3577 360
240 clustered-modules 128 244 balanced-hierarchy 96 136 92 170 6.8458 6.8458 0.1175 0.3296 360
241 clustered-modules 128 244 balanced-hierarchy 224 136 92 170 43.6508 43.6508 0.1095 0.326 360
242 clustered-modules 128 244 quality-spacious 32 180 118 230 5.6318 5.6318 0.0631 0.3736 360
243 clustered-modules 128 244 quality-spacious 96 180 118 230 24.4066 24.4066 0.0413 0.324 360
244 clustered-modules 128 244 quality-spacious 224 180 118 230 86.6985 86.6985 0.0186 0.3121 360
245 clustered-modules 256 496 fast-flat 32 96 76 130 30.7665 30.7665 0.2075 0.4111 360
246 clustered-modules 256 496 fast-flat 96 96 76 130 86.1928 86.1928 0.2057 0.4007 360
247 clustered-modules 256 496 fast-flat 224 96 76 130 121.0846 121.0846 0.202 0.3988 360
248 clustered-modules 256 496 balanced-hierarchy 32 136 92 170 15.2616 15.2616 0.1129 0.4058 360
249 clustered-modules 256 496 balanced-hierarchy 96 136 92 170 71.2521 71.2521 0.1135 0.3776 360
250 clustered-modules 256 496 balanced-hierarchy 224 136 92 170 135.0564 135.0564 0.1091 0.3705 360
251 clustered-modules 256 496 quality-spacious 32 180 118 230 11.7053 11.7053 0.0559 0.4309 360
252 clustered-modules 256 496 quality-spacious 96 180 118 230 50.246 50.246 0.0306 0.3761 360
253 clustered-modules 256 496 quality-spacious 224 180 118 230 133.2498 133.2498 0.0145 0.3616 360
254 clustered-modules 512 1000 fast-flat 32 96 76 130 172.2378 172.2378 0.2014 0.4756 280
255 clustered-modules 512 1000 fast-flat 96 96 76 130 288.0959 288.0959 0.2022 0.4434 280
256 clustered-modules 512 1000 fast-flat 224 96 76 130 502.8001 502.8001 0.1996 0.4416 280
257 clustered-modules 512 1000 balanced-hierarchy 32 136 92 170 95.3391 95.3391 0.107 0.4747 280
258 clustered-modules 512 1000 balanced-hierarchy 96 136 92 170 369.7732 369.7732 0.1089 0.4271 280
259 clustered-modules 512 1000 balanced-hierarchy 224 136 92 170 689.7119 689.7119 0.1069 0.4151 280
260 clustered-modules 512 1000 quality-spacious 32 180 118 230 141.8101 141.8101 0.0517 0.4906 280
261 clustered-modules 512 1000 quality-spacious 96 180 118 230 262.9639 262.9639 0.0188 0.4305 280
262 clustered-modules 512 1000 quality-spacious 224 180 118 230 456.8799 456.8799 0.0047 0.4081 280
263 clustered-modules 1024 2016 fast-flat 32 96 76 130 370.416 370.416 0.2011 0.5064 220
264 clustered-modules 1024 2016 fast-flat 96 96 76 130 1037.4821 1037.4821 0.2025 0.4666 220
265 clustered-modules 1024 2016 fast-flat 224 96 76 130 1788.4993 1788.4993 0.2006 0.47 220
266 clustered-modules 1024 2016 balanced-hierarchy 32 136 92 170 189.8511 189.8511 0.1064 0.5016 220
267 clustered-modules 1024 2016 balanced-hierarchy 96 136 92 170 660.9775 660.9775 0.1077 0.454 220
268 clustered-modules 1024 2016 balanced-hierarchy 224 136 92 170 1581.5562 1581.5562 0.1071 0.4426 220
269 clustered-modules 1024 2016 quality-spacious 32 180 118 230 246.5 246.5 0.0437 0.5135 220
270 clustered-modules 1024 2016 quality-spacious 96 180 118 230 596.422 596.422 0.0164 0.4659 220
271 clustered-modules 1024 2016 quality-spacious 224 180 118 230 2067.5246 2067.5246 0.0003 0.437 220

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"generatedAtUtc": "2026-05-19T13:48:35.155Z",
"generatedAtUtc": "2026-05-19T19:25:01.505Z",
"description": "Smoke baseline for React viewer realtime simulation profile. Measures local JS simulation cost/error over synthetic expansions of mock Epiphany state.",
"method": {
"iterations": 180,
@ -15,8 +15,8 @@
"edgeCount": 10,
"fullSample": {
"id": "full",
"averageCostMs": 0.0128,
"p95CostMs": 0.0257,
"averageCostMs": 0.0113,
"p95CostMs": 0.0186,
"budgetHitRate": 1,
"averageNodeBudget": 10,
"anchorError": 9.6281,
@ -25,8 +25,8 @@
"candidates": [
{
"id": "quality",
"averageCostMs": 0.0036,
"p95CostMs": 0.0038,
"averageCostMs": 0.0045,
"p95CostMs": 0.0049,
"budgetHitRate": 1,
"averageNodeBudget": 10,
"anchorError": 9.6281,
@ -34,8 +34,8 @@
},
{
"id": "balanced",
"averageCostMs": 0.0042,
"p95CostMs": 0.0067,
"averageCostMs": 0.0037,
"p95CostMs": 0.0062,
"budgetHitRate": 1,
"averageNodeBudget": 10,
"anchorError": 9.6281,
@ -44,7 +44,7 @@
{
"id": "fast",
"averageCostMs": 0.0034,
"p95CostMs": 0.0038,
"p95CostMs": 0.004,
"budgetHitRate": 1,
"averageNodeBudget": 10,
"anchorError": 9.6281,
@ -59,7 +59,7 @@
"fullSample": {
"id": "full",
"averageCostMs": 0.0177,
"p95CostMs": 0.0351,
"p95CostMs": 0.0212,
"budgetHitRate": 1,
"averageNodeBudget": 40,
"anchorError": 9.5761,
@ -68,8 +68,8 @@
"candidates": [
{
"id": "quality",
"averageCostMs": 0.0258,
"p95CostMs": 0.0762,
"averageCostMs": 0.0134,
"p95CostMs": 0.014,
"budgetHitRate": 1,
"averageNodeBudget": 40,
"anchorError": 9.5761,
@ -77,8 +77,8 @@
},
{
"id": "balanced",
"averageCostMs": 0.0112,
"p95CostMs": 0.0109,
"averageCostMs": 0.0126,
"p95CostMs": 0.0116,
"budgetHitRate": 1,
"averageNodeBudget": 40,
"anchorError": 9.5761,
@ -86,8 +86,8 @@
},
{
"id": "fast",
"averageCostMs": 0.0126,
"p95CostMs": 0.013,
"averageCostMs": 0.0128,
"p95CostMs": 0.0171,
"budgetHitRate": 1,
"averageNodeBudget": 40,
"anchorError": 9.5761,
@ -101,8 +101,8 @@
"edgeCount": 120,
"fullSample": {
"id": "full",
"averageCostMs": 0.1851,
"p95CostMs": 0.1878,
"averageCostMs": 0.0468,
"p95CostMs": 0.0435,
"budgetHitRate": 1,
"averageNodeBudget": 120,
"anchorError": 9.534,
@ -111,30 +111,30 @@
"candidates": [
{
"id": "quality",
"averageCostMs": 0.2182,
"p95CostMs": 0.15,
"budgetHitRate": 0.9944,
"averageCostMs": 0.036,
"p95CostMs": 0.0723,
"budgetHitRate": 1,
"averageNodeBudget": 120,
"anchorError": 9.534,
"baselineError": 0
},
{
"id": "balanced",
"averageCostMs": 0.5637,
"p95CostMs": 0.8305,
"budgetHitRate": 0.9778,
"averageNodeBudget": 109.75,
"anchorError": 9.179,
"baselineError": 0.6506
"averageCostMs": 0.0357,
"p95CostMs": 0.0521,
"budgetHitRate": 1,
"averageNodeBudget": 120,
"anchorError": 9.534,
"baselineError": 0
},
{
"id": "fast",
"averageCostMs": 0.659,
"p95CostMs": 1.8007,
"budgetHitRate": 0.9778,
"averageNodeBudget": 76.8833,
"anchorError": 6.6998,
"baselineError": 3.0346
"averageCostMs": 0.195,
"p95CostMs": 0.4638,
"budgetHitRate": 1,
"averageNodeBudget": 90,
"anchorError": 7.161,
"baselineError": 2.373
}
]
}
@ -142,8 +142,8 @@
"candidates": [
{
"id": "quality",
"budgetHitRate": 0.9981333333333332,
"averageCostMs": 0.08253333333333333,
"budgetHitRate": 1,
"averageCostMs": 0.017966666666666666,
"baselineError": 0,
"anchorError": 9.5794,
"profile": {
@ -162,10 +162,10 @@
},
{
"id": "balanced",
"budgetHitRate": 0.9926,
"averageCostMs": 0.1930333333333333,
"baselineError": 0.21686666666666665,
"anchorError": 9.461066666666667,
"budgetHitRate": 1,
"averageCostMs": 0.017333333333333336,
"baselineError": 0,
"anchorError": 9.5794,
"profile": {
"id": "balanced",
"simulationBudgetMs": 4,
@ -182,10 +182,10 @@
},
{
"id": "fast",
"budgetHitRate": 0.9926,
"averageCostMs": 0.225,
"baselineError": 1.0115333333333334,
"anchorError": 8.634666666666666,
"budgetHitRate": 1,
"averageCostMs": 0.0704,
"baselineError": 0.791,
"anchorError": 8.788400000000001,
"profile": {
"id": "fast",
"simulationBudgetMs": 2.5,

View file

@ -1,52 +0,0 @@
# ELK Probe
Small local `elkjs` probe for EpiphanyGraph.
This is not the final viewer. It is a migration scout:
- inspect which algorithms and options are actually present in the installed `elkjs` build
- adapt existing EpiphanyGraph partition JSON into ELK graphs
- run sample layouts without crossing the C# boundary
- emit simple SVG artifacts under `out/elk-probe/` for side-by-side comparison
## Install
```powershell
cd E:\Projects\EpiphanyGraph\web\elk-probe
npm install
```
## Inspect Available ELK Surface
```powershell
npm run inspect
```
Writes:
- `out/elk-probe/inspect/known-layout-algorithms.json`
- `out/elk-probe/inspect/known-layout-options.json`
## Probe Existing Graph Output
Point the probe at a render directory that already contains `source-tree.json` and `control-flow.json`:
```powershell
npm run probe -- --render-dir "E:\Projects\EpiphanyGraph\out\benchmarks\aetheria-source-tree-map\20260425T082539Z\candidates\sugiyama-rich\render"
```
Optional flags:
- `--render-dir <path>` explicit input directory; if omitted, the probe tries to use the newest benchmark candidate render it can find
- `--out-dir <path>` custom output directory
- `--source-tree-algorithm <id>` defaults to `org.eclipse.elk.layered`
- `--control-flow-algorithm <id>` defaults to `org.eclipse.elk.stress`
Writes one folder per partition under `out/elk-probe/<timestamp>/` with:
- `input.json`
- `elk-layout.json`
- `layout.svg`
- `layout-summary.json`
The SVG renderer here is deliberately plain. The point is to test layout integration and algorithm availability, not to declare victory with decorative sludge.

View file

@ -1,21 +0,0 @@
{
"name": "@epiphanygraph/elk-probe",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@epiphanygraph/elk-probe",
"version": "0.1.0",
"dependencies": {
"elkjs": "^0.11.0"
}
},
"node_modules/elkjs": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz",
"integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==",
"license": "EPL-2.0"
}
}
}

View file

@ -1,14 +0,0 @@
{
"name": "@epiphanygraph/elk-probe",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Local elkjs probe for EpiphanyGraph layout experiments.",
"scripts": {
"probe": "node ./src/cli.mjs probe",
"inspect": "node ./src/cli.mjs inspect"
},
"dependencies": {
"elkjs": "^0.11.0"
}
}

View file

@ -1,502 +0,0 @@
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import ELK from "elkjs/lib/elk.bundled.js";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
const defaultInspectDir = path.join(repoRoot, "out", "elk-probe", "inspect");
const command = process.argv[2] ?? "inspect";
const args = parseArgs(process.argv.slice(3));
try {
switch (command) {
case "inspect":
await runInspect(args);
break;
case "probe":
await runProbe(args);
break;
default:
throw new Error(`Unknown command '${command}'. Use 'inspect' or 'probe'.`);
}
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}
async function runInspect(args) {
const outDir = resolveMaybeRelative(args["out-dir"] ?? defaultInspectDir);
const elk = createElk();
const algorithms = await elk.knownLayoutAlgorithms();
const options = await elk.knownLayoutOptions();
await fs.mkdir(outDir, { recursive: true });
await writeJson(path.join(outDir, "known-layout-algorithms.json"), algorithms);
await writeJson(path.join(outDir, "known-layout-options.json"), options);
console.log(`Wrote algorithm inventory to ${outDir}`);
console.log("");
console.log("Algorithms:");
for (const algorithm of algorithms) {
console.log(`- ${algorithm.id}`);
}
console.log("");
console.log(`Options: ${options.length}`);
}
async function runProbe(args) {
const renderDir = args["render-dir"]
? resolveMaybeRelative(args["render-dir"])
: await findLatestBenchmarkRenderDir();
const outDir = resolveMaybeRelative(
args["out-dir"] ?? path.join(repoRoot, "out", "elk-probe", timestampNow()),
);
const sourceTreeAlgorithm = args["source-tree-algorithm"] ?? "org.eclipse.elk.layered";
const controlFlowAlgorithm = args["control-flow-algorithm"] ?? "org.eclipse.elk.stress";
await assertFile(path.join(renderDir, "source-tree.json"));
await assertFile(path.join(renderDir, "control-flow.json"));
const elk = createElk();
const algorithms = await elk.knownLayoutAlgorithms();
const algorithmIds = new Set(algorithms.map((algorithm) => algorithm.id));
ensureAlgorithmAvailable(sourceTreeAlgorithm, algorithmIds);
ensureAlgorithmAvailable(controlFlowAlgorithm, algorithmIds);
await fs.mkdir(outDir, { recursive: true });
await writeJson(path.join(outDir, "known-layout-algorithms.json"), algorithms);
const partitions = [
{
graphKey: "source-tree",
algorithm: sourceTreeAlgorithm,
},
{
graphKey: "control-flow",
algorithm: controlFlowAlgorithm,
},
];
for (const partition of partitions) {
const inputPath = path.join(renderDir, `${partition.graphKey}.json`);
const report = JSON.parse(await fs.readFile(inputPath, "utf8"));
const partitionOutDir = path.join(outDir, partition.graphKey);
await fs.mkdir(partitionOutDir, { recursive: true });
const elkGraph = buildElkGraph(report, partition.algorithm);
const layout = await elk.layout(elkGraph);
const summary = buildLayoutSummary(report, layout, partition.algorithm);
const svg = renderSvg(layout, report, partition.algorithm);
await writeJson(path.join(partitionOutDir, "input.json"), report);
await writeJson(path.join(partitionOutDir, "elk-graph.json"), elkGraph);
await writeJson(path.join(partitionOutDir, "elk-layout.json"), layout);
await writeJson(path.join(partitionOutDir, "layout-summary.json"), summary);
await fs.writeFile(path.join(partitionOutDir, "layout.svg"), svg, "utf8");
console.log(
`[${partition.graphKey}] ${partition.algorithm} -> ${path.join(partitionOutDir, "layout.svg")}`,
);
}
console.log("");
console.log(`Probe output: ${outDir}`);
}
async function findLatestBenchmarkRenderDir() {
const benchmarkRoot = path.join(repoRoot, "out", "benchmarks", "aetheria-source-tree-map");
const runEntries = await fs.readdir(benchmarkRoot, { withFileTypes: true });
const runNames = runEntries
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort()
.reverse();
for (const runName of runNames) {
const candidateRoot = path.join(benchmarkRoot, runName, "candidates");
try {
const candidateEntries = await fs.readdir(candidateRoot, { withFileTypes: true });
const candidateNames = candidateEntries
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort((left, right) => {
const leftScore = left.includes("rich") ? 0 : 1;
const rightScore = right.includes("rich") ? 0 : 1;
return leftScore - rightScore || left.localeCompare(right);
});
for (const candidateName of candidateNames) {
const renderDir = path.join(candidateRoot, candidateName, "render");
try {
await assertFile(path.join(renderDir, "source-tree.json"));
await assertFile(path.join(renderDir, "control-flow.json"));
return renderDir;
} catch {
// Keep walking until we find a valid render directory.
}
}
} catch {
// Ignore missing candidate folders and keep scanning older runs.
}
}
throw new Error(
"Could not find a benchmark render directory automatically. Pass --render-dir explicitly.",
);
}
function createElk() {
return new ELK();
}
function buildElkGraph(report, algorithm) {
return {
id: report.GraphKey,
layoutOptions: {
"elk.algorithm": algorithm,
...baseGraphOptions(report.GraphKey, algorithm),
},
children: report.Nodes.map((node) => ({
id: node.Id,
width: normalizeSize(node.Bounds.Width, 68, 260),
height: normalizeSize(node.Bounds.Height, 34, 110),
labels: [
{
text: node.Title,
},
],
layoutOptions: {
...nodeOptions(node, report.GraphKey),
},
})),
edges: report.Edges.map((edge, index) => ({
id: `${report.GraphKey}-edge-${index}`,
sources: [edge.SourceId],
targets: [edge.TargetId],
})),
};
}
function baseGraphOptions(graphKey, algorithm) {
const layered = algorithm === "org.eclipse.elk.layered";
if (graphKey === "source-tree") {
return layered
? {
"elk.direction": "DOWN",
"elk.layered.spacing.nodeNodeBetweenLayers": "92",
"elk.spacing.nodeNode": "42",
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
"elk.edgeRouting": "SPLINES",
}
: {
"elk.spacing.nodeNode": "48",
"elk.padding": "[top=40,left=40,bottom=40,right=40]",
};
}
return layered
? {
"elk.direction": "DOWN",
"elk.layered.spacing.nodeNodeBetweenLayers": "124",
"elk.spacing.nodeNode": "84",
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
"elk.edgeRouting": "SPLINES",
}
: {
"elk.spacing.nodeNode": "80",
"elk.padding": "[top=60,left=60,bottom=60,right=60]",
};
}
function nodeOptions(node, graphKey) {
if (graphKey === "source-tree") {
return {
"elk.nodeSize.constraints": "MINIMUM_SIZE",
};
}
return {
"elk.nodeSize.constraints": "MINIMUM_SIZE",
};
}
function buildLayoutSummary(report, layout, algorithm) {
return {
graphKey: report.GraphKey,
graphTitle: report.GraphTitle,
nodeCount: report.NodeCount,
edgeCount: report.EdgeCount,
algorithm,
width: layout.width ?? 0,
height: layout.height ?? 0,
children: layout.children?.length ?? 0,
edgesWithSections:
layout.edges?.filter((edge) => Array.isArray(edge.sections) && edge.sections.length > 0).length ?? 0,
};
}
function renderSvg(layout, report, algorithm) {
const nodeLookup = new Map(report.Nodes.map((node) => [node.Id, node]));
const width = Math.max(1280, Math.ceil((layout.width ?? 0) + 120));
const height = Math.max(860, Math.ceil((layout.height ?? 0) + 140));
const headerHeight = 92;
const offsetX = 60;
const offsetY = headerHeight;
const children = [...(layout.children ?? [])];
const edges = [...(layout.edges ?? [])];
const svg = [];
svg.push(`<?xml version="1.0" encoding="utf-8"?>`);
svg.push(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-labelledby="title desc">`,
);
svg.push(` <title id="title">${escapeXml(report.GraphTitle)} ELK layout</title>`);
svg.push(
` <desc id="desc">ELK probe render for ${escapeXml(report.GraphKey)} using ${escapeXml(algorithm)}.</desc>`,
);
svg.push(` <defs>`);
svg.push(` <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">`);
svg.push(` <stop offset="0%" stop-color="#09111c" />`);
svg.push(` <stop offset="100%" stop-color="${report.GraphKey === "control-flow" ? "#190d12" : "#111827"}" />`);
svg.push(` </linearGradient>`);
svg.push(` <marker id="arrow" viewBox="0 0 12 12" refX="10" refY="6" markerWidth="7" markerHeight="7" orient="auto-start-reverse">`);
svg.push(` <path d="M 0 0 L 12 6 L 0 12 z" fill="${report.GraphKey === "control-flow" ? "#fb7185" : "#22d3ee"}" />`);
svg.push(` </marker>`);
svg.push(` </defs>`);
svg.push(` <rect x="0" y="0" width="${width}" height="${height}" fill="url(#bg)" />`);
svg.push(
` <rect x="20" y="20" width="${width - 40}" height="54" rx="18" fill="#0c1626" stroke="${report.GraphKey === "control-flow" ? "#fb7185" : "#22d3ee"}" stroke-opacity="0.5" />`,
);
svg.push(
` <text x="38" y="44" fill="#f8fafc" font-family="Bahnschrift, Segoe UI, sans-serif" font-size="28" font-weight="700">${escapeXml(report.GraphTitle)}</text>`,
);
svg.push(
` <text x="38" y="62" fill="${report.GraphKey === "control-flow" ? "#fdba74" : "#a5f3fc"}" font-family="Bahnschrift, Segoe UI, sans-serif" font-size="12" letter-spacing="0.16em">${escapeXml(algorithm)}</text>`,
);
svg.push(` <g id="edges" fill="none">`);
for (const edge of edges) {
const points = edgeSectionsToPoints(edge.sections, offsetX, offsetY);
if (points.length < 2) {
continue;
}
svg.push(
` <path d="${polylinePath(points)}" stroke="${report.GraphKey === "control-flow" ? "#fb7185" : "#22d3ee"}" stroke-opacity="0.42" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)" />`,
);
}
svg.push(` </g>`);
svg.push(` <g id="nodes">`);
for (const child of children) {
const x = (child.x ?? 0) + offsetX;
const y = (child.y ?? 0) + offsetY;
const widthValue = child.width ?? 80;
const heightValue = child.height ?? 42;
const node = nodeLookup.get(child.id);
const color = nodeColor(node, report.GraphKey);
const lines = wrapLabel(node?.Title ?? child.id, widthValue < 120 ? 16 : 20, 2);
svg.push(
` <rect x="${fmt(x)}" y="${fmt(y)}" width="${fmt(widthValue)}" height="${fmt(heightValue)}" rx="16" fill="#101827" fill-opacity="0.92" stroke="${color}" stroke-width="${node?.IsEntry ? "2.2" : "1.4"}" />`,
);
if (node?.IsEntry) {
svg.push(
` <rect x="${fmt(x - 5)}" y="${fmt(y - 5)}" width="${fmt(widthValue + 10)}" height="${fmt(heightValue + 10)}" rx="20" fill="none" stroke="#fde68a" stroke-width="2.1" stroke-opacity="0.9" />`,
);
}
svg.push(
` <circle cx="${fmt(x + 14)}" cy="${fmt(y + 14)}" r="9" fill="${color}" stroke="#e5e7eb" stroke-opacity="0.4" />`,
);
svg.push(
` <text x="${fmt(x + 14)}" y="${fmt(y + 18)}" text-anchor="middle" fill="#0b1220" font-family="Bahnschrift, Segoe UI, sans-serif" font-size="9" font-weight="700">${escapeXml(tokenFor(node))}</text>`,
);
for (const [index, line] of lines.entries()) {
svg.push(
` <text x="${fmt(x + widthValue / 2)}" y="${fmt(y + 24 + index * 14)}" text-anchor="middle" fill="#f8fafc" font-family="Bahnschrift, Segoe UI, sans-serif" font-size="11.5" font-weight="${node?.IsEntry ? "700" : "600"}">${escapeXml(line)}</text>`,
);
}
}
svg.push(` </g>`);
svg.push(`</svg>`);
return svg.join("\n");
}
function edgeSectionsToPoints(sections = [], offsetX, offsetY) {
const points = [];
for (const section of sections ?? []) {
if (section.startPoint) {
points.push({
x: section.startPoint.x + offsetX,
y: section.startPoint.y + offsetY,
});
}
for (const bendPoint of section.bendPoints ?? []) {
points.push({
x: bendPoint.x + offsetX,
y: bendPoint.y + offsetY,
});
}
if (section.endPoint) {
points.push({
x: section.endPoint.x + offsetX,
y: section.endPoint.y + offsetY,
});
}
}
return dedupeSequential(points);
}
function polylinePath(points) {
const [first, ...rest] = points;
return `M ${fmt(first.x)} ${fmt(first.y)} ${rest.map((point) => `L ${fmt(point.x)} ${fmt(point.y)}`).join(" ")}`;
}
function dedupeSequential(points) {
const deduped = [];
for (const point of points) {
const previous = deduped[deduped.length - 1];
if (!previous || previous.x !== point.x || previous.y !== point.y) {
deduped.push(point);
}
}
return deduped;
}
function nodeColor(node, graphKey) {
if (node?.IsEntry) {
return "#fde68a";
}
if (node?.Role === "anchor") {
return "#94a3b8";
}
if (graphKey === "control-flow") {
return node?.Title?.includes("Server") ? "#60a5fa" : "#fb7185";
}
if (node?.Title?.includes("Shared")) {
return "#67e8f9";
}
if (node?.Title?.includes("UI") || node?.Title?.includes("Client")) {
return "#4ade80";
}
if (node?.Title?.includes("Simulation") || node?.Title?.includes("Zone") || node?.Title?.includes("Galaxy")) {
return "#c084fc";
}
return "#22d3ee";
}
function tokenFor(node) {
if (!node) {
return "N";
}
if (node.IsEntry) {
return "E";
}
if (node.Role === "anchor") {
return "A";
}
const words = node.Title.split(/[\s/_-]+/).filter(Boolean);
if (words.length === 1) {
return words[0].slice(0, 2).toUpperCase();
}
return `${words[0][0] ?? "N"}${words[1][0] ?? ""}`.toUpperCase();
}
function wrapLabel(label, maxChars, maxLines) {
const words = label.split(/\s+/).filter(Boolean);
const lines = [];
let current = "";
for (const word of words) {
const candidate = current ? `${current} ${word}` : word;
if (candidate.length > maxChars && current) {
lines.push(current);
current = word;
if (lines.length === maxLines) {
break;
}
} else {
current = candidate;
}
}
if (current && lines.length < maxLines) {
lines.push(current);
}
if (lines.length === 0) {
lines.push(label);
}
if (lines.length === maxLines && words.length > 0) {
const last = lines[lines.length - 1];
if (last.length > maxChars - 3) {
lines[lines.length - 1] = `${last.slice(0, Math.max(1, maxChars - 3))}...`;
}
}
return lines;
}
function ensureAlgorithmAvailable(algorithm, knownAlgorithms) {
if (!knownAlgorithms.has(algorithm)) {
throw new Error(
`Algorithm '${algorithm}' is not available in this elkjs build.`,
);
}
}
async function assertFile(filePath) {
await fs.access(filePath);
}
async function writeJson(filePath, value) {
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function normalizeSize(value, min, max) {
return Math.max(min, Math.min(max, Math.round(value)));
}
function parseArgs(argv) {
const args = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith("--")) {
continue;
}
const key = token.slice(2);
const next = argv[index + 1];
if (!next || next.startsWith("--")) {
args[key] = true;
continue;
}
args[key] = next;
index += 1;
}
return args;
}
function resolveMaybeRelative(targetPath) {
return path.isAbsolute(targetPath) ? targetPath : path.resolve(repoRoot, targetPath);
}
function timestampNow() {
const now = new Date();
const pad = (value) => String(value).padStart(2, "0");
return `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}T${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}Z`;
}
function escapeXml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;");
}
function fmt(value) {
return Number(value).toFixed(2).replace(/\.00$/, "");
}

View file

@ -53,7 +53,8 @@ Regenerate it after changing `E:\Projects\epiphany-graph-rs`:
npm run build:solver:wasm
```
ELK remains only as a fallback if the Rust solver fails to instantiate.
If the Rust solver fails to instantiate, layout fails loudly. There is no shadow
engine behind the component.
## Use In Another App
@ -86,7 +87,7 @@ The package exports:
- `EpiphanyGraphViewer`
- state, selection, node, edge, layout mode, motion, performance, and event payload types
The package does not export demo data, validation helpers, ELK probes, SVG renderers,
The package does not export demo data, validation helpers, probes, SVG renderers,
or engine controls. Those are implementation furniture. Consumers get the couch, not
the warehouse inventory.
@ -178,13 +179,12 @@ the rendered state closer to the current layout truth:
- high-error nodes: farthest from their base layout position
- kinetic nodes: nodes carrying the most residual velocity
The base ELK layout is the current ground-truth anchor for browser motion. Combined
The base Rust solver layout is the current ground-truth anchor for browser motion. Combined
force is a live presentation layer around that anchor, not a second source of graph
truth.
The existing benchmark harness under `benchmarks/` measures rendered graph usefulness
against blind graph-truth probes. It does not yet prove realtime sampling optimality.
The first realtime smoke baseline is generated by:
against blind graph-truth probes. The realtime smoke baseline is generated by:
```powershell
npm run tune:simulation
@ -192,6 +192,14 @@ npm run tune:simulation
That writes `benchmarks/realtime/simulation-profile-baseline.json` with cost,
budget-hit, anchor-error, and full-sample-baseline-error data for synthetic expansions
of the mock graph. The viewer's default profile mirrors that baseline. Do not call
the realtime heuristics "extensively tuned" until broader profiled budget sweeps
exist for real client corpora and their results are checked in.
of the mock graph.
The Rust solver performance baseline is generated by:
```powershell
npm run profile:solver
```
That writes `benchmarks/realtime/rust-solver-performance-baseline.json`, CSV rows,
and SVG charts covering hierarchy families, graph sizes, solver latency windows,
meta-heuristic parameters, and convergence error against a high-iteration reference.

View file

@ -7,9 +7,6 @@
"": {
"name": "@epiphanygraph/epiphany-graph-viewer",
"version": "0.1.0",
"dependencies": {
"elkjs": "^0.11.0"
},
"devDependencies": {
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
@ -1484,12 +1481,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/elkjs": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz",
"integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==",
"license": "EPL-2.0"
},
"node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",

View file

@ -24,15 +24,13 @@
"build:solver:wasm": "powershell -ExecutionPolicy Bypass -File ./scripts/Build-RustSolverWasm.ps1",
"typecheck": "tsc --noEmit",
"tune:simulation": "node ./scripts/tune-simulation-profile.mjs",
"profile:solver": "node ./scripts/profile-rust-solver-performance.mjs",
"preview": "vite preview"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"dependencies": {
"elkjs": "^0.11.0"
},
"devDependencies": {
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",

View file

@ -0,0 +1,459 @@
import fs from "node:fs/promises";
import path from "node:path";
import { performance } from "node:perf_hooks";
const repoRoot = path.resolve(import.meta.dirname, "..", "..", "..");
const packageRoot = path.resolve(import.meta.dirname, "..");
const wasmBytesPath = path.join(packageRoot, "src", "lib", "solver-wasm-bytes.ts");
const outputDir = path.join(repoRoot, "benchmarks", "realtime");
const outputJsonPath = path.join(outputDir, "rust-solver-performance-baseline.json");
const outputCsvPath = path.join(outputDir, "rust-solver-performance-baseline.csv");
const latencySvgPath = path.join(outputDir, "rust-solver-latency.svg");
const convergenceSvgPath = path.join(outputDir, "rust-solver-convergence.svg");
const solver = await loadSolver();
const graphSizes = [24, 64, 128, 256, 512, 1024];
const graphFamilies = [
"chain",
"layered-dag",
"binary-tree",
"hub-spoke",
"clustered-modules",
];
const latencyWindowsMs = [1.5, 3, 6, 12];
const parameterProfiles = [
{ id: "fast-flat", rankGap: 96, nodeGap: 76, edgeLength: 130 },
{ id: "balanced-hierarchy", rankGap: 136, nodeGap: 92, edgeLength: 170 },
{ id: "quality-spacious", rankGap: 180, nodeGap: 118, edgeLength: 230 },
];
const iterationBudgets = [32, 96, 224];
const run = {
generatedAtUtc: new Date().toISOString(),
description:
"Smoke baseline for the Rust WASM graph solver used by the React viewer. Measures graph family, size, latency-window, meta-heuristic parameter, and convergence tradeoffs.",
method: {
graphSizes,
graphFamilies,
latencyWindowsMs,
parameterProfiles,
iterationBudgets,
samplesPerCandidate: 1,
warmupRuns: 0,
reference: "quality-spacious parameters at a size-capped high iteration count",
loss:
"Normalized RMS node displacement from the reference layout after aligning centroids. Lower is closer to the high-iteration solver result.",
},
rows: [],
latencyWindowFrontier: [],
summary: {},
};
for (const family of graphFamilies) {
for (const nodeCount of graphSizes) {
const graph = buildGraph(family, nodeCount);
const referenceIterations = nodeCount <= 256 ? 360 : nodeCount <= 512 ? 280 : 220;
const reference = layoutGraph(graph, {
...parameterProfiles[2],
iterations: referenceIterations,
});
for (const profile of parameterProfiles) {
for (const iterations of iterationBudgets) {
const candidate = measureCandidate(graph, {
...profile,
iterations,
});
run.rows.push({
family,
nodeCount,
edgeCount: graph.edgePairs.length / 2,
profileId: profile.id,
iterations,
rankGap: profile.rankGap,
nodeGap: profile.nodeGap,
edgeLength: profile.edgeLength,
latencyMs: round(candidate.latencyMs),
p95LatencyMs: round(candidate.p95LatencyMs),
loss: round(layoutLoss(candidate.coordinates, reference.coordinates)),
edgeStress: round(edgeStress(graph, candidate.coordinates, profile.edgeLength)),
referenceIterations,
});
}
}
}
}
for (const family of graphFamilies) {
for (const nodeCount of graphSizes) {
const rows = run.rows.filter((row) => row.family === family && row.nodeCount === nodeCount);
for (const budgetMs of latencyWindowsMs) {
const eligible = rows
.filter((row) => row.p95LatencyMs <= budgetMs)
.sort((left, right) => left.loss - right.loss || left.p95LatencyMs - right.p95LatencyMs);
const fallback = rows
.slice()
.sort((left, right) => left.p95LatencyMs - right.p95LatencyMs || left.loss - right.loss);
const best = eligible[0] ?? fallback[0];
run.latencyWindowFrontier.push({
family,
nodeCount,
budgetMs,
metBudget: Boolean(eligible[0]),
profileId: best.profileId,
iterations: best.iterations,
p95LatencyMs: best.p95LatencyMs,
loss: best.loss,
});
}
}
}
run.summary = summarize(run.rows, run.latencyWindowFrontier);
await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(`${outputJsonPath}.tmp`, `${JSON.stringify(run, null, 2)}\n`);
await fs.rename(`${outputJsonPath}.tmp`, outputJsonPath);
await fs.writeFile(`${outputCsvPath}.tmp`, csvRows(run.rows));
await fs.rename(`${outputCsvPath}.tmp`, outputCsvPath);
await fs.writeFile(`${latencySvgPath}.tmp`, latencySvg(run.rows));
await fs.rename(`${latencySvgPath}.tmp`, latencySvgPath);
await fs.writeFile(`${convergenceSvgPath}.tmp`, convergenceSvg(run.rows));
await fs.rename(`${convergenceSvgPath}.tmp`, convergenceSvgPath);
console.log(`Wrote ${outputJsonPath}`);
console.log(`Wrote ${outputCsvPath}`);
console.log(`Wrote ${latencySvgPath}`);
console.log(`Wrote ${convergenceSvgPath}`);
async function loadSolver() {
const source = await fs.readFile(wasmBytesPath, "utf8");
const match = source.match(/epiphanyGraphSolverWasmBase64\s*=\s*"([^"]+)"/);
if (!match) {
throw new Error(`Could not read embedded solver WASM from ${wasmBytesPath}`);
}
const bytes = Buffer.from(match[1], "base64");
const result = await WebAssembly.instantiate(bytes, {});
const exports = result.instance.exports;
for (const name of [
"memory",
"epiphany_graph_alloc_f32",
"epiphany_graph_dealloc_f32",
"epiphany_graph_alloc_u32",
"epiphany_graph_dealloc_u32",
"epiphany_graph_layout_2d",
]) {
if (!(name in exports)) {
throw new Error(`Rust graph solver wasm is missing ${name}`);
}
}
return exports;
}
function measureCandidate(graph, config) {
const samples = [];
let coordinates = null;
for (let index = 0; index < 1; index += 1) {
const start = performance.now();
coordinates = layoutGraph(graph, config).coordinates;
samples.push(performance.now() - start);
}
return {
coordinates,
latencyMs: average(samples),
p95LatencyMs: percentile(samples, 0.95),
};
}
function layoutGraph(graph, config) {
const nodeCount = graph.nodeWeights.length;
const edgeCount = graph.edgePairs.length / 2;
const nodeWeightsPtr = solver.epiphany_graph_alloc_f32(nodeCount);
const edgePairsPtr = solver.epiphany_graph_alloc_u32(graph.edgePairs.length);
const outputPtr = solver.epiphany_graph_alloc_f32(nodeCount * 4);
try {
new Float32Array(solver.memory.buffer, nodeWeightsPtr, nodeCount).set(graph.nodeWeights);
new Uint32Array(solver.memory.buffer, edgePairsPtr, graph.edgePairs.length).set(graph.edgePairs);
const status = solver.epiphany_graph_layout_2d(
nodeWeightsPtr,
nodeCount,
edgePairsPtr,
edgeCount,
outputPtr,
config.iterations,
config.rankGap,
config.nodeGap,
config.edgeLength,
);
if (status !== 0) {
throw new Error(`Rust graph solver failed with status ${status}`);
}
const coordinates = new Float32Array(nodeCount * 2);
const raw = new Float32Array(solver.memory.buffer, outputPtr, nodeCount * 4);
for (let index = 0; index < nodeCount; index += 1) {
coordinates[index * 2] = raw[index * 4];
coordinates[index * 2 + 1] = raw[index * 4 + 1];
}
return { coordinates };
} finally {
solver.epiphany_graph_dealloc_f32(nodeWeightsPtr, nodeCount);
solver.epiphany_graph_dealloc_u32(edgePairsPtr, graph.edgePairs.length);
solver.epiphany_graph_dealloc_f32(outputPtr, nodeCount * 4);
}
}
function buildGraph(family, nodeCount) {
const edges = [];
if (family === "chain") {
for (let index = 1; index < nodeCount; index += 1) {
edges.push(index - 1, index);
}
} else if (family === "layered-dag") {
const width = Math.max(4, Math.round(Math.sqrt(nodeCount)));
for (let index = 0; index < nodeCount; index += 1) {
const nextLayer = index + width;
if (nextLayer < nodeCount) {
edges.push(index, nextLayer);
}
if (index % width !== width - 1 && nextLayer + 1 < nodeCount) {
edges.push(index, nextLayer + 1);
}
}
} else if (family === "binary-tree") {
for (let index = 1; index < nodeCount; index += 1) {
edges.push(Math.floor((index - 1) / 2), index);
}
} else if (family === "hub-spoke") {
const hubs = Math.max(2, Math.floor(Math.sqrt(nodeCount) / 2));
for (let index = hubs; index < nodeCount; index += 1) {
edges.push(index % hubs, index);
if (index + hubs < nodeCount && index % 3 === 0) {
edges.push(index, index + hubs);
}
}
} else if (family === "clustered-modules") {
const clusterSize = Math.max(6, Math.round(Math.sqrt(nodeCount)));
for (let index = 0; index < nodeCount; index += 1) {
const clusterStart = Math.floor(index / clusterSize) * clusterSize;
const next = clusterStart + ((index - clusterStart + 1) % clusterSize);
if (next < nodeCount && next !== index) {
edges.push(index, next);
}
if (index + clusterSize < nodeCount) {
edges.push(index, index + clusterSize);
}
}
} else {
throw new Error(`Unknown graph family ${family}`);
}
const degrees = new Uint32Array(nodeCount);
for (let index = 0; index < edges.length; index += 2) {
degrees[edges[index]] += 1;
degrees[edges[index + 1]] += 1;
}
const nodeWeights = new Float32Array(nodeCount);
for (let index = 0; index < nodeCount; index += 1) {
nodeWeights[index] = 1 + degrees[index] * 0.18 + deterministicNoise(family, index) * 0.24;
}
return {
nodeWeights,
edgePairs: new Uint32Array(edges),
};
}
function layoutLoss(candidate, reference) {
const count = candidate.length / 2;
const candidateCenter = centroid(candidate);
const referenceCenter = centroid(reference);
let sum = 0;
let maxReferenceRadius = 1;
for (let index = 0; index < count; index += 1) {
const candidateX = candidate[index * 2] - candidateCenter.x;
const candidateY = candidate[index * 2 + 1] - candidateCenter.y;
const referenceX = reference[index * 2] - referenceCenter.x;
const referenceY = reference[index * 2 + 1] - referenceCenter.y;
maxReferenceRadius = Math.max(maxReferenceRadius, Math.hypot(referenceX, referenceY));
sum += (candidateX - referenceX) ** 2 + (candidateY - referenceY) ** 2;
}
return Math.sqrt(sum / count) / maxReferenceRadius;
}
function edgeStress(graph, coordinates, targetLength) {
const stresses = [];
for (let index = 0; index < graph.edgePairs.length; index += 2) {
const source = graph.edgePairs[index];
const target = graph.edgePairs[index + 1];
const length = Math.hypot(
coordinates[source * 2] - coordinates[target * 2],
coordinates[source * 2 + 1] - coordinates[target * 2 + 1],
);
stresses.push(Math.abs(length - targetLength) / Math.max(1, targetLength));
}
return average(stresses);
}
function summarize(rows, frontier) {
const bySize = graphSizes.map((nodeCount) => {
const sizeRows = rows.filter((row) => row.nodeCount === nodeCount);
return {
nodeCount,
bestP95LatencyMs: round(Math.min(...sizeRows.map((row) => row.p95LatencyMs))),
bestLoss: round(Math.min(...sizeRows.map((row) => row.loss))),
medianP95LatencyMs: round(percentile(sizeRows.map((row) => row.p95LatencyMs), 0.5)),
};
});
const largestRealtime = [...frontier]
.filter((row) => row.metBudget)
.sort((left, right) => right.nodeCount - left.nodeCount || left.budgetMs - right.budgetMs)[0];
return {
bySize,
largestBudgetMeetingRun: largestRealtime ?? null,
bestOverallLoss: rows.slice().sort((left, right) => left.loss - right.loss)[0],
fastestOverall: rows.slice().sort((left, right) => left.p95LatencyMs - right.p95LatencyMs)[0],
};
}
function csvRows(rows) {
const header = [
"family",
"nodeCount",
"edgeCount",
"profileId",
"iterations",
"rankGap",
"nodeGap",
"edgeLength",
"latencyMs",
"p95LatencyMs",
"loss",
"edgeStress",
"referenceIterations",
];
return [
header.join(","),
...rows.map((row) => header.map((key) => row[key]).join(",")),
"",
].join("\n");
}
function latencySvg(rows) {
const fastestBySize = graphSizes.map((nodeCount) => ({
x: nodeCount,
y: Math.min(...rows.filter((row) => row.nodeCount === nodeCount).map((row) => row.p95LatencyMs)),
}));
return chartSvg({
title: "Rust Solver P95 Latency By Graph Size",
xLabel: "nodes",
yLabel: "p95 ms",
series: [{ id: "fastest", color: "#22d3ee", points: fastestBySize }],
});
}
function convergenceSvg(rows) {
const byIteration = iterationBudgets.map((iterations) => ({
x: iterations,
y: average(rows.filter((row) => row.iterations === iterations).map((row) => row.loss)),
}));
return chartSvg({
title: "Rust Solver Convergence Loss By Iterations",
xLabel: "iterations",
yLabel: "normalized loss",
series: [{ id: "average loss", color: "#f472b6", points: byIteration }],
});
}
function chartSvg({ title, xLabel, yLabel, series }) {
const width = 920;
const height = 460;
const padding = { left: 74, right: 36, top: 58, bottom: 70 };
const allPoints = series.flatMap((item) => item.points);
const xMin = Math.min(...allPoints.map((point) => point.x));
const xMax = Math.max(...allPoints.map((point) => point.x));
const yMin = 0;
const yMax = Math.max(0.001, Math.max(...allPoints.map((point) => point.y)) * 1.12);
const plotWidth = width - padding.left - padding.right;
const plotHeight = height - padding.top - padding.bottom;
const sx = (value) => padding.left + ((value - xMin) / Math.max(1, xMax - xMin)) * plotWidth;
const sy = (value) => padding.top + plotHeight - ((value - yMin) / Math.max(0.001, yMax - yMin)) * plotHeight;
const paths = series.map((item) => {
const d = item.points
.map((point, index) => `${index === 0 ? "M" : "L"} ${sx(point.x).toFixed(2)} ${sy(point.y).toFixed(2)}`)
.join(" ");
const circles = item.points
.map((point) => `<circle cx="${sx(point.x).toFixed(2)}" cy="${sy(point.y).toFixed(2)}" r="4" fill="${item.color}" />`)
.join("");
return `<path d="${d}" fill="none" stroke="${item.color}" stroke-width="3" />${circles}`;
}).join("");
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<rect width="100%" height="100%" fill="#071019" />
<text x="${padding.left}" y="32" fill="#e5eef8" font-family="Arial, sans-serif" font-size="22" font-weight="700">${escapeXml(title)}</text>
<line x1="${padding.left}" y1="${padding.top + plotHeight}" x2="${padding.left + plotWidth}" y2="${padding.top + plotHeight}" stroke="#64748b" />
<line x1="${padding.left}" y1="${padding.top}" x2="${padding.left}" y2="${padding.top + plotHeight}" stroke="#64748b" />
<text x="${padding.left + plotWidth / 2}" y="${height - 22}" fill="#cbd5e1" font-family="Arial, sans-serif" font-size="14" text-anchor="middle">${escapeXml(xLabel)}</text>
<text x="22" y="${padding.top + plotHeight / 2}" fill="#cbd5e1" font-family="Arial, sans-serif" font-size="14" text-anchor="middle" transform="rotate(-90 22 ${padding.top + plotHeight / 2})">${escapeXml(yLabel)}</text>
<text x="${padding.left}" y="${padding.top + plotHeight + 22}" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12">${xMin}</text>
<text x="${padding.left + plotWidth}" y="${padding.top + plotHeight + 22}" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">${xMax}</text>
<text x="${padding.left - 10}" y="${padding.top + plotHeight}" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">${round(yMin)}</text>
<text x="${padding.left - 10}" y="${padding.top + 4}" fill="#94a3b8" font-family="Arial, sans-serif" font-size="12" text-anchor="end">${round(yMax)}</text>
${paths}
</svg>
`;
}
function centroid(coordinates) {
let x = 0;
let y = 0;
const count = coordinates.length / 2;
for (let index = 0; index < count; index += 1) {
x += coordinates[index * 2];
y += coordinates[index * 2 + 1];
}
return { x: x / count, y: y / count };
}
function deterministicNoise(family, index) {
let hash = 2166136261;
for (const char of `${family}:${index}`) {
hash ^= char.charCodeAt(0);
hash = Math.imul(hash, 16777619);
}
return ((hash >>> 0) % 1000) / 1000;
}
function average(values) {
const finite = values.filter(Number.isFinite);
if (finite.length === 0) {
return 0;
}
return finite.reduce((sum, value) => sum + value, 0) / finite.length;
}
function percentile(values, fraction) {
const sorted = values.filter(Number.isFinite).sort((left, right) => left - right);
if (sorted.length === 0) {
return 0;
}
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * fraction))];
}
function round(value) {
return Number(value.toFixed(4));
}
function escapeXml(value) {
return String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}

View file

@ -600,7 +600,7 @@ export function EpiphanyGraphViewer({
<div>Wheel to zoom. Drag the void to pan. Click something interesting and the side panel stops being decorative.</div>
</div>
{status === "loading" && <StageMessage tone="#67e8f9">Laying out the graph. ELK is thinking noble thoughts.</StageMessage>}
{status === "loading" && <StageMessage tone="#67e8f9">Laying out the graph. Rust solver is taking the hill.</StageMessage>}
{status === "error" && <StageMessage tone="#fca5a5">{errorMessage ?? "Layout failed."}</StageMessage>}
{status === "ready" && activeLayout && (
<svg

View file

@ -1,4 +1,3 @@
import ELK from "elkjs/lib/elk.bundled.js";
import { layoutWithRustSolver } from "./solver-wasm";
import type {
EpiphanyGraph,
@ -14,33 +13,7 @@ import type {
PositionedPoint,
} from "./types";
const elk = new ELK();
type ElkSection = {
startPoint?: { x: number; y: number };
endPoint?: { x: number; y: number };
bendPoints?: Array<{ x: number; y: number }>;
};
type ElkLayoutNode = {
id: string;
x?: number;
y?: number;
width?: number;
height?: number;
};
type ElkLayoutEdge = {
id?: string;
sections?: ElkSection[];
};
type ElkLayoutGraph = {
width?: number;
height?: number;
children?: ElkLayoutNode[];
edges?: ElkLayoutEdge[];
};
type NodeSizingMode = "layered" | "compact";
type LayoutViewport = {
width: number;
@ -66,111 +39,6 @@ async function layoutGraph(
links: EpiphanyGraphLink[],
mode: EpiphanyGraphLayoutMode = "layered",
viewport?: LayoutViewport,
): Promise<GraphLayout> {
try {
return await layoutGraphWithRustSolver(graphKey, graph, links, mode, viewport);
} catch (error) {
console.warn("Epiphany Rust graph solver failed; falling back to ELK.", error);
}
return layoutGraphWithElk(graphKey, graph, links, mode, viewport);
}
async function layoutGraphWithElk(
graphKey: GraphKey,
graph: EpiphanyGraph,
links: EpiphanyGraphLink[],
mode: EpiphanyGraphLayoutMode = "layered",
viewport?: LayoutViewport,
): Promise<GraphLayout> {
const algorithm = elkAlgorithmForMode(mode);
const nodeDegrees = buildNodeDegrees(graph);
const linkCounts = buildLinkCounts(graphKey, links);
const nodes = graph.nodes.map((node) => {
const size = estimateNodeSize(node, graphKey, algorithm, graph.nodes.length, viewport);
return {
id: node.id,
width: size.width,
height: size.height,
labels: [{ text: node.title }],
};
});
const edges = graph.edges.map((edge, index) => ({
id: resolveEdgeId(edge, index),
sources: [edge.source_id],
targets: [edge.target_id],
}));
const elkGraph = {
id: graphKey,
layoutOptions: {
"elk.algorithm": algorithm,
...layoutOptionsFor(graphKey, algorithm),
},
children: nodes,
edges,
};
const layout = (await elk.layout(elkGraph)) as ElkLayoutGraph;
const childLookup = new Map((layout.children ?? []).map((child) => [child.id, child]));
const positionedNodes: PositionedNode[] = graph.nodes.map((node) => {
const child = childLookup.get(node.id);
const degree = nodeDegrees.get(node.id) ?? 0;
const linkCount = linkCounts.get(node.id) ?? 0;
return {
...node,
code_refs: node.code_refs ?? [],
graphKey,
x: child?.x ?? 0,
y: child?.y ?? 0,
width: child?.width ?? estimateNodeSize(node, graphKey, algorithm, graph.nodes.length, viewport).width,
height: child?.height ?? estimateNodeSize(node, graphKey, algorithm, graph.nodes.length, viewport).height,
degree,
linkCount,
badgeText: nodeBadgeText(node.title),
fill: nodeFill(graphKey, node.status),
stroke: nodeStroke(graphKey, node.status),
};
});
const separatedNodes = normalizeNodeBounds(resizeNodesForSeparation(positionedNodes, algorithm));
const layoutEdgeLookup = new Map((layout.edges ?? []).map((edge, index) => [edge.id ?? `edge-${graphKey}-${index}`, edge]));
const nodeLookup = new Map(separatedNodes.map((node) => [node.id, node]));
const positionedEdges: PositionedEdge[] = graph.edges.map((source, index) => {
const resolvedId = resolveEdgeId(source, index);
const layoutEdge = layoutEdgeLookup.get(resolvedId);
const points = isCompactAlgorithm(algorithm) ? [] : edgeSectionsToPoints(layoutEdge?.sections ?? []);
const fallbackPoints = points.length > 0 ? points : straightEdgePoints(source, nodeLookup);
return {
...source,
id: source.id ?? null,
label: source.label ?? null,
mechanism: source.mechanism ?? null,
code_refs: source.code_refs ?? [],
graphKey,
resolvedId,
points: fallbackPoints,
path: pointsToPath(fallbackPoints),
midpoint: edgeMidpoint(fallbackPoints),
};
});
return {
graphKey,
width: Math.max(720, layoutBounds(separatedNodes).width, layout.width ?? 720),
height: Math.max(520, layoutBounds(separatedNodes).height, layout.height ?? 520),
nodes: separatedNodes,
edges: positionedEdges,
};
}
async function layoutGraphWithRustSolver(
graphKey: GraphKey,
graph: EpiphanyGraph,
links: EpiphanyGraphLink[],
mode: EpiphanyGraphLayoutMode,
viewport?: LayoutViewport,
): Promise<GraphLayout> {
const sizingAlgorithm = sizingAlgorithmForMode(mode);
const nodeDegrees = buildNodeDegrees(graph);
@ -261,20 +129,8 @@ function layoutModeFor(
return mode[graphKey] ?? "layered";
}
function elkAlgorithmForMode(mode: EpiphanyGraphLayoutMode) {
if (mode === "stress" || mode === "combined-force") {
return "org.eclipse.elk.stress";
}
if (mode === "force") {
return "org.eclipse.elk.force";
}
return "org.eclipse.elk.layered";
}
function sizingAlgorithmForMode(mode: EpiphanyGraphLayoutMode) {
return mode === "layered" ? "org.eclipse.elk.layered" : "org.eclipse.elk.stress";
function sizingAlgorithmForMode(mode: EpiphanyGraphLayoutMode): NodeSizingMode {
return mode === "layered" ? "layered" : "compact";
}
function rustSolverConfigFor(graphKey: GraphKey, mode: EpiphanyGraphLayoutMode) {
@ -336,8 +192,8 @@ function layoutBounds(nodes: PositionedNode[]) {
};
}
function resizeNodesForSeparation(nodes: PositionedNode[], algorithm: string): PositionedNode[] {
if (!isCompactAlgorithm(algorithm) || nodes.length < 2) {
function resizeNodesForSeparation(nodes: PositionedNode[], sizingMode: NodeSizingMode): PositionedNode[] {
if (!isCompactSizingMode(sizingMode) || nodes.length < 2) {
return nodes;
}
@ -458,8 +314,8 @@ function relaxOverlappingNodes(nodes: PositionedNode[]): PositionedNode[] {
return relaxedNodes;
}
function isCompactAlgorithm(algorithm: string) {
return algorithm === "org.eclipse.elk.stress" || algorithm === "org.eclipse.elk.force";
function isCompactSizingMode(sizingMode: NodeSizingMode) {
return sizingMode === "compact";
}
function nodeCenter(node: { x: number; y: number; width: number; height: number }) {
@ -469,51 +325,6 @@ function nodeCenter(node: { x: number; y: number; width: number; height: number
};
}
function layoutOptionsFor(graphKey: GraphKey, algorithm: string): Record<string, string> {
if (algorithm === "org.eclipse.elk.stress") {
return {
"elk.padding": "[top=96,left=96,bottom=96,right=96]",
"elk.spacing.nodeNode": graphKey === "architecture" ? "96" : "128",
"elk.stress.desiredEdgeLength": graphKey === "architecture" ? "260" : "340",
"elk.stress.iterationLimit": graphKey === "architecture" ? "240" : "180",
"elk.stress.epsilon": "0.0001",
};
}
if (algorithm === "org.eclipse.elk.force") {
return {
"elk.padding": "[top=96,left=96,bottom=96,right=96]",
"elk.spacing.nodeNode": graphKey === "architecture" ? "96" : "132",
"elk.force.model": "FRUCHTERMAN_REINGOLD",
"elk.force.iterations": graphKey === "architecture" ? "650" : "360",
"elk.force.repulsion": graphKey === "architecture" ? "72" : "120",
"elk.force.temperature": "0.1",
"elk.randomSeed": "17",
};
}
return graphKey === "architecture"
? {
"elk.direction": "DOWN",
"elk.edgeRouting": "SPLINES",
"elk.spacing.nodeNode": "48",
"elk.layered.spacing.nodeNodeBetweenLayers": "88",
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
"elk.padding": "[top=40,left=40,bottom=40,right=40]",
}
: {
"elk.direction": "RIGHT",
"elk.edgeRouting": "SPLINES",
"elk.spacing.nodeNode": "88",
"elk.layered.spacing.nodeNodeBetweenLayers": "176",
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
"elk.layered.unnecessaryBendpoints": "true",
"elk.padding": "[top=72,left=84,bottom=72,right=96]",
};
}
function buildNodeDegrees(graph: EpiphanyGraph) {
const degrees = new Map<string, number>();
for (const node of graph.nodes ?? []) {
@ -543,11 +354,11 @@ function resolveEdgeId(edge: EpiphanyGraphEdge, index: number) {
function estimateNodeSize(
node: { title: string; purpose: string; mechanism?: string | null; metaphor?: string | null },
graphKey: GraphKey,
algorithm: string,
sizingMode: NodeSizingMode,
nodeCount: number,
viewport?: LayoutViewport,
) {
const compact = algorithm === "org.eclipse.elk.stress" || algorithm === "org.eclipse.elk.force";
const compact = isCompactSizingMode(sizingMode);
const densityScale = compact ? graphDensityScale(nodeCount, viewport) : 1;
const titleWeight = Math.min(node.title.length, 36);
const purposeWeight = Math.min(node.purpose.length, 110);
@ -638,22 +449,6 @@ function estimateCharacterCapacity(pixelWidth: number, averageCharWidth: number)
return Math.max(12, Math.floor(pixelWidth / averageCharWidth));
}
function edgeSectionsToPoints(sections: ElkSection[]) {
const points: PositionedPoint[] = [];
for (const section of sections) {
if (section.startPoint) {
points.push(section.startPoint);
}
for (const bendPoint of section.bendPoints ?? []) {
points.push(bendPoint);
}
if (section.endPoint) {
points.push(section.endPoint);
}
}
return dedupeSequentialPoints(points);
}
function straightEdgePoints(
edge: EpiphanyGraphEdge,
nodes: Map<string, PositionedNode>,
@ -695,17 +490,6 @@ function edgeMidpoint(points: PositionedPoint[]) {
return points[midIndex];
}
function dedupeSequentialPoints(points: PositionedPoint[]) {
const deduped: PositionedPoint[] = [];
for (const point of points) {
const previous = deduped[deduped.length - 1];
if (!previous || previous.x !== point.x || previous.y !== point.y) {
deduped.push(point);
}
}
return deduped;
}
function nodeBadgeText(title: string) {
const words = title.split(/[\s/_-]+/).filter(Boolean);
if (words.length === 0) {

View file

@ -8,7 +8,6 @@ export default defineConfig({
output: {
manualChunks: {
react: ["react", "react-dom"],
elk: ["elkjs/lib/elk.bundled.js"],
},
},
},