From 32546adb878d495a766980baf754c55516de2529 Mon Sep 17 00:00:00 2001 From: manbir Date: Mon, 15 Sep 2025 13:51:16 +0530 Subject: [PATCH 1/3] Added VoltageBiasSolver and OverlapResolutionSolver to pipeline and added a test for bias and overlap resolution --- .../LayoutPipelineSolver.ts | 28 ++++++- .../OverlapResolutionSolver.ts | 74 +++++++++++++++++++ .../LayoutPipelineSolver/VoltageBiasSolver.ts | 67 +++++++++++++++++ ...youtPipelineSolverTestIntegration.page.tsx | 20 +++++ 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts create mode 100644 lib/solvers/LayoutPipelineSolver/VoltageBiasSolver.ts create mode 100644 tests/LayoutPipelineSolver/LayoutPipelineSolverTestIntegration.page.tsx diff --git a/lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver.ts b/lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver.ts index 33c7dd2..288d119 100644 --- a/lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver.ts +++ b/lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver.ts @@ -53,7 +53,8 @@ export class LayoutPipelineSolver extends BaseSolver { chipPartitionsSolver?: ChipPartitionsSolver packInnerPartitionsSolver?: PackInnerPartitionsSolver partitionPackingSolver?: PartitionPackingSolver - + voltageBiasSolver?: any + overlapResolutionSolver?: any startTimeOfPhase: Record endTimeOfPhase: Record timeSpentOnPhase: Record @@ -93,6 +94,17 @@ export class LayoutPipelineSolver extends BaseSolver { }, }, ), + // Voltage bias phase + definePipelineStep( + "voltageBiasSolver", + require("./VoltageBiasSolver").VoltageBiasSolver, + () => [this.inputProblem], + { + onSolved: (_solver) => { + // Optionally store voltage-biased layout + }, + }, + ), definePipelineStep( "packInnerPartitionsSolver", PackInnerPartitionsSolver, @@ -124,6 +136,20 @@ export class LayoutPipelineSolver extends BaseSolver { }, }, ), + // Overlap resolution phase + definePipelineStep( + "overlapResolutionSolver", + require("./OverlapResolutionSolver").OverlapResolutionSolver, + () => [ + this.partitionPackingSolver?.finalLayout, + this.inputProblem.chipMap, + ], + { + onSolved: (_solver) => { + // Optionally store resolved layout + }, + }, + ), ] constructor(inputProblem: InputProblem) { diff --git a/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts b/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts new file mode 100644 index 0000000..7371564 --- /dev/null +++ b/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts @@ -0,0 +1,74 @@ +import { BaseSolver } from "../BaseSolver" +import type { OutputLayout } from "../../types/OutputLayout" + +/** + * OverlapResolutionSolver nudges overlapping chips apart for clarity. + */ +export class OverlapResolutionSolver extends BaseSolver { + inputLayout: OutputLayout + chipMap?: Record + outputLayout: OutputLayout | null = null + override solved = false + + constructor( + inputLayout: OutputLayout, + chipMap?: Record, + ) { + super() + this.inputLayout = inputLayout + this.chipMap = chipMap + } + + override _step() { + // Iteratively resolve overlaps until none remain or max iterations reached + const placements = { ...this.inputLayout.chipPlacements } + const chipIds = Object.keys(placements) + const minGap = 0.2 + const maxIterations = 100 + let iteration = 0 + let moved = false + + do { + moved = false + for (let i = 0; i < chipIds.length; i++) { + for (let j = i + 1; j < chipIds.length; j++) { + const chipIdA = chipIds[i] + const chipIdB = chipIds[j] + if (chipIdA === undefined || chipIdB === undefined) continue + const a = placements[chipIdA] + const b = placements[chipIdB] + if (!a || !b) continue + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 } + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 } + // Check overlap (bounding box) + if ( + Math.abs(a.x - b.x) < (sizeA.x + sizeB.x) / 2 + minGap && + Math.abs(a.y - b.y) < (sizeA.y + sizeB.y) / 2 + minGap + ) { + // Nudge apart + const dx = a.x - b.x + const dy = a.y - b.y + const dist = Math.sqrt(dx * dx + dy * dy) || 1 + const push = ((sizeA.x + sizeB.x) / 2 + minGap - dist) / 2 + a.x += (dx / dist) * push + b.x -= (dx / dist) * push + a.y += (dy / dist) * push + b.y -= (dy / dist) * push + moved = true + } + } + } + iteration++ + } while (moved && iteration < maxIterations) + + this.outputLayout = { + ...this.inputLayout, + chipPlacements: placements, + } + this.solved = true + } + + getOutputLayout(): OutputLayout | null { + return this.outputLayout + } +} diff --git a/lib/solvers/LayoutPipelineSolver/VoltageBiasSolver.ts b/lib/solvers/LayoutPipelineSolver/VoltageBiasSolver.ts new file mode 100644 index 0000000..2d2ca74 --- /dev/null +++ b/lib/solvers/LayoutPipelineSolver/VoltageBiasSolver.ts @@ -0,0 +1,67 @@ +import { BaseSolver } from "../BaseSolver" +import type { InputProblem } from "../../types/InputProblem" +import type { OutputLayout } from "../../types/OutputLayout" + +/** + * VoltageBiasSolver moves chips with VCC/V* connections upward in the layout. + */ +export class VoltageBiasSolver extends BaseSolver { + inputProblem: InputProblem + outputLayout: OutputLayout | null = null + override solved = false + + constructor(inputProblem: InputProblem) { + super() + this.inputProblem = inputProblem + } + + override _step() { + const chipMap = this.inputProblem.chipMap + const netMap = this.inputProblem.netMap + const chipPlacements: Record< + string, + { x: number; y: number; ccwRotationDegrees: number } + > = {} + + // Gather all netIds that are positive voltage sources + const positiveVoltageNetIds = Object.keys(netMap).filter( + (netId) => netMap[netId]?.isPositiveVoltageSource, + ) + + let yBase = 0 + let yStep = 2 + for (const chipId in chipMap) { + const chip = chipMap[chipId] + let hasVccPin = false + for (const pinId of chip?.pins ?? []) { + for (const netId of positiveVoltageNetIds) { + const net = netMap[netId] + // Check if this pin is part of the net's groupPins + if (Array.isArray((net as any).groupPins)) { + if ((net as any).groupPins.some((gp: any) => gp.pinId === pinId)) { + hasVccPin = true + break + } + } + } + if (hasVccPin) break + } + chipPlacements[chipId] = { + x: 0, + y: hasVccPin ? yBase + yStep : yBase, + ccwRotationDegrees: 0, + } + yBase += yStep + } + + this.outputLayout = { + chipPlacements, + groupPlacements: {}, + } + this.solved = true + } + + getOutputLayout(): OutputLayout | null { + return this.outputLayout + } +} diff --git a/tests/LayoutPipelineSolver/LayoutPipelineSolverTestIntegration.page.tsx b/tests/LayoutPipelineSolver/LayoutPipelineSolverTestIntegration.page.tsx new file mode 100644 index 0000000..a9d8b80 --- /dev/null +++ b/tests/LayoutPipelineSolver/LayoutPipelineSolverTestIntegration.page.tsx @@ -0,0 +1,20 @@ +import { LayoutPipelineDebugger } from "lib/components/LayoutPipelineDebugger" +import type { InputProblem } from "lib/types/InputProblem" +import { getExampleCircuitJson } from "../../tests/assets/ExampleCircuit01" +import { getInputProblemFromCircuitJsonSchematic } from "lib/testing/getInputProblemFromCircuitJsonSchematic" + +export default function LayoutPipelineSolverTestIntegrationPage() { + // Convert ExampleCircuit01 to InputProblem with readable IDs + const circuitJson = getExampleCircuitJson() + const problem: InputProblem = getInputProblemFromCircuitJsonSchematic( + circuitJson, + { useReadableIds: true }, + ) + + return ( + + ) +} From 27da7f63c23b1361570ec1bb69940b32d2a1985c Mon Sep 17 00:00:00 2001 From: MANBIR SINGH <163514635+ManbirS07@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:01:17 +0530 Subject: [PATCH 2/3] Update OverlapResolutionSolver.ts fix: robustly resolve all chip overlaps in OverlapResolutionSolver Deep clone chip placements to avoid mutation Iteratively push overlapping chips apart until no overlaps remain If overlaps persist, aggressively scatter chips and rerun resolution Throw error if overlaps remain after max attempts --- .../OverlapResolutionSolver.ts | 225 ++++++++++++++---- 1 file changed, 181 insertions(+), 44 deletions(-) diff --git a/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts b/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts index 7371564..659ffcb 100644 --- a/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts +++ b/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts @@ -5,68 +5,205 @@ import type { OutputLayout } from "../../types/OutputLayout" * OverlapResolutionSolver nudges overlapping chips apart for clarity. */ export class OverlapResolutionSolver extends BaseSolver { - inputLayout: OutputLayout - chipMap?: Record - outputLayout: OutputLayout | null = null - override solved = false + inputLayout: OutputLayout; + chipMap?: Record; + outputLayout: OutputLayout | null = null; + override solved = false; constructor( inputLayout: OutputLayout, - chipMap?: Record, + chipMap?: Record ) { - super() - this.inputLayout = inputLayout - this.chipMap = chipMap + super(); + // Deep copy placements to avoid mutation + this.inputLayout = { + ...inputLayout, + chipPlacements: JSON.parse(JSON.stringify(inputLayout.chipPlacements)) + }; + this.chipMap = chipMap; + } + + // Helper to get rotated bounding box (same as pipeline) + getRotatedBounds(placement: { x: number; y: number; ccwRotationDegrees: number }, size: { x: number; y: number }) { + const halfWidth = size.x / 2 + const halfHeight = size.y / 2 + const angleRad = (placement.ccwRotationDegrees * Math.PI) / 180 + const cos = Math.abs(Math.cos(angleRad)) + const sin = Math.abs(Math.sin(angleRad)) + const rotatedWidth = halfWidth * cos + halfHeight * sin + const rotatedHeight = halfWidth * sin + halfHeight * cos + return { + minX: placement.x - rotatedWidth, + maxX: placement.x + rotatedWidth, + minY: placement.y - rotatedHeight, + maxY: placement.y + rotatedHeight, + } + } + + // Helper to check overlap area (same as pipeline) + calculateOverlapArea(bounds1: { minX: number; maxX: number; minY: number; maxY: number }, bounds2: { minX: number; maxX: number; minY: number; maxY: number }) { + if ( + bounds1.maxX <= bounds2.minX || + bounds1.minX >= bounds2.maxX || + bounds1.maxY <= bounds2.minY || + bounds1.minY >= bounds2.maxY + ) { + return 0 + } + const overlapWidth = Math.min(bounds1.maxX, bounds2.maxX) - Math.max(bounds1.minX, bounds2.minX) + const overlapHeight = Math.min(bounds1.maxY, bounds2.maxY) - Math.max(bounds1.minY, bounds2.minY) + return overlapWidth * overlapHeight } override _step() { - // Iteratively resolve overlaps until none remain or max iterations reached - const placements = { ...this.inputLayout.chipPlacements } - const chipIds = Object.keys(placements) - const minGap = 0.2 - const maxIterations = 100 - let iteration = 0 - let moved = false + // Deep clone placements to avoid mutation + const placements: typeof this.inputLayout.chipPlacements = JSON.parse(JSON.stringify(this.inputLayout.chipPlacements)); + const chipIds = Object.keys(placements).filter((id): id is string => typeof id === 'string' && !!placements[id]); + const minGap = 0.2; + let maxIterations = 500; + let iteration = 0; + let moved = false; + // Main resolution loop do { - moved = false + moved = false; for (let i = 0; i < chipIds.length; i++) { for (let j = i + 1; j < chipIds.length; j++) { - const chipIdA = chipIds[i] - const chipIdB = chipIds[j] - if (chipIdA === undefined || chipIdB === undefined) continue - const a = placements[chipIdA] - const b = placements[chipIdB] - if (!a || !b) continue - const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 } - const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 } - // Check overlap (bounding box) - if ( - Math.abs(a.x - b.x) < (sizeA.x + sizeB.x) / 2 + minGap && - Math.abs(a.y - b.y) < (sizeA.y + sizeB.y) / 2 + minGap - ) { - // Nudge apart - const dx = a.x - b.x - const dy = a.y - b.y - const dist = Math.sqrt(dx * dx + dy * dy) || 1 - const push = ((sizeA.x + sizeB.x) / 2 + minGap - dist) / 2 - a.x += (dx / dist) * push - b.x -= (dx / dist) * push - a.y += (dy / dist) * push - b.y -= (dy / dist) * push - moved = true + const chipIdA = chipIds[i]; + const chipIdB = chipIds[j]; + if (!chipIdA || !chipIdB) continue; + const a = placements[chipIdA]; + const b = placements[chipIdB]; + if (!a || !b) continue; + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; + + const boundsA = this.getRotatedBounds(a, sizeA); + const boundsB = this.getRotatedBounds(b, sizeB); + const overlapArea = this.calculateOverlapArea(boundsA, boundsB); + + if (overlapArea > 0) { + let dx = a.x - b.x; + let dy = a.y - b.y; + let dist = Math.sqrt(dx * dx + dy * dy); + if (dist === 0) { + dx = (Math.random() - 0.5) * 0.1; + dy = (Math.random() - 0.5) * 0.1; + dist = Math.sqrt(dx * dx + dy * dy); + } + const push = ((sizeA.x + sizeB.x) / 2 + minGap - dist + Math.sqrt(overlapArea)) / 2; + a.x += (dx / dist) * push; + b.x -= (dx / dist) * push; + a.y += (dy / dist) * push; + b.y -= (dy / dist) * push; + moved = true; } } } - iteration++ - } while (moved && iteration < maxIterations) + iteration++; + } while (moved && iteration < maxIterations); + + // Check for unresolved overlaps + let unresolvedOverlaps = 0; + for (let i = 0; i < chipIds.length; i++) { + for (let j = i + 1; j < chipIds.length; j++) { + const chipIdA = chipIds[i]; + const chipIdB = chipIds[j]; + if (!chipIdA || !chipIdB) continue; + const a = placements[chipIdA]; + const b = placements[chipIdB]; + if (!a || !b) continue; + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; + const boundsA = this.getRotatedBounds(a, sizeA); + const boundsB = this.getRotatedBounds(b, sizeB); + const overlapArea = this.calculateOverlapArea(boundsA, boundsB); + if (overlapArea > 0) unresolvedOverlaps++; + } + } + + // Aggressive fallback: scatter and rerun resolution if needed + let scatterAttempts = 0; + const maxScatterAttempts = 5; + while (unresolvedOverlaps > 0 && scatterAttempts < maxScatterAttempts) { + // Scatter chips randomly within a larger radius + for (let i = 0; i < chipIds.length; i++) { + const chipId = chipIds[i]; + if (typeof chipId === "string") { + const chip = placements[chipId]; + if (chip) { + chip.x += (Math.random() - 0.5) * 2; + chip.y += (Math.random() - 0.5) * 2; + } + } + } + // Rerun resolution loop + iteration = 0; + do { + moved = false; + for (let i = 0; i < chipIds.length; i++) { + for (let j = i + 1; j < chipIds.length; j++) { + const chipIdA = chipIds[i]; + const chipIdB = chipIds[j]; + if (!chipIdA || !chipIdB) continue; + const a = placements[chipIdA]; + const b = placements[chipIdB]; + if (!a || !b) continue; + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; + const boundsA = this.getRotatedBounds(a, sizeA); + const boundsB = this.getRotatedBounds(b, sizeB); + const overlapArea = this.calculateOverlapArea(boundsA, boundsB); + if (overlapArea > 0) { + let dx = a.x - b.x; + let dy = a.y - b.y; + let dist = Math.sqrt(dx * dx + dy * dy); + if (dist === 0) { + dx = (Math.random() - 0.5) * 0.1; + dy = (Math.random() - 0.5) * 0.1; + dist = Math.sqrt(dx * dx + dy * dy); + } + const push = ((sizeA.x + sizeB.x) / 2 + minGap - dist + Math.sqrt(overlapArea)) / 2; + a.x += (dx / dist) * push; + b.x -= (dx / dist) * push; + a.y += (dy / dist) * push; + b.y -= (dy / dist) * push; + moved = true; + } + } + } + iteration++; + } while (moved && iteration < maxIterations); + // Check for remaining overlaps + unresolvedOverlaps = 0; + for (let i = 0; i < chipIds.length; i++) { + for (let j = i + 1; j < chipIds.length; j++) { + const chipIdA = chipIds[i]; + const chipIdB = chipIds[j]; + if (!chipIdA || !chipIdB) continue; + const a = placements[chipIdA]; + const b = placements[chipIdB]; + if (!a || !b) continue; + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; + const boundsA = this.getRotatedBounds(a, sizeA); + const boundsB = this.getRotatedBounds(b, sizeB); + const overlapArea = this.calculateOverlapArea(boundsA, boundsB); + if (overlapArea > 0) unresolvedOverlaps++; + } + } + scatterAttempts++; + } + if (unresolvedOverlaps > 0) { + throw new Error("Could not resolve all overlaps"); + } this.outputLayout = { ...this.inputLayout, chipPlacements: placements, - } - this.solved = true - } + }; + this.solved = true; +} getOutputLayout(): OutputLayout | null { return this.outputLayout From 26e4d6d9e74054cf4fd8dcd736168d424b6c1703 Mon Sep 17 00:00:00 2001 From: MANBIR SINGH <163514635+ManbirS07@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:06:12 +0530 Subject: [PATCH 3/3] Refactor OverlapResolutionSolver for cleaner code Ran npm run format to format the code according to the requirements --- .../OverlapResolutionSolver.ts | 242 ++++++++++-------- 1 file changed, 133 insertions(+), 109 deletions(-) diff --git a/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts b/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts index 659ffcb..ef6b39c 100644 --- a/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts +++ b/lib/solvers/LayoutPipelineSolver/OverlapResolutionSolver.ts @@ -5,26 +5,29 @@ import type { OutputLayout } from "../../types/OutputLayout" * OverlapResolutionSolver nudges overlapping chips apart for clarity. */ export class OverlapResolutionSolver extends BaseSolver { - inputLayout: OutputLayout; - chipMap?: Record; - outputLayout: OutputLayout | null = null; - override solved = false; + inputLayout: OutputLayout + chipMap?: Record + outputLayout: OutputLayout | null = null + override solved = false constructor( inputLayout: OutputLayout, - chipMap?: Record + chipMap?: Record, ) { - super(); + super() // Deep copy placements to avoid mutation this.inputLayout = { ...inputLayout, - chipPlacements: JSON.parse(JSON.stringify(inputLayout.chipPlacements)) - }; - this.chipMap = chipMap; + chipPlacements: JSON.parse(JSON.stringify(inputLayout.chipPlacements)), + } + this.chipMap = chipMap } // Helper to get rotated bounding box (same as pipeline) - getRotatedBounds(placement: { x: number; y: number; ccwRotationDegrees: number }, size: { x: number; y: number }) { + getRotatedBounds( + placement: { x: number; y: number; ccwRotationDegrees: number }, + size: { x: number; y: number }, + ) { const halfWidth = size.x / 2 const halfHeight = size.y / 2 const angleRad = (placement.ccwRotationDegrees * Math.PI) / 180 @@ -41,7 +44,10 @@ export class OverlapResolutionSolver extends BaseSolver { } // Helper to check overlap area (same as pipeline) - calculateOverlapArea(bounds1: { minX: number; maxX: number; minY: number; maxY: number }, bounds2: { minX: number; maxX: number; minY: number; maxY: number }) { + calculateOverlapArea( + bounds1: { minX: number; maxX: number; minY: number; maxY: number }, + bounds2: { minX: number; maxX: number; minY: number; maxY: number }, + ) { if ( bounds1.maxX <= bounds2.minX || bounds1.minX >= bounds2.maxX || @@ -50,160 +56,178 @@ export class OverlapResolutionSolver extends BaseSolver { ) { return 0 } - const overlapWidth = Math.min(bounds1.maxX, bounds2.maxX) - Math.max(bounds1.minX, bounds2.minX) - const overlapHeight = Math.min(bounds1.maxY, bounds2.maxY) - Math.max(bounds1.minY, bounds2.minY) + const overlapWidth = + Math.min(bounds1.maxX, bounds2.maxX) - + Math.max(bounds1.minX, bounds2.minX) + const overlapHeight = + Math.min(bounds1.maxY, bounds2.maxY) - + Math.max(bounds1.minY, bounds2.minY) return overlapWidth * overlapHeight } override _step() { // Deep clone placements to avoid mutation - const placements: typeof this.inputLayout.chipPlacements = JSON.parse(JSON.stringify(this.inputLayout.chipPlacements)); - const chipIds = Object.keys(placements).filter((id): id is string => typeof id === 'string' && !!placements[id]); - const minGap = 0.2; - let maxIterations = 500; - let iteration = 0; - let moved = false; + const placements: typeof this.inputLayout.chipPlacements = JSON.parse( + JSON.stringify(this.inputLayout.chipPlacements), + ) + const chipIds = Object.keys(placements).filter( + (id): id is string => typeof id === "string" && !!placements[id], + ) + const minGap = 0.2 + let maxIterations = 500 + let iteration = 0 + let moved = false // Main resolution loop do { - moved = false; + moved = false for (let i = 0; i < chipIds.length; i++) { for (let j = i + 1; j < chipIds.length; j++) { - const chipIdA = chipIds[i]; - const chipIdB = chipIds[j]; - if (!chipIdA || !chipIdB) continue; - const a = placements[chipIdA]; - const b = placements[chipIdB]; - if (!a || !b) continue; - const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; - const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; + const chipIdA = chipIds[i] + const chipIdB = chipIds[j] + if (!chipIdA || !chipIdB) continue + const a = placements[chipIdA] + const b = placements[chipIdB] + if (!a || !b) continue + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 } + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 } - const boundsA = this.getRotatedBounds(a, sizeA); - const boundsB = this.getRotatedBounds(b, sizeB); - const overlapArea = this.calculateOverlapArea(boundsA, boundsB); + const boundsA = this.getRotatedBounds(a, sizeA) + const boundsB = this.getRotatedBounds(b, sizeB) + const overlapArea = this.calculateOverlapArea(boundsA, boundsB) if (overlapArea > 0) { - let dx = a.x - b.x; - let dy = a.y - b.y; - let dist = Math.sqrt(dx * dx + dy * dy); + let dx = a.x - b.x + let dy = a.y - b.y + let dist = Math.sqrt(dx * dx + dy * dy) if (dist === 0) { - dx = (Math.random() - 0.5) * 0.1; - dy = (Math.random() - 0.5) * 0.1; - dist = Math.sqrt(dx * dx + dy * dy); + dx = (Math.random() - 0.5) * 0.1 + dy = (Math.random() - 0.5) * 0.1 + dist = Math.sqrt(dx * dx + dy * dy) } - const push = ((sizeA.x + sizeB.x) / 2 + minGap - dist + Math.sqrt(overlapArea)) / 2; - a.x += (dx / dist) * push; - b.x -= (dx / dist) * push; - a.y += (dy / dist) * push; - b.y -= (dy / dist) * push; - moved = true; + const push = + ((sizeA.x + sizeB.x) / 2 + + minGap - + dist + + Math.sqrt(overlapArea)) / + 2 + a.x += (dx / dist) * push + b.x -= (dx / dist) * push + a.y += (dy / dist) * push + b.y -= (dy / dist) * push + moved = true } } } - iteration++; - } while (moved && iteration < maxIterations); + iteration++ + } while (moved && iteration < maxIterations) // Check for unresolved overlaps - let unresolvedOverlaps = 0; + let unresolvedOverlaps = 0 for (let i = 0; i < chipIds.length; i++) { for (let j = i + 1; j < chipIds.length; j++) { - const chipIdA = chipIds[i]; - const chipIdB = chipIds[j]; - if (!chipIdA || !chipIdB) continue; - const a = placements[chipIdA]; - const b = placements[chipIdB]; - if (!a || !b) continue; - const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; - const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; - const boundsA = this.getRotatedBounds(a, sizeA); - const boundsB = this.getRotatedBounds(b, sizeB); - const overlapArea = this.calculateOverlapArea(boundsA, boundsB); - if (overlapArea > 0) unresolvedOverlaps++; + const chipIdA = chipIds[i] + const chipIdB = chipIds[j] + if (!chipIdA || !chipIdB) continue + const a = placements[chipIdA] + const b = placements[chipIdB] + if (!a || !b) continue + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 } + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 } + const boundsA = this.getRotatedBounds(a, sizeA) + const boundsB = this.getRotatedBounds(b, sizeB) + const overlapArea = this.calculateOverlapArea(boundsA, boundsB) + if (overlapArea > 0) unresolvedOverlaps++ } } // Aggressive fallback: scatter and rerun resolution if needed - let scatterAttempts = 0; - const maxScatterAttempts = 5; + let scatterAttempts = 0 + const maxScatterAttempts = 5 while (unresolvedOverlaps > 0 && scatterAttempts < maxScatterAttempts) { // Scatter chips randomly within a larger radius for (let i = 0; i < chipIds.length; i++) { - const chipId = chipIds[i]; + const chipId = chipIds[i] if (typeof chipId === "string") { - const chip = placements[chipId]; + const chip = placements[chipId] if (chip) { - chip.x += (Math.random() - 0.5) * 2; - chip.y += (Math.random() - 0.5) * 2; + chip.x += (Math.random() - 0.5) * 2 + chip.y += (Math.random() - 0.5) * 2 } } } // Rerun resolution loop - iteration = 0; + iteration = 0 do { - moved = false; + moved = false for (let i = 0; i < chipIds.length; i++) { for (let j = i + 1; j < chipIds.length; j++) { - const chipIdA = chipIds[i]; - const chipIdB = chipIds[j]; - if (!chipIdA || !chipIdB) continue; - const a = placements[chipIdA]; - const b = placements[chipIdB]; - if (!a || !b) continue; - const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; - const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; - const boundsA = this.getRotatedBounds(a, sizeA); - const boundsB = this.getRotatedBounds(b, sizeB); - const overlapArea = this.calculateOverlapArea(boundsA, boundsB); + const chipIdA = chipIds[i] + const chipIdB = chipIds[j] + if (!chipIdA || !chipIdB) continue + const a = placements[chipIdA] + const b = placements[chipIdB] + if (!a || !b) continue + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 } + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 } + const boundsA = this.getRotatedBounds(a, sizeA) + const boundsB = this.getRotatedBounds(b, sizeB) + const overlapArea = this.calculateOverlapArea(boundsA, boundsB) if (overlapArea > 0) { - let dx = a.x - b.x; - let dy = a.y - b.y; - let dist = Math.sqrt(dx * dx + dy * dy); + let dx = a.x - b.x + let dy = a.y - b.y + let dist = Math.sqrt(dx * dx + dy * dy) if (dist === 0) { - dx = (Math.random() - 0.5) * 0.1; - dy = (Math.random() - 0.5) * 0.1; - dist = Math.sqrt(dx * dx + dy * dy); + dx = (Math.random() - 0.5) * 0.1 + dy = (Math.random() - 0.5) * 0.1 + dist = Math.sqrt(dx * dx + dy * dy) } - const push = ((sizeA.x + sizeB.x) / 2 + minGap - dist + Math.sqrt(overlapArea)) / 2; - a.x += (dx / dist) * push; - b.x -= (dx / dist) * push; - a.y += (dy / dist) * push; - b.y -= (dy / dist) * push; - moved = true; + const push = + ((sizeA.x + sizeB.x) / 2 + + minGap - + dist + + Math.sqrt(overlapArea)) / + 2 + a.x += (dx / dist) * push + b.x -= (dx / dist) * push + a.y += (dy / dist) * push + b.y -= (dy / dist) * push + moved = true } } } - iteration++; - } while (moved && iteration < maxIterations); + iteration++ + } while (moved && iteration < maxIterations) // Check for remaining overlaps - unresolvedOverlaps = 0; + unresolvedOverlaps = 0 for (let i = 0; i < chipIds.length; i++) { for (let j = i + 1; j < chipIds.length; j++) { - const chipIdA = chipIds[i]; - const chipIdB = chipIds[j]; - if (!chipIdA || !chipIdB) continue; - const a = placements[chipIdA]; - const b = placements[chipIdB]; - if (!a || !b) continue; - const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 }; - const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 }; - const boundsA = this.getRotatedBounds(a, sizeA); - const boundsB = this.getRotatedBounds(b, sizeB); - const overlapArea = this.calculateOverlapArea(boundsA, boundsB); - if (overlapArea > 0) unresolvedOverlaps++; + const chipIdA = chipIds[i] + const chipIdB = chipIds[j] + if (!chipIdA || !chipIdB) continue + const a = placements[chipIdA] + const b = placements[chipIdB] + if (!a || !b) continue + const sizeA = this.chipMap?.[chipIdA]?.size || { x: 2, y: 2 } + const sizeB = this.chipMap?.[chipIdB]?.size || { x: 2, y: 2 } + const boundsA = this.getRotatedBounds(a, sizeA) + const boundsB = this.getRotatedBounds(b, sizeB) + const overlapArea = this.calculateOverlapArea(boundsA, boundsB) + if (overlapArea > 0) unresolvedOverlaps++ } } - scatterAttempts++; + scatterAttempts++ } if (unresolvedOverlaps > 0) { - throw new Error("Could not resolve all overlaps"); + throw new Error("Could not resolve all overlaps") } this.outputLayout = { ...this.inputLayout, chipPlacements: placements, - }; - this.solved = true; -} + } + this.solved = true + } getOutputLayout(): OutputLayout | null { return this.outputLayout