From 64dc9d3270e381b867288fafffc1008c4a1a158c Mon Sep 17 00:00:00 2001 From: Raghav Arora Date: Sun, 5 Oct 2025 02:40:48 +0530 Subject: [PATCH 1/4] feat: use collision-free base elbow paths directly - Add early collision detection for base elbow paths in SchematicTraceSingleLineSolver2 - Skip complex pathfinding when no collisions exist with the default calculateElbow path - Improves performance and creates cleaner trace routing for collision-free cases - Maintains backward compatibility with existing pathfinding for collision cases - Add test to verify collision-free optimization works correctly Resolves tscircuit/schematic-trace-solver#68 --- .../SchematicTraceSingleLineSolver2.ts | 11 ++- .../collision-free-optimization.test.ts | 72 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts diff --git a/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts b/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts index 6795ff7..c77f5bd 100644 --- a/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +++ b/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts @@ -83,7 +83,16 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver { { x: pin2.x, y: pin2.y }, ) - // Seed search + // Check if base elbow path has no collisions - if so, use it directly + const baseCollision = findFirstCollision(this.baseElbow, this.obstacles) + if (!baseCollision) { + // No collisions found, use the base elbow path as the solution + this.solvedTracePath = this.baseElbow + this.solved = true + return + } + + // Base elbow has collisions, proceed with pathfinding this.queue.push({ path: this.baseElbow, collisionChipIds: new Set() }) this.visited.add(pathKey(this.baseElbow)) } diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts new file mode 100644 index 0000000..80fb4bb --- /dev/null +++ b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts @@ -0,0 +1,72 @@ +import { expect, test } from "bun:test" +import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2" + +test("collision-free base elbow should be used directly without pathfinding", () => { + // Simple test case where pins are far apart with no obstacles in between + const inputProblem = { + chips: [ + { + chipId: "chip1", + center: { x: -2, y: 0 }, + width: 1, + height: 1, + pins: [ + { + pinId: "pin1", + x: -1.5, + y: 0, + }, + ], + }, + { + chipId: "chip2", + center: { x: 2, y: 0 }, + width: 1, + height: 1, + pins: [ + { + pinId: "pin2", + x: 1.5, + y: 0, + }, + ], + }, + ], + directConnections: [], + netConnections: [], + availableNetLabelOrientations: {}, + } + + const chipMap = { + chip1: inputProblem.chips[0], + chip2: inputProblem.chips[1], + } + + const pins = [ + { + pinId: "pin1", + x: -1.5, + y: 0, + chipId: "chip1", + _facingDirection: "x+" as const, + }, + { + pinId: "pin2", + x: 1.5, + y: 0, + chipId: "chip2", + _facingDirection: "x-" as const, + }, + ] + + const solver = new SchematicTraceSingleLineSolver2({ + inputProblem: inputProblem as any, + chipMap: chipMap as any, + pins: pins as any, + }) + + // Should be solved immediately since there are no collisions + expect(solver.solved).toBe(true) + expect(solver.solvedTracePath).not.toBeNull() + expect(solver.solvedTracePath?.length).toBeGreaterThan(0) +}) \ No newline at end of file From aac774875683be626103e4e8ddaffbd184d1b609 Mon Sep 17 00:00:00 2001 From: Raghav Arora Date: Sun, 5 Oct 2025 02:55:26 +0530 Subject: [PATCH 2/4] fix: format collision-free-optimization test file --- .../collision-free-optimization.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts index 80fb4bb..5717ced 100644 --- a/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts +++ b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-optimization.test.ts @@ -19,7 +19,7 @@ test("collision-free base elbow should be used directly without pathfinding", () ], }, { - chipId: "chip2", + chipId: "chip2", center: { x: 2, y: 0 }, width: 1, height: 1, @@ -51,7 +51,7 @@ test("collision-free base elbow should be used directly without pathfinding", () _facingDirection: "x+" as const, }, { - pinId: "pin2", + pinId: "pin2", x: 1.5, y: 0, chipId: "chip2", @@ -69,4 +69,4 @@ test("collision-free base elbow should be used directly without pathfinding", () expect(solver.solved).toBe(true) expect(solver.solvedTracePath).not.toBeNull() expect(solver.solvedTracePath?.length).toBeGreaterThan(0) -}) \ No newline at end of file +}) From b60f9505d2fadf8e3955d945940cd0524012d0f0 Mon Sep 17 00:00:00 2001 From: Raghav Arora Date: Mon, 6 Oct 2025 23:15:34 +0530 Subject: [PATCH 3/4] feat: Add SVG snapshots for collision-free long traces feature - Added comprehensive test cases demonstrating bounty #68 implementation - SVG snapshots show collision-free traces being used directly without pathfinding - Tests showcase long diagonal traces with obstacles that don't interfere - Visual proof that collision-free base elbow paths are used immediately This demonstrates the optimization where traces that don't cross any obstacles use the direct elbow path instead of going through the pathfinding algorithm. --- .../collision-free-long-traces.snap.svg | 86 ++++++++ .../diagonal-collision-free.snap.svg | 89 +++++++++ .../collision-free-long-traces.test.ts | 187 ++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-long-traces.snap.svg create mode 100644 tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/diagonal-collision-free.snap.svg create mode 100644 tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-long-traces.snap.svg b/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-long-traces.snap.svg new file mode 100644 index 0000000..aff3f62 --- /dev/null +++ b/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/collision-free-long-traces.snap.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/diagonal-collision-free.snap.svg b/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/diagonal-collision-free.snap.svg new file mode 100644 index 0000000..2d63165 --- /dev/null +++ b/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/diagonal-collision-free.snap.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts new file mode 100644 index 0000000..b77a2c4 --- /dev/null +++ b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts @@ -0,0 +1,187 @@ +import { expect, test } from "bun:test" +import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2" + +test("long collision-free traces should be used directly - showcasing bounty #68", () => { + // This test demonstrates the bounty #68 feature: "Allow long traces that don't cross any other traces" + // When the direct elbow path has no collisions, it should be used immediately without pathfinding + + const inputProblem = { + chips: [ + // Left chip with pin facing right + { + chipId: "chip_left", + center: { x: -5, y: 0 }, + width: 1, + height: 1, + pins: [ + { + pinId: "left_pin", + x: -4.5, // Right edge of left chip + y: 0, + }, + ], + }, + // Right chip with pin facing left - far away to create a long trace + { + chipId: "chip_right", + center: { x: 5, y: 2 }, + width: 1, + height: 1, + pins: [ + { + pinId: "right_pin", + x: 4.5, // Left edge of right chip + y: 2, + }, + ], + }, + // Obstacle chip that doesn't interfere with the direct path + { + chipId: "obstacle", + center: { x: 0, y: -3 }, + width: 1.5, + height: 1.5, + pins: [], + }, + ], + directConnections: [], + netConnections: [], + availableNetLabelOrientations: {}, + } + + const chipMap = { + chip_left: inputProblem.chips[0], + chip_right: inputProblem.chips[1], + obstacle: inputProblem.chips[2], + } + + const pins = [ + { + pinId: "left_pin", + x: -4.5, + y: 0, + chipId: "chip_left", + _facingDirection: "x+" as const, // Facing right + }, + { + pinId: "right_pin", + x: 4.5, + y: 2, + chipId: "chip_right", + _facingDirection: "x-" as const, // Facing left + }, + ] + + const solver = new SchematicTraceSingleLineSolver2({ + inputProblem: inputProblem as any, + chipMap: chipMap as any, + pins: pins as any, + }) + + // Should be solved immediately since the long trace has no collisions + expect(solver.solved).toBe(true) + expect(solver.solvedTracePath).not.toBeNull() + expect(solver.solvedTracePath?.length).toBeGreaterThan(2) // Elbow path has multiple points + + // The path should be the collision-free base elbow path + expect(solver.solvedTracePath).toEqual(solver.baseElbow) + + // Verify the path connects the pins correctly + const path = solver.solvedTracePath! + expect(path[0]).toEqual({ x: -4.5, y: 0 }) + expect(path[path.length - 1]).toEqual({ x: 4.5, y: 2 }) + + expect(solver).toMatchSolverSnapshot(import.meta.path) +}) + +test("collision-free diagonal connection with obstacles nearby", () => { + // Another test case showing long traces work when obstacles don't interfere + + const inputProblem = { + chips: [ + // Bottom-left chip + { + chipId: "chip_bl", + center: { x: -4, y: -3 }, + width: 1, + height: 1, + pins: [ + { + pinId: "bl_pin", + x: -3.5, // Right edge + y: -3, + }, + ], + }, + // Top-right chip - creating a diagonal long trace + { + chipId: "chip_tr", + center: { x: 4, y: 3 }, + width: 1, + height: 1, + pins: [ + { + pinId: "tr_pin", + x: 3.5, // Left edge + y: 3, + }, + ], + }, + // Obstacle chips that don't block the path (positioned away from the elbow) + { + chipId: "obstacle1", + center: { x: -1, y: 2 }, + width: 0.5, + height: 0.5, + pins: [], + }, + { + chipId: "obstacle2", + center: { x: 1, y: -2 }, + width: 0.5, + height: 0.5, + pins: [], + }, + ], + directConnections: [], + netConnections: [], + availableNetLabelOrientations: {}, + } + + const chipMap = { + chip_bl: inputProblem.chips[0], + chip_tr: inputProblem.chips[1], + obstacle1: inputProblem.chips[2], + obstacle2: inputProblem.chips[3], + } + + const pins = [ + { + pinId: "bl_pin", + x: -3.5, + y: -3, + chipId: "chip_bl", + _facingDirection: "x+" as const, + }, + { + pinId: "tr_pin", + x: 3.5, + y: 3, + chipId: "chip_tr", + _facingDirection: "x-" as const, + }, + ] + + const solver = new SchematicTraceSingleLineSolver2({ + inputProblem: inputProblem as any, + chipMap: chipMap as any, + pins: pins as any, + }) + + // Should be solved immediately with collision-free elbow + expect(solver.solved).toBe(true) + expect(solver.solvedTracePath).not.toBeNull() + expect(solver.solvedTracePath).toEqual(solver.baseElbow) + + expect(solver).toMatchSolverSnapshot(import.meta.path, "diagonal-collision-free") +}) \ No newline at end of file From c5212a1fde9cd5c25f6c12123d3c880bc62c6c56 Mon Sep 17 00:00:00 2001 From: Raghav Arora Date: Mon, 6 Oct 2025 23:22:16 +0530 Subject: [PATCH 4/4] fix: Format collision-free long traces test file - Applied biome formatting to test file - Ensures consistent code style across the project --- .../collision-free-long-traces.test.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts index b77a2c4..13c0be9 100644 --- a/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts +++ b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts @@ -4,7 +4,7 @@ import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLines test("long collision-free traces should be used directly - showcasing bounty #68", () => { // This test demonstrates the bounty #68 feature: "Allow long traces that don't cross any other traces" // When the direct elbow path has no collisions, it should be used immediately without pathfinding - + const inputProblem = { chips: [ // Left chip with pin facing right @@ -23,7 +23,7 @@ test("long collision-free traces should be used directly - showcasing bounty #68 }, // Right chip with pin facing left - far away to create a long trace { - chipId: "chip_right", + chipId: "chip_right", center: { x: 5, y: 2 }, width: 1, height: 1, @@ -51,7 +51,7 @@ test("long collision-free traces should be used directly - showcasing bounty #68 const chipMap = { chip_left: inputProblem.chips[0], - chip_right: inputProblem.chips[1], + chip_right: inputProblem.chips[1], obstacle: inputProblem.chips[2], } @@ -64,7 +64,7 @@ test("long collision-free traces should be used directly - showcasing bounty #68 _facingDirection: "x+" as const, // Facing right }, { - pinId: "right_pin", + pinId: "right_pin", x: 4.5, y: 2, chipId: "chip_right", @@ -82,10 +82,10 @@ test("long collision-free traces should be used directly - showcasing bounty #68 expect(solver.solved).toBe(true) expect(solver.solvedTracePath).not.toBeNull() expect(solver.solvedTracePath?.length).toBeGreaterThan(2) // Elbow path has multiple points - + // The path should be the collision-free base elbow path expect(solver.solvedTracePath).toEqual(solver.baseElbow) - + // Verify the path connects the pins correctly const path = solver.solvedTracePath! expect(path[0]).toEqual({ x: -4.5, y: 0 }) @@ -96,7 +96,7 @@ test("long collision-free traces should be used directly - showcasing bounty #68 test("collision-free diagonal connection with obstacles nearby", () => { // Another test case showing long traces work when obstacles don't interfere - + const inputProblem = { chips: [ // Bottom-left chip @@ -136,7 +136,7 @@ test("collision-free diagonal connection with obstacles nearby", () => { pins: [], }, { - chipId: "obstacle2", + chipId: "obstacle2", center: { x: 1, y: -2 }, width: 0.5, height: 0.5, @@ -167,7 +167,7 @@ test("collision-free diagonal connection with obstacles nearby", () => { pinId: "tr_pin", x: 3.5, y: 3, - chipId: "chip_tr", + chipId: "chip_tr", _facingDirection: "x-" as const, }, ] @@ -183,5 +183,8 @@ test("collision-free diagonal connection with obstacles nearby", () => { expect(solver.solvedTracePath).not.toBeNull() expect(solver.solvedTracePath).toEqual(solver.baseElbow) - expect(solver).toMatchSolverSnapshot(import.meta.path, "diagonal-collision-free") -}) \ No newline at end of file + expect(solver).toMatchSolverSnapshot( + import.meta.path, + "diagonal-collision-free", + ) +})