@tscircuit/matchpack 0.0.11 → 0.0.13

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.d.ts CHANGED
@@ -47,6 +47,7 @@ type Chip = {
47
47
  chipId: ChipId;
48
48
  pins: PinId[];
49
49
  size: Point;
50
+ isDecouplingCap?: boolean;
50
51
  availableRotations?: Array<0 | 90 | 180 | 270>;
51
52
  };
52
53
  type Group = {
@@ -73,6 +74,7 @@ type InputProblem = {
73
74
  chipGap: number;
74
75
  /** The minimum gap between two partitions */
75
76
  partitionGap: number;
77
+ inferDecouplingCaps?: boolean;
76
78
  };
77
79
 
78
80
  /**
@@ -104,6 +106,47 @@ declare class ChipPartitionsSolver extends BaseSolver {
104
106
  visualize(): GraphicsObject;
105
107
  }
106
108
 
109
+ /**
110
+ * Identifies decoupling capacitor groups based on specific criteria:
111
+ * 1. Component has exactly 2 pins and restricted rotation (0/180 only or no rotation)
112
+ * 2. One pin indirectly connected to net with "y+" restriction, one to "y-" restriction
113
+ * 3. At least one pin directly connected to another chip
114
+ */
115
+
116
+ interface DecouplingCapGroup {
117
+ decouplingCapGroupId: string;
118
+ mainChipId: ChipId;
119
+ decouplingCapChipIds: ChipId[];
120
+ }
121
+ /**
122
+ * Identify decoupling capacitor groups based on specific criteria:
123
+ * 1. Component has exactly 2 pins and restricted rotation (0/180 only or no rotation)
124
+ * 2. One pin indirectly connected to net with "y+" restriction, one to "y-" restriction
125
+ * 3. At least one pin directly connected to a chip (the main chip, typically a microcontroller)
126
+ */
127
+ declare class IdentifyDecouplingCapsSolver extends BaseSolver {
128
+ inputProblem: InputProblem;
129
+ queuedChips: Chip[];
130
+ outputDecouplingCapGroups: DecouplingCapGroup[];
131
+ /** Quick lookup of groups by main chip for accumulation */
132
+ private groupsByMainChipId;
133
+ constructor(inputProblem: InputProblem);
134
+ /** Determine if chip is a 2-pin component with restricted rotation */
135
+ private isTwoPinRestrictedRotation;
136
+ /** Check that the two pins are on opposite Y sides (y+ and y-) */
137
+ private pinsOnOppositeYSides;
138
+ /** Get chips strongly connected (direct pin-to-pin) to this pin */
139
+ private getStronglyConnectedNeighborChips;
140
+ /** Find the main chip id for a decoupling capacitor candidate */
141
+ private findMainChipIdForCap;
142
+ /** Adds a decoupling capacitor to the group for the given main chip */
143
+ private addToGroup;
144
+ _step(): void;
145
+ visualize(): GraphicsObject;
146
+ getConstructorParams(): [InputProblem];
147
+ computeProgress(): number;
148
+ }
149
+
107
150
  type Placement = Point & {
108
151
  ccwRotationDegrees: number;
109
152
  };
@@ -186,6 +229,7 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
186
229
  onSolved?: (instance: LayoutPipelineSolver) => void;
187
230
  };
