From 5dbde500fd7dc927e732529de48d61184a953e2d Mon Sep 17 00:00:00 2001 From: Incharajayaram Date: Tue, 7 Oct 2025 00:37:01 +0530 Subject: [PATCH] Allow straight-line traces when no collisions exist Fixes: Allow drawing traces using the default calculateElbow path when there are no direct collisions, eliminating unnecessary complex routing. Changes - Added early collision detection for base elbow paths in SchematicTraceSingleLineSolver2 - When the default elbow path has no collisions, use it directly instead of complex pathfinding - Added 4-layer validation: collisions, path length, orthogonality, pin connections - Maintains full backward compatibility - existing pathfinding used when collisions exist - Improves performance for collision-free cases Testing - All existing tests pass (39/44 tests, 5 skipped as expected) - Added new test with visual snapshot verifying collision-free optimization works correctly - Verified no regressions in complex routing scenarios Impact This will create cleaner, more direct traces in circuits like the boost converter example, where many connections can use straight-line routing without obstruction. Fixes #68 --- .../SchematicTraceSingleLineSolver2.ts | 38 +++++++- ...LineSolver2_01-example17-d1_1-u1_1.test.ts | 84 ++++++++++++++++++ .../collision-free-optimization.snap.svg | 86 +++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-optimization.snap.svg diff --git a/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts b/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts index 6795ff7..056f5a9 100644 --- a/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +++ b/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts @@ -83,9 +83,41 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver { { x: pin2.x, y: pin2.y }, ) - // Seed search - this.queue.push({ path: this.baseElbow, collisionChipIds: new Set() }) - this.visited.add(pathKey(this.baseElbow)) + // Optimization: Use base elbow directly if collision-free + const baseCollision = findFirstCollision(this.baseElbow, this.obstacles) + if (!baseCollision && this.baseElbow.length >= 2) { + const first = this.baseElbow[0]! + const last = this.baseElbow[this.baseElbow.length - 1]! + const EPS = 1e-9 + const samePoint = (p: Point, q: Point) => + Math.abs(p.x - q.x) < EPS && Math.abs(p.y - q.y) < EPS + + // Verify path is orthogonal and connects pins correctly + let isOrthogonal = true + for (let i = 0; i < this.baseElbow.length - 1; i++) { + const a = this.baseElbow[i]! + const b = this.baseElbow[i + 1]! + if (!isHorizontal(a, b) && !isVertical(a, b)) { + isOrthogonal = false + break + } + } + + if ( + isOrthogonal && + samePoint(first, { x: pin1.x, y: pin1.y }) && + samePoint(last, { x: pin2.x, y: pin2.y }) + ) { + this.solvedTracePath = this.baseElbow + this.solved = true + } + } + + // Fallback: Use pathfinding if optimization didn't solve it + if (!this.solved) { + this.queue.push({ path: this.baseElbow, collisionChipIds: new Set() }) + this.visited.add(pathKey(this.baseElbow)) + } } override getConstructorParams(): ConstructorParameters< diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.test.ts b/tests/solvers/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.test.ts index 585a2b7..c45b680 100644 --- a/tests/solvers/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.test.ts +++ b/tests/solvers/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.test.ts @@ -321,3 +321,87 @@ test("SchematicTraceSingleLineSolver2 should solve problem correctly", () => { expect(solver).toMatchSolverSnapshot(import.meta.path) }) + +test("SchematicTraceSingleLineSolver2 collision-free optimization", () => { + const input = { + chipMap: { + chip1: { + chipId: "chip1", + center: { x: -2, y: -1 }, + width: 1, + height: 1, + pins: [{ pinId: "pin1", x: -1.5, y: -1 }], + }, + chip2: { + chipId: "chip2", + center: { x: 2, y: 1 }, + width: 1, + height: 1, + pins: [{ pinId: "pin2", x: 1.5, y: 1 }], + }, + chip3: { + chipId: "chip3", + center: { x: 0, y: -3 }, + width: 1, + height: 1, + pins: [], + }, + }, + pins: [ + { + pinId: "pin1", + x: -1.5, + y: -1, + chipId: "chip1", + _facingDirection: "x+" as const, + }, + { + pinId: "pin2", + x: 1.5, + y: 1, + chipId: "chip2", + _facingDirection: "x-" as const, + }, + ], + inputProblem: { + chips: [ + { + chipId: "chip1", + center: { x: -2, y: -1 }, + width: 1, + height: 1, + pins: [{ pinId: "pin1", x: -1.5, y: -1 }], + }, + { + chipId: "chip2", + center: { x: 2, y: 1 }, + width: 1, + height: 1, + pins: [{ pinId: "pin2", x: 1.5, y: 1 }], + }, + { + chipId: "chip3", + center: { x: 0, y: -3 }, + width: 1, + height: 1, + pins: [], + }, + ], + directConnections: [], + netConnections: [], + availableNetLabelOrientations: {}, + }, + } + + const solver = new SchematicTraceSingleLineSolver2(input as any) + + // Should be solved immediately via collision-free optimization + expect(solver.solved).toBe(true) + expect(solver.solvedTracePath).not.toBeNull() + + solver.solve() + expect(solver).toMatchSolverSnapshot( + import.meta.path, + "collision-free-optimization", + ) +}) diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-optimization.snap.svg b/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-optimization.snap.svg new file mode 100644 index 0000000..5077018 --- /dev/null +++ b/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-optimization.snap.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file