@tscircuit/solver-utils
v0.0.16
Published
Reusable building blocks for iterative solvers, multi-stage solver pipelines, and React-based solver debugging UIs.
Readme
@tscircuit/solver-utils
Reusable building blocks for iterative solvers, multi-stage solver pipelines, and React-based solver debugging UIs.
@tscircuit/solver-utils gives you:
BaseSolver: a safe base class for step-based solvers.BasePipelineSolver: orchestration for sequential solver stages.- React debugger components for stepping, animating, and inspecting solver state.
Table of Contents
- Install
- Quick Start
- Core Concepts
- BaseSolver Guide
- BasePipelineSolver Guide
- React Debugger Guide
- Testing Solvers
- API Reference
- Development
- Troubleshooting
Install
bun add @tscircuit/solver-utilsYou will also need peer dependencies:
bun add graphics-debug
bun add -d typescriptFor React debugger usage:
bun add react react-domQuick Start
1) Create a simple solver
import { BaseSolver } from "@tscircuit/solver-utils"
import type { GraphicsObject } from "graphics-debug"
class CountToTenSolver extends BaseSolver {
target = 10
value = 0
override _step() {
this.value += 1
this.stats = { value: this.value, target: this.target }
if (this.value >= this.target) {
this.solved = true
}
}
override getConstructorParams() {
return []
}
override getOutput() {
return { finalValue: this.value }
}
override visualize(): GraphicsObject {
return {
points: [{ x: this.value, y: 0, color: "blue" }],
lines: [],
rects: [],
circles: [],
texts: [{ x: 0, y: 1, text: `value=${this.value}` }],
}
}
}
const solver = new CountToTenSolver()
solver.solve()
console.log({
solved: solver.solved,
iterations: solver.iterations,
output: solver.getOutput(),
timeMs: solver.timeToSolve,
})2) Use a pipeline solver
import {
BasePipelineSolver,
definePipelineStep,
} from "@tscircuit/solver-utils"
import { BaseSolver } from "@tscircuit/solver-utils"
type Input = { initial: number; target: number }
class AddOneSolver extends BaseSolver {
value: number
target: number
constructor(params: { start: number; target: number }) {
super()
this.value = params.start
this.target = params.target
}
override _step() {
this.value += 1
if (this.value >= this.target) this.solved = true
}
override getOutput() {
return { value: this.value }
}
override getConstructorParams() {
return [{ start: this.value, target: this.target }]
}
}
class DoubleSolver extends BaseSolver {
value: number
constructor(params: { start: number; min: number }) {
super()
this.value = params.start
}
override _step() {
this.value *= 2
if (this.value >= 100) this.solved = true
}
override getOutput() {
return { value: this.value }
}
override getConstructorParams() {
return [{ start: this.value, min: 100 }]
}
}
class ExamplePipeline extends BasePipelineSolver<Input> {
addOneSolver?: AddOneSolver
doubleSolver?: DoubleSolver
pipelineDef = [
definePipelineStep("addOneSolver", AddOneSolver, (instance) => [
{ start: instance.inputProblem.initial, target: instance.inputProblem.target },
]),
definePipelineStep("doubleSolver", DoubleSolver, (instance) => [
{
start: instance.getSolver<AddOneSolver>("addOneSolver")!.value,
min: 100,
},
]),
]
override getConstructorParams() {
return [this.inputProblem]
}
}
const pipeline = new ExamplePipeline({ initial: 0, target: 10 })
pipeline.solve()
console.log(pipeline.getAllOutputs())
console.log(pipeline.getStageStats())3) Debug in React
import { useMemo } from "react"
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
export default function SolverPage() {
const solver = useMemo(() => new CountToTenSolver(), [])
return (
<GenericSolverDebugger
solver={solver}
animationSpeed={25}
onSolverStarted={(s) => console.log("started", s.getSolverName())}
onSolverCompleted={(s) => console.log("done", s.stats)}
/>
)
}Core Concepts
step(): perform one unit of work.solve(): repeatedly callstep()until solved or failed.stats: free-form live diagnostics shown in debugger tables.visualize(): return aGraphicsObjectfor rendering.getConstructorParams(): return reproducible constructor input (used by download helpers).getOutput(): standardized solved result (especially useful in pipelines).
BaseSolver Guide
Lifecycle
setup()runs once before first step (via_setup()).- Each
step()calls your_step(). - Solver ends when you set
solved = trueorfailed = true. - If max iterations are reached, solver auto-fails with an error.
Important fields
MAX_ITERATIONS(default100_000)iterationsprogress(auto-updated if you implementcomputeProgress())timeToSolveerroractiveSubSolver(for nested solver composition)stats(arbitrary debugging info)
Recommended implementation pattern
class MySolver extends BaseSolver {
constructor(private input: InputType) {
super()
this.MAX_ITERATIONS = 20_000
}
override _setup() {
// optional one-time initialization
}
override _step() {
// mutate state toward solution
// set this.solved = true when complete
// set this.failed = true and this.error when unrecoverable
this.stats = { ...this.stats, someMetric: 123 }
}
computeProgress() {
return 0.0 // number between 0 and 1
}
override visualize() {
return { points: [], lines: [], rects: [], circles: [], texts: [] }
}
override getConstructorParams() {
return [this.input]
}
override getOutput() {
return { /* final result */ }
}
}Error handling behavior
- Exceptions thrown in
_step()are captured,failedis set, and the error is re-thrown. - On max-iteration exhaustion,
tryFinalAcceptance()is called before failing. - You can override
tryFinalAcceptance()for “best effort” acceptance logic.
BasePipelineSolver Guide
BasePipelineSolver<TInput> is a BaseSolver that runs multiple solver stages in sequence.
Defining stages with definePipelineStep
definePipelineStep(
"stageName",
StageSolverClass,
(pipeline) => [constructorParamObject],
{
onSolved: (pipeline) => {
// optional callback after this stage solves
},
},
)Pipeline capabilities
- Auto-instantiates each stage solver when needed.
- Tracks per-stage timing and iteration metadata.
- Captures stage outputs automatically from
getOutput(). - Supports nested pipelines (a stage can itself be
BasePipelineSolver). - Merges per-stage visualizations into a combined
GraphicsObject.
Useful methods
solveUntilStage(stageName)getCurrentStageName()getStageProgress()getStageStats()getStageOutput<T>(stageName)getAllOutputs()hasStageOutput(stageName)getSolver<T>(stageName)
Initial and final visuals
You can override:
initialVisualize()for pre-stage context,finalVisualize()for final summary output.
These are inserted as extra visualization steps around stage visuals.
React Debugger Guide
Import from:
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"GenericSolverDebugger props
solver?: BaseSolvercreateSolver?: () => BaseSolver(use this when you need lazy creation)animationSpeed?: number(ms interval, default25)onSolverStarted?: (solver) => voidonSolverCompleted?: (solver) => void
What the debugger UI provides
- Step once, solve fully, animate, or step until target iteration.
- Renderer toggle (
vectororcanvas) with localStorage persistence. - Download current visualization JSON.
- Live iteration count, elapsed solve time, and solved/failed badges.
- Pipeline stage table (for pipeline solvers), including nested pipelines.
- Breadcrumb chain with per-solver download menus.
Download features
From the debugger dropdowns, you can download:
- constructor params JSON,
- generated
*.page.tsxdemo scaffold, - generated
*.test.tsscaffold.
Downloaded JSON automatically strips keys beginning with _.
Testing Solvers
Use Bun tests:
import { test, expect } from "bun:test"
test("solver reaches solved state", () => {
const solver = new CountToTenSolver()
solver.solve()
expect(solver.solved).toBe(true)
expect(solver.failed).toBe(false)
expect(solver.iterations).toBeGreaterThan(0)
})Run tests:
bun testAPI Reference
Package exports
- Root:
@tscircuit/solver-utilsBaseSolverBasePipelineSolverdefinePipelineStep
- React:
@tscircuit/solver-utils/reactGenericSolverDebuggerGenericSolverToolbarPipelineStagesTableSolverBreadcrumbInputDownloaderDownloadDropdown
BaseSolver default state
solved = falsefailed = falseiterations = 0progress = 0error = nullstats = {}MAX_ITERATIONS = 100_000
BasePipelineSolver notable state
currentPipelineStageIndexpipelineOutputsstartTimeOfStageendTimeOfStagetimeSpentOnStagefirstIterationOfStage
Development
Clone and install:
bun installRun interactive demo site (React Cosmos):
bun run startRun tests:
bun testBuild package:
bun run buildExport demo site:
bun run build:siteFormat:
bun run formatTroubleshooting
- If
progressstays at0, implementcomputeProgress()in your solver. - If input download fails, ensure
getConstructorParams()is implemented. - If solver never terminates, confirm
_step()eventually setssolvedorfailed. - If pipeline stage lookup fails, confirm
solverNamematches property access ingetSolver("name"). - If React debugger renders nothing, make sure
visualize()returns at least one non-empty primitive array.
