@tscircuit/schematic-trace-solver 0.0.197 → 0.0.198
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 +4 -0
- package/dist/index.js +82 -4
- package/lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts +128 -2
- package/lib/solvers/RailNetLabelCornerPlacementSolver/types.ts +1 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +2 -0
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/repro-am3352-sheet3-power-rails.page.tsx +7 -0
- package/site/bug-reports/bug-report-20260911T195723Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260911T195723Z/__snapshots__/bug-report-20260911T195723Z.snap.svg +559 -0
- package/tests/bug-reports/bug-report-20260911T195723Z/bug-report-20260911T195723Z.json +1792 -0
- package/tests/bug-reports/bug-report-20260911T195723Z/bug-report-20260911T195723Z.test.ts +35 -0
- package/tests/repros/__snapshots__/repro-am3352-sheet3-power-rails.snap.svg +187 -0
- package/tests/repros/__snapshots__/repro-normal-net-label-branch-from-junction.snap.svg +31 -31
- package/tests/repros/assets/repro-am3352-sheet3-power-rails.input.json +698 -0
- package/tests/repros/repro-am3352-sheet3-power-rails.md +66 -0
- package/tests/repros/repro-am3352-sheet3-power-rails.test.ts +39 -0
- package/tests/repros/repro-normal-net-label-branch-from-junction.test.ts +13 -0
package/dist/index.d.ts
CHANGED
|
@@ -963,6 +963,7 @@ interface RailNetLabelCornerPlacementSolverParams {
|
|
|
963
963
|
inputProblem: InputProblem;
|
|
964
964
|
traces: SolvedTracePath[];
|
|
965
965
|
netLabelPlacements: NetLabelPlacement[];
|
|
966
|
+
netLabelConnectorTraceIds?: ReadonlySet<string>;
|
|
966
967
|
}
|
|
967
968
|
type TraceCornerCandidate = {
|
|
968
969
|
anchorPoint: Point;
|
|
@@ -996,6 +997,7 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
996
997
|
private queuedCornerCandidates;
|
|
997
998
|
private shouldAdvanceToNextLabel;
|
|
998
999
|
private traceMap;
|
|
1000
|
+
private netLabelConnectorTraceIds;
|
|
999
1001
|
constructor(params: RailNetLabelCornerPlacementSolverParams);
|
|
1000
1002
|
getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
|
|
1001
1003
|
_step(): void;
|
|
@@ -1015,6 +1017,8 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
1015
1017
|
private intersectsAnyChip;
|
|
1016
1018
|
private intersectsAnyOtherNetLabel;
|
|
1017
1019
|
private getCornerCandidatesForLabel;
|
|
1020
|
+
private getStraightConnectorCandidates;
|
|
1021
|
+
private isComponentAdjacentCorner;
|
|
1018
1022
|
private isConfiguredRailLabel;
|
|
1019
1023
|
private isLabelCrossedByOtherNetTrace;
|
|
1020
1024
|
private pointsEqual;
|
package/dist/index.js
CHANGED
|
@@ -11181,6 +11181,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11181
11181
|
queuedCornerCandidates = [];
|
|
11182
11182
|
shouldAdvanceToNextLabel = false;
|
|
11183
11183
|
traceMap;
|
|
11184
|
+
netLabelConnectorTraceIds;
|
|
11184
11185
|
constructor(params) {
|
|
11185
11186
|
super();
|
|
11186
11187
|
this.inputProblem = params.inputProblem;
|
|
@@ -11190,6 +11191,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11190
11191
|
this.traceMap = Object.fromEntries(
|
|
11191
11192
|
params.traces.map((trace) => [trace.mspPairId, trace])
|
|
11192
11193
|
);
|
|
11194
|
+
this.netLabelConnectorTraceIds = params.netLabelConnectorTraceIds ?? /* @__PURE__ */ new Set();
|
|
11193
11195
|
this.queuedLabelIndices = this.getProcessableLabelIndices();
|
|
11194
11196
|
this.prepareNextLabel();
|
|
11195
11197
|
}
|
|
@@ -11197,7 +11199,8 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11197
11199
|
return {
|
|
11198
11200
|
inputProblem: this.inputProblem,
|
|
11199
11201
|
traces: this.traces,
|
|
11200
|
-
netLabelPlacements: this.netLabelPlacements
|
|
11202
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
11203
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
|
|
11201
11204
|
};
|
|
11202
11205
|
}
|
|
11203
11206
|
_step() {
|
|
@@ -11248,7 +11251,10 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11248
11251
|
this.currentLabelIndex = labelIndex;
|
|
11249
11252
|
this.currentLabel = label;
|
|
11250
11253
|
this.currentCandidateResults = [];
|
|
11251
|
-
this.queuedCornerCandidates =
|
|
11254
|
+
this.queuedCornerCandidates = [
|
|
11255
|
+
...this.getStraightConnectorCandidates(label),
|
|
11256
|
+
...this.getCornerCandidatesForLabel(label)
|
|
11257
|
+
];
|
|
11252
11258
|
return true;
|
|
11253
11259
|
}
|
|
11254
11260
|
advanceToNextLabel() {
|
|
@@ -11330,7 +11336,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11330
11336
|
}
|
|
11331
11337
|
shouldProcessLabel(label) {
|
|
11332
11338
|
const isVerticalRailLabel = label.orientation === "y+" || label.orientation === "y-";
|
|
11333
|
-
return isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0;
|
|
11339
|
+
return isVerticalRailLabel && (this.getStraightConnectorCandidates(label).length > 0 || this.getCornerCandidatesForLabel(label).length > 0);
|
|
11334
11340
|
}
|
|
11335
11341
|
intersectsAnyChip(bounds) {
|
|
11336
11342
|
return this.inputProblem.chips.some(
|
|
@@ -11387,6 +11393,77 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
|
|
|
11387
11393
|
...railAlignedCandidates.sort((a, b) => a.distance - b.distance)
|
|
11388
11394
|
];
|
|
11389
11395
|
}
|
|
11396
|
+
getStraightConnectorCandidates(label) {
|
|
11397
|
+
if (label.pinIds.length !== 2 || !this.isConfiguredRailLabel(label)) {
|
|
11398
|
+
return [];
|
|
11399
|
+
}
|
|
11400
|
+
const pins = this.inputProblem.chips.flatMap((chip) => chip.pins).filter((pin) => label.pinIds.includes(pin.pinId));
|
|
11401
|
+
if (pins.length !== 2 || pins[0]._facingDirection !== pins[1]._facingDirection || pins[0]._facingDirection !== "x+" && pins[0]._facingDirection !== "x-" || Math.abs(pins[0].y - pins[1].y) > EPS8) {
|
|
11402
|
+
return [];
|
|
11403
|
+
}
|
|
11404
|
+
const configuredOrientations = this.inputProblem.availableNetLabelOrientations[label.netId];
|
|
11405
|
+
if (configuredOrientations?.length !== 1 || configuredOrientations[0] !== label.orientation) {
|
|
11406
|
+
return [];
|
|
11407
|
+
}
|
|
11408
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
11409
|
+
(connection) => connection.netId === label.netId
|
|
11410
|
+
);
|
|
11411
|
+
if (netConnection?.isGround) return [];
|
|
11412
|
+
const connector = this.traces.find(
|
|
11413
|
+
(trace) => this.netLabelConnectorTraceIds.has(trace.mspPairId) && trace.globalConnNetId === label.globalConnNetId && label.pinIds.every((pinId) => trace.pinIds.includes(pinId)) && trace.tracePath.length >= 2 && (this.pointsEqual(trace.tracePath[0], label.anchorPoint) || this.pointsEqual(trace.tracePath.at(-1), label.anchorPoint))
|
|
11414
|
+
);
|
|
11415
|
+
if (!connector) return [];
|
|
11416
|
+
const sourcePoint = this.pointsEqual(
|
|
11417
|
+
connector.tracePath[0],
|
|
11418
|
+
label.anchorPoint
|
|
11419
|
+
) ? connector.tracePath.at(-1) : connector.tracePath[0];
|
|
11420
|
+
if (!this.isComponentAdjacentCorner(label, sourcePoint)) return [];
|
|
11421
|
+
const direction = label.orientation === "y+" ? 1 : -1;
|
|
11422
|
+
const maxSearchDistance = Math.min(
|
|
11423
|
+
getMaxSearchDistance(this.inputProblem),
|
|
11424
|
+
label.height * 2
|
|
11425
|
+
);
|
|
11426
|
+
for (let distance7 = WICK_CLEARANCE + LABEL_SEARCH_STEP; distance7 <= maxSearchDistance + EPS8; distance7 += LABEL_SEARCH_STEP) {
|
|
11427
|
+
const anchorPoint = {
|
|
11428
|
+
x: sourcePoint.x,
|
|
11429
|
+
y: sourcePoint.y + direction * distance7
|
|
11430
|
+
};
|
|
11431
|
+
const center = getCenterFromAnchor(
|
|
11432
|
+
anchorPoint,
|
|
11433
|
+
label.orientation,
|
|
11434
|
+
label.width,
|
|
11435
|
+
label.height
|
|
11436
|
+
);
|
|
11437
|
+
if (this.intersectsAnyChip(getRectBounds(center, label.width, label.height))) {
|
|
11438
|
+
continue;
|
|
11439
|
+
}
|
|
11440
|
+
return [
|
|
11441
|
+
{
|
|
11442
|
+
anchorPoint,
|
|
11443
|
+
traceId: connector.mspPairId,
|
|
11444
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
11445
|
+
pinAligned: true,
|
|
11446
|
+
reroutedTracePath: [sourcePoint, anchorPoint]
|
|
11447
|
+
}
|
|
11448
|
+
];
|
|
11449
|
+
}
|
|
11450
|
+
return [];
|
|
11451
|
+
}
|
|
11452
|
+
isComponentAdjacentCorner(label, corner) {
|
|
11453
|
+
return label.mspConnectionPairIds.some((traceId) => {
|
|
11454
|
+
const trace = this.traceMap[traceId];
|
|
11455
|
+
if (!trace || trace.tracePath.length < 2) return false;
|
|
11456
|
+
return [
|
|
11457
|
+
{ point: trace.tracePath[0], neighbor: trace.tracePath[1] },
|
|
11458
|
+
{
|
|
11459
|
+
point: trace.tracePath.at(-1),
|
|
11460
|
+
neighbor: trace.tracePath.at(-2)
|
|
11461
|
+
}
|
|
11462
|
+
].some(
|
|
11463
|
+
({ point, neighbor }) => this.pointsEqual(neighbor, corner) && trace.pins.some((pin) => this.pointsEqual(pin, point))
|
|
11464
|
+
);
|
|
11465
|
+
});
|
|
11466
|
+
}
|
|
11390
11467
|
isConfiguredRailLabel(label) {
|
|
11391
11468
|
if (!label.netId) return false;
|
|
11392
11469
|
return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
|
|
@@ -18710,7 +18787,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
18710
18787
|
traces: Object.values(
|
|
18711
18788
|
instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
|
|
18712
18789
|
),
|
|
18713
|
-
netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements
|
|
18790
|
+
netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements,
|
|
18791
|
+
netLabelConnectorTraceIds: instance.availableNetOrientationSolver.netLabelConnectorTraceIds
|
|
18714
18792
|
}
|
|
18715
18793
|
];
|
|
18716
18794
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
2
|
import type { Point } from "@tscircuit/math-utils"
|
|
3
3
|
import {
|
|
4
|
+
getMaxSearchDistance,
|
|
4
5
|
segmentCrossesBoundsInterior,
|
|
5
6
|
traceCrossesBoundsInterior,
|
|
6
7
|
} from "lib/solvers/AvailableNetOrientationSolver/geometry"
|
|
8
|
+
import {
|
|
9
|
+
LABEL_SEARCH_STEP,
|
|
10
|
+
WICK_CLEARANCE,
|
|
11
|
+
} from "lib/solvers/AvailableNetOrientationSolver/constants"
|
|
7
12
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
8
13
|
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
|
|
9
14
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
@@ -51,6 +56,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
51
56
|
private queuedCornerCandidates: TraceCornerCandidate[] = []
|
|
52
57
|
private shouldAdvanceToNextLabel = false
|
|
53
58
|
private traceMap: Record<string, SolvedTracePath>
|
|
59
|
+
private netLabelConnectorTraceIds: ReadonlySet<string>
|
|
54
60
|
|
|
55
61
|
constructor(params: RailNetLabelCornerPlacementSolverParams) {
|
|
56
62
|
super()
|
|
@@ -61,6 +67,8 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
61
67
|
this.traceMap = Object.fromEntries(
|
|
62
68
|
params.traces.map((trace) => [trace.mspPairId, trace]),
|
|
63
69
|
)
|
|
70
|
+
this.netLabelConnectorTraceIds =
|
|
71
|
+
params.netLabelConnectorTraceIds ?? new Set()
|
|
64
72
|
this.queuedLabelIndices = this.getProcessableLabelIndices()
|
|
65
73
|
this.prepareNextLabel()
|
|
66
74
|
}
|
|
@@ -72,6 +80,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
72
80
|
inputProblem: this.inputProblem,
|
|
73
81
|
traces: this.traces,
|
|
74
82
|
netLabelPlacements: this.netLabelPlacements,
|
|
83
|
+
netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
|
|
75
84
|
}
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -135,7 +144,10 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
135
144
|
this.currentLabelIndex = labelIndex
|
|
136
145
|
this.currentLabel = label
|
|
137
146
|
this.currentCandidateResults = []
|
|
138
|
-
this.queuedCornerCandidates =
|
|
147
|
+
this.queuedCornerCandidates = [
|
|
148
|
+
...this.getStraightConnectorCandidates(label),
|
|
149
|
+
...this.getCornerCandidatesForLabel(label),
|
|
150
|
+
]
|
|
139
151
|
|
|
140
152
|
return true
|
|
141
153
|
}
|
|
@@ -250,7 +262,9 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
250
262
|
const isVerticalRailLabel =
|
|
251
263
|
label.orientation === "y+" || label.orientation === "y-"
|
|
252
264
|
return (
|
|
253
|
-
isVerticalRailLabel &&
|
|
265
|
+
isVerticalRailLabel &&
|
|
266
|
+
(this.getStraightConnectorCandidates(label).length > 0 ||
|
|
267
|
+
this.getCornerCandidatesForLabel(label).length > 0)
|
|
254
268
|
)
|
|
255
269
|
}
|
|
256
270
|
|
|
@@ -323,6 +337,118 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
|
|
|
323
337
|
]
|
|
324
338
|
}
|
|
325
339
|
|
|
340
|
+
private getStraightConnectorCandidates(
|
|
341
|
+
label: NetLabelPlacement,
|
|
342
|
+
): TraceCornerCandidate[] {
|
|
343
|
+
if (label.pinIds.length !== 2 || !this.isConfiguredRailLabel(label)) {
|
|
344
|
+
return []
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const pins = this.inputProblem.chips
|
|
348
|
+
.flatMap((chip) => chip.pins)
|
|
349
|
+
.filter((pin) => label.pinIds.includes(pin.pinId))
|
|
350
|
+
if (
|
|
351
|
+
pins.length !== 2 ||
|
|
352
|
+
pins[0]!._facingDirection !== pins[1]!._facingDirection ||
|
|
353
|
+
(pins[0]!._facingDirection !== "x+" &&
|
|
354
|
+
pins[0]!._facingDirection !== "x-") ||
|
|
355
|
+
Math.abs(pins[0]!.y - pins[1]!.y) > EPS
|
|
356
|
+
) {
|
|
357
|
+
return []
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const configuredOrientations =
|
|
361
|
+
this.inputProblem.availableNetLabelOrientations[label.netId!]
|
|
362
|
+
if (
|
|
363
|
+
configuredOrientations?.length !== 1 ||
|
|
364
|
+
configuredOrientations[0] !== label.orientation
|
|
365
|
+
) {
|
|
366
|
+
return []
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const netConnection = this.inputProblem.netConnections.find(
|
|
370
|
+
(connection) => connection.netId === label.netId,
|
|
371
|
+
)
|
|
372
|
+
if (netConnection?.isGround) return []
|
|
373
|
+
|
|
374
|
+
const connector = this.traces.find(
|
|
375
|
+
(trace) =>
|
|
376
|
+
this.netLabelConnectorTraceIds.has(trace.mspPairId) &&
|
|
377
|
+
trace.globalConnNetId === label.globalConnNetId &&
|
|
378
|
+
label.pinIds.every((pinId) => trace.pinIds.includes(pinId)) &&
|
|
379
|
+
trace.tracePath.length >= 2 &&
|
|
380
|
+
(this.pointsEqual(trace.tracePath[0]!, label.anchorPoint) ||
|
|
381
|
+
this.pointsEqual(trace.tracePath.at(-1)!, label.anchorPoint)),
|
|
382
|
+
)
|
|
383
|
+
if (!connector) return []
|
|
384
|
+
|
|
385
|
+
const sourcePoint = this.pointsEqual(
|
|
386
|
+
connector.tracePath[0]!,
|
|
387
|
+
label.anchorPoint,
|
|
388
|
+
)
|
|
389
|
+
? connector.tracePath.at(-1)!
|
|
390
|
+
: connector.tracePath[0]!
|
|
391
|
+
if (!this.isComponentAdjacentCorner(label, sourcePoint)) return []
|
|
392
|
+
|
|
393
|
+
const direction = label.orientation === "y+" ? 1 : -1
|
|
394
|
+
const maxSearchDistance = Math.min(
|
|
395
|
+
getMaxSearchDistance(this.inputProblem),
|
|
396
|
+
label.height * 2,
|
|
397
|
+
)
|
|
398
|
+
for (
|
|
399
|
+
let distance = WICK_CLEARANCE + LABEL_SEARCH_STEP;
|
|
400
|
+
distance <= maxSearchDistance + EPS;
|
|
401
|
+
distance += LABEL_SEARCH_STEP
|
|
402
|
+
) {
|
|
403
|
+
const anchorPoint = {
|
|
404
|
+
x: sourcePoint.x,
|
|
405
|
+
y: sourcePoint.y + direction * distance,
|
|
406
|
+
}
|
|
407
|
+
const center = getCenterFromAnchor(
|
|
408
|
+
anchorPoint,
|
|
409
|
+
label.orientation,
|
|
410
|
+
label.width,
|
|
411
|
+
label.height,
|
|
412
|
+
)
|
|
413
|
+
if (
|
|
414
|
+
this.intersectsAnyChip(getRectBounds(center, label.width, label.height))
|
|
415
|
+
) {
|
|
416
|
+
continue
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return [
|
|
420
|
+
{
|
|
421
|
+
anchorPoint,
|
|
422
|
+
traceId: connector.mspPairId,
|
|
423
|
+
distance: getDistance(anchorPoint, label.anchorPoint),
|
|
424
|
+
pinAligned: true,
|
|
425
|
+
reroutedTracePath: [sourcePoint, anchorPoint],
|
|
426
|
+
},
|
|
427
|
+
]
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return []
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private isComponentAdjacentCorner(label: NetLabelPlacement, corner: Point) {
|
|
434
|
+
return label.mspConnectionPairIds.some((traceId) => {
|
|
435
|
+
const trace = this.traceMap[traceId]
|
|
436
|
+
if (!trace || trace.tracePath.length < 2) return false
|
|
437
|
+
|
|
438
|
+
return [
|
|
439
|
+
{ point: trace.tracePath[0]!, neighbor: trace.tracePath[1]! },
|
|
440
|
+
{
|
|
441
|
+
point: trace.tracePath.at(-1)!,
|
|
442
|
+
neighbor: trace.tracePath.at(-2)!,
|
|
443
|
+
},
|
|
444
|
+
].some(
|
|
445
|
+
({ point, neighbor }) =>
|
|
446
|
+
this.pointsEqual(neighbor, corner) &&
|
|
447
|
+
trace.pins.some((pin) => this.pointsEqual(pin, point)),
|
|
448
|
+
)
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
326
452
|
private isConfiguredRailLabel(label: NetLabelPlacement) {
|
|
327
453
|
if (!label.netId) return false
|
|
328
454
|
return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
|
|
@@ -402,6 +402,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
402
402
|
),
|
|
403
403
|
netLabelPlacements:
|
|
404
404
|
instance.availableNetOrientationSolver!.outputNetLabelPlacements,
|
|
405
|
+
netLabelConnectorTraceIds:
|
|
406
|
+
instance.availableNetOrientationSolver!.netLabelConnectorTraceIds,
|
|
405
407
|
},
|
|
406
408
|
]
|
|
407
409
|
},
|
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import { PipelineDebugger } from "site/components/PipelineDebugger"
|
|
3
|
+
import input from "../../tests/repros/assets/repro-am3352-sheet3-power-rails.input.json"
|
|
4
|
+
|
|
5
|
+
export default () => (
|
|
6
|
+
<PipelineDebugger inputProblem={structuredClone(input) as InputProblem} />
|
|
7
|
+
)
|