@tscircuit/matchpack 0.0.12 → 0.0.14

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,8 +74,56 @@ 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
 
80
+ /**
81
+ * Identifies decoupling capacitor groups based on specific criteria:
82
+ * 1. Component has exactly 2 pins and restricted rotation (0/180 only or no rotation)
83
+ * 2. One pin indirectly connected to net with "y+" restriction, one to "y-" restriction
84
+ * 3. At least one pin directly connected to another chip
85
+ */
86
+
87
+ interface DecouplingCapGroup {
88
+ decouplingCapGroupId: string;
89
+ mainChipId: ChipId;
90
+ netPair: [NetId, NetId];
91
+ decouplingCapChipIds: ChipId[];
92
+ }
93
+ /**
94
+ * Identify decoupling capacitor groups based on specific criteria:
95
+ * 1. Component has exactly 2 pins and restricted rotation (0/180 only or no rotation)
96
+ * 2. One pin indirectly connected to net with "y+" restriction, one to "y-" restriction
97
+ * 3. At least one pin directly connected to a chip (the main chip, typically a microcontroller)
98
+ */
99
+ declare class IdentifyDecouplingCapsSolver extends BaseSolver {
100
+ inputProblem: InputProblem;
101
+ queuedChips: Chip[];
102
+ outputDecouplingCapGroups: DecouplingCapGroup[];
103
+ /** Quick lookup of groups by main chip and net pair for accumulation */
104
+ private groupsByMainChipId;
105
+ constructor(inputProblem: InputProblem);
106
+ /** Determine if chip is a 2-pin component with restricted rotation */
107
+ private isTwoPinRestrictedRotation;
108
+ /** Check that the two pins are on opposite Y sides (y+ and y-) */
109
+ private pinsOnOppositeYSides;
110
+ /** Get chips strongly connected (direct pin-to-pin) to this pin */
111
+ private getStronglyConnectedNeighborChips;
112
+ /** Find the main chip id for a decoupling capacitor candidate */
113
+ private findMainChipIdForCap;
114
+ /** Get all net IDs connected to a pin */
115
+ private getNetIdsForPin;
116
+ /** Get a normalized, sorted pair of net IDs connected across the two pins of a capacitor chip */
117
+ private getNormalizedNetPair;
118
+ /** Adds a decoupling capacitor to the group for the given main chip and net pair */
119
+ private addToGroup;
120
+ lastChip: Chip | null;
121
+ _step(): void;
122
+ visualize(): GraphicsObject;
123
+ getConstructorParams(): [InputProblem];
124
+ computeProgress(): number;
125
+ }
126
+
78
127
  /**
79
128
  * Creates partitions (small subset groups) surrounding complex chips.
80
129
  * Divides the layout problem into manageable sections for more efficient processing.
@@ -83,10 +132,16 @@ type InputProblem = {
83
132
  declare class ChipPartitionsSolver extends BaseSolver {
84
133
  inputProblem: InputProblem;
85
134
  partitions: InputProblem[];
86
- constructor(inputProblem: InputProblem);
135
+ decouplingCapGroups?: DecouplingCapGroup[];
136
+ constructor({ inputProblem, decouplingCapGroups, }: {
137
+ inputProblem: InputProblem;
138
+ decouplingCapGroups?: DecouplingCapGroup[];
139
+ });
87
140
  _step(): void;
88
141
  /**
89
- * Creates partitions by finding connected components through strong pin connections
142
+ * Creates partitions by:
143
+ * - Separating each decoupling capacitor group into its own partition (caps only, excluding the main chip)
144
+ * - Partitioning remaining chips by connected components through strong pin connections
90
145
  */
91
146
  private createPartitions;
