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

Post-processing loop removal #59

Merged
merged 3 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions algos/infinite-grid-ijump-astar/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function autorouteMultiMargin(

const autorouter = new IJumpMultiMarginAutorouter({
input,
isRemovePathLoopsEnabled: true,
})

const solution = autorouter.solveAndMapToTraces()
Expand Down
11 changes: 9 additions & 2 deletions algos/infinite-grid-ijump-astar/v2/lib/GeneralizedAstar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
} from "autorouting-dataset"
import { getObstaclesFromRoute } from "autorouting-dataset/lib/solver-utils/getObstaclesFromRoute"
import { ObstacleList } from "./ObstacleList"
import { removePathLoops } from "autorouting-dataset/lib/solver-postprocessing/remove-path-loops"

const debug = Debug("autorouting-dataset:astar")

Expand Down Expand Up @@ -40,7 +41,7 @@ export class GeneralizedAstarAutorouter {
GRID_STEP: number
OBSTACLE_MARGIN: number
MAX_ITERATIONS: number

isRemovePathLoopsEnabled: boolean
/**
* Setting this greater than 1 makes the algorithm find suboptimal paths and
* act more greedy, but at greatly improves performance.
Expand All @@ -58,6 +59,7 @@ export class GeneralizedAstarAutorouter {
GRID_STEP?: number
OBSTACLE_MARGIN?: number
MAX_ITERATIONS?: number
isRemovePathLoopsEnabled?: boolean
debug?: boolean
}) {
this.input = opts.input
Expand All @@ -68,6 +70,7 @@ export class GeneralizedAstarAutorouter {
this.OBSTACLE_MARGIN = opts.OBSTACLE_MARGIN ?? 0.15
this.MAX_ITERATIONS = opts.MAX_ITERATIONS ?? 100
this.debug = opts.debug ?? debug.enabled
this.isRemovePathLoopsEnabled = opts.isRemovePathLoopsEnabled ?? false
if (this.debug) {
debug.enabled = true
}
Expand Down Expand Up @@ -197,7 +200,7 @@ export class GeneralizedAstarAutorouter {
const { solved, current } = this.solveOneStep()

if (solved) {
const route: PointWithLayer[] = []
let route: PointWithLayer[] = []
let node: Node | null = current
while (node) {
route.unshift({
Expand All @@ -213,6 +216,10 @@ export class GeneralizedAstarAutorouter {
this.debugMessage += `t${this.debugTraceCount}: ${this.iterations} iterations\n`
}

if (this.isRemovePathLoopsEnabled) {
route = removePathLoops(route)
}

return { solved: true, route, connectionName: connection.name }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ export class IJumpMultiMarginAutorouter extends GeneralizedAstarAutorouter {
enterCost: 0,
travelCostFactor: 1,
},
// {
// margin: 0.25,
// enterCost: 5,
// travelCostFactor: 1.5,
// },
{
margin: 0.15,
enterCost: 10,
Expand Down
95 changes: 95 additions & 0 deletions module/lib/solver-postprocessing/remove-path-loops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
interface Point {
x: number
y: number
}

export function removePathLoops<T extends Point>(path: T[]): T[] {
if (path.length < 4) return path // No loops possible with less than 4 points

const result: Point[] = [path[0]]

for (let i = 1; i < path.length; i++) {
const currentSegment = { start: path[i - 1], end: path[i] }

let intersectionFound = false
let intersectionPoint: Point | null = null
let intersectionIndex = -1

// Check for intersections with all previous segments
for (let j = 0; j < result.length - 1; j++) {
const previousSegment = { start: result[j], end: result[j + 1] }
const intersection = findIntersection(previousSegment, currentSegment)

if (intersection) {
intersectionFound = true
intersectionPoint = intersection
intersectionIndex = j
break
}
}

if (intersectionFound && intersectionPoint) {
// Remove the loop
result.splice(intersectionIndex + 1)
// Add the intersection point
result.push(intersectionPoint)
}

// Add the current point if it's not the same as the last point in result
const lastPoint = result[result.length - 1]
if (lastPoint.x !== path[i].x || lastPoint.y !== path[i].y) {
result.push(path[i])
}
}

return result as T[]
}

function findIntersection(
segment1: { start: Point; end: Point },
segment2: { start: Point; end: Point },
): Point | null {
// Check if segments are parallel
if (
(segment1.start.x === segment1.end.x &&
segment2.start.x === segment2.end.x) ||
(segment1.start.y === segment1.end.y && segment2.start.y === segment2.end.y)
) {
return null
}

// Find intersection point
let intersectionPoint: Point

if (segment1.start.x === segment1.end.x) {
const x = segment1.start.x
const y = segment2.start.y
intersectionPoint = { ...segment1.start, x, y }
} else {
const x = segment2.start.x
const y = segment1.start.y
intersectionPoint = { ...segment1.start, x, y }
}

// Check if intersection point is within both segments
if (
isPointInSegment(intersectionPoint, segment1) &&
isPointInSegment(intersectionPoint, segment2)
) {
return intersectionPoint
}

return null
}

function isPointInSegment(
point: Point,
segment: { start: Point; end: Point },
): boolean {
return (
point.x >= Math.min(segment.start.x, segment.end.x) &&
point.x <= Math.max(segment.start.x, segment.end.x) &&
point.y >= Math.min(segment.start.y, segment.end.y) &&
point.y <= Math.max(segment.start.y, segment.end.y)
)
}
23 changes: 23 additions & 0 deletions module/tests/solver-postprocessing/remove-path-loops.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test, expect } from "bun:test"
import { removePathLoops } from "../../lib/solver-postprocessing/remove-path-loops"
import type { Point } from "autorouting-dataset/lib/types"

test("removePathLoops removes loops from path", () => {
const path: Point[] = [
{ x: 0, y: 0 },
{ x: 0, y: 5 },
{ x: -3, y: 5 },
{ x: -3, y: 3 },
{ x: 3, y: 3 },
{ x: 3, y: 6 },
]

const shortenedPath = removePathLoops(path)

expect(shortenedPath).toEqual([
{ x: 0, y: 0 },
{ x: 0, y: 3 },
{ x: 3, y: 3 },
{ x: 3, y: 6 },
])
})
Loading