@tscircuit/matchpack 0.0.12 → 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
 
@@ -1023,6 +1189,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
1023
1189
  };
1024
1190
  }
1025
1191
  var LayoutPipelineSolver = class extends BaseSolver {
1192
+ identifyDecouplingCapsSolver;
1026
1193
  chipPartitionsSolver;
1027
1194
  packInnerPartitionsSolver;
1028
1195
  partitionPackingSolver;
@@ -1034,6 +1201,15 @@ var LayoutPipelineSolver = class extends BaseSolver {
1034
1201
  chipPartitions;
1035
1202
  packedPartitions;
1036
1203
  pipelineDef = [
1204
+ definePipelineStep(
1205
+ "identifyDecouplingCapsSolver",
1206
+ IdentifyDecouplingCapsSolver,
1207
+ () => [this.inputProblem],
1208
+ {
1209
+ onSolved: (_layoutSolver) => {
1210
+ }
1211
+ }
1212
+ ),
1037
1213
  definePipelineStep(
1038
1214
  "chipPartitionsSolver",
1039
1215
  ChipPartitionsSolver,
@@ -1121,6 +1297,7 @@ var LayoutPipelineSolver = class extends BaseSolver {
1121
1297
  if (this.solved && this.partitionPackingSolver?.solved) {
1122
1298
  return this.partitionPackingSolver.visualize();
1123
1299
  }
1300
+ const identifyDecouplingCapsViz = this.identifyDecouplingCapsSolver?.visualize();
1124
1301
  const chipPartitionsViz = this.chipPartitionsSolver?.visualize();
1125
1302
  const packInnerPartitionsViz = this.packInnerPartitionsSolver?.visualize();
1126
1303
  const partitionPackingViz = this.partitionPackingSolver?.visualize();
@@ -1128,6 +1305,7 @@ var LayoutPipelineSolver = class extends BaseSolver {
1128
1305
  const inputViz = visualizeInputProblem(this.inputProblem, basicLayout);
1129
1306
  const visualizations = [
1130
1307
  inputViz,
1308
+ identifyDecouplingCapsViz,
1131
1309
  chipPartitionsViz,
1132
1310
  packInnerPartitionsViz,
1133
1311
  partitionPackingViz
@@ -1175,6 +1353,9 @@ var LayoutPipelineSolver = class extends BaseSolver {
1175
1353
  if (this.chipPartitionsSolver?.solved) {
1176
1354
  return this.chipPartitionsSolver.visualize();
1177
1355
  }
1356
+ if (this.identifyDecouplingCapsSolver?.solved) {
1357
+ return this.identifyDecouplingCapsSolver.visualize();
1358
+ }
1178
1359
  return super.preview();
1179
1360
  }
1180
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.12",
5
+ "version": "0.0.13",
6
6
  "files": [
7
7
  "dist"
8
8
  ],