92
147
  /**
@@ -186,6 +241,7 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
186
241
  onSolved?: (instance: LayoutPipelineSolver) => void;
187
242
  };
188
243
  declare class LayoutPipelineSolver extends BaseSolver {
244
+ identifyDecouplingCapsSolver?: IdentifyDecouplingCapsSolver;
189
245
  chipPartitionsSolver?: ChipPartitionsSolver;
190
246
  packInnerPartitionsSolver?: PackInnerPartitionsSolver;
191
247
  partitionPackingSolver?: PartitionPackingSolver;
@@ -196,7 +252,7 @@ declare class LayoutPipelineSolver extends BaseSolver {
196
252
  inputProblem: InputProblem;
197
253
  chipPartitions?: ChipPartitionsSolver["partitions"];
198
254
  packedPartitions?: PackedPartition[];
199
- pipelineDef: (PipelineStep<typeof ChipPartitionsSolver> | PipelineStep<typeof PackInnerPartitionsSolver> | PipelineStep<typeof PartitionPackingSolver>)[];
255
+ pipelineDef: (PipelineStep<typeof IdentifyDecouplingCapsSolver> | PipelineStep<typeof ChipPartitionsSolver> | PipelineStep<typeof PackInnerPartitionsSolver> | PipelineStep<typeof PartitionPackingSolver>)[];
200
256
  constructor(inputProblem: InputProblem);
201
257
  currentPipelineStepIndex: number;
202
258
  _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,32 +304,58 @@ 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
303
317
  var ChipPartitionsSolver = class extends BaseSolver {
304
318
  inputProblem;
305
319
  partitions = [];
306
- constructor(inputProblem) {
320
+ decouplingCapGroups;
321
+ constructor({
322
+ inputProblem,
323
+ decouplingCapGroups
324
+ }) {
307
325
  super();
308
326
  this.inputProblem = inputProblem;
327
+ this.decouplingCapGroups = decouplingCapGroups;
309
328
  }
310
329
  _step() {
311
330
  this.partitions = this.createPartitions(this.inputProblem);
312
331
  this.solved = true;
313
332
  }
314
333
  /**
315
- * Creates partitions by finding connected components through strong pin connections
334
+ * Creates partitions by:
335
+ * - Separating each decoupling capacitor group into its own partition (caps only, excluding the main chip)
336
+ * - Partitioning remaining chips by connected components through strong pin connections
316
337
  */
