@mark1russell7/highways 0.9.3 → 0.9.5
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.
- package/README.md +85 -5
- package/build/index.d.ts +954 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +10689 -5294
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/screen/exercise.ts +4 -1
- package/src/bench/screen/set.ts +58 -1
- package/src/families/cases.ts +17 -9
- package/src/families/coverage.ts +502 -0
- package/src/families/index.ts +4 -1
- package/src/highways/balance/index.ts +93 -0
- package/src/highways/bronchial/index.ts +144 -0
- package/src/highways/index.ts +5 -0
- package/src/highways/knee/index.ts +154 -0
- package/src/highways/knee/root.ts +19 -0
- package/src/highways/manifolds/index.ts +157 -0
- package/src/highways/slots/index.ts +178 -0
- package/src/index.ts +8 -0
- package/src/levels/detail/index.ts +140 -0
- package/src/levels/venation/index.ts +191 -0
- package/src/plugs/leaders/index.ts +42 -0
- package/src/plugs/spread/index.ts +39 -5
- package/src/routers/adam/index.ts +294 -0
- package/src/routers/annealing/index.ts +260 -0
- package/src/routers/canals/index.ts +191 -0
- package/src/routers/frames.ts +80 -16
- package/src/routers/gutter.ts +545 -0
- package/src/routers/index.ts +8 -2
- package/src/routers/reroute/index.ts +164 -0
- package/src/routers/shared.ts +43 -2
- package/src/routers/tight/index.ts +122 -0
- package/src/strategies/builtins.ts +158 -2
- package/src/strategies/schemas.ts +143 -0
- package/src/whole/curriculum/index.ts +243 -0
- package/src/whole/fireflies/index.ts +177 -0
- package/src/whole/portfolio/index.ts +108 -0
- package/src/whole/preferences/index.ts +247 -0
- package/src/whole/tracing/index.ts +144 -0
|
@@ -9,6 +9,15 @@ import type { CutOptions } from "../groups/cut/index.ts";
|
|
|
9
9
|
import type { JustifyOptions } from "../plugs/justify.ts";
|
|
10
10
|
import type { SidePlugOptions } from "../plugs/side.ts";
|
|
11
11
|
import type { SpreadOptions } from "../plugs/spread/index.ts";
|
|
12
|
+
import type { LeaderOptions } from "../plugs/leaders/index.ts";
|
|
13
|
+
import type { BalanceOptions } from "../highways/balance/index.ts";
|
|
14
|
+
import type { ManifoldOptions } from "../highways/manifolds/index.ts";
|
|
15
|
+
import type { DetailOptions } from "../levels/detail/index.ts";
|
|
16
|
+
import type { CurriculumOptions } from "../whole/curriculum/index.ts";
|
|
17
|
+
import type { SlotOptions } from "../highways/slots/index.ts";
|
|
18
|
+
import type { PreferenceOptions } from "../whole/preferences/index.ts";
|
|
19
|
+
import type { FireflyOptions } from "../whole/fireflies/index.ts";
|
|
20
|
+
import type { TracingOptions } from "../whole/tracing/index.ts";
|
|
12
21
|
import type { KdOptions } from "../groups/kd/index.ts";
|
|
13
22
|
import type { KeyOptions } from "../groups/keys/index.ts";
|
|
14
23
|
import type { HighwayBase } from "../highways/base.ts";
|
|
@@ -44,6 +53,8 @@ import type { RoadClassOptions } from "../highways/road-classes/index.ts";
|
|
|
44
53
|
import type { ScaleSpaceOptions } from "../highways/scale-space/index.ts";
|
|
45
54
|
import type { GapOptions } from "../highways/gaps/index.ts";
|
|
46
55
|
import type { InterchangeOptions } from "../highways/interchanges/index.ts";
|
|
56
|
+
import type { KneeOptions } from "../highways/knee/index.ts";
|
|
57
|
+
import type { BronchialOptions } from "../highways/bronchial/index.ts";
|
|
47
58
|
import type { SearchOptions } from "../highways/search/index.ts";
|
|
48
59
|
import type { SteinerOptions } from "../highways/steiner/index.ts";
|
|
49
60
|
import type { StreetOptions } from "../highways/streets/index.ts";
|
|
@@ -59,6 +70,8 @@ import type { WspdOptions } from "../highways/wspd/index.ts";
|
|
|
59
70
|
import type { ZipperOptions } from "../highways/zipper/index.ts";
|
|
60
71
|
import type { LaneStretchOptions } from "../lane-orders/stretch/index.ts";
|
|
61
72
|
import type { DepthLevelOptions } from "../levels/depths/index.ts";
|
|
73
|
+
import type { VenationOptions } from "../levels/venation/index.ts";
|
|
74
|
+
import type { BoardPortfolioOptions } from "../whole/portfolio/index.ts";
|
|
62
75
|
import type { CornersOptions } from "../routers/corners.ts";
|
|
63
76
|
import type { SectionsOptions } from "../routers/sections.ts";
|
|
64
77
|
import type { SharedOptions } from "../routers/shared.ts";
|
|
@@ -141,6 +154,20 @@ export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = {
|
|
|
141
154
|
step: 1,
|
|
142
155
|
describe: "The distance from the walls of a frame to the bounds of its inner routes. Without a value, the inset is the margin.",
|
|
143
156
|
},
|
|
157
|
+
inner: {
|
|
158
|
+
type: "enum",
|
|
159
|
+
values: ["corners", "gutter"],
|
|
160
|
+
default: "corners",
|
|
161
|
+
describe: "The mode of the inner routes. With `gutter`, each inner route is orthogonal in the gaps between the nodes of the frame, with a lane in each shared gap.",
|
|
162
|
+
},
|
|
163
|
+
bendCost: {
|
|
164
|
+
type: "number",
|
|
165
|
+
min: 0,
|
|
166
|
+
max: 100,
|
|
167
|
+
step: 1,
|
|
168
|
+
default: 16,
|
|
169
|
+
describe: "The cost of one bend of an inner route of the mode `gutter`, in units of length. A larger cost gives fewer bends.",
|
|
170
|
+
},
|
|
144
171
|
};
|
|
145
172
|
|
|
146
173
|
/** The declarations of the options of the router `trunks`. */
|
|
@@ -1016,3 +1043,119 @@ export const INTERCHANGE_SCHEMA: OptionSchema<InterchangeOptions> = {
|
|
|
1016
1043
|
discount: { type: "number", min: 0.1, max: 1, step: 0.05, marks: [0.3, 0.5, 0.7], default: 0.5, describe: "The cost of a length on a highway against a length off the highways." },
|
|
1017
1044
|
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "An edge rides the network only when its way is this many times its first route or less." },
|
|
1018
1045
|
};
|
|
1046
|
+
|
|
1047
|
+
/** The declarations of the options of the highway strategy `knee` (NAT-018). */
|
|
1048
|
+
export const KNEE_SCHEMA: OptionSchema<KneeOptions> = {
|
|
1049
|
+
sweep: { type: "integer", min: 5, max: 21, step: 1, marks: [5, 11, 21], default: 11, describe: "The number of points of the sweep of the balancing factor." },
|
|
1050
|
+
rule: { type: "enum", values: ["chord", "weights"], default: "chord", describe: "The rule that picks one tree: the knee of the chord, or the least weighted sum." },
|
|
1051
|
+
ink: { type: "number", min: 0, max: 1, step: 0.05, marks: [0.25, 0.5, 0.75], default: 0.5, describe: "The weight of the ink in the rule `weights`. The rest of the weight goes to the path length." },
|
|
1052
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "An edge rides the tree only when its path in the tree is this many times its first route or less." },
|
|
1053
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A stretch of a tree with fewer edges than this number is not a highway." },
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
/** The declarations of the options of the highway strategy `bronchial` (NAT-019). */
|
|
1057
|
+
export const BRONCHIAL_SCHEMA: OptionSchema<BronchialOptions> = {
|
|
1058
|
+
split: { type: "enum", values: ["count", "weight", "gap"], default: "count", describe: "The rule that splits the targets of a tip in two: by count, by weight, or at the widest gap." },
|
|
1059
|
+
share: { type: "number", min: 0.3, max: 0.7, step: 0.05, marks: [0.3, 0.5, 0.7], default: 0.5, describe: "The share of the distance to the center of the targets that a branch grows." },
|
|
1060
|
+
smallest: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "A set with this number of targets or fewer stops the splits." },
|
|
1061
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "An edge rides the tree only when its path in the tree is this many times its first route or less." },
|
|
1062
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A stretch of a tree with fewer edges than this number is not a highway." },
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
/** The declarations of the options of the level strategy `venation` (NAT-016): the options of `depths`, and the options of the backbone. */
|
|
1066
|
+
export const VENATION_SCHEMA: OptionSchema<VenationOptions> = {
|
|
1067
|
+
...DEPTHS_SCHEMA,
|
|
1068
|
+
backbone: { type: "enum", values: ["rng", "gabriel"], default: "rng", describe: "The graph of the backbone: the relative neighbourhood graph, or the Gabriel graph." },
|
|
1069
|
+
discount: { type: "number", min: 0, max: 0.8, step: 0.05, marks: [0, 0.4, 0.8], default: 0.4, describe: "The share of the cost of a backbone link that a later level link saves when an earlier link uses it." },
|
|
1070
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "A level link takes its way on the backbone only when that way is this many times its direct link or less." },
|
|
1071
|
+
maxItems: { type: "integer", min: 10, max: 2000, step: 10, marks: [100, 400, 1000], default: 400, describe: "A level with more items than this number keeps the links of `depths`." },
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
/** The declarations of the options of the whole strategy `board-portfolio` (WILD-045). */
|
|
1075
|
+
export const BOARD_PORTFOLIO_SCHEMA: OptionSchema<BoardPortfolioOptions> = {
|
|
1076
|
+
settings: { type: "integer", min: 3, max: 12, step: 1, marks: [3, 4, 8, 12], default: 4, describe: "The number of settings that the portfolio tries, from the start of its list." },
|
|
1077
|
+
crossings: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2], default: 1, describe: "The weight of the crossings (R1) in the score." },
|
|
1078
|
+
detour: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2], default: 1, describe: "The weight of the median detour (R5) in the score." },
|
|
1079
|
+
ink: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2], default: 1, describe: "The weight of the ink saved (B1) in the score." },
|
|
1080
|
+
budget: { type: "number", min: 0, max: 10, step: 0.1, marks: [0, 1, 10], default: 0, describe: "The time budget in seconds. The value 0 sets no budget." },
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
/** The declarations of the options of the highway strategy `balance` (EDA-017). */
|
|
1084
|
+
export const BALANCE_SCHEMA: OptionSchema<BalanceOptions> = {
|
|
1085
|
+
tau: { type: "number", min: 1.1, max: 2, step: 0.05, marks: [1.1, 1.2, 1.5, 2], default: 1.2, describe: "The target of the stretch of an edge: its route on the plan over its route alone." },
|
|
1086
|
+
iterations: { type: "integer", min: 5, max: 30, step: 1, marks: [5, 10, 30], default: 5, describe: "The number of updates of the prices." },
|
|
1087
|
+
end: { type: "enum", values: ["exit", "entry", "both"], default: "exit", describe: "The end of a ride that the price moves: the exit, the entry, or both by half." },
|
|
1088
|
+
from: { ...HIGHWAY_BASE, default: "shared-ways" },
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
/** The declarations of the options of the highway strategy `manifolds` (WILD-036). */
|
|
1092
|
+
export const MANIFOLD_SCHEMA: OptionSchema<ManifoldOptions> = {
|
|
1093
|
+
kMin: { type: "integer", min: 3, max: 8, step: 1, marks: [3, 4, 6, 8], default: 3, describe: "A manifold has this number of taps or more." },
|
|
1094
|
+
band: { type: "number", min: 0.5, max: 2, step: 0.25, marks: [0.5, 1, 2], default: 1, describe: "The nodes of one row have centers in a band of this many median box heights." },
|
|
1095
|
+
side: { type: "enum", values: ["near", "far"], default: "near", describe: "The place of the header in the gap before the row: near the row, or near the feed node." },
|
|
1096
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "A tap stays on the manifold only when its way is this many times the direct line or less." },
|
|
1097
|
+
from: { ...HIGHWAY_BASE, default: "shared-ways" },
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
/** The declarations of the options of the highway strategy `slots` (RULE-047). */
|
|
1101
|
+
export const SLOT_SCHEMA: OptionSchema<SlotOptions> = {
|
|
1102
|
+
pull: { type: "number", min: 0.25, max: 8, step: 0.25, marks: [0.5, 1, 2, 4, 8], default: 2, describe: "The weight of the pull toward the slot of a new lane." },
|
|
1103
|
+
reach: { type: "number", min: 8, max: 64, step: 4, marks: [8, 16, 24, 48, 64], default: 24, describe: "A route point farther than this distance from a trunk gets no pull." },
|
|
1104
|
+
laneSpacing: { type: "number", min: 2, max: 8, step: 1, marks: [2, 4, 8], default: 4, describe: "The distance between two lanes of a trunk." },
|
|
1105
|
+
maxStretch: { type: "number", min: 1.05, max: 2, step: 0.05, marks: [1.1, 1.25, 1.5, 2], default: 1.25, describe: "The largest new length of a route over its first length after it joins a trunk." },
|
|
1106
|
+
keys: { type: "enum", values: ["same", "any"], default: "same", describe: "The edges that can join a trunk: only the edges with the key of the trunk, or each edge." },
|
|
1107
|
+
from: { ...HIGHWAY_BASE, default: "shared-ways" },
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
/** The declarations of the options of the plug strategy `leaders` (EDA-041). */
|
|
1111
|
+
export const LEADER_SCHEMA: OptionSchema<LeaderOptions> = {
|
|
1112
|
+
leader: { type: "enum", values: ["s", "po", "opo"], default: "po", describe: "The type of leader: the order of the angles, the order along the wall, or even places." },
|
|
1113
|
+
slotSpacing: { type: "number", min: 2, max: 8, step: 1, marks: [2, 4, 8], default: 8, describe: "The least gap between two plugs of one wall." },
|
|
1114
|
+
inset: SPREAD_SCHEMA.inset,
|
|
1115
|
+
rounds: { type: "integer", min: 0, max: 4, step: 1, default: 2, describe: "The passes that sort each wall again by the new places of the far ends." },
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
/** The declarations of the options of the level strategy `detail` (WILD-016 with EDA-038): the options of `depths`, and the limits of the collapse. */
|
|
1119
|
+
export const DETAIL_SCHEMA: OptionSchema<DetailOptions> = {
|
|
1120
|
+
...DEPTHS_SCHEMA,
|
|
1121
|
+
pxMin: { type: "number", min: 0.5, max: 4, step: 0.5, marks: [1, 2, 3, 4], default: 3, describe: "Two parallel legs closer than this number of pixels on the screen become one stroke." },
|
|
1122
|
+
minCount: { type: "integer", min: 2, max: 5, step: 1, default: 2, describe: "A stroke holds this number of edges or more." },
|
|
1123
|
+
w0: { type: "number", min: 1, max: 4, step: 0.5, default: 1.5, describe: "The width of a stroke of one edge, in pixels." },
|
|
1124
|
+
widthScale: { type: "enum", values: ["linear", "sqrt", "log"], default: "log", describe: "The growth of the width of a stroke with its count." },
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
/** The declarations of the options of the whole strategy `curriculum` (RULE-048). */
|
|
1128
|
+
export const CURRICULUM_SCHEMA: OptionSchema<CurriculumOptions> = {
|
|
1129
|
+
order: { type: "enum", values: ["R1-R12-R2", "R12-R1-R2", "R1-R2-R12"], default: "R1-R12-R2", describe: "The order in which the stages add the rules." },
|
|
1130
|
+
passes: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "The number of passes over the edges in each stage." },
|
|
1131
|
+
settings: { type: "integer", min: 2, max: 3, step: 1, default: 3, describe: "The number of settings in the pool of routes, from the start of its list." },
|
|
1132
|
+
detour: { type: "number", min: 1.5, max: 4, step: 0.25, marks: [1.5, 2, 3], default: 2, describe: "A new route has a detour of at most this value, or of its old route." },
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
/** The declarations of the options of the whole strategy `preferences` (RULE-038). */
|
|
1136
|
+
export const PREFERENCE_SCHEMA: OptionSchema<PreferenceOptions> = {
|
|
1137
|
+
l2: { type: "number", min: 0, max: 1, step: 0.01, marks: [0, 0.01, 0.1, 1], default: 0.01, describe: "The weight of the L2 term of the fit of the weights to the made-up pairs." },
|
|
1138
|
+
passes: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "The number of passes over the edges." },
|
|
1139
|
+
settings: { type: "integer", min: 2, max: 3, step: 1, default: 3, describe: "The number of settings in the pool of routes, from the start of its list." },
|
|
1140
|
+
detour: { type: "number", min: 1.5, max: 4, step: 0.25, marks: [1.5, 2, 3], default: 2, describe: "A new route has a detour of at most this value, or of its old route." },
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
/** The declarations of the options of the whole strategy `fireflies` (NAT-047). */
|
|
1144
|
+
export const FIREFLY_SCHEMA: OptionSchema<FireflyOptions> = {
|
|
1145
|
+
search: { type: "enum", values: ["firefly", "bees"], default: "firefly", describe: "The search: the firefly algorithm, or the artificial bee colony." },
|
|
1146
|
+
budget: { type: "integer", min: 4, max: 40, step: 1, marks: [4, 12, 20, 40], default: 12, describe: "The largest number of different settings that the search routes." },
|
|
1147
|
+
colony: { type: "integer", min: 4, max: 20, step: 1, marks: [4, 6, 10, 20], default: 6, describe: "The number of fireflies or food sources." },
|
|
1148
|
+
gamma: { type: "number", min: 0.01, max: 10, step: 0.01, scale: "log", marks: [0.01, 0.1, 1, 10], default: 1, describe: "The absorption of the light of a firefly." },
|
|
1149
|
+
alpha: { type: "number", min: 0.01, max: 0.5, step: 0.01, marks: [0.01, 0.1, 0.2, 0.5], default: 0.2, describe: "The size of the random step of a firefly, as a share of the range." },
|
|
1150
|
+
limit: { type: "integer", min: 10, max: 100, step: 10, marks: [10, 50, 100], default: 10, describe: "A scout bee leaves a food source after this number of tries with no gain." },
|
|
1151
|
+
seed: { type: "integer", min: 0, max: 1_000_000, step: 1, marks: [1, 2, 3], default: 1, describe: "The seed of the search. The same seed gives the same routes." },
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
/** The declarations of the options of the whole strategy `tracing` (WILD-057). */
|
|
1155
|
+
export const TRACING_SCHEMA: OptionSchema<TracingOptions> = {
|
|
1156
|
+
crossing: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2, 4], default: 1, describe: "The weight of a crossing in the tracing load." },
|
|
1157
|
+
merge: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2, 4], default: 2, describe: "The weight of a merge or a split where the edge is not an outer lane." },
|
|
1158
|
+
bend: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 0.5, 1, 4], default: 0.5, describe: "The weight of a bend in the tracing load." },
|
|
1159
|
+
settings: { type: "integer", min: 1, max: 12, step: 1, marks: [1, 4, 8, 12], default: 4, describe: "The number of settings that the strategy tries, from the start of its list." },
|
|
1160
|
+
quantile: { type: "enum", values: ["q50", "q95", "mean"], default: "q95", describe: "The value of the loads of the edges that the pick reads." },
|
|
1161
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { checkProperties } from "../../bench/properties.ts";
|
|
2
|
+
import { dist, polylineLength, segmentsCross, type Point } from "../../geom.ts";
|
|
3
|
+
import type { Graph, Route } from "../../model.ts";
|
|
4
|
+
import type { WholeOutput } from "../../strategies/model.ts";
|
|
5
|
+
import { runPipeline } from "../pipeline/index.ts";
|
|
6
|
+
import type { PortfolioSetting } from "../portfolio/index.ts";
|
|
7
|
+
|
|
8
|
+
/** One rule of the curriculum: the crossings (R1), the lane separation (R12) or the crossing angle (R2). */
|
|
9
|
+
export type CurriculumRule = "R1" | "R12" | "R2";
|
|
10
|
+
|
|
11
|
+
/** The order in which the curriculum adds its rules. */
|
|
12
|
+
export type CurriculumOrder = "R1-R12-R2" | "R12-R1-R2" | "R1-R2-R12";
|
|
13
|
+
|
|
14
|
+
/** The values of `CurriculumOrder`. */
|
|
15
|
+
export const CURRICULUM_ORDERS: readonly CurriculumOrder[] = Object.freeze(["R1-R12-R2", "R12-R1-R2", "R1-R2-R12"]);
|
|
16
|
+
|
|
17
|
+
/** The options of the whole strategy `curriculum` (RULE-048). */
|
|
18
|
+
export interface CurriculumOptions {
|
|
19
|
+
/** The order in which the stages add the rules. The default is `R1-R12-R2`. */
|
|
20
|
+
readonly order: CurriculumOrder;
|
|
21
|
+
/** The number of passes over the edges in each stage, from 1 to 3. The default is 1. */
|
|
22
|
+
readonly passes: number;
|
|
23
|
+
/** The number of settings in the pool of routes, from the start of its list, from 2 to 3. The default is 3. */
|
|
24
|
+
readonly settings: number;
|
|
25
|
+
/** A new route has a detour of at most this value, or of its old route, from 1.5 to 4. The default is 2. */
|
|
26
|
+
readonly detour: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The default options of the strategy `curriculum`. */
|
|
30
|
+
export const CURRICULUM_DEFAULTS: Readonly<CurriculumOptions> = Object.freeze({ order: "R1-R12-R2", passes: 1, settings: 3, detour: 2 });
|
|
31
|
+
|
|
32
|
+
/** Two legs closer than this distance count for the lane separation (R12), as in the screen. */
|
|
33
|
+
const NEAR = 4;
|
|
34
|
+
|
|
35
|
+
/** A route with its bounding box, for the quick test of two routes. */
|
|
36
|
+
interface Shape {
|
|
37
|
+
readonly route: Route;
|
|
38
|
+
readonly x0: number;
|
|
39
|
+
readonly y0: number;
|
|
40
|
+
readonly x1: number;
|
|
41
|
+
readonly y1: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** This function gives the shape of a route. */
|
|
45
|
+
function shapeOf(route: Route): Shape {
|
|
46
|
+
let x0 = Infinity;
|
|
47
|
+
let y0 = Infinity;
|
|
48
|
+
let x1 = -Infinity;
|
|
49
|
+
let y1 = -Infinity;
|
|
50
|
+
for (const p of route.points) {
|
|
51
|
+
x0 = Math.min(x0, p.x);
|
|
52
|
+
y0 = Math.min(y0, p.y);
|
|
53
|
+
x1 = Math.max(x1, p.x);
|
|
54
|
+
y1 = Math.max(y1, p.y);
|
|
55
|
+
}
|
|
56
|
+
return { route, x0, y0, x1, y1 };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The sine of 5 degrees, as a literal. A literal gives the same bits on each platform, so a decision does not depend on `Math.sin`. */
|
|
60
|
+
const SIN_5 = 0.08715574274765817;
|
|
61
|
+
|
|
62
|
+
/** The constants of the arc sine of fdlibm (`e_asin.c`). */
|
|
63
|
+
const PIO2_HI = 1.5707963267948966;
|
|
64
|
+
const PIO2_LO = 6.123233995736766e-17;
|
|
65
|
+
const PIO4_HI = 0.7853981633974483;
|
|
66
|
+
const PS = [1.66666666666666657415e-1, -3.25565818622400915405e-1, 2.01212532134862925881e-1, -4.00555345006794114027e-2, 7.91534994289814532176e-4, 3.47933107596021167570e-5] as const;
|
|
67
|
+
const QS = [-2.40339491173441421878, 2.02094576023350569471, -6.88283971605453293030e-1, 7.70381505559019352791e-2] as const;
|
|
68
|
+
const WORDS = new DataView(new ArrayBuffer(8));
|
|
69
|
+
|
|
70
|
+
/** This function gives the rational part `p / q` of the arc sine of fdlibm at `t`. */
|
|
71
|
+
function asinRatio(t: number): [number, number] {
|
|
72
|
+
const p = t * (PS[0] + t * (PS[1] + t * (PS[2] + t * (PS[3] + t * (PS[4] + t * PS[5])))));
|
|
73
|
+
const q = 1 + t * (QS[0] + t * (QS[1] + t * (QS[2] + t * QS[3])));
|
|
74
|
+
return [p, q];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* This function gives the arc sine of `x` in radians, from -1 to 1. It is the algorithm of fdlibm, with only `+`, `*`, `/` and `Math.sqrt`.
|
|
79
|
+
* These operations are exact to the last bit on each platform, so the result does not depend on the engine.
|
|
80
|
+
*/
|
|
81
|
+
export function arcsin(x: number): number {
|
|
82
|
+
const ax = Math.abs(x);
|
|
83
|
+
if (Number.isNaN(x) || ax > 1) return Number.NaN;
|
|
84
|
+
if (ax === 1) return x * PIO2_HI + x * PIO2_LO;
|
|
85
|
+
if (ax < 0.5) {
|
|
86
|
+
if (ax < 2 ** -27) return x;
|
|
87
|
+
const [p, q] = asinRatio(x * x);
|
|
88
|
+
return x + x * (p / q);
|
|
89
|
+
}
|
|
90
|
+
const t = (1 - ax) * 0.5;
|
|
91
|
+
const [p, q] = asinRatio(t);
|
|
92
|
+
const s = Math.sqrt(t);
|
|
93
|
+
let r: number;
|
|
94
|
+
WORDS.setFloat64(0, ax);
|
|
95
|
+
if (WORDS.getUint32(0) >= 0x3fef3333) {
|
|
96
|
+
r = PIO2_HI - (2 * (s + s * (p / q)) - PIO2_LO);
|
|
97
|
+
} else {
|
|
98
|
+
WORDS.setFloat64(0, s);
|
|
99
|
+
WORDS.setUint32(4, 0);
|
|
100
|
+
const w = WORDS.getFloat64(0);
|
|
101
|
+
const c = (t - w * w) / (s + w);
|
|
102
|
+
const pp = 2 * s * (p / q) - (PIO2_LO - 2 * c);
|
|
103
|
+
const qq = PIO4_HI - 2 * w;
|
|
104
|
+
r = PIO4_HI - (pp - qq);
|
|
105
|
+
}
|
|
106
|
+
return x > 0 ? r : -r;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** This function gives the distance between the segments `ab` and `cd`, when they do not cross. */
|
|
110
|
+
function segmentGap(a: Point, b: Point, c: Point, d: Point): number {
|
|
111
|
+
const at = (p: Point, u: Point, v: Point): number => {
|
|
112
|
+
const dx = v.x - u.x;
|
|
113
|
+
const dy = v.y - u.y;
|
|
114
|
+
const l2 = dx * dx + dy * dy;
|
|
115
|
+
const t = l2 > 0 ? Math.max(0, Math.min(1, ((p.x - u.x) * dx + (p.y - u.y) * dy) / l2)) : 0;
|
|
116
|
+
const ex = p.x - u.x - t * dx;
|
|
117
|
+
const ey = p.y - u.y - t * dy;
|
|
118
|
+
return Math.sqrt(ex * ex + ey * ey);
|
|
119
|
+
};
|
|
120
|
+
return Math.min(at(a, c, d), at(b, c, d), at(c, a, b), at(d, a, b));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* This function gives the costs of the rules between two routes. R1 is the number of crossings.
|
|
125
|
+
* R2 is the sum of `1 - θ / 90` over the crossings. θ is the crossing angle in degrees.
|
|
126
|
+
* R12 is the overlap of the legs of `a` and `b` that are within 5 degrees of parallel. The two legs are nearer than 4 units.
|
|
127
|
+
*/
|
|
128
|
+
export function pairCosts(a: readonly Point[], b: readonly Point[]): Record<CurriculumRule, number> {
|
|
129
|
+
let r1 = 0;
|
|
130
|
+
let r2 = 0;
|
|
131
|
+
let r12 = 0;
|
|
132
|
+
for (let i = 1; i < a.length; i++) {
|
|
133
|
+
const p = a[i - 1]!;
|
|
134
|
+
const q = a[i]!;
|
|
135
|
+
const ux = q.x - p.x;
|
|
136
|
+
const uy = q.y - p.y;
|
|
137
|
+
const lu = Math.sqrt(ux * ux + uy * uy);
|
|
138
|
+
if (lu <= 1e-9) continue;
|
|
139
|
+
for (let j = 1; j < b.length; j++) {
|
|
140
|
+
const r = b[j - 1]!;
|
|
141
|
+
const s = b[j]!;
|
|
142
|
+
const vx = s.x - r.x;
|
|
143
|
+
const vy = s.y - r.y;
|
|
144
|
+
const lv = Math.sqrt(vx * vx + vy * vy);
|
|
145
|
+
if (lv <= 1e-9) continue;
|
|
146
|
+
if (segmentsCross(p, q, r, s)) {
|
|
147
|
+
r1++;
|
|
148
|
+
const angle = (arcsin(Math.min(1, Math.abs(ux * vy - uy * vx) / (lu * lv))) * 180) / Math.PI;
|
|
149
|
+
r2 += 1 - angle / 90;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (Math.abs(ux * vy - uy * vx) > SIN_5 * lu * lv) continue;
|
|
153
|
+
const t0 = ((r.x - p.x) * ux + (r.y - p.y) * uy) / lu;
|
|
154
|
+
const t1 = ((s.x - p.x) * ux + (s.y - p.y) * uy) / lu;
|
|
155
|
+
const overlap = Math.min(lu, Math.max(t0, t1)) - Math.max(0, Math.min(t0, t1));
|
|
156
|
+
if (overlap > 1e-9 && segmentGap(p, q, r, s) < NEAR) r12 += overlap;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { R1: r1, R2: r2, R12: r12 };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** This function gives the detour of a route: its length over the distance between its ends. */
|
|
163
|
+
function detourOf(points: readonly Point[]): number {
|
|
164
|
+
const span = dist(points[0]!, points[points.length - 1]!);
|
|
165
|
+
return span > 1e-9 ? polylineLength(points) / span : 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* This function runs the whole strategy `curriculum` (RULE-048, after curriculum learning, Bengio 2009). The record has no energy solver yet (RULE-019).
|
|
170
|
+
* Thus a pool of routes of fixed settings takes its place. Each edge can take its route from any setting of the pool.
|
|
171
|
+
* The first answer is the first setting. Each stage adds one rule, in the order `order`. A pass of a stage visits the edges, the worst first.
|
|
172
|
+
* An edge takes the route of the pool with the least cost of the new rule. That cost is lower, and the earlier rules do not get worse.
|
|
173
|
+
* A route with a break of a hard rule is not in the pool. The output has the routes alone, with no runs, bundles or levels.
|
|
174
|
+
*/
|
|
175
|
+
export function curriculum(graph: Graph, list: readonly PortfolioSetting[], options: Partial<CurriculumOptions> = {}): WholeOutput {
|
|
176
|
+
const o = { ...CURRICULUM_DEFAULTS, ...options };
|
|
177
|
+
const count = Math.max(1, Math.min(list.length, Math.floor(o.settings)));
|
|
178
|
+
const pool = new Map<string, Route[]>();
|
|
179
|
+
let first: WholeOutput | undefined;
|
|
180
|
+
for (const s of list.slice(0, count)) {
|
|
181
|
+
const limit = (s.pipeline.highway ?? s.pipeline.router).maxNodes;
|
|
182
|
+
if (limit !== undefined && graph.nodes.length > limit) continue;
|
|
183
|
+
const output = runPipeline(graph, s.pipeline);
|
|
184
|
+
first ??= output;
|
|
185
|
+
const faulty = new Set(checkProperties(output.graph, output.result, 1_000_000).map((v) => v.edge));
|
|
186
|
+
for (const r of output.result.routes) {
|
|
187
|
+
if (faulty.has(r.edge) || r.points.length < 2) continue;
|
|
188
|
+
const list2 = pool.get(r.edge);
|
|
189
|
+
if (list2) list2.push(r);
|
|
190
|
+
else pool.set(r.edge, [r]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!first) return runPipeline(graph, list[0]!.pipeline);
|
|
194
|
+
const rules = o.order.split("-") as CurriculumRule[];
|
|
195
|
+
const current = new Map<string, Shape>(first.result.routes.map((r) => [r.edge, shapeOf(r)]));
|
|
196
|
+
// A faulty first route takes the first route of the pool, so no stage starts from a break of a hard rule.
|
|
197
|
+
for (const [edge, shape] of current) {
|
|
198
|
+
const own = pool.get(edge);
|
|
199
|
+
if (own && !own.includes(shape.route)) current.set(edge, shapeOf(own[0]!));
|
|
200
|
+
}
|
|
201
|
+
const costOf = (edge: string, s: Shape): Record<CurriculumRule, number> => {
|
|
202
|
+
const sum = { R1: 0, R2: 0, R12: 0 };
|
|
203
|
+
for (const [other, t] of current) {
|
|
204
|
+
if (other === edge || t.x0 > s.x1 + NEAR || t.x1 < s.x0 - NEAR || t.y0 > s.y1 + NEAR || t.y1 < s.y0 - NEAR) continue;
|
|
205
|
+
const c = pairCosts(s.route.points, t.route.points);
|
|
206
|
+
sum.R1 += c.R1;
|
|
207
|
+
sum.R2 += c.R2;
|
|
208
|
+
sum.R12 += c.R12;
|
|
209
|
+
}
|
|
210
|
+
return sum;
|
|
211
|
+
};
|
|
212
|
+
const edges = [...current.keys()];
|
|
213
|
+
for (let stage = 0; stage < rules.length; stage++) {
|
|
214
|
+
const rule = rules[stage]!;
|
|
215
|
+
const earlier = rules.slice(0, stage);
|
|
216
|
+
for (let pass = 0; pass < Math.max(1, Math.floor(o.passes)); pass++) {
|
|
217
|
+
const costs = new Map(edges.map((e) => [e, costOf(e, current.get(e)!)]));
|
|
218
|
+
const order = [...edges].sort((a, b) => costs.get(b)![rule] - costs.get(a)![rule] || (a < b ? -1 : a > b ? 1 : 0));
|
|
219
|
+
let moved = 0;
|
|
220
|
+
for (const edge of order) {
|
|
221
|
+
const now = current.get(edge)!;
|
|
222
|
+
const base = costOf(edge, now);
|
|
223
|
+
if (base[rule] <= 1e-9) continue;
|
|
224
|
+
const cap = Math.max(o.detour, detourOf(now.route.points));
|
|
225
|
+
let best: { shape: Shape; cost: number } | undefined;
|
|
226
|
+
for (const r of pool.get(edge) ?? []) {
|
|
227
|
+
if (r === now.route || detourOf(r.points) > cap + 1e-9) continue;
|
|
228
|
+
const shape = shapeOf(r);
|
|
229
|
+
const c = costOf(edge, shape);
|
|
230
|
+
if (c[rule] >= base[rule] - 1e-9 || earlier.some((k) => c[k] > base[k] + 1e-9)) continue;
|
|
231
|
+
if (!best || c[rule] < best.cost - 1e-9) best = { shape, cost: c[rule] };
|
|
232
|
+
}
|
|
233
|
+
if (best) {
|
|
234
|
+
current.set(edge, best.shape);
|
|
235
|
+
moved++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (moved === 0) break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const routes = first.result.routes.map((r) => current.get(r.edge)?.route ?? r);
|
|
242
|
+
return { graph: first.graph, result: { routes } };
|
|
243
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { inkSaved } from "../../bench/measures/bundles.ts";
|
|
2
|
+
import { detourSpread } from "../../bench/measures/routes.ts";
|
|
3
|
+
import { checkProperties } from "../../bench/properties.ts";
|
|
4
|
+
import { measureQuality } from "../../bench/quality.ts";
|
|
5
|
+
import { rng } from "../../fixtures.ts";
|
|
6
|
+
import type { Graph } from "../../model.ts";
|
|
7
|
+
import type { Strategy, WholeOutput } from "../../strategies/model.ts";
|
|
8
|
+
import { runPipeline } from "../pipeline/index.ts";
|
|
9
|
+
|
|
10
|
+
/** The search of the whole strategy `fireflies`: the firefly algorithm, or the artificial bee colony. */
|
|
11
|
+
export type SwarmSearch = "firefly" | "bees";
|
|
12
|
+
|
|
13
|
+
/** The values of `SwarmSearch`. */
|
|
14
|
+
export const SWARM_SEARCHES: readonly SwarmSearch[] = Object.freeze(["firefly", "bees"]);
|
|
15
|
+
|
|
16
|
+
/** The options of the whole strategy `fireflies` (NAT-047). */
|
|
17
|
+
export interface FireflyOptions {
|
|
18
|
+
/** The search. The default is `firefly`. */
|
|
19
|
+
readonly search: SwarmSearch;
|
|
20
|
+
/** The budget: the largest number of different settings that the search routes, from 4 to 40. The default is 12. */
|
|
21
|
+
readonly budget: number;
|
|
22
|
+
/** The number of fireflies or food sources, from 4 to 20. The default is 6. */
|
|
23
|
+
readonly colony: number;
|
|
24
|
+
/** The absorption `gamma` of the light of a firefly, from 0.01 to 10. The default is 1. */
|
|
25
|
+
readonly gamma: number;
|
|
26
|
+
/** The size `alpha` of the random step of a firefly, as a share of the range, from 0.01 to 0.5. The default is 0.2. */
|
|
27
|
+
readonly alpha: number;
|
|
28
|
+
/** The number of tries with no gain, after which a scout bee leaves a food source, from 10 to 100. The default is 10. */
|
|
29
|
+
readonly limit: number;
|
|
30
|
+
/** The seed of the search, a whole number from 0. The same seed gives the same routes. The default is 1. */
|
|
31
|
+
readonly seed: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The default options of the strategy `fireflies`. */
|
|
35
|
+
export const FIREFLY_DEFAULTS: Readonly<FireflyOptions> = Object.freeze({ search: "firefly", budget: 12, colony: 6, gamma: 1, alpha: 0.2, limit: 10, seed: 1 });
|
|
36
|
+
|
|
37
|
+
/** The attraction of a firefly at the distance 0, as the record gives it. */
|
|
38
|
+
const BETA0 = 1;
|
|
39
|
+
|
|
40
|
+
/** The grid of the option `discount` of the router `shared`: 0.05 to 1 in steps of 0.05. */
|
|
41
|
+
const DISCOUNTS = 20;
|
|
42
|
+
|
|
43
|
+
/** The grid of the option `margin` of the router `shared`: 3 to 10 in steps of 1. */
|
|
44
|
+
const MARGINS = 8;
|
|
45
|
+
|
|
46
|
+
/** The largest number of rounds of a search. A round with no new setting still counts. */
|
|
47
|
+
const ROUNDS = 60;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* This function gives the attraction of a firefly at the distance `r`: `BETA0 / (1 + u + u² / 2)` with `u = gamma × r²`.
|
|
51
|
+
* It is a rational stand-in for `BETA0 × exp(-u)`, with only `+`, `*` and `/`, so it is exact to the last bit on each platform.
|
|
52
|
+
*/
|
|
53
|
+
export function attraction(gamma: number, r2: number): number {
|
|
54
|
+
const u = gamma * r2;
|
|
55
|
+
return BETA0 / (1 + u + (u * u) / 2);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One setting of the router `shared`: the option `discount` and the option `margin`. */
|
|
59
|
+
export interface SwarmSetting {
|
|
60
|
+
readonly discount: number;
|
|
61
|
+
readonly margin: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** This function gives the setting of a point of the unit square, on the grid of the search. */
|
|
65
|
+
export function settingAt(x: number, y: number): SwarmSetting {
|
|
66
|
+
const i = Math.max(0, Math.min(DISCOUNTS - 1, Math.round(x * (DISCOUNTS - 1))));
|
|
67
|
+
const j = Math.max(0, Math.min(MARGINS - 1, Math.round(y * (MARGINS - 1))));
|
|
68
|
+
return { discount: (i + 1) / 20, margin: 3 + j };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The point of the unit square of the defaults of `shared`: the discount 0.5 and the margin 5. */
|
|
72
|
+
const HOME: readonly [number, number] = [9 / (DISCOUNTS - 1), 2 / (MARGINS - 1)];
|
|
73
|
+
|
|
74
|
+
/** One setting that the search routed: its score and its output. A setting with a break of a hard rule has the score `Infinity`. */
|
|
75
|
+
export interface SwarmEntry {
|
|
76
|
+
readonly setting: SwarmSetting;
|
|
77
|
+
readonly score: number;
|
|
78
|
+
readonly output: WholeOutput;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The result of `fireflies`: the output, the best setting and each setting that the search routed, in order. */
|
|
82
|
+
export interface SwarmRun {
|
|
83
|
+
readonly output: WholeOutput;
|
|
84
|
+
readonly best: SwarmSetting;
|
|
85
|
+
readonly entries: readonly SwarmEntry[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* This function gives the score of an output on its board. A lower score is better.
|
|
90
|
+
* The score is `crossings / edges + (median detour - 1) - ink saved`. A break of a hard rule gives `Infinity`.
|
|
91
|
+
*/
|
|
92
|
+
export function swarmScore(output: WholeOutput): number {
|
|
93
|
+
if (checkProperties(output.graph, output.result, 1).length > 0) return Infinity;
|
|
94
|
+
const edges = Math.max(1, output.result.routes.length);
|
|
95
|
+
return measureQuality(output.graph, output.result).crossings / edges + (detourSpread(output.result.routes).median - 1) - inkSaved(output.result.routes);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* This function runs the whole strategy `fireflies` (NAT-047, after the firefly algorithm of Yang 2008 and the bee colony of Karaboga 2005).
|
|
100
|
+
* It searches the options `discount` and `margin` of the router `router` on the board, on a grid of 20 × 8 settings.
|
|
101
|
+
* The first firefly or food source is the default setting, so the score of the output is not worse than the score of the default.
|
|
102
|
+
* A firefly moves toward each brighter one by `attraction(gamma, r²)` of the distance, plus a seeded step of size `alpha`.
|
|
103
|
+
* A bee tries `v = x + phi (x - x_k)` in one dimension, and a scout takes a new seeded place after `limit` tries with no gain.
|
|
104
|
+
* The search stops after `budget` different settings, or after 60 rounds. It reads only the board and the outputs of its own settings.
|
|
105
|
+
*/
|
|
106
|
+
export function fireflies(graph: Graph, router: Strategy<"router">, options: Partial<FireflyOptions> = {}): SwarmRun {
|
|
107
|
+
const o = { ...FIREFLY_DEFAULTS, ...options };
|
|
108
|
+
const budget = Math.max(1, Math.floor(o.budget));
|
|
109
|
+
const random = rng(Math.max(0, Math.floor(o.seed)));
|
|
110
|
+
const known = new Map<string, SwarmEntry>();
|
|
111
|
+
const entries: SwarmEntry[] = [];
|
|
112
|
+
const score = (x: number, y: number): number => {
|
|
113
|
+
const setting = settingAt(x, y);
|
|
114
|
+
const key = `${setting.discount}:${setting.margin}`;
|
|
115
|
+
const seen = known.get(key);
|
|
116
|
+
if (seen) return seen.score;
|
|
117
|
+
if (entries.length >= budget) return Infinity;
|
|
118
|
+
const output = runPipeline(graph, { router, routerOptions: { ...setting } });
|
|
119
|
+
const entry = { setting, score: swarmScore(output), output };
|
|
120
|
+
known.set(key, entry);
|
|
121
|
+
entries.push(entry);
|
|
122
|
+
return entry.score;
|
|
123
|
+
};
|
|
124
|
+
const clamp = (v: number): number => Math.max(0, Math.min(1, v));
|
|
125
|
+
const colony = Math.max(2, Math.min(20, Math.floor(o.colony)));
|
|
126
|
+
const xs: [number, number][] = [[HOME[0], HOME[1]]];
|
|
127
|
+
while (xs.length < colony) xs.push([random(), random()]);
|
|
128
|
+
const fs = xs.map(([x, y]) => score(x, y));
|
|
129
|
+
if (o.search === "firefly") {
|
|
130
|
+
for (let round = 0; round < ROUNDS && entries.length < budget; round++) {
|
|
131
|
+
for (let i = 0; i < colony; i++) {
|
|
132
|
+
for (let j = 0; j < colony; j++) {
|
|
133
|
+
if (!(fs[j]! < fs[i]!)) continue;
|
|
134
|
+
const dx = xs[j]![0] - xs[i]![0];
|
|
135
|
+
const dy = xs[j]![1] - xs[i]![1];
|
|
136
|
+
const b = attraction(o.gamma, dx * dx + dy * dy);
|
|
137
|
+
xs[i] = [clamp(xs[i]![0] + b * dx + o.alpha * (random() - 0.5)), clamp(xs[i]![1] + b * dy + o.alpha * (random() - 0.5))];
|
|
138
|
+
fs[i] = score(xs[i]![0], xs[i]![1]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
const trials = new Array<number>(colony).fill(0);
|
|
144
|
+
const tryNear = (i: number): void => {
|
|
145
|
+
let k = Math.floor(random() * (colony - 1));
|
|
146
|
+
if (k >= i) k++;
|
|
147
|
+
const d = random() < 0.5 ? 0 : 1;
|
|
148
|
+
const phi = 2 * random() - 1;
|
|
149
|
+
const v: [number, number] = [xs[i]![0], xs[i]![1]];
|
|
150
|
+
v[d] = clamp(v[d] + phi * (v[d] - xs[k]![d]));
|
|
151
|
+
const f = score(v[0], v[1]);
|
|
152
|
+
if (f < fs[i]!) {
|
|
153
|
+
xs[i] = v;
|
|
154
|
+
fs[i] = f;
|
|
155
|
+
trials[i] = 0;
|
|
156
|
+
} else trials[i]!++;
|
|
157
|
+
};
|
|
158
|
+
for (let round = 0; round < ROUNDS && entries.length < budget; round++) {
|
|
159
|
+
for (let i = 0; i < colony; i++) tryNear(i);
|
|
160
|
+
// The onlooker bees go to the sources by rank: the best source gets the most visits.
|
|
161
|
+
const order = xs.map((_, i) => i).sort((a, b) => fs[a]! - fs[b]! || a - b);
|
|
162
|
+
for (let r = 0; r < colony; r++) {
|
|
163
|
+
const pick = order[Math.min(colony - 1, Math.floor(random() * random() * colony))]!;
|
|
164
|
+
tryNear(pick);
|
|
165
|
+
}
|
|
166
|
+
const worn = trials.findIndex((t, i) => t > o.limit && fs[i]! !== Math.min(...fs));
|
|
167
|
+
if (worn >= 0) {
|
|
168
|
+
xs[worn] = [random(), random()];
|
|
169
|
+
fs[worn] = score(xs[worn]![0], xs[worn]![1]);
|
|
170
|
+
trials[worn] = 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
let best = entries[0]!;
|
|
175
|
+
for (const e of entries) if (e.score < best.score - 1e-12) best = e;
|
|
176
|
+
return { output: best.output, best: best.setting, entries };
|
|
177
|
+
}
|