@mark1russell7/highways 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +112 -8
  2. package/build/index.d.ts +989 -187
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +9761 -6738
  5. package/build/index.js.map +1 -1
  6. package/package.json +3 -2
  7. package/src/bench/compare.ts +174 -5
  8. package/src/bench/room.ts +1 -1
  9. package/src/bench/run.ts +38 -3
  10. package/src/bench/screen/exercise.ts +5 -0
  11. package/src/bench/screen/forms.ts +5 -1
  12. package/src/bench/screen/generators.ts +11 -41
  13. package/src/bench/screen/screen.ts +16 -10
  14. package/src/bench/screen/set.ts +156 -49
  15. package/src/bench/suite.ts +110 -12
  16. package/src/cases.ts +64 -18
  17. package/src/families/cases.ts +108 -0
  18. package/src/families/fixtures.ts +101 -0
  19. package/src/families/index.ts +36 -0
  20. package/src/families/model.ts +55 -0
  21. package/src/fixtures.ts +8 -2
  22. package/src/highways/fixtures.test-util.ts +13 -0
  23. package/src/highways/follow.ts +3 -2
  24. package/src/highways/free-lines.ts +141 -0
  25. package/src/highways/gaps/index.ts +85 -0
  26. package/src/highways/index.ts +6 -0
  27. package/src/highways/interchanges/index.ts +231 -0
  28. package/src/highways/search/index.ts +170 -0
  29. package/src/highways/snap.ts +4 -2
  30. package/src/highways/soft-keys/index.ts +8 -4
  31. package/src/highways/steiner/index.ts +215 -0
  32. package/src/highways/streets/index.ts +78 -0
  33. package/src/index.ts +8 -0
  34. package/src/lane-order.ts +37 -0
  35. package/src/lane-orders/stretch/index.ts +486 -0
  36. package/src/lane-orders/stretch/router.ts +22 -0
  37. package/src/levels/depths/index.ts +57 -0
  38. package/src/model.ts +60 -4
  39. package/src/plugs/facing/index.ts +22 -0
  40. package/src/portfolio/router.ts +44 -0
  41. package/src/routers/attraction/index.ts +61 -0
  42. package/src/routers/corners.ts +31 -7
  43. package/src/routers/index.ts +2 -1
  44. package/src/routers/plugs.ts +3 -2
  45. package/src/routers/sections.ts +25 -42
  46. package/src/routers/shared.ts +96 -14
  47. package/src/routers/straight.ts +3 -2
  48. package/src/routers/trunks.ts +24 -5
  49. package/src/segments.ts +4 -25
  50. package/src/strategies/builtins.ts +109 -2
  51. package/src/strategies/model.ts +55 -10
  52. package/src/strategies/options.ts +36 -4
  53. package/src/strategies/schemas.ts +124 -5
  54. package/src/whole/pipeline/index.ts +66 -0
package/README.md CHANGED
@@ -26,6 +26,29 @@ const result = cornersRouter({ name: "corners" }).route(graph);
26
26
  // Each route is a polyline: result.routes[0].points
