Add a new single-objective optimizer

SkillDev tools

Add a new single-objective optimization algorithm to the sci-comp library. Use when the user asks to implement a new optimizer (e.g. gradient descent, BFGS, simulated annealing, differential evolution).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Add a new single-objective optimizer skill

What this skill tells your AI

The instructions your AI receives, as published by datagrok-ai/public in libraries/sci-comp/.claude/skills/add-optimizer/SKILL.md and read by ahel’s review.

The user wants to add a new optimizer: $ARGUMENTS

Follow these steps exactly. Do not skip any step.

Step 1: Understand the algorithm

Before writing code, research the algorithm:

  • What are its tunable hyperparameters?
  • Is it population-based or single-point?
  • Does it require gradients?
  • What is the iteration logic?

Step 2: Read the reference implementation

Read these files to understand codebase patterns:

  • src/optimization/single-objective/optimizers/nelder-mead.ts — reference optimizer
  • src/optimization/single-objective/__tests__/nelder-mead.test.ts — reference tests (the new optimizer MUST include all the same test cases)
  • src/optimization/single-objective/__tests__/helpers.ts — test functions and helpers
  • src/optimization/single-objective/examples/unconstrained.ts — reference example

Step 3: Create the optimizer file

Create src/optimization/single-objective/optimizers/<name>.ts following the pattern from nelder-mead.ts.

Rules (beyond what's visible in the reference)

  • runInternal receives the already-penalized objective — do NOT handle constraints
  • runInternal receives settings with defaults already applied via withDefaults
  • Set converged = true only if a convergence criterion was met, not just maxIterations
  • Pre-allocate all buffers before the loop — zero allocations inside the iteration body
  • Helper methods must write into caller-provided buffers (out-parameter pattern), not allocate new arrays
  • costHistory: pre-allocate Float64Array(maxIter) with a separate costLen counter; return costHistory.subarray(0, costLen)

Step 4: Export from index.ts

Edit src/optimization/single-objective/index.ts:

  1. Add export for the class and settings type (in the "Built-in optimizers" section):

    export {<Name>} from './optimizers/<name>';
    export type {<Name>Settings} from './optimizers/<name>';
    
  2. Add auto-registration (in the "Auto-register" section at the bottom):

    import {<Name>} from './optimizers/<name>';
    registerOptimizer('<kebab-name>', () => new <Name>());
    

Step 5: Write tests

Create src/optimization/single-objective/__tests__/<name>.test.ts.

The new test file MUST replicate ALL test cases from nelder-mead.test.ts — every describe block, each with both sync and async variants.

You may adjust:

  • x0 starting points (if the algorithm needs a closer start)
  • maxIterations, tolerance, and algorithm-specific settings
  • Precision in toBeCloseTo / expectPointClose (if the algorithm is less precise)

You must NOT:

  • Remove any test group
  • Change expected values or expected points

Step 6: Create example file

Create src/optimization/single-objective/examples/<name>.ts following the structure of unconstrained.ts. Must show minimize, maximize, and at least one constrained example with boxConstraints + applyPenalty.

Step 7: Verify

Run in order:

  1. npm run lint-fix
  2. npm run build — must compile without errors
  3. npm test — run all tests

CRITICAL: If any tests fail, do NOT silently fix or skip them. Instead:

  1. Collect the full list of failing test names and reasons
  2. Present the list to the user
  3. Wait for the user's response — do NOT proceed until the user explicitly approves a course of action This rule applies to every test run, including re-runs after fixes.

Step 8: Add to benchmarks

The benchmark suite is split into two runners that share objective functions via benchmarks/test-functions.ts. Register the new optimizer in both runners:

  • src/optimization/single-objective/benchmarks/unconstrained-benchmarks.ts — single x₀ per problem
  • src/optimization/single-objective/benchmarks/multistart-benchmarks.ts — three x₀ per problem

In each file:

  1. Import the new optimizer class

  2. Add an entry to the optimizers array:

    {
      name: '<Name>',
      optimizer: new <Name>(),
      settings: {maxIterations: 10_000, /* algorithm-specific defaults */},
    },
    
  3. Run both benchmarks:

    • npx tsx src/optimization/single-objective/benchmarks/unconstrained-benchmarks.ts
    • npx tsx src/optimization/single-objective/benchmarks/multistart-benchmarks.ts
  4. Regenerate both markdown reports (unconstrained-benchmarks.md and multistart-benchmarks.md) with the new optimizer's rows/columns.

  5. Update the problem count / optimizer count in the banner if changed.

Do NOT add new objective functions inline — export them from test-functions.ts so both runners pick them up.

Step 9: Update CLAUDE.md

In CLAUDE.md, update the architecture tree — add the new optimizer file under optimizers/.

Step 10: Add to README.md

In README.md, add the new optimizer to the list under "Single-objective" section, with a Wikipedia or reference link.

Signals

GitHub stars
72
Forks
32
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
add-optimizer
Source
github.com/datagrok-ai/public