188
231
  declare class LayoutPipelineSolver extends BaseSolver {
232
+ identifyDecouplingCapsSolver?: IdentifyDecouplingCapsSolver;
189
233
  chipPartitionsSolver?: ChipPartitionsSolver;
190
234
  packInnerPartitionsSolver?: PackInnerPartitionsSolver;
191
235
  partitionPackingSolver?: PartitionPackingSolver;
@@ -196,7 +240,7 @@ declare class LayoutPipelineSolver extends BaseSolver {
196
240
  inputProblem: InputProblem;
197
241
  chipPartitions?: ChipPartitionsSolver["partitions"];
198
242
  packedPartitions?: PackedPartition[];
199
- pipelineDef: (PipelineStep<typeof ChipPartitionsSolver> | PipelineStep<typeof PackInnerPartitionsSolver> | PipelineStep<typeof PartitionPackingSolver>)[];
243
+ pipelineDef: (PipelineStep<typeof IdentifyDecouplingCapsSolver> | PipelineStep<typeof ChipPartitionsSolver> | PipelineStep<typeof PackInnerPartitionsSolver> | PipelineStep<typeof PartitionPackingSolver>)[];
200
244
  constructor(inputProblem: InputProblem);
201
245
  currentPipelineStepIndex: number;
202
246
  _step(): void;
package/dist/index.js CHANGED
@@ -239,7 +239,19 @@ function visualizeInputProblem(inputProblem, basicLayout) {
239
239
 
240
240
  // lib/solvers/LayoutPipelineSolver/doBasicInputProblemLayout.ts
241
241
  import { pack } from "calculate-packing";
242
+
243
+ // lib/solvers/LayoutPipelineSolver/hashInputProblem.ts
244
+ var hashInputProblem = (inputProblem) => {
245
+ return JSON.stringify(inputProblem);
246
+ };
247
+
248
+ // lib/solvers/LayoutPipelineSolver/doBasicInputProblemLayout.ts
249
+ var cachedProblems = /* @__PURE__ */ new Map();
242
250
  function doBasicInputProblemLayout(inputProblem) {
251
+ const problemHash = hashInputProblem(inputProblem);
252
+ if (cachedProblems.has(problemHash)) {
253
+ return structuredClone(cachedProblems.get(problemHash));
254
+ }
243
255
  const components = Object.entries(inputProblem.chipMap).map(
244
256
  ([chipId, chip]) => {
245
257
  const chipPins = chip.pins.map((pinId) => inputProblem.chipPinMap[pinId]);
@@ -292,11 +304,13 @@ function doBasicInputProblemLayout(inputProblem) {
292
304
  ccwRotationDegrees: component.ccwRotationOffset
293
305
  };
294
306
  }
295
- return {
307
+ const outputLayout = {
296
308
  chipPlacements,
297
309
  groupPlacements: {}
298
310
  // No groups for now
299
311
  };
312
+ cachedProblems.set(problemHash, outputLayout);
313
+ return structuredClone(outputLayout);
300
314
  }
301
315
 
302
316
  // lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts
@@ -452,6 +466,158 @@ var ChipPartitionsSolver = class extends BaseSolver {
452
466
  }
453
467
  };
454
468
 
469
+ // lib/utils/getColorFromString.ts
470
+ var getColorFromString = (string, alpha = 1) => {
471
+ const hash = string.split("").reduce((acc, char) => {
472
+ return acc * 31 + char.charCodeAt(0);
473
+ }, 0);
474
+ return `hsl(${hash % 360}, 100%, 50%, ${alpha})`;
475
+ };
476
+
477
+ // lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver.ts
478
+ var IdentifyDecouplingCapsSolver = class extends BaseSolver {
479
+ inputProblem;
480
+ queuedChips;
481
+ outputDecouplingCapGroups = [];
482
+ /** Quick lookup of groups by main chip for accumulation */
483
+ groupsByMainChipId = /* @__PURE__ */ new Map();
484
+ constructor(inputProblem) {
485
+ super();
486
+ this.inputProblem = inputProblem;
487
+ this.queuedChips = Object.values(inputProblem.chipMap);
488
+ }
489
+ /** Determine if chip is a 2-pin component with restricted rotation */
490
+ isTwoPinRestrictedRotation(chip) {
491
+ if (chip.pins.length !== 2) return false;
492
+ if (!chip.availableRotations) return false;
493
+ const allowed = /* @__PURE__ */ new Set([0, 180]);
494
+ return chip.availableRotations.length > 0 && chip.availableRotations.every((r) => allowed.has(r));
495
+ }
496
+ /** Check that the two pins are on opposite Y sides (y+ and y-) */
497
+ pinsOnOppositeYSides(chip) {
498
+ if (chip.pins.length !== 2) return false;
499
+ const [p1, p2] = chip.pins;
500
+ const cp1 = this.inputProblem.chipPinMap[p1];
501
+ const cp2 = this.inputProblem.chipPinMap[p2];
502
+ if (!cp1 || !cp2) return false;
503
+ const sides = /* @__PURE__ */ new Set([cp1.side, cp2.side]);
504
+ return sides.has("y+") && sides.has("y-");
505
+ }
506
+ /** Get chips strongly connected (direct pin-to-pin) to this pin */
507
+ getStronglyConnectedNeighborChips(pinId) {
508
+ const neighbors = /* @__PURE__ */ new Set();
509
+ for (const [connKey, connected] of Object.entries(
510
+ this.inputProblem.pinStrongConnMap
511
+ )) {
512
+ if (!connected) continue;
513
+ const [a, b] = connKey.split("-");
514
+ if (a === pinId) {
515
+ const otherChipId = b.split(".")[0];
516
+ neighbors.add(otherChipId);
517
+ } else if (b === pinId) {
518
+ const otherChipId = a.split(".")[0];
519
+ neighbors.add(otherChipId);
520
+ }
521
+ }
522
+ return neighbors;
523
+ }
524
+ /** Find the main chip id for a decoupling capacitor candidate */
525
+ findMainChipIdForCap(capChip) {
526
+ const strongNeighbors = /* @__PURE__ */ new Map();
527
+ for (const pinId of capChip.pins) {
528
+ const neighbors = this.getStronglyConnectedNeighborChips(pinId);
529
+ for (const n of neighbors) {
530
+ if (n === capChip.chipId) continue;
531
+ strongNeighbors.set(n, (strongNeighbors.get(n) || 0) + 1);
532
+ }
533
+ }
534
+ if (strongNeighbors.size === 0) return null;
535
+ let best = null;
536
+ for (const [id, score] of strongNeighbors.entries()) {
537
+ if (!best || score > best.score || score === best.score && id < best.id)
538
+ best = { id, score };
539
+ }
540
+ return best ? best.id : null;
541
+ }
542
+ /** Adds a decoupling capacitor to the group for the given main chip */
543
+ addToGroup(mainChipId, capChipId) {
544
+ let group = this.groupsByMainChipId.get(mainChipId);
545
+ if (!group) {
546
+ group = {
547
+ decouplingCapGroupId: `decap_group_${mainChipId}`,
548
+ mainChipId,
549
+ decouplingCapChipIds: []
550
+ };
551
+ this.groupsByMainChipId.set(mainChipId, group);
552
+ this.outputDecouplingCapGroups.push(group);
553
+ }
554
+ if (!group.decouplingCapChipIds.includes(capChipId)) {
555
+ group.decouplingCapChipIds.push(capChipId);
556
+ }
557
+ }
558
+ _step() {
559
+ const currentChip = this.queuedChips.shift();
560
+ if (!currentChip) {
561
+ this.solved = true;
562
+ return;
563
+ }
564
+ const candidate = this.isTwoPinRestrictedRotation(currentChip) && this.pinsOnOppositeYSides(currentChip);
565
+ if (!candidate) return;
566
+ const mainChipId = this.findMainChipIdForCap(currentChip);
567
+ if (!mainChipId) return;
568
+ this.addToGroup(mainChipId, currentChip.chipId);
569
+ }
570
+ visualize() {
571
+ const basicLayout = doBasicInputProblemLayout(this.inputProblem);
572
+ const graphics = visualizeInputProblem(
573
+ this.inputProblem,
574
+ basicLayout
575
+ );
576
+ const chipRoleMap = /* @__PURE__ */ new Map();
577
+ for (const group of this.outputDecouplingCapGroups) {
578
+ const color = getColorFromString(group.mainChipId, 0.8);
579
+ chipRoleMap.set(group.mainChipId, { type: "main", color });
580
+ for (const capChipId of group.decouplingCapChipIds) {
581
+ chipRoleMap.set(capChipId, { type: "decap", color });
582
+ }
583
+ }
584
+ for (const rect of graphics.rects || []) {
585
+ rect.fill = "rgba(0,0,0,0.5)";
586
+ }
587
+ for (const rect of graphics.rects || []) {
588
+ const chipId = rect.label;
589
+ const role = chipRoleMap.get(chipId);
590
+ if (!role) continue;
591
+ const alpha = role.type === "main" ? 0.18 : 0.36;
592
+ rect.label = `${rect.label}
593
+ ${role.type}`;
594
+ rect.fill = getColorFromString(role.color, alpha);
595
+ }
596
+ if (!graphics.texts) graphics.texts = [];
597
+ for (const rect of graphics.rects || []) {
598
+ const chipId = rect.label;
599
+ const role = chipRoleMap.get(chipId);
600
+ if (!role) continue;
601
+ graphics.texts.push({
602
+ x: rect.center.x,
603
+ y: rect.center.y - (rect.height || 0) / 2,
604
+ text: role.type === "main" ? "MAIN" : "DECAP",
605
+ fillColor: role.type === "main" ? role.color : "rgba(0,0,0,0.6)",
606
+ fontSize: 8
607
+ });
608
+ }
609
+ return graphics;
610
+ }
611
+ getConstructorParams() {
612
+ return [this.inputProblem];
613
+ }
614
+ computeProgress() {
615
+ const total = Object.keys(this.inputProblem.chipMap).length || 1;
616
+ const processed = total - this.queuedChips.length;
617
+ return Math.min(1, Math.max(0, processed / total));
618
+ }
619
+ };
620
+
455
621
  // lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts
456
622
  import { PackSolver2 } from "calculate-packing";
457
623
 
@@ -645,7 +811,7 @@ var SingleInnerPartitionPackingSolver = class extends BaseSolver {
645
811
  components: packComponents,
646
812
  minGap: this.inputProblem.chipGap,
647
813
  packOrderStrategy: "largest_to_smallest",
648
- packPlacementStrategy: "minimum_sum_squared_distance_to_network"
814
+ packPlacementStrategy: "minimum_closest_sum_squared_distance"
649
815
  };
650
816
  }
651
817
  createLayoutFromPackingResult(packedComponents) {
@@ -668,7 +834,8 @@ var SingleInnerPartitionPackingSolver = class extends BaseSolver {
668
834
  return this.activeSubSolver.visualize();
669
835
  }
670
836
  if (!this.layout) {
671
- return super.visualize();
837
+ const basicLayout = doBasicInputProblemLayout(this.inputProblem);
838
+ return visualizeInputProblem(this.inputProblem, basicLayout);
672
839
  }
673
840
  return visualizeInputProblem(this.inputProblem, this.layout);
674
841
  }
@@ -1022,6 +1189,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
1022
1189
  };
1023
1190
  }
1024
1191
  var LayoutPipelineSolver = class extends BaseSolver {
1192
+ identifyDecouplingCapsSolver;
1025
1193
  chipPartitionsSolver;
1026
1194
  packInnerPartitionsSolver;
1027
1195
  partitionPackingSolver;
@@ -1033,6 +1201,15 @@ var LayoutPipelineSolver = class extends BaseSolver {
1033
1201
  chipPartitions;
1034
1202
  packedPartitions;
1035
1203
  pipelineDef = [
1204
+ definePipelineStep(
1205
+ "identifyDecouplingCapsSolver",
1206
+ IdentifyDecouplingCapsSolver,
1207
+ () => [this.inputProblem],
1208
+ {
1209
+ onSolved: (_layoutSolver) => {
1210
+ }
1211
+ }
1212
+ ),
1036
1213
  definePipelineStep(
1037
1214
  "chipPartitionsSolver",
1038
1215
  ChipPartitionsSolver,
@@ -1120,6 +1297,7 @@ var LayoutPipelineSolver = class extends BaseSolver {
1120
1297
  if (this.solved && this.partitionPackingSolver?.solved) {
1121
1298
  return this.partitionPackingSolver.visualize();
1122
1299
  }
1300
+ const identifyDecouplingCapsViz = this.identifyDecouplingCapsSolver?.visualize();
1123
1301
  const chipPartitionsViz = this.chipPartitionsSolver?.visualize();
1124
1302
  const packInnerPartitionsViz = this.packInnerPartitionsSolver?.visualize();
1125
1303
  const partitionPackingViz = this.partitionPackingSolver?.visualize();
@@ -1127,6 +1305,7 @@ var LayoutPipelineSolver = class extends BaseSolver {
1127
1305
  const inputViz = visualizeInputProblem(this.inputProblem, basicLayout);
1128
1306
  const visualizations = [
1129
1307
  inputViz,
1308
+ identifyDecouplingCapsViz,
1130
1309
  chipPartitionsViz,
1131
1310
  packInnerPartitionsViz,
1132
1311
  partitionPackingViz
@@ -1174,6 +1353,9 @@ var LayoutPipelineSolver = class extends BaseSolver {
1174
1353
  if (this.chipPartitionsSolver?.solved) {
1175
1354
  return this.chipPartitionsSolver.visualize();
1176
1355
  }
1356
+ if (this.identifyDecouplingCapsSolver?.solved) {
1357
+ return this.identifyDecouplingCapsSolver.visualize();
1358
+ }
1177
1359
  return super.preview();
1178
1360
  }
1179
1361
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tscircuit/matchpack",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.11",
5
+ "version": "0.0.13",
6
6
  "files": [
7
7
  "dist"
8
8
  ],
@@ -21,7 +21,7 @@
21
21
  "@tscircuit/schematic-viewer": "^2.0.26",
22
22
  "@types/bun": "latest",
23
23
  "bpc-graph": "^0.0.66",
24
- "calculate-packing": "^0.0.28",
24
+ "calculate-packing": "^0.0.31",
25
25
  "circuit-json": "^0.0.226",
26
26
  "graphics-debug": "^0.0.62",
27
27
  "react-cosmos": "^7.0.0",