27
27
  ```
28
28
 
29
+ ### Plugs
30
+
31
+ A node can have `ports`, and an end of an edge can name one of them in `port`. Each plug has one of these forms:
32
+
33
+ | Form | The place of the plug |
34
+ |---|---|
35
+ | `{ id }` | The router picks the side and the point. |
36
+ | `{ id, side }` | The router picks the point on that side. |
37
+ | `{ id, at }` | The plug is fixed at `at`, relative to the top-left corner of the box. |
38
+ | `{ id, side, along }` | The plug is fixed at the side start plus `along` times the side length. |
39
+
40
+ - `along` goes from 0 to 1. The start of `north` and `south` is the left end. The start of `east` and `west` is the top end.
41
+ - When the box gets taller or wider, a plug with `along` keeps its fraction of the side.
42
+ - `resolvePorts(graph)` gives each plug with `along` a fixed `at`. Each router calls it at the start of `route`.
43
+ - The arithmetic is the arithmetic of `portPoint` of `@mark1russell7/layout-contract`. `portOffset` calls it.
44
+ - `resolvePorts` throws a `RangeError` for a bad `along`. A bad value is not finite or is outside 0 to 1. A plug with `along` also needs a `side` and has no `at`.
45
+ - In an input file, such a plug has `side` and `along`, with no `x` and no `y`.
46
+
47
+ ```ts
48
+ const node = { id: "a", box: { x: 0, y: 0, w: 80, h: 40 }, ports: [{ id: "out", side: "east", along: 0.25 }] };
49
+ // The plug "out" is at (80, 10). With h = 120, it is at (80, 30).
50
+ ```
51
+
29
52
  ## Curve types
30
53
 
31
54
  A router gives polylines. The curve layer turns a polyline into a curve of a type. The polyline can come from any router, from a lane of `laneRoutes` or from a link of a zoom level.
@@ -107,6 +130,8 @@ Two edges can go along several runs one after the other. The option `laneOrder`
107
130
  - The default of `trunks` and `shared` is `bundle`. The default of `sections` is `run`, so its runs keep the lane order of version 0.2.0.
108
131
  - `orderLanes(run, routes, order)` gives the order of one run. Its default order is `run`.
109
132
  - The option changes only the lane order. The routes, the runs and their keys stay the same.
133
+ - The option `stretchOrder` of `shared` and `shared-all` gives one order to each stretch of runs: `none` (the default), `consensus`, `periphery` or `sifting`. The default of `shared-stretch` is `sifting`. `siftPasses` sets the passes of `sifting`.
134
+ - `stretchLaneOrders(runs, options?)` gives the order of each run. `laneStretches` gives the stretches. [One lane order for each stretch](../../docs/research/XPLAT.md#one-lane-order-for-each-stretch-lane-stretch) gives the numbers.
110
135
 
111
136
  The benchmark measures the lanes of `highwayCurves` on the case boards, with each lane order. A crossing in the body is between the middles of the first and the last leg of a bundle. An end crossing is near a merge or a split.
112
137
 
@@ -345,10 +370,19 @@ The steps of the pipeline that vary have strategies. Each step has one interface
345
370
  | Step | Input | Output | Built-in strategies |
346
371
  |---|---|---|---|
347
372
  | `group` | `Graph` | `Section`: the tree of groups of the router `sections` | `quadtree`, `keys`, `kd`, `cut` |
373
+ | `plugs` | `PlugInput`: the graph, and optionally the ways of the router | `PlugPair[]`: the two plugs of each edge | `facing` |
348
374
  | `router` | `Graph` | `Result` | `straight`, `plugs`, `corners`, `corners-smooth`, `trunks`, `shared`, `shared-all`, `sections`, `sections-tree` |
349
375
  | `highway` | `HighwayInput`: the graph, optionally the first routes of a router, and optionally the plan before an edit | `HighwayPlan`: the highways as data | `keys`, `shared-ways`, `corridors`, `meet`, `weiszfeld`, `stubs`, `zipper`, `level-links`, `quickbundles`, `road-classes`, `flow-field`, `wspd`, `od-cells`, `select`, `mingle`, `spiral`, `flow-clusters`, `traclus`, `diffusion`, `nested`, `lanes`, `growth`, `edge-paths`, `soft-keys`, `hough`, `ridges`, `hdbscan`, `hub-arcs`, `divided`, `hierarchy`, `fdeb`, `mesh`, `kdeeb`, `cubu`, `mlseb`, `sbeb`, `areas`, `confluent`, `power`, `crossbars`, `frechet`, `spectral`, `resistance`, `link-communities`, `territories`, `consensus`, `p-hub`, `scale-space`, `corridors+breakeven`, `corridors+stretch`, `corridors+ambiguity`, `corridors+hysteresis`, `corridors+ids` |
376
+ | `levels` | `LevelInput`: the graph, the tree of groups and the routes | `Level[]`: the zoom levels of the router `sections` | `depths` |
350
377
  | `curve` | `CurveInput`: the points, the normals of the plugs, the free space and the lane on its board | `CurveSegment[]` | `straight`, `rounded`, `spline`, `arc`, `smooth`, `clothoid`, `highway` |
351
378
  | `room` | `RoomInput`: the boxes and the links of a board, and the tree of sections for `levels` | `Gap[]` | `routes`, `lines`, `levels` |
379
+ | `whole` | `Graph` | `WholeOutput`: the board and its `Result` | `pipeline` |
380
+
381
+ `STEPS` gives the steps in the order of the pipeline: `group`, `plugs`, `router`, `highway`, `levels`, `curve`, `room`, `whole`.
382
+
383
+ - The plugs come before the router, because the router finds its ways between the plugs.
384
+ - The levels come after the routes, because the last level uses the routes.
385
+ - The step `whole` runs the other steps, so it comes last.
352
386
 
353
387
  A strategy has a `step`, a `name`, a `describe` text, its `defaults` and the method `run(input, options?)`. The type of the options comes from `defaults`. Each option that a call does not give gets its default value.
354
388
 
@@ -357,7 +391,29 @@ A strategy has a `step`, a `name`, a `describe` text, its `defaults` and the met
357
391
  - `register(registry, ...strategies)` gives a new registry. It does not change the registry before it.
358
392
  - `listStrategies(registry, step)` gives the strategies of a step, in order. `findStrategy` and `getStrategy` give one strategy by its name.
359
393
  - `asRouter(strategy, options?)` gives a `Router`, and `routersOf(registry)` gives the routers of a registry.
360
- - `STRATEGIES` is the registry of `BUILT_IN_STRATEGIES`. The records `groupStrategies`, `routerStrategies`, `highwayStrategies`, `curveStrategies` and `roomStrategies` give each built-in strategy with its typed options.
394
+ - `STRATEGIES` is the registry of `BUILT_IN_STRATEGIES`. The records `groupStrategies`, `plugStrategies`, `routerStrategies`, `highwayStrategies`, `levelStrategies`, `curveStrategies`, `roomStrategies` and `wholeStrategies` give each built-in strategy with its typed options.
395
+
396
+ ### The steps `plugs`, `levels` and `whole`
397
+
398
+ Each of these steps has one default strategy. The default gives the output of the code before the step, so each route stays the same.
399
+
400
+ - `facing` (the step `plugs`) puts each free plug on the side that faces the other end of its edge. The router `corners` uses it by default.
401
+ The option `plug` of `corners` takes a plug strategy, and `plugOptions` gives its options.
402
+ - `depths` (the step `levels`) makes one zoom level for each depth of the tree, and a last level with each node alone (`depthLevels`).
403
+ The option `level` of `sections` takes a level strategy, and `levelOptions` gives its options. The strategy also gets the level options of the router.
404
+ - `pipeline` (the step `whole`) runs a room strategy, then a router or a highway plan, with the rules that the router reads (`runPipeline`).
405
+ Without options, it routes with `corners`. The demo of the site runs it. It does not draw the curves.
406
+ - `strategyKey(router, step)` gives the option of a router that takes a strategy of the step. `pipeline` uses it for the steps `group`, `plugs` and `levels`.
407
+ - `stepRouterStrategy(strategy)` runs a strategy of a step of `RoutedStep` as a router. `stepRouters(registry, step)` gives one router for each strategy of the step.
408
+
409
+ ```ts
410
+ import { asRouter, CASES, plugStrategies, routerStrategies, wholeStrategies } from "@mark1russell7/highways";
411
+
412
+ const graph = CASES.find((c) => c.name === "case-hub-chain")!.make();
413
+ // The two calls give the routes of `corners` with no option.
414
+ const a = asRouter(routerStrategies.corners, { plug: plugStrategies.facing }).route(graph);
415
+ const b = wholeStrategies.pipeline.run(graph).result;
416
+ ```
361
417
 
362
418
  ### The step `highway`
363
419
 
@@ -420,6 +476,11 @@ The step `highway` decides which edges share a way. It gives a plan, not routes.
420
476
  | `consensus` | The consensus of HWY-035. The finders vote on the pairs of edges, and the pairs that most finders agree on stay. |
421
477
  | `p-hub` | The p-hub median of HWY-041. The p-median of the plugs gives the hubs, and the links between two hubs become highways. |
422
478
  | `scale-space` | The scale space of HWY-053. Only the clusters that live over a long range of scales get highways. |
479
+ | `steiner` | The Steiner trees of HWY-036. Each group of edges gets one rectilinear tree around the boxes, and the shared parts of the tree become highways. |
480
+ | `gaps` | The gap highways of HWY-057. A free line in a gap between boxes holds a highway when enough edges can ride it. |
481
+ | `streets` | The orthogonal streets of HWY-058. A greedy set cover picks the free horizontal and vertical lines that carry the most edge length. |
482
+ | `search` | The local search of HWY-059. It adds, drops and changes the highways of a start plan, and it keeps the plan with the best score. |
483
+ | `interchanges` | The interchanges of HWY-060. An edge can change from one highway to another at an interchange, where two spines come near. |
423
484
 
424
485
  The strategy `corridors` joins two ways when they share a long length. The length is `BREAK_EVEN` times the lane width or more, and the share `rho` of the shorter way or more.
425
486
  The strategies `meet`, `weiszfeld`, `stubs`, `zipper`, `level-links`, `lanes`, `divided` and `crossbars` change the plan of the option `from`: `shared-ways` (the default), `corridors` or `none`.
@@ -485,6 +546,16 @@ const shared = stableOrderStrategy(routerStrategies.shared);
485
546
  const result = repairStrategy(shared).run(graph); // The router `shared+stable+repair`.
486
547
  ```
