@tscircuit/capacity-autorouter 0.0.57 → 0.0.59

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/dist/index.js CHANGED
@@ -48,6 +48,12 @@ var BaseSolver = class {
48
48
  failedSubSolvers;
49
49
  timeToSolve;
50
50
  stats = {};
51
+ /**
52
+ * For cached solvers
53
+ **/
54
+ cacheHit;
55
+ cacheKey;
56
+ cacheToSolveSpaceTransform;
51
57
  /** DO NOT OVERRIDE! Override _step() instead */
52
58
  step() {
53
59
  if (this.solved) return;
@@ -8053,13 +8059,17 @@ var MultiHeadPolyLineIntraNodeSolver = class extends BaseSolver {
8053
8059
  return [newNeighbor];
8054
8060
  }
8055
8061
  checkIfSolved(candidate) {
8056
- return candidate.minGaps.every((minGap) => minGap >= this.obstacleMargin) && candidate.polyLines.every((polyLine) => {
8062
+ const minGapsToOtherConnectionsValid = candidate.minGaps.every(
8063
+ (minGap) => minGap >= this.obstacleMargin
8064
+ );
8065
+ const allPointsWithinBounds = candidate.polyLines.every((polyLine) => {
8057
8066
  return polyLine.mPoints.every((mPoint) => {
8058
- const padding = (mPoint.z1 !== mPoint.z2 ? this.viaDiameter / 2 : this.traceWidth / 2) * // Forgiveness outside bounds
8067
+ const padding = (mPoint.z1 !== mPoint.z2 ? this.viaDiameter / 2 : 0) * // Forgiveness outside bounds
8059
8068
  0.9;
8060
8069
  return withinBounds(mPoint, this.bounds, padding);
8061
8070
  });
8062
8071
  });
8072
+ return minGapsToOtherConnectionsValid && allPointsWithinBounds;
8063
8073
  }
8064
8074
  _step() {
8065
8075
  if (this.phase === "setup") {
@@ -10259,6 +10269,7 @@ var UnravelSectionSolver = class extends BaseSolver {
10259
10269
  tunedNodeCapacityMap;
10260
10270
  MAX_CANDIDATES = 500;
10261
10271
  iterationsSinceImprovement = 0;
10272
+ hyperParameters;
10262
10273
  selectedCandidateIndex = null;
10263
10274
  queuedOrExploredCandidatePointModificationHashes = /* @__PURE__ */ new Set();
10264
10275
  constructorParams;
@@ -10267,6 +10278,10 @@ var UnravelSectionSolver = class extends BaseSolver {
10267
10278
  this.constructorParams = params;
10268
10279
  this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
10269
10280
  this.MAX_ITERATIONS = 5e4;
10281
+ this.hyperParameters = {
10282
+ ...params.hyperParameters,
10283
+ MAX_ITERATIONS_WITHOUT_IMPROVEMENT: 200
10284
+ };
10270
10285
  this.nodeMap = params.nodeMap;
10271
10286
  this.dedupedSegments = params.dedupedSegments;
10272
10287
  if (params.dedupedSegmentMap) {
@@ -10429,16 +10444,21 @@ var UnravelSectionSolver = class extends BaseSolver {
10429
10444
  });
10430
10445
  return {
10431
10446
  pointModifications,
10432
- issues,
10433
10447
  g,
10434
10448
  h: 0,
10435
10449
  f: g,
10436
10450
  operationsPerformed: 0,
10437
- candidateHash: createPointModificationsHash(pointModifications)
10451
+ candidateHash: createPointModificationsHash(pointModifications),
10438
10452
  // candidateFullHash: createFullPointModificationsHash(
10439
10453
  // this.unravelSection.segmentPointMap,
10440
10454
  // pointModifications,
10441
10455
  // ),
10456
+ // Ensure original candidate has issues calculated
10457
+ issues: getIssuesInSection(
10458
+ this.unravelSection,
10459
+ this.nodeMap,
10460
+ pointModifications
10461
+ )
10442
10462
  };
10443
10463
  }
10444
10464
  get nextCandidate() {
@@ -10690,6 +10710,10 @@ var UnravelSectionSolver = class extends BaseSolver {
10690
10710
  _step() {
10691
10711
  const candidate = this.candidates.shift();
10692
10712
  this.iterationsSinceImprovement++;
10713
+ if (this.iterationsSinceImprovement > this.hyperParameters.MAX_ITERATIONS_WITHOUT_IMPROVEMENT) {
10714
+ this.solved = true;
10715
+ return;
10716
+ }
10693
10717
  if (!candidate) {
10694
10718
  this.solved = true;
10695
10719
  return;
@@ -10733,6 +10757,8 @@ var UnravelSectionSolver = class extends BaseSolver {
10733
10757
  } else {
10734
10758
  candidate = this.candidates[this.selectedCandidateIndex];
10735
10759
  }
10760
+ } else if (this.solved) {
10761
+ candidate = this.bestCandidate;
10736
10762
  } else {
10737
10763
  candidate = this.lastProcessedCandidate || this.candidates[0];
10738
10764
  }
@@ -10898,6 +10924,640 @@ New: (${modifiedPoint.x.toFixed(2)}, ${modifiedPoint.y.toFixed(2)}, ${modifiedPo
10898
10924
  }
10899
10925
  };
10900
10926
 
10927
+ // lib/solvers/UnravelSolver/CachedUnravelSectionSolver.ts
10928
+ import objectHash from "object-hash";
10929
+
10930
+ // lib/cache/InMemoryCache.ts
10931
+ var InMemoryCache = class {
10932
+ isSyncCache = true;
10933
+ cacheHits = 0;
10934
+ cacheMisses = 0;
10935
+ cache = /* @__PURE__ */ new Map();
10936
+ /**
10937
+ * Retrieves a cached solution synchronously based on the cache key.
10938
+ * Increments cache hit/miss counters.
10939
+ * @param cacheKey The key to look up in the cache.
10940
+ * @returns The cached solution if found, otherwise undefined.
10941
+ */
10942
+ getCachedSolutionSync(cacheKey) {
10943
+ const cachedSolution = this.cache.get(cacheKey);
10944
+ if (cachedSolution !== void 0) {
10945
+ this.cacheHits++;
10946
+ return structuredClone(cachedSolution);
10947
+ } else {
10948
+ this.cacheMisses++;
10949
+ return void 0;
10950
+ }
10951
+ }
10952
+ /**
10953
+ * Retrieves a cached solution asynchronously. Wraps the synchronous method.
10954
+ * @param cacheKey The key to look up in the cache.
10955
+ * @returns A promise that resolves with the cached solution or undefined.
10956
+ */
10957
+ async getCachedSolution(cacheKey) {
10958
+ return this.getCachedSolutionSync(cacheKey);
10959
+ }
10960
+ /**
10961
+ * Stores a solution in the cache synchronously.
10962
+ * Uses structured cloning to store a copy, preventing external modifications.
10963
+ * @param cacheKey The key under which to store the solution.
10964
+ * @param cachedSolution The solution data to cache.
10965
+ */
10966
+ setCachedSolutionSync(cacheKey, cachedSolution) {
10967
+ this.cache.set(cacheKey, structuredClone(cachedSolution));
10968
+ }
10969
+ /**
10970
+ * Stores a solution in the cache asynchronously. Wraps the synchronous method.
10971
+ * @param cacheKey The key under which to store the solution.
10972
+ * @param cachedSolution The solution data to cache.
10973
+ * @returns A promise that resolves when the solution is cached.
10974
+ */
10975
+ async setCachedSolution(cacheKey, cachedSolution) {
10976
+ this.setCachedSolutionSync(cacheKey, cachedSolution);
10977
+ }
10978
+ /**
10979
+ * Clears the entire cache and resets hit/miss counters.
10980
+ */
10981
+ clearCache() {
10982
+ this.cache.clear();
10983
+ this.cacheHits = 0;
10984
+ this.cacheMisses = 0;
10985
+ }
10986
+ };
10987
+
10988
+ // lib/cache/LocalStorageCache.ts
10989
+ var CACHE_PREFIX = "tscircuit_autorouter_cache_";
10990
+ var LocalStorageCache = class {
10991
+ isSyncCache = true;
10992
+ cacheHits = 0;
10993
+ cacheMisses = 0;
10994
+ constructor() {
10995
+ if (typeof localStorage === "undefined") {
10996
+ console.warn(
10997
+ "LocalStorage is not available. LocalStorageCache will not function."
10998
+ );
10999
+ }
11000
+ }
11001
+ getKey(cacheKey) {
11002
+ return `${CACHE_PREFIX}${cacheKey}`;
11003
+ }
11004
+ /**
11005
+ * Retrieves a cached solution synchronously from localStorage.
11006
+ * Increments cache hit/miss counters.
11007
+ * @param cacheKey The key to look up in the cache.
11008
+ * @returns The cached solution if found and parsed correctly, otherwise undefined.
11009
+ */
11010
+ getCachedSolutionSync(cacheKey) {
11011
+ if (typeof localStorage === "undefined") return void 0;
11012
+ const key = this.getKey(cacheKey);
11013
+ try {
11014
+ const cachedItem = localStorage.getItem(key);
11015
+ if (cachedItem !== null) {
11016
+ const solution = JSON.parse(cachedItem);
11017
+ this.cacheHits++;
11018
+ return solution;
11019
+ } else {
11020
+ this.cacheMisses++;
11021
+ return void 0;
11022
+ }
11023
+ } catch (error) {
11024
+ console.error(`Error getting cached solution sync for ${key}:`, error);
11025
+ this.cacheMisses++;
11026
+ return void 0;
11027
+ }
11028
+ }
11029
+ /**
11030
+ * Retrieves a cached solution asynchronously. Wraps the synchronous method.
11031
+ * @param cacheKey The key to look up in the cache.
11032
+ * @returns A promise that resolves with the cached solution or undefined.
11033
+ */
11034
+ async getCachedSolution(cacheKey) {
11035
+ return this.getCachedSolutionSync(cacheKey);
11036
+ }
11037
+ /**
11038
+ * Stores a solution in localStorage synchronously.
11039
+ * The solution is JSON stringified before storing.
11040
+ * @param cacheKey The key under which to store the solution.
11041
+ * @param cachedSolution The solution data to cache.
11042
+ */
11043
+ setCachedSolutionSync(cacheKey, cachedSolution) {
11044
+ if (typeof localStorage === "undefined") return;
11045
+ const key = this.getKey(cacheKey);
11046
+ try {
11047
+ const stringifiedSolution = JSON.stringify(cachedSolution);
11048
+ localStorage.setItem(key, stringifiedSolution);
11049
+ } catch (error) {
11050
+ console.error(`Error setting cached solution sync for ${key}:`, error);
11051
+ if (error instanceof DOMException && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED")) {
11052
+ console.warn(
11053
+ `LocalStorage quota exceeded. Failed to cache solution for ${key}. Consider clearing the cache.`
11054
+ );
11055
+ }
11056
+ }
11057
+ }
11058
+ /**
11059
+ * Stores a solution in the cache asynchronously. Wraps the synchronous method.
11060
+ * @param cacheKey The key under which to store the solution.
11061
+ * @param cachedSolution The solution data to cache.
11062
+ * @returns A promise that resolves when the solution is cached.
11063
+ */
11064
+ async setCachedSolution(cacheKey, cachedSolution) {
11065
+ this.setCachedSolutionSync(cacheKey, cachedSolution);
11066
+ }
11067
+ /**
11068
+ * Clears all cache entries created by this instance from localStorage
11069
+ * and resets hit/miss counters.
11070
+ */
11071
+ clearCache() {
11072
+ if (typeof localStorage === "undefined") return;
11073
+ try {
11074
+ const keysToRemove = [];
11075
+ for (let i = 0; i < localStorage.length; i++) {
11076
+ const key = localStorage.key(i);
11077
+ if (key?.startsWith(CACHE_PREFIX)) {
11078
+ keysToRemove.push(key);
11079
+ }
11080
+ }
11081
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
11082
+ console.log(
11083
+ `Cleared ${keysToRemove.length} items from LocalStorage cache.`
11084
+ );
11085
+ } catch (error) {
11086
+ console.error("Error clearing LocalStorage cache:", error);
11087
+ } finally {
11088
+ this.cacheHits = 0;
11089
+ this.cacheMisses = 0;
11090
+ }
11091
+ }
11092
+ };
11093
+
11094
+ // lib/cache/setupGlobalCaches.ts
11095
+ function getGlobalLocalStorageCache() {
11096
+ if (!globalThis.TSCIRCUIT_AUTOROUTER_LOCAL_STORAGE_CACHE) {
11097
+ setupGlobalCaches();
11098
+ }
11099
+ return globalThis.TSCIRCUIT_AUTOROUTER_LOCAL_STORAGE_CACHE;
11100
+ }
11101
+ function setupGlobalCaches() {
11102
+ globalThis.TSCIRCUIT_AUTOROUTER_LOCAL_STORAGE_CACHE ??= new LocalStorageCache();
11103
+ globalThis.TSCIRCUIT_AUTOROUTER_IN_MEMORY_CACHE ??= new InMemoryCache();
11104
+ }
11105
+
11106
+ // node_modules/transformation-matrix/src/applyToPoint.js
11107
+ function applyToPoint(matrix, point) {
11108
+ return Array.isArray(point) ? [
11109
+ matrix.a * point[0] + matrix.c * point[1] + matrix.e,
11110
+ matrix.b * point[0] + matrix.d * point[1] + matrix.f
11111
+ ] : {
11112
+ x: matrix.a * point.x + matrix.c * point.y + matrix.e,
11113
+ y: matrix.b * point.x + matrix.d * point.y + matrix.f
11114
+ };
11115
+ }
11116
+
11117
+ // node_modules/transformation-matrix/src/translate.js
11118
+ function translate(tx, ty = 0) {
11119
+ return {
11120
+ a: 1,
11121
+ c: 0,
11122
+ e: tx,
11123
+ b: 0,
11124
+ d: 1,
11125
+ f: ty
11126
+ };
11127
+ }
11128
+
11129
+ // node_modules/transformation-matrix/src/rotate.js
11130
+ var { cos, sin, PI } = Math;
11131
+
11132
+ // node_modules/transformation-matrix/src/skew.js
11133
+ var { tan } = Math;
11134
+
11135
+ // node_modules/transformation-matrix/src/fromTransformAttribute.autogenerated.js
11136
+ function peg$subclass(child, parent) {
11137
+ function C() {
11138
+ this.constructor = child;
11139
+ }
11140
+ C.prototype = parent.prototype;
11141
+ child.prototype = new C();
11142
+ }
11143
+ function peg$SyntaxError(message, expected, found, location) {
11144
+ var self = Error.call(this, message);
11145
+ if (Object.setPrototypeOf) {
11146
+ Object.setPrototypeOf(self, peg$SyntaxError.prototype);
11147
+ }
11148
+ self.expected = expected;
11149
+ self.found = found;
11150
+ self.location = location;
11151
+ self.name = "SyntaxError";
11152
+ return self;
11153
+ }
11154
+ peg$subclass(peg$SyntaxError, Error);
11155
+ function peg$padEnd(str, targetLength, padString) {
11156
+ padString = padString || " ";
11157
+ if (str.length > targetLength) {
11158
+ return str;
11159
+ }
11160
+ targetLength -= str.length;
11161
+ padString += padString.repeat(targetLength);
11162
+ return str + padString.slice(0, targetLength);
11163
+ }
11164
+ peg$SyntaxError.prototype.format = function(sources) {
11165
+ var str = "Error: " + this.message;
11166
+ if (this.location) {
11167
+ var src = null;
11168
+ var k;
11169
+ for (k = 0; k < sources.length; k++) {
11170
+ if (sources[k].source === this.location.source) {
11171
+ src = sources[k].text.split(/\r\n|\n|\r/g);
11172
+ break;
11173
+ }
11174
+ }
11175
+ var s = this.location.start;
11176
+ var offset_s = this.location.source && typeof this.location.source.offset === "function" ? this.location.source.offset(s) : s;
11177
+ var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
11178
+ if (src) {
11179
+ var e = this.location.end;
11180
+ var filler = peg$padEnd("", offset_s.line.toString().length, " ");
11181
+ var line = src[s.line - 1];
11182
+ var last = s.line === e.line ? e.column : line.length + 1;
11183
+ var hatLen = last - s.column || 1;
11184
+ str += "\n --> " + loc + "\n" + filler + " |\n" + offset_s.line + " | " + line + "\n" + filler + " | " + peg$padEnd("", s.column - 1, " ") + peg$padEnd("", hatLen, "^");
11185
+ } else {
11186
+ str += "\n at " + loc;
11187
+ }
11188
+ }
11189
+ return str;
11190
+ };
11191
+ peg$SyntaxError.buildMessage = function(expected, found) {
11192
+ var DESCRIBE_EXPECTATION_FNS = {
11193
+ literal: function(expectation) {
11194
+ return '"' + literalEscape(expectation.text) + '"';
11195
+ },
11196
+ class: function(expectation) {
11197
+ var escapedParts = expectation.parts.map(function(part) {
11198
+ return Array.isArray(part) ? classEscape(part[0]) + "-" + classEscape(part[1]) : classEscape(part);
11199
+ });
11200
+ return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]";
11201
+ },
11202
+ any: function() {
11203
+ return "any character";
11204
+ },
11205
+ end: function() {
11206
+ return "end of input";
11207
+ },
11208
+ other: function(expectation) {
11209
+ return expectation.description;
11210
+ }
11211
+ };
11212
+ function hex(ch) {
11213
+ return ch.charCodeAt(0).toString(16).toUpperCase();
11214
+ }
11215
+ function literalEscape(s) {
11216
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, function(ch) {
11217
+ return "\\x0" + hex(ch);
11218
+ }).replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) {
11219
+ return "\\x" + hex(ch);
11220
+ });
11221
+ }
11222
+ function classEscape(s) {
11223
+ return s.replace(/\\/g, "\\\\").replace(/\]/g, "\\]").replace(/\^/g, "\\^").replace(/-/g, "\\-").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, function(ch) {
11224
+ return "\\x0" + hex(ch);
11225
+ }).replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) {
11226
+ return "\\x" + hex(ch);
11227
+ });
11228
+ }
11229
+ function describeExpectation(expectation) {
11230
+ return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
11231
+ }
11232
+ function describeExpected(expected2) {
11233
+ var descriptions = expected2.map(describeExpectation);
11234
+ var i, j;
11235
+ descriptions.sort();
11236
+ if (descriptions.length > 0) {
11237
+ for (i = 1, j = 1; i < descriptions.length; i++) {
11238
+ if (descriptions[i - 1] !== descriptions[i]) {
11239
+ descriptions[j] = descriptions[i];
11240
+ j++;
11241
+ }
11242
+ }
11243
+ descriptions.length = j;
11244
+ }
11245
+ switch (descriptions.length) {
11246
+ case 1:
11247
+ return descriptions[0];
11248
+ case 2:
11249
+ return descriptions[0] + " or " + descriptions[1];
11250
+ default:
11251
+ return descriptions.slice(0, -1).join(", ") + ", or " + descriptions[descriptions.length - 1];
11252
+ }
11253
+ }
11254
+ function describeFound(found2) {
11255
+ return found2 ? '"' + literalEscape(found2) + '"' : "end of input";
11256
+ }
11257
+ return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
11258
+ };
11259
+
11260
+ // lib/solvers/UnravelSolver/CachedUnravelSectionSolver.ts
11261
+ var approximateCoordinate = (coord) => {
11262
+ return (Math.round(coord * 20) / 20).toFixed(2);
11263
+ };
11264
+ setupGlobalCaches();
11265
+ var CachedUnravelSectionSolver = class extends UnravelSectionSolver {
11266
+ cacheHit = false;
11267
+ cacheProvider;
11268
+ hasAttemptedToUseCache = false;
11269
+ constructor(params) {
11270
+ super(params);
11271
+ this.cacheProvider = params.cacheProvider === void 0 ? getGlobalLocalStorageCache() : params.cacheProvider;
11272
+ }
11273
+ _step() {
11274
+ if (!this.hasAttemptedToUseCache && this.cacheProvider) {
11275
+ if (this.attemptToUseCacheSync()) return;
11276
+ }
11277
+ super._step();
11278
+ if ((this.solved || this.failed) && this.cacheProvider) {
11279
+ this.saveToCacheSync();
11280
+ }
11281
+ }
11282
+ computeCacheKeyAndTransform() {
11283
+ const rootNode = this.nodeMap.get(this.rootNodeId);
11284
+ const realToCacheTransform = translate(
11285
+ -rootNode.center.x,
11286
+ -rootNode.center.y
11287
+ );
11288
+ const nodeIdMap = /* @__PURE__ */ new Map();
11289
+ const reverseNodeIdMap = /* @__PURE__ */ new Map();
11290
+ const segmentIdMap = /* @__PURE__ */ new Map();
11291
+ const reverseSegmentIdMap = /* @__PURE__ */ new Map();
11292
+ const segmentPointIdMap = /* @__PURE__ */ new Map();
11293
+ const reverseSegmentPointIdMap = /* @__PURE__ */ new Map();
11294
+ let nodeCounter = 0;
11295
+ let segmentCounter = 0;
11296
+ let spCounter = 0;
11297
+ const sortedNodeIds = [...this.unravelSection.allNodeIds].sort(
11298
+ (aNId, bNId) => {
11299
+ const n1 = this.nodeMap.get(aNId);
11300
+ const n2 = this.nodeMap.get(bNId);
11301
+ if (n1.center.x !== n2.center.x) {
11302
+ return n1.center.x - n2.center.x;
11303
+ }
11304
+ return n1.center.y - n2.center.y;
11305
+ }
11306
+ );
11307
+ for (const nodeId of sortedNodeIds) {
11308
+ const normId = `node_${nodeCounter++}`;
11309
+ nodeIdMap.set(nodeId, normId);
11310
+ reverseNodeIdMap.set(normId, nodeId);
11311
+ }
11312
+ const sortedSegmentPointIds = [
11313
+ ...Array.from(this.unravelSection.segmentPointMap.entries()).sort(([, a], [, b]) => {
11314
+ if (a.x !== b.x) {
11315
+ return a.x - b.x;
11316
+ }
11317
+ return a.y - b.y;
11318
+ }).map(([id]) => id)
11319
+ ].sort();
11320
+ for (const spId of sortedSegmentPointIds) {
11321
+ const normSpId = `sp_${spCounter++}`;
11322
+ segmentPointIdMap.set(spId, normSpId);
11323
+ reverseSegmentPointIdMap.set(normSpId, spId);
11324
+ const segmentId = this.unravelSection.segmentPointMap.get(spId).segmentId;
11325
+ if (!segmentIdMap.has(segmentId)) {
11326
+ const normSegId = `seg_${segmentCounter++}`;
11327
+ segmentIdMap.set(segmentId, normSegId);
11328
+ reverseSegmentIdMap.set(normSegId, segmentId);
11329
+ }
11330
+ }
11331
+ const normalizedNodes = {};
11332
+ for (const [nodeId, normNodeId] of nodeIdMap.entries()) {
11333
+ const node = this.nodeMap.get(nodeId);
11334
+ const transformedCenter = applyToPoint(realToCacheTransform, node.center);
11335
+ normalizedNodes[normNodeId] = {
11336
+ // ...node,
11337
+ width: node.width,
11338
+ // TODO: Scale width/height if transform includes scaling
11339
+ height: node.height,
11340
+ availableZ: node.availableZ,
11341
+ center: {
11342
+ x: approximateCoordinate(transformedCenter.x),
11343
+ y: approximateCoordinate(transformedCenter.y)
11344
+ }
11345
+ };
11346
+ }
11347
+ const normalizedSegmentPoints = {};
11348
+ for (const [spId, normSpId] of segmentPointIdMap.entries()) {
11349
+ const sp = this.unravelSection.segmentPointMap.get(spId);
11350
+ const transformedPoint = applyToPoint(realToCacheTransform, {
11351
+ x: sp.x,
11352
+ y: sp.y
11353
+ });
11354
+ normalizedSegmentPoints[normSpId] = {
11355
+ x: approximateCoordinate(transformedPoint.x),
11356
+ y: approximateCoordinate(transformedPoint.y),
11357
+ z: sp.z
11358
+ // Z is not transformed by 2D matrix
11359
+ // segmentId: segmentIdMap.get(sp.segmentId)!,
11360
+ // connectionName: sp.connectionName,
11361
+ };
11362
+ }
11363
+ const keyData = {
11364
+ hyperParameters: this.hyperParameters,
11365
+ normalizedNodes,
11366
+ normalizedSegmentPoints,
11367
+ mutableHops: this.MUTABLE_HOPS
11368
+ };
11369
+ const cacheKey = `unravelsec:${objectHash(keyData)}`;
11370
+ const cacheToSolveSpaceTransform = {
11371
+ realToCacheTransform,
11372
+ nodeIdMap,
11373
+ segmentIdMap,
11374
+ segmentPointIdMap,
11375
+ reverseNodeIdMap,
11376
+ reverseSegmentIdMap,
11377
+ reverseSegmentPointIdMap
11378
+ };
11379
+ this.cacheKey = cacheKey;
11380
+ this.cacheToSolveSpaceTransform = cacheToSolveSpaceTransform;
11381
+ return { cacheKey, cacheToSolveSpaceTransform };
11382
+ }
11383
+ applyCachedSolution(cachedSolution) {
11384
+ if (cachedSolution.success === false) {
11385
+ this.failed = true;
11386
+ return;
11387
+ }
11388
+ if (!this.cacheToSolveSpaceTransform) {
11389
+ console.error("Cache transform not available to apply cached solution.");
11390
+ return;
11391
+ }
11392
+ const {
11393
+ // realToCacheTransform, // Not needed to apply deltas
11394
+ reverseSegmentPointIdMap,
11395
+ reverseNodeIdMap
11396
+ // Needed if issues depend on node IDs
11397
+ } = this.cacheToSolveSpaceTransform;
11398
+ const pointModifications = /* @__PURE__ */ new Map();
11399
+ for (const [
11400
+ normSpId,
11401
+ normDelta
11402
+ // normDelta.dx and normDelta.dy are strings here
11403
+ ] of cachedSolution.bestCandidatePointModificationsDelta) {
11404
+ const originalSpId = reverseSegmentPointIdMap.get(normSpId);
11405
+ if (!originalSpId) {
11406
+ console.warn(
11407
+ `Could not find original ID for normalized SP ID: ${normSpId} when applying cache.`
11408
+ );
11409
+ continue;
11410
+ }
11411
+ const originalSegmentPoint = this.unravelSection.segmentPointMap.get(originalSpId);
11412
+ if (!originalSegmentPoint) {
11413
+ console.warn(
11414
+ `Could not find original segment point for ID: ${originalSpId} when applying cache.`
11415
+ );
11416
+ continue;
11417
+ }
11418
+ const modifiedPoint = {};
11419
+ if (normDelta.dx !== void 0) {
11420
+ const dxNum = parseFloat(normDelta.dx);
11421
+ if (!Number.isNaN(dxNum)) {
11422
+ modifiedPoint.x = originalSegmentPoint.x + dxNum;
11423
+ } else {
11424
+ console.warn(`Failed to parse cached dx coordinate: ${normDelta.dx}`);
11425
+ }
11426
+ }
11427
+ if (normDelta.dy !== void 0) {
11428
+ const dyNum = parseFloat(normDelta.dy);
11429
+ if (!Number.isNaN(dyNum)) {
11430
+ modifiedPoint.y = originalSegmentPoint.y + dyNum;
11431
+ } else {
11432
+ console.warn(`Failed to parse cached dy coordinate: ${normDelta.dy}`);
11433
+ }
11434
+ }
11435
+ if (normDelta.dz !== void 0) {
11436
+ modifiedPoint.z = originalSegmentPoint.z + normDelta.dz;
11437
+ }
11438
+ if (Object.keys(modifiedPoint).length > 0) {
11439
+ pointModifications.set(originalSpId, modifiedPoint);
11440
+ }
11441
+ }
11442
+ const issues = getIssuesInSection(
11443
+ this.unravelSection,
11444
+ this.nodeMap,
11445
+ pointModifications
11446
+ );
11447
+ this.bestCandidate = {
11448
+ pointModifications,
11449
+ issues,
11450
+ f: cachedSolution.bestCandidateF,
11451
+ g: cachedSolution.bestCandidateF,
11452
+ // Assume g is the main component off for cached solution
11453
+ h: 0,
11454
+ // Heuristic is 0 when solution is loaded
11455
+ operationsPerformed: -1,
11456
+ // Indicate it's from cache, operation count unknown
11457
+ candidateHash: createPointModificationsHash(pointModifications)
11458
+ };
11459
+ this.cacheHit = true;
11460
+ this.solved = true;
11461
+ }
11462
+ attemptToUseCacheSync() {
11463
+ this.hasAttemptedToUseCache = true;
11464
+ if (!this.cacheProvider?.isSyncCache) {
11465
+ console.log(
11466
+ "Cache provider is not synchronous, skipping sync cache check."
11467
+ );
11468
+ return false;
11469
+ }
11470
+ if (!this.cacheKey) {
11471
+ this.computeCacheKeyAndTransform();
11472
+ }
11473
+ if (!this.cacheKey) {
11474
+ console.error("Failed to compute cache key.");
11475
+ return false;
11476
+ }
11477
+ try {
11478
+ const cachedSolution = this.cacheProvider.getCachedSolutionSync(
11479
+ this.cacheKey
11480
+ );
11481
+ if (cachedSolution) {
11482
+ this.applyCachedSolution(cachedSolution);
11483
+ return true;
11484
+ } else {
11485
+ }
11486
+ } catch (error) {
11487
+ console.error("Error attempting to use cache:", error);
11488
+ }
11489
+ return false;
11490
+ }
11491
+ saveToCacheSync() {
11492
+ if (this.failed) {
11493
+ this.cacheProvider?.setCachedSolutionSync(this.cacheKey, {
11494
+ success: false
11495
+ });
11496
+ return;
11497
+ }
11498
+ if (!this.bestCandidate) return;
11499
+ const {
11500
+ // realToCacheTransform, // Not needed to calculate deltas
11501
+ segmentPointIdMap
11502
+ } = this.cacheToSolveSpaceTransform;
11503
+ const normalizedDeltas = [];
11504
+ for (const [
11505
+ originalSpId,
11506
+ modifiedPoint
11507
+ // This contains the absolute modified coordinates {x?, y?, z?}
11508
+ ] of this.bestCandidate.pointModifications.entries()) {
11509
+ const normSpId = segmentPointIdMap.get(originalSpId);
11510
+ if (!normSpId) {
11511
+ console.warn(
11512
+ `Could not find normalized ID for original SP ID: ${originalSpId} when saving to cache.`
11513
+ );
11514
+ continue;
11515
+ }
11516
+ const originalSegmentPoint = this.unravelSection.segmentPointMap.get(originalSpId);
11517
+ if (!originalSegmentPoint) {
11518
+ console.warn(
11519
+ `Could not find original segment point for ID: ${originalSpId} when saving cache.`
11520
+ );
11521
+ continue;
11522
+ }
11523
+ const normDelta = {};
11524
+ let hasDelta = false;
11525
+ if (modifiedPoint.x !== void 0) {
11526
+ const dx = modifiedPoint.x - originalSegmentPoint.x;
11527
+ const approxDx = approximateCoordinate(dx);
11528
+ if (parseFloat(approxDx) !== 0) {
11529
+ normDelta.dx = approxDx;
11530
+ hasDelta = true;
11531
+ }
11532
+ }
11533
+ if (modifiedPoint.y !== void 0) {
11534
+ const dy = modifiedPoint.y - originalSegmentPoint.y;
11535
+ const approxDy = approximateCoordinate(dy);
11536
+ if (parseFloat(approxDy) !== 0) {
11537
+ normDelta.dy = approxDy;
11538
+ hasDelta = true;
11539
+ }
11540
+ }
11541
+ if (modifiedPoint.z !== void 0) {
11542
+ const dz = modifiedPoint.z - originalSegmentPoint.z;
11543
+ if (dz !== 0) {
11544
+ normDelta.dz = dz;
11545
+ hasDelta = true;
11546
+ }
11547
+ }
11548
+ if (hasDelta) {
11549
+ normalizedDeltas.push([normSpId, normDelta]);
11550
+ }
11551
+ }
11552
+ const cachedSolution = {
11553
+ success: true,
11554
+ bestCandidatePointModificationsDelta: normalizedDeltas,
11555
+ bestCandidateF: this.bestCandidate.f
11556
+ };
11557
+ this.cacheProvider?.setCachedSolutionSync(this.cacheKey, cachedSolution);
11558
+ }
11559
+ };
11560
+
10901
11561
  // lib/solvers/UnravelSolver/getDedupedSegments.ts
10902
11562
  var getDedupedSegments = (assignedSegments) => {
10903
11563
  const dedupedSegments = [];
@@ -11024,12 +11684,19 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
11024
11684
  attemptsToFixNode;
11025
11685
  activeSubSolver = null;
11026
11686
  segmentPointMap;
11687
+ cacheProvider = null;
11027
11688
  constructor({
11028
11689
  assignedSegments,
11029
11690
  colorMap,
11030
- nodes
11691
+ nodes,
11692
+ cacheProvider
11031
11693
  }) {
11032
11694
  super();
11695
+ this.stats.successfulOptimizations = 0;
11696
+ this.stats.failedOptimizations = 0;
11697
+ this.stats.cacheHits = 0;
11698
+ this.stats.cacheMisses = 0;
11699
+ this.cacheProvider = cacheProvider ?? null;
11033
11700
  this.MAX_ITERATIONS = 1e6;
11034
11701
  this.dedupedSegments = getDedupedSegments(assignedSegments);
11035
11702
  this.dedupedSegmentMap = /* @__PURE__ */ new Map();
@@ -11112,7 +11779,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
11112
11779
  highestPfNodeId,
11113
11780
  (this.attemptsToFixNode.get(highestPfNodeId) ?? 0) + 1
11114
11781
  );
11115
- this.activeSubSolver = new UnravelSectionSolver({
11782
+ this.activeSubSolver = new CachedUnravelSectionSolver({
11116
11783
  dedupedSegments: this.dedupedSegments,
11117
11784
  dedupedSegmentMap: this.dedupedSegmentMap,
11118
11785
  nodeMap: this.nodeMap,
@@ -11123,15 +11790,26 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
11123
11790
  MUTABLE_HOPS: this.MUTABLE_HOPS,
11124
11791
  segmentPointMap: this.segmentPointMap,
11125
11792
  nodeToSegmentPointMap: this.nodeToSegmentPointMap,
11126
- segmentToSegmentPointMap: this.segmentToSegmentPointMap
11793
+ segmentToSegmentPointMap: this.segmentToSegmentPointMap,
11794
+ cacheProvider: this.cacheProvider
11127
11795
  });
11128
11796
  }
11129
11797
  this.activeSubSolver.step();
11130
11798
  const { bestCandidate, originalCandidate, lastProcessedCandidate } = this.activeSubSolver;
11131
- const shouldEarlyStop = this.activeSubSolver.iterationsSinceImprovement > this.MAX_ITERATIONS_WITHOUT_IMPROVEMENT;
11132
- if (this.activeSubSolver.solved || shouldEarlyStop) {
11799
+ if (this.activeSubSolver.failed) {
11800
+ this.stats.failedOptimizations += 1;
11801
+ this.activeSubSolver = null;
11802
+ return;
11803
+ }
11804
+ if (this.activeSubSolver.solved) {
11805
+ if (this.activeSubSolver.cacheHit) {
11806
+ this.stats.cacheHits += 1;
11807
+ } else {
11808
+ this.stats.cacheMisses += 1;
11809
+ }
11133
11810
  const foundBetterSolution = bestCandidate && bestCandidate.g < originalCandidate.g;
11134
11811
  if (foundBetterSolution) {
11812
+ this.stats.successfulOptimizations += 1;
11135
11813
  for (const [
11136
11814
  segmentPointId,
11137
11815
  pointModification
@@ -11147,6 +11825,8 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
11147
11825
  this.computeNodePf(this.nodeMap.get(nodeId))
11148
11826
  );
11149
11827
  }
11828
+ } else {
11829
+ this.stats.failedOptimizations += 1;
11150
11830
  }
11151
11831
  this.activeSubSolver = null;
11152
11832
  }
@@ -12074,7 +12754,7 @@ var CapacityPathingSingleSectionSolver = class extends BaseSolver {
12074
12754
  this.nodeMap = new Map(
12075
12755
  this.sectionNodes.map((n) => [n.capacityMeshNodeId, n])
12076
12756
  );
12077
- this.nodeEdgeMap = getNodeEdgeMap(this.sectionEdges);
12757
+ this.nodeEdgeMap = params.nodeEdgeMap ?? getNodeEdgeMap(this.sectionEdges);
12078
12758
  this.colorMap = params.colorMap ?? {};
12079
12759
  this.usedNodeCapacityMap = new Map(
12080
12760
  this.sectionNodes.map((node) => [node.capacityMeshNodeId, 0])
@@ -12551,6 +13231,7 @@ var CapacityPathingMultiSectionSolver = class extends BaseSolver {
12551
13231
  this.simpleRouteJson = params.simpleRouteJson;
12552
13232
  this.nodes = params.nodes;
12553
13233
  this.edges = params.edges;
13234
+ this.nodeEdgeMap = getNodeEdgeMap(this.edges);
12554
13235
  this.colorMap = params.colorMap ?? {};
12555
13236
  this.nodeMap = new Map(
12556
13237
  this.nodes.map((node) => [node.capacityMeshNodeId, node])
@@ -12635,7 +13316,8 @@ var CapacityPathingMultiSectionSolver = class extends BaseSolver {
12635
13316
  sectionEdges: section.sectionEdges,
12636
13317
  sectionNodes: section.sectionNodes,
12637
13318
  colorMap: this.colorMap,
12638
- centerNodeId: section.centerNodeId
13319
+ centerNodeId: section.centerNodeId,
13320
+ nodeEdgeMap: this.nodeEdgeMap
12639
13321
  });
12640
13322
  this.activeSubSolver = this.sectionSolver;
12641
13323
  this.nodeOptimizationAttemptCountMap.set(
@@ -15089,6 +15771,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
15089
15771
  }
15090
15772
  this.connMap = getConnectivityMapFromSimpleRouteJson(srj);
15091
15773
  this.colorMap = getColorMap(srj, this.connMap);
15774
+ this.cacheProvider = opts.cacheProvider ?? null;
15092
15775
  this.startTimeOfPhase = {};
15093
15776
  this.endTimeOfPhase = {};
15094
15777
  this.timeSpentOnPhase = {};
@@ -15119,6 +15802,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
15119
15802
  connMap;
15120
15803
  srjWithPointPairs;
15121
15804
  capacityNodes = null;
15805
+ cacheProvider = null;
15122
15806
  pipelineDef = [
15123
15807
  definePipelineStep(
15124
15808
  "netToPointPairsSolver",
@@ -15266,7 +15950,8 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
15266
15950
  {
15267
15951
  assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
15268
15952
  colorMap: cms.colorMap,
15269
- nodes: cms.capacityNodes
15953
+ nodes: cms.capacityNodes,
15954
+ cacheProvider: this.cacheProvider
15270
15955
  }
15271
15956
  ]
15272
15957
  ),