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/__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..13c0be9 --- /dev/null +++ b/tests/solvers/SchematicTraceSingleLineSolver2/collision-free-long-traces.test.ts @@ -0,0 +1,190 @@ +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", + ) +}) 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..5717ced --- /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) +})