Skip to content
Open
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
89 changes: 77 additions & 12 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export interface TinyHyperGraphWorkingState {

export interface TinyHyperGraphSolverOptions {
DISTANCE_TO_COST?: number
NON_CENTER_COST_PER_MM?: number
RIP_THRESHOLD_START?: number
RIP_THRESHOLD_END?: number
RIP_THRESHOLD_RAMP_ATTEMPTS?: number
Expand All @@ -160,6 +161,7 @@ export interface TinyHyperGraphSolverOptions {

export interface TinyHyperGraphSolverOptionTarget {
DISTANCE_TO_COST: number
NON_CENTER_COST_PER_MM: number
RIP_THRESHOLD_START: number
RIP_THRESHOLD_END: number
RIP_THRESHOLD_RAMP_ATTEMPTS: number
Expand All @@ -178,6 +180,9 @@ export const applyTinyHyperGraphSolverOptions = (
if (options.DISTANCE_TO_COST !== undefined) {
solver.DISTANCE_TO_COST = options.DISTANCE_TO_COST
}
if (options.NON_CENTER_COST_PER_MM !== undefined) {
solver.NON_CENTER_COST_PER_MM = options.NON_CENTER_COST_PER_MM
}
if (options.RIP_THRESHOLD_START !== undefined) {
solver.RIP_THRESHOLD_START = options.RIP_THRESHOLD_START
}
Expand All @@ -200,6 +205,7 @@ export const getTinyHyperGraphSolverOptions = (
solver: TinyHyperGraphSolverOptionTarget,
): TinyHyperGraphSolverOptions => ({
DISTANCE_TO_COST: solver.DISTANCE_TO_COST,
NON_CENTER_COST_PER_MM: solver.NON_CENTER_COST_PER_MM,
RIP_THRESHOLD_START: solver.RIP_THRESHOLD_START,
RIP_THRESHOLD_END: solver.RIP_THRESHOLD_END,
RIP_THRESHOLD_RAMP_ATTEMPTS: solver.RIP_THRESHOLD_RAMP_ATTEMPTS,
Expand All @@ -217,6 +223,8 @@ interface SegmentGeometryScratch {
entryExitLayerChanges: number
}

export const DEFAULT_NON_CENTER_COST_PER_MM = 0.31

export class TinyHyperGraphSolver extends BaseSolver {
state: TinyHyperGraphWorkingState
private _problemSetup?: TinyHyperGraphProblemSetup
Expand All @@ -228,6 +236,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
}

DISTANCE_TO_COST = 0.05 // 50mm = 1 cost unit (1 cost unit ~ 100% chance of failure)
NON_CENTER_COST_PER_MM = DEFAULT_NON_CENTER_COST_PER_MM

RIP_THRESHOLD_START = 0.05
RIP_THRESHOLD_END = 0.8
Expand Down Expand Up @@ -515,25 +524,79 @@ export class TinyHyperGraphSolver extends BaseSolver {
return isKnownSingleLayerMask(regionAvailableZMask)
}

getPortAngleForRegion(regionId: RegionId, portId: PortId): number {
const incidentRegions = this.topology.incidentPortRegion[portId] ?? []

if (incidentRegions[0] === regionId || incidentRegions[1] !== regionId) {
return this.topology.portAngleForRegion1[portId]
}

return (
this.topology.portAngleForRegion2?.[portId] ??
this.topology.portAngleForRegion1[portId]
)
}

computePortNonCenterDistance(regionId: RegionId, portId: PortId): number {
const angle = this.getPortAngleForRegion(regionId, portId)

if (angle <= 9000) {
return (Math.abs(angle - 4500) * this.topology.regionHeight[regionId]) / 9000
}

if (angle <= 18000) {
return (Math.abs(angle - 13500) * this.topology.regionWidth[regionId]) / 9000
}

if (angle <= 27000) {
return (Math.abs(angle - 22500) * this.topology.regionHeight[regionId]) / 9000
}

return (Math.abs(angle - 31500) * this.topology.regionWidth[regionId]) / 9000
}

computePortNonCenterPenalty(regionId: RegionId, portId: PortId): number {
if (this.NON_CENTER_COST_PER_MM <= 0) {
return 0
}

return (
this.computePortNonCenterDistance(regionId, portId) *
this.NON_CENTER_COST_PER_MM
)
}

computeBestPortNonCenterPenalty(portId: PortId): number {
if (this.NON_CENTER_COST_PER_MM <= 0) {
return 0
}

const incidentRegions = this.topology.incidentPortRegion[portId] ?? []
let bestPenalty = Number.POSITIVE_INFINITY

for (const regionId of incidentRegions) {
if (regionId === undefined || regionId < 0) {
continue
}

bestPenalty = Math.min(
bestPenalty,
this.computePortNonCenterPenalty(regionId, portId),
)
}

return Number.isFinite(bestPenalty) ? bestPenalty : 0
}

populateSegmentGeometryScratch(
regionId: RegionId,
port1Id: PortId,
port2Id: PortId,
): SegmentGeometryScratch {
const { topology } = this
const scratch = this.segmentGeometryScratch
const port1IncidentRegions = topology.incidentPortRegion[port1Id]
const port2IncidentRegions = topology.incidentPortRegion[port2Id]
const angle1 =
port1IncidentRegions[0] === regionId || port1IncidentRegions[1] !== regionId
? topology.portAngleForRegion1[port1Id]
: topology.portAngleForRegion2?.[port1Id] ??
topology.portAngleForRegion1[port1Id]
const angle2 =
port2IncidentRegions[0] === regionId || port2IncidentRegions[1] !== regionId
? topology.portAngleForRegion1[port2Id]
: topology.portAngleForRegion2?.[port2Id] ??
topology.portAngleForRegion1[port2Id]
const angle1 = this.getPortAngleForRegion(regionId, port1Id)
const angle2 = this.getPortAngleForRegion(regionId, port2Id)
const z1 = topology.portZ[port1Id]
const z2 = topology.portZ[port2Id]
scratch.lesserAngle = angle1 < angle2 ? angle1 : angle2
Expand Down Expand Up @@ -817,10 +880,12 @@ export class TinyHyperGraphSolver extends BaseSolver {
regionCache.existingSegmentCount + 1,
topology.regionAvailableZMask?.[nextRegionId] ?? 0,
) - regionCache.existingRegionCost
const nonCenterPenalty = this.computeBestPortNonCenterPenalty(neighborPortId)

return (
currentCandidate.g +
newRegionCost +
nonCenterPenalty +
state.regionCongestionCost[nextRegionId]
)
}
Expand Down
4 changes: 3 additions & 1 deletion lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
TinyHyperGraphSolverOptions,
TinyHyperGraphTopology,
} from "../core"
import { TinyHyperGraphSolver } from "../core"
import { DEFAULT_NON_CENTER_COST_PER_MM, TinyHyperGraphSolver } from "../core"
import type { RegionId } from "../types"
import type { TinyHyperGraphSectionSolverOptions } from "./index"
import { getActiveSectionRouteIds, TinyHyperGraphSectionSolver } from "./index"
Expand Down Expand Up @@ -59,11 +59,13 @@ type AutomaticSectionSearchResult = {
}

const DEFAULT_SOLVE_GRAPH_OPTIONS: TinyHyperGraphSolverOptions = {
NON_CENTER_COST_PER_MM: DEFAULT_NON_CENTER_COST_PER_MM,
RIP_THRESHOLD_RAMP_ATTEMPTS: 5,
}

const DEFAULT_SECTION_SOLVER_OPTIONS: TinyHyperGraphSectionSolverOptions = {
DISTANCE_TO_COST: 0.05,
NON_CENTER_COST_PER_MM: DEFAULT_NON_CENTER_COST_PER_MM,
RIP_THRESHOLD_RAMP_ATTEMPTS: 16,
RIP_CONGESTION_REGION_COST_FACTOR: 0.1,
MAX_ITERATIONS: 1e6,
Expand Down
2 changes: 2 additions & 0 deletions lib/section-solver/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseSolver } from "@tscircuit/solver-utils"
import type { GraphicsObject } from "graphics-debug"
import {
DEFAULT_NON_CENTER_COST_PER_MM,
applyTinyHyperGraphSolverOptions,
createEmptyRegionIntersectionCache,
getTinyHyperGraphSolverOptions,
Expand Down Expand Up @@ -857,6 +858,7 @@ export class TinyHyperGraphSectionSolver extends BaseSolver {
activeRouteIds: RouteId[] = []

DISTANCE_TO_COST = 0.05
NON_CENTER_COST_PER_MM = DEFAULT_NON_CENTER_COST_PER_MM

RIP_THRESHOLD_START = 0.05
RIP_THRESHOLD_END = 0.8
Expand Down
9 changes: 8 additions & 1 deletion scripts/benchmarking/hg07-section-benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SerializedHyperGraph } from "@tscircuit/hypergraph"
import * as datasetHg07 from "dataset-hg07"
import { loadSerializedHyperGraph } from "../../lib/compat/loadSerializedHyperGraph"
import {
DEFAULT_NON_CENTER_COST_PER_MM,
TinyHyperGraphSectionSolver,
TinyHyperGraphSolver,
type TinyHyperGraphProblem,
Expand Down Expand Up @@ -83,6 +84,7 @@ export type SectionSolverBenchmarkConfig = {
improvementEpsilon: number
sectionSolver: {
distanceToCost: number
nonCenterCostPerMm: number
ripThresholdStart: number
ripThresholdEnd: number
ripThresholdRampAttempts: number
Expand Down Expand Up @@ -162,6 +164,7 @@ export const legacySectionSolverBenchmarkConfig: SectionSolverBenchmarkConfig =
improvementEpsilon: 1e-9,
sectionSolver: {
distanceToCost: 0.05,
nonCenterCostPerMm: DEFAULT_NON_CENTER_COST_PER_MM,
ripThresholdStart: 0.05,
ripThresholdEnd: 0.8,
ripThresholdRampAttempts: 50,
Expand Down Expand Up @@ -406,6 +409,8 @@ const applySectionSolverConfig = (
config: SectionSolverBenchmarkConfig,
) => {
sectionSolver.DISTANCE_TO_COST = config.sectionSolver.distanceToCost
sectionSolver.NON_CENTER_COST_PER_MM =
config.sectionSolver.nonCenterCostPerMm
sectionSolver.RIP_THRESHOLD_START = config.sectionSolver.ripThresholdStart
sectionSolver.RIP_THRESHOLD_END = config.sectionSolver.ripThresholdEnd
sectionSolver.RIP_THRESHOLD_RAMP_ATTEMPTS =
Expand All @@ -427,7 +432,9 @@ const runBestSectionOptimizationPass = (
): SectionPassResult => {
const solveGraphStartTime = performance.now()
const { topology, problem } = loadSerializedHyperGraph(serializedHyperGraph)
const solveGraphSolver = new TinyHyperGraphSolver(topology, problem)
const solveGraphSolver = new TinyHyperGraphSolver(topology, problem, {
NON_CENTER_COST_PER_MM: config.sectionSolver.nonCenterCostPerMm,
})
solveGraphSolver.solve()
profiling.totalSolveGraphMs += performance.now() - solveGraphStartTime

Expand Down
76 changes: 76 additions & 0 deletions tests/solver/non-center-cost.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { expect, test } from "bun:test"
import { TinyHyperGraphSolver, type TinyHyperGraphProblem, type TinyHyperGraphTopology } from "lib/index"

const createNonCenterTestSolver = (
options?: ConstructorParameters<typeof TinyHyperGraphSolver>[2],
) => {
const topology: TinyHyperGraphTopology = {
portCount: 4,
regionCount: 2,
regionIncidentPorts: [
[0, 1, 2, 3],
[0, 1, 2, 3],
],
incidentPortRegion: [
[0, 1],
[0, 1],
[0, 1],
[1, 0],
],
regionWidth: new Float64Array([6, 6]),
regionHeight: new Float64Array([10, 10]),
regionCenterX: new Float64Array([0, 0]),
regionCenterY: new Float64Array([0, 0]),
portAngleForRegion1: new Int32Array([4500, 0, 13500, 0]),
portAngleForRegion2: new Int32Array([4500, 0, 13500, 18000]),
portX: new Float64Array(4),
portY: new Float64Array(4),
portZ: new Int32Array(4),
}

topology.portAngleForRegion1[3] = 4500

const problem: TinyHyperGraphProblem = {
routeCount: 1,
portSectionMask: new Int8Array([1, 1, 1, 1]),
routeStartPort: new Int32Array([0]),
routeEndPort: new Int32Array([1]),
routeNet: new Int32Array([0]),
regionNetId: new Int32Array(2).fill(-1),
}

return new TinyHyperGraphSolver(topology, problem, options)
}

test("non-center penalty is based on distance from the middle of the touched side", () => {
const solver = createNonCenterTestSolver({
NON_CENTER_COST_PER_MM: 0.2,
})

expect(solver.computePortNonCenterPenalty(0, 0)).toBeCloseTo(0, 6)
expect(solver.computePortNonCenterPenalty(0, 1)).toBeCloseTo(1, 6)
expect(solver.computePortNonCenterPenalty(0, 2)).toBeCloseTo(0, 6)
expect(solver.computePortNonCenterPenalty(0, 3)).toBeCloseTo(0.6, 6)
expect(solver.computeBestPortNonCenterPenalty(3)).toBeCloseTo(0, 6)
})

test("computeG uses the lower non-center penalty across both incident regions", () => {
const solver = createNonCenterTestSolver({
NON_CENTER_COST_PER_MM: 0.2,
})

solver.state.currentRouteId = 0
solver.state.currentRouteNetId = 0

const currentCandidate = {
portId: 0,
nextRegionId: 0,
g: 0,
h: 0,
f: 0,
}

expect(solver.computeG(currentCandidate, 2)).toBeCloseTo(0, 6)
expect(solver.computeG(currentCandidate, 1)).toBeCloseTo(1, 6)
expect(solver.computeG(currentCandidate, 3)).toBeCloseTo(0, 6)
})
2 changes: 2 additions & 0 deletions tests/solver/on-all-routes-routed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ test("completed routing is accepted once all region costs are under the threshol
test("constructor options override snake-case hyperparameters before setup", () => {
const solver = createTestSolver({
DISTANCE_TO_COST: 0.25,
NON_CENTER_COST_PER_MM: 0.125,
RIP_THRESHOLD_START: 0.12,
RIP_THRESHOLD_END: 0.34,
RIP_THRESHOLD_RAMP_ATTEMPTS: 7,
Expand All @@ -150,6 +151,7 @@ test("constructor options override snake-case hyperparameters before setup", ()
})

expect(solver.DISTANCE_TO_COST).toBe(0.25)
expect(solver.NON_CENTER_COST_PER_MM).toBe(0.125)
expect(solver.RIP_THRESHOLD_START).toBe(0.12)
expect(solver.RIP_THRESHOLD_END).toBe(0.34)
expect(solver.RIP_THRESHOLD_RAMP_ATTEMPTS).toBe(7)
Expand Down
3 changes: 3 additions & 0 deletions tests/solver/section-solver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ test("section solver enforces section-specific rip thresholds and max rip cap",
solution,
{
DISTANCE_TO_COST: 0.2,
NON_CENTER_COST_PER_MM: 0.07,
RIP_THRESHOLD_START: 0.11,
RIP_THRESHOLD_END: 0.22,
RIP_THRESHOLD_RAMP_ATTEMPTS: 9,
Expand All @@ -148,7 +149,9 @@ test("section solver enforces section-specific rip thresholds and max rip cap",
sectionSolver.setup()

expect(sectionSolver.DISTANCE_TO_COST).toBe(0.2)
expect(sectionSolver.NON_CENTER_COST_PER_MM).toBe(0.07)
expect(sectionSolver.sectionSolver?.DISTANCE_TO_COST).toBe(0.2)
expect(sectionSolver.sectionSolver?.NON_CENTER_COST_PER_MM).toBe(0.07)
expect(sectionSolver.RIP_THRESHOLD_START).toBe(0.05)
expect(sectionSolver.sectionSolver?.RIP_THRESHOLD_START).toBe(0.05)
expect(sectionSolver.RIP_THRESHOLD_END).toBe(
Expand Down
Loading