487
548
 
549
+ ### The router `attraction`
550
+
551
+ The router `attraction` is opt-in. It is not in `STRATEGIES`, so the default outputs do not change.
552
+
553
+ - `attractionRouter(options)` gives the negotiated attraction of HWY-044, and `attractionStrategy` is its router strategy.
554
+ - Each pass after the first routes each edge again. A link that many edges used in the passes before gets a lower cost.
555
+ - The options are `passes` (1 to 10), `attraction` (0 to 1), `capacityWeight` (0 to 10) and `laneWidth` (1 to 12).
556
+ - A capacity term raises the cost of a link when its users are more than its width holds.
557
+ - With 1 pass, `attraction: 0` and `capacityWeight: 0`, the routes are the routes of `shared`.
558
+
488
559
  ### The portfolio router
489
560
 
490
561
  The router `portfolio` is opt-in. It is not in `STRATEGIES`, so the default outputs do not change. [`PORTFOLIO.md`](../../docs/research/PORTFOLIO.md#the-portfolio-router-portfolio-api) gives its tables.
@@ -505,6 +576,7 @@ The router `portfolio` is opt-in. It is not in `STRATEGIES`, so the default outp
505
576
  - The result has the output, the winner, the trace of the pick and the move count `move.pick`.
506
577
  - `exactStretches(graph, options?)` gives a function from the routes to the stretch of each edge in the exact model. The router uses it for the cap. `exactStretch` gives the largest stretch.
507
578
  - `portfolioRouter(options?)` gives a `Router`, and `portfolioStrategy` is the router strategy. Add `portfolioStrategy` to a registry of your own to race it.
579
+ - `portfolioStrategy.schema` declares each option except the clock `now` (SCHEMA-GAPS). The lists of `rule`, `cap`, `k` and `hashes` come from `PORTFOLIO_RULES`, `PORTFOLIO_CAPS`, `PORTFOLIO_KS_API` and `PORTFOLIO_HASHES`.
508
580
 
509
581
  ```ts
510
582
  import { routePortfolio } from "@mark1russell7/highways";
@@ -512,15 +584,24 @@ import { routePortfolio } from "@mark1russell7/highways";
512
584
  const { result, winner } = routePortfolio(graph, { rule: "all", cap: 1.25, k: 5, budgetMs: 50 });
513
585
  ```
514
586
 
587
+ ### The router `shared-stretch`
588
+
589
+ The router `shared-stretch` is opt-in. It is `shared` with the stretch order `sifting`. It is not in `STRATEGIES`, so the default outputs do not change.
590
+
591
+ - `sharedStretchStrategy` is the router strategy. Add it to a registry of your own to race it.
592
+ - Its routes are the routes of `shared`. Only the lane order of the runs changes.
593
+ - On `screen-v1`, the order `sifting` cuts the lane crossings and the broken pairs of `shared` from 21 to 16.
594
+ - Each run calculates the relation of each pair of its lanes. On a dense board of 1000 boxes, a route takes 25 times the time of `shared`.
595
+
515
596
  ### Declarations of the options
516
597
 
517
598
  Each strategy declares its options in `schema`. A declaration tells the type, the range, the step and the meaning of one option. Tools make controls and sweeps from these declarations, with no other data.
518
599
 
519
600
  | Type | Fields | Values |
520
601
  |---|---|---|
521
- | `number`, `integer` | `min`, `max`, `step`, `scale`, `special`, `marks` | A number. An `integer` takes only whole numbers. |
602
+ | `number`, `integer` | `min`, `max`, `step`, `scale`, `special`, `marks`, `values` | A number. An `integer` takes only whole numbers. With `values`, only one of `values`. |
522
603
  | `boolean` | | `false` or `true`. |
523
- | `enum` | `values` | One of `values`. |
604
+ | `enum` | `values`, `custom` | One of `values`. With `custom: "function"`, also a rule of the caller. |
524
605
  | `function`, `strategy`, `list`, `object` | `step` (for `strategy`) | A rule, a strategy, an array or an object. A sweep cannot set them. |
525
606
 
526
607
  Each declaration has a `describe` sentence and an optional `default`.
@@ -529,18 +610,27 @@ Each declaration has a `describe` sentence and an optional `default`.
529
610
  - The `scale` is `linear` or `log`. A sweep on the scale `log` gives values with equal ratios.
530
611
  - The `special` values are valid outside the range. `Infinity` sets no limit.
531
612
  - The `marks` are the notable values: the default and the values of the sweeps of the benchmark.
613
+ - The `values` of a number option are its only valid values. A control shows them as a list, and a sweep gives them. The `cap` of the portfolio router uses them.
614
+ - An enum with `custom: "function"` names the known rules of an option that also takes a rule. A control sets the names, and a caller can still give its own rule.
532
615
  - The `default` is the value that the strategy uses when the caller and `defaults` give no value. A value of `defaults` comes first.
533
616
 
534
617
  The type `OptionSchema<O>` needs one declaration for each key of the options, and no other key. A number option needs a number declaration, and an enum declaration takes only the values of its type.
618
+ An option of a rule or its name, for example `GroupRule | "collection" | "all"`, takes an enum of the names or a declaration of the rule.
619
+
620
+ The option `rule` of the routers `trunks`, `shared` and `shared-all`, and of the highway strategies `keys`, `shared-ways` and `soft-keys`, takes a name (SCHEMA-GAPS):
621
+
622
+ - With `collection`, the rule is `byCollection`. With `all`, each edge has one key. For the router `shared`, `all` is the same as no rule.
623
+ - The `defaults` of each strategy keep the rule `byCollection`, as before. The declaration gives its name `collection` as its `default`. The router `shared-all` sets no rule.
624
+ - A rule of the caller stays valid. The name `collection` gives the same output as the rule `byCollection`.
535
625
 
536
626
  The helpers read the declarations:
537
627
 
538
628
  - `optionDefaults(strategy)` gives the effective default of each option. The strategy gives the same output with this object as with no options.
539
629
  - `listOptions(strategy)` gives each option with its key, its declaration and its default, in the order of the schema.
540
- - `checkOptions(strategy, options)` reports each value without a declaration, of the wrong type, out of the range or not a whole number. It changes no value.
541
- - `clampOptions(strategy, options)` moves each number into its range, for a control. A special value stays.
630
+ - `checkOptions(strategy, options)` reports each value without a declaration, of the wrong type, out of the range, not a whole number or not one of `values`. It changes no value.
631
+ - `clampOptions(strategy, options)` moves each number into its range, for a control. A number with `values` moves to the nearest value. A special value stays.
542
632
  - `checkSchema(strategy)` reports each option of `defaults` without a declaration, and each bad declaration.
543
- - `sweepValues(declaration, { count, special })` gives the values of a sweep. Without `count`, a number option gives its `marks`.
633
+ - `sweepValues(declaration, { count, special })` gives the values of a sweep. Without `count`, a number option gives its `marks`. Else, a number option with `values` gives its `values`.
544
634
  - `sweepOf(strategy, key, options)` gives one setting for each value of a sweep. The name of a setting is the key and the value.
545
635
  - `sweepGrid(strategy, keys, options)` gives one setting for each combination of the values of the keys.
546
636
 
@@ -556,7 +646,7 @@ const rows = sweepSections("case-hub-chain", graph, sweepOf(routerStrategies.sec
556
646
 
557
647
  A test examines each built-in strategy with `checkSchema`. Thus an option of `defaults` without a declaration makes the test fail.
558
648
 
559
- The plugs have one strategy, so they have no step yet. The step `group` gives the grouping rule of the zoom levels.
649
+ The step `plugs` gives the plugs of the router `corners`. The step `group` gives the grouping rule of the zoom levels, and the step `levels` makes the levels.
560
650
 
561
651
  ## Measures and the report
562
652
 
@@ -596,7 +686,20 @@ The script `bench` takes `--seeds <n>` and `--report <file>`. A family with more
596
686
 
597
687
  The workflow `Bench` runs the suite in parts. Each part writes its report, and `joinReports` joins the reports into the report of one run.
598
688
 
599
- `compareStep` runs each strategy of one step on one board, with the measures of the suite. It gives one row for each strategy and the Pareto front of the step. The options give each strategy its own options. The compare page of the site uses it.
689
+ Three options of `runSuite` keep each part short (BENCH-GREEN):
690
+
691
+ - `shard` runs one shard of the cells of the steps `router` and `highway`. The strategy at place `s` on the seed `k` belongs to the shard `(s + k) % count`.
692
+ - `caps` stops a strategy above a fixture size. The report lists each such cell in `capped`.
693
+ - `pair` names a baseline router, for example `shared`. On the first seed of each fixture to `pairMax` nodes, the baseline routes in turn with each strategy. The row gets `paired`, with the two medians and their ratio.
694
+
695
+ `compareStep` runs each strategy of one step on one board, with the measures of the suite. It gives one row for each strategy and the Pareto front of the step. The options give each strategy its own options.
696
+
697
+ `compareAnyStep` does the same for each step of `COMPARE_ALL_STEPS`, which is `STEPS`. The compare page of the site uses it.
698
+
699
+ - The step `highway` makes the plan of each strategy, and `follow` routes the plan, as in the screen. `highwayRouters` gives these routers.
700
+ - The steps `plugs`, `levels` and `whole` run each strategy as a router (`stepRouterStrategy`), and measure the routes as the step `router`.
701
+ - The step `room` opens the gaps of a packed board with each strategy and measures the open board with `roomValues`.
702
+ - The option `onRow` gets each row when its strategy is done, so a host can show the progress.
600
703
 
601
704
  With the option `lanes`, the step `curve` opens each shared way of the routes of `curveRouter` into lanes, and each curve draws the lanes. Each input then has its `lane`, so the curve `highway` bends the lanes of a bundle as one with each of its options.
602
705
 
@@ -615,6 +718,7 @@ A transcendental Math function, for example `Math.atan2` or `Math.hypot`, can gi
615
718
  - For a new decision, use exact arithmetic: a cross product, a dot product or a squared length.
616
719
  - A compare function of a sort must be a total order. Otherwise, two engines can give two orders.
617
720
  - The lane order of a run comes from a merge sort of the package, so each engine gives the same order (LANE-ORDER).
721
+ - Each sort of the stretch orders (LANE-STRETCH) has a total order. Its ties break by the key of the edge or of the run. Thus the 4 cells of `Xplat` agree.
618
722
  - Version 0.8.0 changes the default lane order of some shared runs. No route point moves. [The lane order](../../docs/research/XPLAT.md#the-lane-order-lane-order) gives the changes.
619
723
  - The note [`XPLAT.md`](../../docs/research/XPLAT.md) gives the result of the item XPLAT-HASH.
620
724