Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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: 0 additions & 1 deletion lib/RectDiffPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
}

override initialVisualize(): GraphicsObject {
console.log("RectDiffPipeline - initialVisualize")
const graphics = createBaseVisualization(
this.inputProblem.simpleRouteJson,
"RectDiffPipeline - Initial",
Expand Down
20 changes: 2 additions & 18 deletions lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
import { allLayerNode } from "../../utils/buildHardPlacedByLayer"
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
import { getColorForZLayer } from "lib/utils/getColorForZLayer"

export type RectDiffSeedingSolverInput = {
simpleRouteJson: SimpleRouteJson
Expand Down Expand Up @@ -365,23 +366,6 @@ export class RectDiffSeedingSolver extends BaseSolver {
}
}

/** Get color based on z layer for visualization. */
private getColorForZLayer(zLayers: number[]): {
fill: string
stroke: string
} {
const minZ = Math.min(...zLayers)
const colors = [
{ fill: "#dbeafe", stroke: "#3b82f6" },
{ fill: "#fef3c7", stroke: "#f59e0b" },
{ fill: "#d1fae5", stroke: "#10b981" },
{ fill: "#e9d5ff", stroke: "#a855f7" },
{ fill: "#fed7aa", stroke: "#f97316" },
{ fill: "#fecaca", stroke: "#ef4444" },
] as const
return colors[minZ % colors.length]!
}

/** Visualization focused on the grid seeding phase. */
override visualize(): GraphicsObject {
const rects: NonNullable<GraphicsObject["rects"]> = []
Expand Down Expand Up @@ -489,7 +473,7 @@ export class RectDiffSeedingSolver extends BaseSolver {
// current placements (streaming) during grid fill
if (this.placed?.length) {
for (const placement of this.placed) {
const colors = this.getColorForZLayer(placement.zLayers)
const colors = getColorForZLayer(placement.zLayers)
rects.push({
center: {
x: placement.rect.x + placement.rect.width / 2,
Expand Down
17 changes: 17 additions & 0 deletions lib/utils/getColorForZLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const getColorForZLayer = (
zLayers: number[],
): {
fill: string
stroke: string
} => {
const minZ = Math.min(...zLayers)
const colors = [
{ fill: "#dbeafe", stroke: "#3b82f6" },
{ fill: "#fef3c7", stroke: "#f59e0b" },
{ fill: "#d1fae5", stroke: "#10b981" },
{ fill: "#e9d5ff", stroke: "#a855f7" },
{ fill: "#fed7aa", stroke: "#f97316" },
{ fill: "#fecaca", stroke: "#ef4444" },
] as const
return colors[minZ % colors.length]!
}
130 changes: 130 additions & 0 deletions tests/fixtures/getPerLayerVisualizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { GraphicsObject, Line, Point, Rect } from "graphics-debug"

export function getPerLayerVisualizations(
graphics: GraphicsObject,
): Map<string, GraphicsObject> {
const rects = (graphics.rects ?? []) as NonNullable<Rect[]>
const lines = (graphics.lines ?? []) as NonNullable<Line[]>
const points = (graphics.points ?? []) as NonNullable<Point[]>

const zValues = new Set<number>()

const addZValuesFromLayer = (layer: string) => {
if (!layer.startsWith("z")) return
const rest = layer.slice(1)
if (!rest) return
for (const part of rest.split(",")) {
const value = Number.parseInt(part, 10)
if (!Number.isNaN(value)) zValues.add(value)
}
}

for (const rect of rects) addZValuesFromLayer(rect.layer!)
for (const line of lines) addZValuesFromLayer(line.layer!)
for (const point of points) addZValuesFromLayer(point.layer!)

const result = new Map<string, GraphicsObject>()
if (!zValues.size) return result

const sortedZ = Array.from(zValues).sort((a, b) => a - b)

const commonRects: NonNullable<Rect[]> = []
const perLayerRects: { layers: number[]; rect: Rect }[] = []

for (const rect of rects) {
const layer = rect.layer!
if (layer.startsWith("z")) {
const rest = layer.slice(1)
if (rest) {
const layers = rest
.split(",")
.map((part) => Number.parseInt(part, 10))
.filter((value) => !Number.isNaN(value))
if (layers.length) {
perLayerRects.push({ layers, rect })
continue
}
}
}
commonRects.push(rect)
}

const commonLines: NonNullable<Line[]> = []
const perLayerLines: { layers: number[]; line: Line }[] = []

for (const line of lines) {
const layer = line.layer!
if (layer.startsWith("z")) {
const rest = layer.slice(1)
if (rest) {
const layers = rest
.split(",")
.map((part) => Number.parseInt(part, 10))
.filter((value) => !Number.isNaN(value))
if (layers.length) {
perLayerLines.push({ layers, line })
continue
}
}
}
commonLines.push(line)
}

const commonPoints: NonNullable<Point[]> = []
const perLayerPoints: { layers: number[]; point: Point }[] = []

for (const point of points) {
const layer = point.layer!
if (layer.startsWith("z")) {
const rest = layer.slice(1)
if (rest) {
const layers = rest
.split(",")
.map((part) => Number.parseInt(part, 10))
.filter((value) => !Number.isNaN(value))
if (layers.length) {
perLayerPoints.push({ layers, point })
continue
}
}
}
commonPoints.push(point)
}

const allCombos: number[][] = [[]]
for (const z of sortedZ) {
const withZ = allCombos.map((combo) => [...combo, z])
allCombos.push(...withZ)
}

for (const combo of allCombos.filter((c) => c.length > 0)) {
const key = `z${combo.join(",")}`

const layerRects: NonNullable<Rect[]> = [...commonRects]
const layerLines: NonNullable<Line[]> = [...commonLines]
const layerPoints: NonNullable<Point[]> = [...commonPoints]

const intersects = (layers: number[]) =>
layers.some((layer) => combo.includes(layer))

for (const { layers, rect } of perLayerRects) {
if (intersects(layers)) layerRects.push(rect)
}
for (const { layers, line } of perLayerLines) {
if (intersects(layers)) layerLines.push(line)
}
for (const { layers, point } of perLayerPoints) {
if (intersects(layers)) layerPoints.push(point)
}

result.set(key, {
title: `${graphics.title ?? ""} - z${combo.join(",")}`,
coordinateSystem: graphics.coordinateSystem,
rects: layerRects,
lines: layerLines,
points: layerPoints,
})
}

return result
}
35 changes: 35 additions & 0 deletions tests/fixtures/makeCapqcityNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Rect } from "graphics-debug"
import type { RectDiffPipeline } from "lib/RectDiffPipeline"
import { getColorForZLayer } from "lib/utils/getColorForZLayer"

type MeshNodes = ReturnType<RectDiffPipeline["getOutput"]>["meshNodes"]

export const makeCapacityMeshNodeWithLayerInfo = (
nodes: MeshNodes,
): Map<string, Rect[]> => {
const map = new Map<string, Rect[]>()

for (const node of nodes) {
if (!node.availableZ.length) continue
const key = node.availableZ.join(",")
const colors = getColorForZLayer(node.availableZ)
const rect: Rect = {
center: node.center,
width: node.width,
height: node.height,
layer: `z${key}`,
stroke: "black",
fill: node._containsObstacle ? "red" : colors.fill,
label: "node",
}

const existing = map.get(key)
if (existing) {
existing.push(rect)
} else {
map.set(key, [rect])
}
}

return map
}
44 changes: 44 additions & 0 deletions tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions tests/solver/rectDiffGridSolverPipeline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { expect, test } from "bun:test"
import srj from "test-assets/bugreport11-b2de3c.json"
import {
getSvgFromGraphicsObject,
stackGraphicsVertically,
type GraphicsObject,
type Rect,
} from "graphics-debug"
import { RectDiffPipeline } from "lib/RectDiffPipeline"
import { makeCapacityMeshNodeWithLayerInfo } from "tests/fixtures/makeCapqcityNode"

test("RectDiffPipeline mesh layer snapshots", async () => {
const solver = new RectDiffPipeline({
simpleRouteJson: srj.simple_route_json,
})

solver.solve()

const { meshNodes } = solver.getOutput()
const rectsByCombo = makeCapacityMeshNodeWithLayerInfo(meshNodes)
const allGraphicsObjects: GraphicsObject[] = []

// Generate a snapshot for each z-layer
for (const z of [0, 1, 2, 3]) {
const layerRects: Rect[] = []

for (const [key, rects] of rectsByCombo) {
const layers = key
.split(",")
.map((value) => Number.parseInt(value, 10))
.filter((value) => !Number.isNaN(value))

if (layers.includes(z)) {
layerRects.push(...rects)
}
}

let labelY = 0

if (layerRects.length > 0) {
let maxY = -Infinity

for (const rect of layerRects) {
const top = rect.center.y + rect.height

if (top > maxY) maxY = top
}

labelY = maxY
}

const graphics: GraphicsObject = {
title: `RectDiffPipeline - z${z}`,
texts: [
{
anchorSide: "top_right",
text: `Layer z=${z}`,
x: 0,
y: labelY,
fontSize: 1,
},
],
coordinateSystem: "cartesian",
rects: layerRects,
points: [],
lines: [],
}

allGraphicsObjects.push(graphics)
}

const svg = getSvgFromGraphicsObject(
stackGraphicsVertically(allGraphicsObjects),
)
await expect(svg).toMatchSvgSnapshot(import.meta.path)
})