Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove path loop fix to simplify output routes #88

Merged
merged 10 commits into from
Oct 26, 2024
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Point } from "@tscircuit/math-utils"
import type { PointWithLayer } from "solver-utils"
import {
scale,
fromTriangles,
translate,
compose,
applyToPoint,
} from "transformation-matrix"

export const getPathComparisonSvg = (
pathMap: Record<string, PointWithLayer[]>,
) => {
const svgWidth = 640
const svgHeight = 480

// Find min and max coordinates
let minX = Infinity,
maxX = -Infinity,
minY = Infinity,
maxY = -Infinity
Object.values(pathMap)
.flat()
.forEach((point) => {
minX = Math.min(minX, point.x)
maxX = Math.max(maxX, point.x)
minY = Math.min(minY, point.y)
maxY = Math.max(maxY, point.y)
})

// Compute scale and translation
const padding = 20 // Padding around the edges

// Define triangles in path coordinate space and SVG coordinate space
const pathTriangle = [
{ x: minX, y: minY },
{ x: maxX, y: minY },
{ x: minX, y: maxY },
]
const svgTriangle = [
{ x: padding, y: padding },
{ x: svgWidth - padding, y: padding },
{ x: padding, y: svgHeight - padding },
]

// Compute the transform using fromTriangles
const transform = fromTriangles(pathTriangle, svgTriangle)

let svg = `<svg width="${svgWidth}" height="${svgHeight}" xmlns="http://www.w3.org/2000/svg">`

const legendItems: string[] = []

Object.entries(pathMap).forEach(([pathName, points], pathIndex) => {
const color = `hsl(${pathIndex * 137.5}, 70%, 40%)`

// Draw lines between adjacent points
for (let i = 0; i < points.length - 1; i++) {
const start = applyToPoint(transform, points[i])
const end = applyToPoint(transform, points[i + 1])
start.x -= 8 * pathIndex
start.y -= 8 * pathIndex
end.x -= 8 * pathIndex
end.y -= 8 * pathIndex
const isDashed =
points[i].layer === "bottom" || points[i + 1].layer === "bottom"
svg += `<line x1="${start.x}" y1="${start.y}" x2="${end.x}" y2="${end.y}" stroke="${color}" stroke-width="2" ${isDashed ? 'stroke-dasharray="4"' : ""} />`
}

// Draw points and add index numbers
points.forEach((point, index) => {
let { x, y } = applyToPoint(transform, point)
x -= 8 * pathIndex
y -= 8 * pathIndex

svg += `<circle cx="${x}" cy="${y}" r="3" fill="${color}" />`
svg += `<text x="${x + 5}" y="${y - 5}" font-size="10" fill="${color}">${index}</text>`
})

// Add legend item
legendItems.push(
`<text x="5" y="${15 * (pathIndex + 1)}" font-size="12" fill="${color}">${pathName}</text>`,
)
})

// Add legend
svg += `<g transform="translate(${svgWidth - 100}, ${svgHeight - 20 - legendItems.length * 15})">`
svg += `<rect x="0" y="0" width="95" height="${legendItems.length * 15 + 10}" fill="white" opacity="0.7" />`
svg += legendItems.join("")
svg += "</g>"

svg += "</svg>"
return svg
}
36 changes: 36 additions & 0 deletions algos/infinite-grid-ijump-astar/tests/remove-path-loops-1.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Circuit } from "@tscircuit/core"
import { expect, test } from "bun:test"
import type { AnyCircuitElement } from "circuit-json"
import { getSimpleRouteJson } from "solver-utils"
import { IJumpMultiMarginAutorouter } from "../v2/lib/IJumpMultiMarginAutorouter"
import { getDebugSvg } from "./fixtures/get-debug-svg"
import { removePathLoops } from "solver-postprocessing/remove-path-loops"
import { getPathComparisonSvg } from "./fixtures/get-path-comparison-svg"

test("remove-path-loops 1: simple loop", () => {
/**
* Ascii art of the path:
* ......
* . .
* ..........
* .
* .
*/
// Create a path with an intentional loop
const pathWithLoop: Array<{ x: number; y: number; layer: string }> = [
{ x: 0, y: 0, layer: "top" },
{ x: 5, y: 0, layer: "top" },
{ x: 5, y: 3, layer: "top" },
{ x: 3, y: 3, layer: "top" },
{ x: 3, y: -3, layer: "top" },
]

const simplifiedPath = removePathLoops(pathWithLoop)

expect(
getPathComparisonSvg({
pathWithLoop,
simplifiedPath,
}),
).toMatchSvgSnapshot(import.meta.path)
})
38 changes: 38 additions & 0 deletions algos/infinite-grid-ijump-astar/tests/remove-path-loops-2.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Circuit } from "@tscircuit/core"
import { expect, test } from "bun:test"
import type { AnyCircuitElement } from "circuit-json"
import { getSimpleRouteJson } from "solver-utils"
import { IJumpMultiMarginAutorouter } from "../v2/lib/IJumpMultiMarginAutorouter"
import { getDebugSvg } from "./fixtures/get-debug-svg"
import { removePathLoops } from "solver-postprocessing/remove-path-loops"
import { getPathComparisonSvg } from "./fixtures/get-path-comparison-svg"

test("remove-path-loops 2: simple loop", () => {
/**
* Ascii art of the path:
* ......
* . .
* ..........
* .
* .
*/
// Create a path with an intentional loop
const pathWithLoop: Array<{ x: number; y: number; layer: string }> = [
{ x: 0, y: 0, layer: "top" },
{ x: 5, y: 0, layer: "top" },
{ x: 5, y: 3, layer: "top" },
{ x: 5, y: 3, layer: "top" },
{ x: 3, y: 3, layer: "top" },
{ x: 3, y: 3, layer: "bottom" },
{ x: 3, y: -3, layer: "bottom" },
]

const simplifiedPath = removePathLoops(pathWithLoop)

expect(
getPathComparisonSvg({
pathWithLoop,
simplifiedPath,
}),
).toMatchSvgSnapshot(import.meta.path)
})
2 changes: 1 addition & 1 deletion algos/multi-layer-ijump/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function autoroute(soup: AnySoupElement[]): SolutionWithDebugInfo {
const autorouter = new MultilayerIjump({
input,
connMap,
// isRemovePathLoopsEnabled: true,
isRemovePathLoopsEnabled: true,
optimizeWithGoalBoxes: true,
})

Expand Down
Loading
Loading