317
338
  createPartitions(inputProblem) {
318
339
  const chipIds = Object.keys(inputProblem.chipMap);
340
+ const decapChipIdSet = /* @__PURE__ */ new Set();
341
+ const decapGroupPartitions = [];
342
+ if (this.decouplingCapGroups && this.decouplingCapGroups.length > 0) {
343
+ for (const group of this.decouplingCapGroups) {
344
+ const capsOnly = [];
345
+ for (const capId of group.decouplingCapChipIds) {
346
+ if (inputProblem.chipMap[capId]) {
347
+ capsOnly.push(capId);
348
+ decapChipIdSet.add(capId);
349
+ }
350
+ }
351
+ if (capsOnly.length > 0) {
352
+ decapGroupPartitions.push(capsOnly);
353
+ }
354
+ }
355
+ }
356
+ const nonDecapChipIds = chipIds.filter((id) => !decapChipIdSet.has(id));
319
357
  const adjacencyMap = /* @__PURE__ */ new Map();
320
- for (const chipId of chipIds) {
358
+ for (const chipId of nonDecapChipIds) {
321
359
  adjacencyMap.set(chipId, /* @__PURE__ */ new Set());
322
360
  }
323
361
  for (const [connKey, isConnected] of Object.entries(
@@ -327,22 +365,23 @@ var ChipPartitionsSolver = class extends BaseSolver {
327
365
  const [pin1Id, pin2Id] = connKey.split("-");
328
366
  const owner1 = this.findPinOwner(pin1Id, inputProblem);
329
367
  const owner2 = this.findPinOwner(pin2Id, inputProblem);
330
- if (owner1 && owner2 && owner1 !== owner2) {
368
+ if (owner1 && owner2 && owner1 !== owner2 && !decapChipIdSet.has(owner1) && !decapChipIdSet.has(owner2)) {
331
369
  adjacencyMap.get(owner1).add(owner2);
332
370
  adjacencyMap.get(owner2).add(owner1);
333
371
  }
334
372
  }
335
373
  const visited = /* @__PURE__ */ new Set();
336
- const partitions = [];
337
- for (const componentId of chipIds) {
374
+ const nonDecapPartitions = [];
375
+ for (const componentId of nonDecapChipIds) {
338
376
  if (!visited.has(componentId)) {
339
377
  const partition = this.dfs(componentId, adjacencyMap, visited);
340
378
  if (partition.length > 0) {
341
- partitions.push(partition);
379
+ nonDecapPartitions.push(partition);
342
380
  }
343
381
  }
344
382
  }
345
- return partitions.map(
383
+ const allPartitions = [...decapGroupPartitions, ...nonDecapPartitions];
384
+ return allPartitions.map(
346
385
  (partition) => this.createInputProblemFromPartition(partition, inputProblem)
347
386
  );
348
387
  }
@@ -452,6 +491,179 @@ var ChipPartitionsSolver = class extends BaseSolver {
452
491
  }
453
492
  };
454
493
 
494
+ // lib/utils/getColorFromString.ts
495
+ var getColorFromString = (string, alpha = 1) => {
496
+ let hash = 0;
497
+ for (let i = 0; i < string.length; i++) {
498
+ const char = string.charCodeAt(i);
499
+ hash = (hash << 5) - hash + char;
500
+ hash = hash & hash;
501
+ }
502
+ return `hsl(${Math.abs(hash) % 360}, 70%, 50%, ${alpha})`;
503
+ };
504
+
505
+ // lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver.ts
506
+ var IdentifyDecouplingCapsSolver = class extends BaseSolver {
507
+ inputProblem;
508
+ queuedChips;
509
+ outputDecouplingCapGroups = [];
510
+ /** Quick lookup of groups by main chip and net pair for accumulation */
511
+ groupsByMainChipId = /* @__PURE__ */ new Map();
512
+ constructor(inputProblem) {
513
+ super();
514
+ this.inputProblem = inputProblem;
515
+ this.queuedChips = Object.values(inputProblem.chipMap);
516
+ }
517
+ /** Determine if chip is a 2-pin component with restricted rotation */
518
+ isTwoPinRestrictedRotation(chip) {
519
+ if (chip.pins.length !== 2) return false;
520
+ if (!chip.availableRotations) return false;
521
+ const allowed = /* @__PURE__ */ new Set([0, 180]);
522
+ return chip.availableRotations.length > 0 && chip.availableRotations.every((r) => allowed.has(r));
523
+ }
524
+ /** Check that the two pins are on opposite Y sides (y+ and y-) */
525
+ pinsOnOppositeYSides(chip) {
526
+ if (chip.pins.length !== 2) return false;
527
+ const [p1, p2] = chip.pins;
528
+ const cp1 = this.inputProblem.chipPinMap[p1];
529
+ const cp2 = this.inputProblem.chipPinMap[p2];
530
+ if (!cp1 || !cp2) return false;
531
+ const sides = /* @__PURE__ */ new Set([cp1.side, cp2.side]);
532
+ return sides.has("y+") && sides.has("y-");
533
+ }
534
+ /** Get chips strongly connected (direct pin-to-pin) to this pin */
535
+ getStronglyConnectedNeighborChips(pinId) {
536
+ const neighbors = /* @__PURE__ */ new Set();
537
+ for (const [connKey, connected] of Object.entries(
538
+ this.inputProblem.pinStrongConnMap
539
+ )) {
540
+ if (!connected) continue;
541
+ const [a, b] = connKey.split("-");
542
+ if (a === pinId) {
543
+ const otherChipId = b.split(".")[0];
544
+ neighbors.add(otherChipId);
545
+ } else if (b === pinId) {
546
+ const otherChipId = a.split(".")[0];
547
+ neighbors.add(otherChipId);
548
+ }
549
+ }
550
+ return neighbors;
551
+ }
552
+ /** Find the main chip id for a decoupling capacitor candidate */
553
+ findMainChipIdForCap(capChip) {
554
+ const strongNeighbors = /* @__PURE__ */ new Map();
555
+ for (const pinId of capChip.pins) {
556
+ const neighbors = this.getStronglyConnectedNeighborChips(pinId);
557
+ for (const n of neighbors) {
558
+ if (n === capChip.chipId) continue;
559
+ strongNeighbors.set(n, (strongNeighbors.get(n) || 0) + 1);
560
+ }
561
+ }
562
+ if (strongNeighbors.size === 0) return null;
563
+ let best = null;
564
+ for (const [id, score] of strongNeighbors.entries()) {
565
+ if (!best || score > best.score || score === best.score && id < best.id)
566
+ best = { id, score };
567
+ }
568
+ return best ? best.id : null;
569
+ }
570
+ /** Get all net IDs connected to a pin */
571
+ getNetIdsForPin(pinId) {
572
+ const nets = /* @__PURE__ */ new Set();
573
+ for (const [connKey, connected] of Object.entries(
574
+ this.inputProblem.netConnMap
575
+ )) {
576
+ if (!connected) continue;
577
+ const [p, n] = connKey.split("-");
578
+ if (p === pinId) nets.add(n);
579
+ }
580
+ return nets;
581
+ }
582
+ /** Get a normalized, sorted pair of net IDs connected across the two pins of a capacitor chip */
583
+ getNormalizedNetPair(capChip) {
584
+ if (capChip.pins.length !== 2) return null;
585
+ const nets = /* @__PURE__ */ new Set();
586
+ for (const pinId of capChip.pins) {
587
+ const pinNets = this.getNetIdsForPin(pinId);
588
+ for (const n of pinNets) nets.add(n);
589
+ }
590
+ if (nets.size !== 2) return null;
591
+ const [a, b] = Array.from(nets).sort();
592
+ return [a, b];
593
+ }
594
+ /** Adds a decoupling capacitor to the group for the given main chip and net pair */
595
+ addToGroup(mainChipId, netPair, capChipId) {
596
+ const [n1, n2] = netPair;
597
+ const groupKey = `${mainChipId}__${n1}__${n2}`;
598
+ let group = this.groupsByMainChipId.get(groupKey);
599
+ if (!group) {
600
+ group = {
601
+ decouplingCapGroupId: `decap_group_${mainChipId}__${n1}__${n2}`,
602
+ mainChipId,
603
+ netPair: [n1, n2],
604
+ decouplingCapChipIds: []
605
+ };
606
+ this.groupsByMainChipId.set(groupKey, group);
607
+ this.outputDecouplingCapGroups.push(group);
608
+ }
609
+ if (!group.decouplingCapChipIds.includes(capChipId)) {
610
+ group.decouplingCapChipIds.push(capChipId);
611
+ }
612
+ }
613
+ lastChip = null;
614
+ _step() {
615
+ const currentChip = this.queuedChips.shift();
616
+ this.lastChip = currentChip ?? null;
617
+ if (!currentChip) {
618
+ this.solved = true;
619
+ return;
620
+ }
621
+ const isDecouplingCap = this.isTwoPinRestrictedRotation(currentChip) && this.pinsOnOppositeYSides(currentChip);
622
+ if (!isDecouplingCap) return;
623
+ const mainChipId = this.findMainChipIdForCap(currentChip);
624
+ if (!mainChipId) return;
625
+ const netPair = this.getNormalizedNetPair(currentChip);
626
+ if (!netPair) return;
627
+ this.addToGroup(mainChipId, netPair, currentChip.chipId);
628
+ }
629
+ visualize() {
630
+ const basicLayout = doBasicInputProblemLayout(this.inputProblem);
631
+ const graphics = visualizeInputProblem(
632
+ this.inputProblem,
633
+ basicLayout
634
+ );
635
+ const chipDecapGroupMap = /* @__PURE__ */ new Map();
636
+ for (const group of this.outputDecouplingCapGroups) {
637
+ chipDecapGroupMap.set(group.mainChipId, group);
638
+ for (const capChipId of group.decouplingCapChipIds) {
639
+ chipDecapGroupMap.set(capChipId, group);
640
+ }
641
+ }
642
+ for (const rect of graphics.rects || []) {
643
+ if (rect.label !== this.lastChip?.chipId) {
644
+ rect.fill = "rgba(0,0,0,0.5)";
645
+ }
646
+ }
647
+ for (const rect of graphics.rects || []) {
648
+ const chipId = rect.label;
649
+ const group = chipDecapGroupMap.get(chipId);
650
+ if (!group) continue;
651
+ rect.label = `${rect.label}
652
+ ${group.decouplingCapGroupId}`;
653
+ rect.fill = getColorFromString(group.decouplingCapGroupId, 0.8);
654
+ }
655
+ return graphics;
656
+ }
657
+ getConstructorParams() {
658
+ return [this.inputProblem];
659
+ }
660
+ computeProgress() {
661
+ const total = Object.keys(this.inputProblem.chipMap).length || 1;
662
+ const processed = total - this.queuedChips.length;
663
+ return Math.min(1, Math.max(0, processed / total));
664
+ }
665
+ };
666
+
455
667
  // lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts
456
668
  import { PackSolver2 } from "calculate-packing";
457
669
 
@@ -1023,6 +1235,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
1023
1235
  };
1024
1236
  }
1025
1237
  var LayoutPipelineSolver = class extends BaseSolver {
1238
+ identifyDecouplingCapsSolver;
1026
1239
  chipPartitionsSolver;
1027
1240
  packInnerPartitionsSolver;
1028
1241
  partitionPackingSolver;
@@ -1034,10 +1247,24 @@ var LayoutPipelineSolver = class extends BaseSolver {
1034
1247
  chipPartitions;
1035
1248
  packedPartitions;
1036
1249
  pipelineDef = [
1250
+ definePipelineStep(
1251
+ "identifyDecouplingCapsSolver",
1252
+ IdentifyDecouplingCapsSolver,
1253
+ () => [this.inputProblem],
1254
+ {
1255
+ onSolved: (_layoutSolver) => {
1256
+ }
1257
+ }
1258
+ ),
1037
1259
  definePipelineStep(
1038
1260
  "chipPartitionsSolver",
1039
1261
  ChipPartitionsSolver,
1040
- () => [this.inputProblem],
1262
+ () => [
1263
+ {
1264
+ inputProblem: this.inputProblem,
1265
+ decouplingCapGroups: this.identifyDecouplingCapsSolver?.outputDecouplingCapGroups
1266
+ }
1267
+ ],
1041
1268
  {
1042
1269
  onSolved: (_layoutSolver) => {
1043
1270
  this.chipPartitions = this.chipPartitionsSolver.partitions;
@@ -1121,6 +1348,7 @@ var LayoutPipelineSolver = class extends BaseSolver {
1121
1348
  if (this.solved && this.partitionPackingSolver?.solved) {
1122
1349
  return this.partitionPackingSolver.visualize();
1123
1350
  }
1351
+ const identifyDecouplingCapsViz = this.identifyDecouplingCapsSolver?.visualize();
1124
1352
  const chipPartitionsViz = this.chipPartitionsSolver?.visualize();
1125
1353
  const packInnerPartitionsViz = this.packInnerPartitionsSolver?.visualize();
1126
1354
  const partitionPackingViz = this.partitionPackingSolver?.visualize();
@@ -1128,6 +1356,7 @@ var LayoutPipelineSolver = class extends BaseSolver {
1128
1356
  const inputViz = visualizeInputProblem(this.inputProblem, basicLayout);
1129
1357
  const visualizations = [
1130
1358
  inputViz,
1359
+ identifyDecouplingCapsViz,
1131
1360
  chipPartitionsViz,
1132
1361
  packInnerPartitionsViz,
1133
1362
  partitionPackingViz
@@ -1175,6 +1404,9 @@ var LayoutPipelineSolver = class extends BaseSolver {
1175
1404
  if (this.chipPartitionsSolver?.solved) {
1176
1405
  return this.chipPartitionsSolver.visualize();
1177
1406
  }
1407
+ if (this.identifyDecouplingCapsSolver?.solved) {
1408
+ return this.identifyDecouplingCapsSolver.visualize();
1409
+ }
1178
1410
  return super.preview();
1179
1411
  }
1180
1412
  /**
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.14",
6
6
  "files": [
7
7
  "dist"
8
8
  ],
@@ -23,7 +23,7 @@
23
23
  "bpc-graph": "^0.0.66",
24
24
  "calculate-packing": "^0.0.31",
25
25
  "circuit-json": "^0.0.226",
26
- "graphics-debug": "^0.0.62",
26
+ "graphics-debug": "^0.0.64",
27
27
  "react-cosmos": "^7.0.0",
28
28
  "react-cosmos-plugin-vite": "^7.0.0",
29
29
  "tscircuit": "^0.0.593",