Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

tellegen reactive power flow visualization

Introduction

tellegen is a reactive visualization interface for power flow cases. The name refers to Tellegen’s theorem and the adjoint sensitivity calculations.

The app uses a gradient preview, exact commit interaction model. Perturbations update the display from KKT sensitivity columns. Exact solves (DC OPF, AC power flow, and the SOCWR relaxation) run in the browser in WebAssembly. Case parsing uses powerio.

Demo

The public demo at tellegen.dev serves three TAMU ACTIVSg synthetic grids and the CATS California Test System at their staged geographic coordinates. These are synthetic grids on geographic footprints, not surveyed infrastructure:

caseterritorybusesbranches
ACTIVSg200central Illinois200245
ACTIVSg500South Carolina500597
ACTIVSg7000Texas67179140
CATSCalifornia887010823

Each case solves as DC OPF by default; a selector switches to the SOCWR relaxation, solved in the browser in WebAssembly. Bus color shows locational marginal price. Selecting a bus shows the dLMP/dd column for a demand perturbation at that bus. Moving the demand slider applies the local sensitivity immediately; releasing it computes the exact solution in WebAssembly.

Local Files

Dropped .m, .raw, and .aux files are parsed in the browser by the WebAssembly build of powerio. Files with coordinates render on the map. Files without coordinates can be placed by clicking the map, or paired with local geographic files in .csv, .json, or .geojson form. A dropped PowerWorld .pwd file is decoded as display data and rendered as approximate substation positions. Parsed local case files solve in the browser and are not uploaded.

API

  • GET /api/health
  • GET /api/cases
  • GET /api/cases/{id}/case
  • GET /api/cases/{id}/network
  • GET /api/cases/{id}/solution
  • GET /api/cases/{id}/sensitivity/lmp/d/{bus}
  • GET /api/cases/{id}/solve

The sensitivity and solve endpoints accept ?d=bus:mw,bus:mw, where each value is a MW delta from the base case. The solve stream emits status, solution, optional sensitivity, and done events.

Direction

Where the project is going and why. Architecture describes the boundary as shipped; this page describes the intent behind it. The ecosystem claims date from a June 2026 review; sources at the end.

Landscape

The sources below show no web native, developer facing framework for reactive power systems (public sources; a private implementation could exist). The closest tools:

  • Electrisim runs pandapower and OpenDSS from the browser; the compute is server side, and it reports no locational marginal prices.
  • RTE’s GridSuite (PowSyBl) is the most mature browser grid application in the field: closed operator software without a reactive price map.
  • GridStatus.io has a polished nodal price map of ISO published prices the user cannot recompute.
  • PowerPlots.jl is hover to inspect, driven from Julia.

None of them let a user load a case, click a bus, drag demand, and watch prices and flows re-solve live against exact KKT sensitivities. tellegen ships that interaction. PowerDiff.jl worked out the sensitivity columns behind it; tellegen exposes them as a framework, and the demo is one application built on it.

The boundary

Sensitivity columns give an immediate matrix vector preview during a drag; the exact solve fires on release and reconciles the preview. Around that loop:

  • powerio (Rust): parsing, encoding, the formats, the network data model, and the canonical display format (display-data.md).
  • tellegen (browser): interaction, the fast math, rendering. Its Rust is built in this repository against powerio and compiled to WebAssembly.
  • tellegen backend (Rust): the same numerical core compiled native. It hosts the bundled cases; its compute endpoints can serve browsers without a working WebAssembly path and ship disabled behind TELLEGEN_SERVER_COMPUTE, a single switch. Per endpoint control and authentication come before AC OPF ships as a server product.

PowerDiff.jl remains the reference harness for parity checks.

Browser solver status

Julia has no production WebAssembly path: the runtime port has been dormant since 2021, and the function level compilers cannot reach BLAS or JuMP. The numerics are therefore written in Rust and compiled to wasm.

  • DC OPF (sparse LP/QP): shipped. Clarabel.rs, a pure Rust interior point solver with no BLAS dependency, solves 200 to 7000 bus cases in the browser as the exact commit.
  • DC sensitivities (the dLMP/dd columns): shipped. One linear solve against the KKT factorization at the active set, reimplemented in Rust on faer.
  • AC power flow (Newton with sparse LU): feasible. faer provides sparse LU under wasm. There is no shipped precedent, and faer’s wasm sparse path has an open crash report, so it needs validation on real case matrices first.
  • AC OPF (nonconvex): the holdout. There is no Ipopt in wasm. The candidates are a second order cone relaxation through Clarabel (shipped as SOCWR) and a Rust nonlinear solver (thin ground). Until one matures, AC OPF is the reason the backend exists.

The browser owns the whole DC pipeline: parse, solve, differentiate, render (issue #2).

Decisions

  • wasm-pack and wasm-bindgen stay. Both shipped releases in 2026 (wasm-bindgen 0.2.125 in June). The WebAssembly Component Model targets server and edge with no browser DOM host, so it does not apply here.
  • deck.gl and MapLibre on WebGL2 stay. deck.gl holds 60 fps to about a million elements; grids run at ten thousand to a hundred thousand. WebGPU in deck.gl lacks picking and basemap interleave today and can be adopted later without touching the rendering code.
  • $state.raw on large payloads. Shared reactive state is scoped through context, so the packages stay SSR safe.

Roadmap

Near term: harden the framework package boundary as outside apps consume @tellegen/engine and @tellegen/svelte. The hosted demo is the reference consumer.

Mid term: prebake the bundled cases as static assets and make the no backend deployment the default. The DC pipeline in the browser has landed, including the Safari sensitivity gap (issue #8).

Long term: AC power flow in the browser once faer’s wasm sparse path is validated. AC OPF moves to the browser when a wasm nonlinear or cone path is solid, and stays in the backend until then. WebGPU when deck.gl’s backend gains picking and basemap support. Synthetic grid generation pairs with in browser compute.

Sources

Framework Quickstart

There are two integration references:

Install

For a Svelte app:

npm install @tellegen/svelte

For a custom UI:

npm install @tellegen/engine

For local development in this repository:

npm ci
npm run wasm
npm run build:engine
npm run build:svelte
npm --workspace @tellegen/example-svelte-minimal run dev

The engine package resolves wasm assets relative to its packaged modules with new URL(..., import.meta.url). Vite and SvelteKit handle that path for the Svelte package and for custom engine consumers.

Svelte Viewer

<script lang="ts">
  import { TellegenViewer } from "@tellegen/svelte";
  import "@tellegen/svelte/styles.css";
</script>

<TellegenViewer />

For local files only:

<script lang="ts">
  import { TellegenViewer } from "@tellegen/svelte";
  import "@tellegen/svelte/styles.css";
</script>

<TellegenViewer loadDefaultCases={false} showFooter={false} />

The viewer accepts at most 32 files in one ingestion batch. No individual file and no batch total may exceed 128 MiB.

Run the local example:

npm --workspace @tellegen/example-svelte-minimal run dev

Engine Flow

Run the engine example:

npm --workspace @tellegen/example-browser-minimal run dev
import { createStudy, formatOf, ingestCase } from "@tellegen/engine";

const format = formatOf("case14.m");
if (!format) throw new Error("unsupported case format");

const bytes = new TextEncoder().encode(caseText);
const parsed = await ingestCase(bytes, format);
const study = await createStudy(parsed.module_json, "dcopf");

try {
  const preview = await study.preview({ 3: 25 });
  const committed = await study.commit(parsed.name, { 3: 25 }, {}, { bus: 3 });
  console.log(preview.objectiveDelta, committed.sensitivity);
} finally {
  study.free();
}

The call sequence is:

  1. detect the case format;
  2. parse the case in browser WebAssembly;
  3. create a Study;
  4. preview a demand edit without a solve;
  5. commit the edit with a sensitivity request; and
  6. free the Study.

JSON Drops

Use ingestJsonDrop when a .json file may be a PowerIO module, model JSON, or a transmission or distribution document:

import { ingestJsonDrop } from "@tellegen/engine";

const bytes = new Uint8Array(await file.arrayBuffer());
const result = await ingestJsonDrop(bytes);

if (result.kind === "unknown" || result.kind === "ambiguous") {
  console.log("JSON was not ingested", result.kind);
} else {
  console.log(result.kind, result.format, result.payload);
}

The result is discriminated by kind; payload is null only for unknown and ambiguous input.

Privacy Boundary

Dropped files stay in the browser when the app uses the browser wasm path. The parser, solve, preview, commit, and sensitivity paths all run in WebAssembly in the page. A host app only sends data to a server if it chooses an HTTP path or writes its own upload path.

Framework Packages

The reusable browser packages live under packages/. The first framework release publishes both @tellegen/engine and @tellegen/svelte.

Use @tellegen/svelte when a Svelte app wants the map, panels, local file flow, and solve card as components. Use @tellegen/engine when an app wants case parsing, browser WebAssembly solves, studies, previews, and sensitivities without the tellegen UI.

apps/web is the hosted demo. It consumes @tellegen/svelte and keeps only route level concerns such as SEO pages, /credits, and /privacy.

Svelte UI Package

Install:

npm install @tellegen/svelte

Render the full viewer:

<script lang="ts">
  import { TellegenViewer } from "@tellegen/svelte";
  import "@tellegen/svelte/styles.css";
</script>

<TellegenViewer />

The default viewer loads bundled cases from /api, parses dropped local case files in the browser, and runs supported local solves in WebAssembly.

TellegenViewer accepts:

  • apiBase, default /api
  • loadDefaultCases, default true
  • docsHref
  • orgHref
  • orgLabel
  • showFooter, default true

Use a different backend base path like this:

<TellegenViewer apiBase="/tellegen/api" />

Use local files only by disabling bundled case loading:

<script lang="ts">
  import { TellegenViewer } from "@tellegen/svelte";
  import "@tellegen/svelte/styles.css";
</script>

<TellegenViewer loadDefaultCases={false} showFooter={false} />

For apps where state should survive route changes, mount the provider in a persistent layout and render the shell on the page:

<script lang="ts">
  import { TellegenProvider } from "@tellegen/svelte";
  import "@tellegen/svelte/styles.css";

  let { children } = $props();
</script>

<TellegenProvider>
  {@render children()}
</TellegenProvider>
<script lang="ts">
  import { TellegenShell } from "@tellegen/svelte";
</script>

<TellegenShell />

@tellegen/svelte also exports lower level pieces for custom shells:

  • TellegenMap
  • AppState, CaseState, LocalCase, and createAppState
  • Controller and createController
  • createApiClient
  • panels and controls from @tellegen/svelte/components
  • colors, display helpers, formatting helpers, and public types

Engine Package

Install:

npm install @tellegen/engine

Use the engine package when you want to build your own UI:

import { createStudy, formatOf, ingestCase, solveModule } from "@tellegen/engine";

ingestCase and solvable JSON ingestion return a retained PowerIO module in module_json. Both createStudy and solveModule accept that generation-2 PowerIO IR module. Geographic transforms update and return the same module.

The engine package resolves its wasm files relative to the package module. Apps must serve package asset files from node_modules; Vite and SvelteKit handle that path.

Examples

  • examples/svelte-minimal imports @tellegen/svelte and runs with loadDefaultCases={false} for local files only.
  • examples/browser-minimal imports @tellegen/engine directly and has no map stack.

Downstream apps should not import from apps/web/src/lib or from generated wasm folders.

Persistent Studies

A Study records a goal, the electrical states explored in pursuit of it, and the exact evidence behind a recommendation. Open Studies in the network view, state a goal, resolve the equipment and weights, and review the interpretation before creating the Study. The editable JSON is the numerical request.

The starting PowerIO calculation instance preserves the inner OPF objective and constraints. The Study objective is a separate scalar expression over its solved prices, dispatch, flows or voltages. Expressions compose weighted observables, sums, scaling, squared target deviations and direct intervention penalties. The engine evaluates the complete derivative with a combined adjoint solve, including the direct penalty terms. It does not build a dense matrix containing all observable/decision pairs.

Explore, compare and apply

Capacity decisions use MW for DC OPF and MVA for SOCWR. Active-demand decisions use MW. Each decision states a stable element identity, bounds and an increment. The shared feasible set adds a weighted absolute-change budget and a maximum number of changed elements. A goal retains its starting anchor: continuing from a candidate measures cumulative changes from that anchor and does not reset the budget. Placement allocates the declared total additional load; redistribution uses paired transfers that preserve total demand.

Find a proposal ranks feasible moves with the current gradient, solves a bounded beam exactly, keeps a verified improvement and recomputes the direction. Every attempted solve consumes the budget, including baseline reconstruction and failed trials. The returned recommendation is the best verified candidate found, not a certificate of global optimality. Active-set changes can invalidate a local derivative. Trial evidence records predictions, exact values, changed active constraints, failures and termination. Derivative evidence includes its regularization and refinement settings.

The Study keeps three separate state pointers:

PointerMeaning
InspectingThe saved state shown on the map and used for the next action
RecommendedThe best verified candidate from the current proposal
AppliedThe state explicitly accepted through the Apply action

Selecting a state or branching from it changes the view. Applying a proposal is an explicit action. Approval binds the proposal to its goal, base state and revision; changing any of them expires the approval. A goal revision retains the previous interpretation and its evidence. Comparisons can evaluate saved candidates under a selected goal revision. Inspection and attached evidence do not invent a new electrical state. A challenge can name the recommendation it assesses with assessed_recommendation.

The saved-state map belongs to the Study. Return to live case returns to the interactive case loaded outside it. Importing or inspecting a Study does not execute imported text or silently overwrite that live case.

Demand edits and base-case reset

Solve demand edit adds a signed MW increment at a permitted bus in the selected saved state. Successive edits accumulate across buses and survive export and reload. Bounds, increments, the absolute-change budget and the number of changed buses apply to the cumulative changes from the goal anchor. A partial placement or redistribution remains a saved candidate until its total is satisfied; it cannot be applied as a recommendation yet. Edit evidence includes the sparse demand-change vector relative to the original network data.

Prepare base-case reset exactly solves the original network input and saves a reset candidate. Applying that candidate restores the original demand and ratings without deleting the goal revisions, earlier states or activity. The original input is distinct from the Study’s starting point, which may already contain live edits. Native CreateStudy accepts optional base_input for that original PowerIO IR; without it, the supplied input is the base. Older imported bundles without retained base data report that reset is unavailable.

The shared operations are edit_demand and restore_base. Both leave the applied pointer unchanged until an explicit Apply action. Observations, edits and solves, planning trials, and decisions remain distinct in Activity.

Storage and continuation

Electrical inputs, calculation instances and exact solutions use PowerIO IR generation 2. Study semantics live in the application document. A portable bundle contains a deduplicated SHA-256 artifact map, immutable state and goal records, experiments, evidence references and decisions. Browser storage uses IndexedDB; the native interface saves the same bundle on the filesystem. Completed operations save atomically before the controller publishes them.

Export a Study to continue in another browser or through the native CLI. Import checks document versions, identities, artifact hashes and references, and verifies that saved solution instances agree with their state inputs. Approval tokens never enter the bundle. Older experiment journals import as historical evidence; unavailable electrical states remain explicitly unavailable.

A storage failure reports the failed save and recovery choices. Free storage or export the current saved Study before retrying. Filesystem writers use a revision check and an exclusive lock; inspect a leftover lock’s process before removing it. A second writer cannot silently replace a newer revision.

Cancellation finishes the current exact trial, then saves the completed trials, best candidate and cancelled termination. An exact solve is indivisible, so cancellation latency depends on that trial. Closing or forcibly killing the process before the save can lose the current operation; the previous completed revision remains durable. PowerMCP defaults to a 300-second graceful termination window before forcing a stopped process to exit. Its runtime and cancellation limits are configurable.

Native and agent interfaces

cargo build -p tellegen-cli --features conic
tellegen contract
tellegen study create study.json < create-request.json
tellegen study inspect study.json
tellegen study run study.json < operation-request.json
tellegen study export study.json > portable-study.json
tellegen study import another-study.json < portable-study.json

tellegen contract returns generated schemas for CreateStudy, StudyRequest, StudyBundle and the operation result. Rust is the contract authority; the browser’s TypeScript types and runtime schemas are generated from those same records. expected_revision accompanies each mutation.

Browser WebMCP exposes create_study, inspect_study, revise_study_goal, branch_study, compare_study_states, propose_study and record_study_evidence, edit_demand and restore_base_case. Inspection returns compact continuation context and bounded pages of larger records. The browser controls call the same controller. PowerMCP’s tellegen adapter invokes the native CLI directly and uses the same request schemas. Its agent interface leaves application to an explicit user action.

A sensitivity build supports DC OPF and AC power flow Studies. The conic feature adds SOCWR. Nonlinear AC OPF and multiconductor solving are unavailable; unsupported objective/formulation combinations fail before exploration.

The capacity WebMCP tools create persistent Studies and use the same bounded search as propose_study. Their compact response includes study_id for continuation. The creation solve and every planning solve count toward the capacity call’s budget. A human capacity approval binds the Study’s goal, starting state and recommended state. Goal changes expire it; recording inspection evidence does not. Failed persistence leaves the approval available for a retry.

When a matching live case is open, case inspection, network queries and sensitivity queries attach evidence to its captured Study state. They create no electrical state. Queries about a different or edited live case remain independent until a new Study captures that case. If a different case finishes loading while an approved capacity change is being saved, the saved Study remains the authoritative result and the tool reports case_updated: false; the new live case is untouched.

Reproducible large-case declarations and an installed-CLI runner live in evidence/studies. The original Texas7k cost curves include decreasing piecewise slopes, which the convex DC OPF formulation rejects. A separately labelled example constructs and records their lower convex envelope through PowerIO’s typed network API. It changes the inner economic model and does not establish an optimum for the original nonconvex case.

WebMCP

Open Agent, then Connect, on the map for connection status, a copyable example request and the connection guide. A connected agent must discover the WebMCP tools exposed by this browser tab. A regular browser without WebMCP still supports manual interaction. The first-visit introduction links to the application changelog.

Tellegen exposes the solved case in the current browser tab through structured tools. They read and update the state shown in the interface and run the same WebAssembly solver.

Browsers without document.modelContext run the application without these tools.

Tools

ToolBehavior
inspect_caseRead the active case, formulation, solve summary, edit counts, case ID, and revision.
query_networkReturn a bounded set of buses or branches by stable identity or solved metric.
analyze_sensitivityCompute an LMP sensitivity column for the current DC OPF solution.
focus_networkSelect a bus or branch in the visible interface.
preview_case_updatePredict demand or rating edits from cloned state.
update_caseSolve and commit a bounded batch of demand or rating edits.
reset_caseSolve and commit the base state with the current formulation.
propose_capacity_planUse implicit derivatives to choose bounded capacity increase trials, verify them with exact solves, and stage an unapplied proposal.
apply_capacity_planApply an approved proposal after checking its session, case, and revision.

The general OPF tools register for the page lifecycle. inspect_case reports when no solvable case is active; the other general tools require one. propose_capacity_plan registers when the active formulation supports it. apply_capacity_plan registers only while a staged proposal matches the current state.

Revisions and mutations

Every page load receives a random session identifier. Each case has a monotonic revision within that session. A committed edit, reset, formulation change, or visible network selection advances the revision and expires pending proposals and approvals.

Preview, standalone sensitivity analysis, and planning operations use cloned state. Electrical mutations enter one queue, check their expected revision inside the queue, solve a candidate state, and commit once after the solve succeeds. A failed solve leaves the case, formulation, revision, proposal, and approval unchanged.

Tool callback options are optional because WebMCP clients do not all provide an AbortSignal. When a caller supplies one, reads and planning stop at safe boundaries. The browser reports dynamic registration failures instead of claiming that a missing tool group registered successfully.

Capacity planning

propose_capacity_plan accepts a weighted LMP objective, stable candidate branch identities, a total MW budget, a maximum increase per branch, a fixed increment, a global line count, and an exact solve budget. The solve budget counts the baseline and every accepted or rejected trial.

The planner computes a vector product through the solved DC OPF KKT system, orders capacity increases by that local direction, and checks each trial with an exact solve. It recomputes the direction after every accepted trial. Each solved trial records its first order prediction and exact result; a failed trial records the failure reason without an exact delta.

The proposal changes no electrical state. A visible control grants one use approval for its proposal ID and base revision. Applying it repeats every identity and revision check inside the mutation queue. A failed or stale application consumes neither the proposal nor its approval.

Persistence

The activity panel records the last 100 completed tool calls in an experiment journal, including validated requests, bounded results, failures, and elapsed time. Export journal downloads that record with the retained capacity planning trials and their decisions. The export identifies its browser session and reports how many older records were discarded. Invalid requests are omitted from the recorded input.

When a preview and a successful update name the same case, revision, formulation, and edits, the panel shows the predicted objective change beside the exact change and absolute error. A failed update leaves the comparison empty. The relative error is undefined when the exact change is zero.

Journal exports contain data only; importing one never authorizes or executes an edit. Proposals and approvals stay in memory. Saving a case writes its materialized PowerIO module. Loading that module starts a fresh runtime Study; its history records transformations applied to the stored value. Saving an exact result writes a PowerIO solution module containing the amended instance that was solved.

Package boundary

@tellegen/webmcp owns tool descriptions, JSON Schemas, runtime validation, annotations, bounded result shaping, and registration lifecycle. It depends on a TellegenWebMcpAdapter, not on Svelte or the Rust engine. The application adapter connects that interface to the active controller and visible activity panel.

Imported labels and identifiers are treated as untrusted content. Every mutation requires the active case ID and revision. The package rejects unknown fields, nonfinite values, duplicate edits, excessive arrays, and unsupported enum values at the execute boundary as well as in its schemas.

Testing

Run the package and application checks from the repository root:

npm run check:webmcp
npm run check:web
npm run build:web
npm run test:browser

The tests cover registration and teardown, calls with and without callback options, validation, output bounds, stale revisions, one use approval, transaction rollback, queued mutations, cancellation, and visible state. Playwright uses a test host with the WebMCP API shape. Before release, invoke the tools through native WebMCP in the in-app browser as a separate bridge check.

References

Tellegen product showcase

Tellegen is a power-system laboratory shared by a user and an agent. The network stays central while a persistent Study records the goal, alternatives explored, exact results and recommendation. OPF supplies the operating point; an outer Study objective describes what the user wants to improve.

For example:

Lower demand-weighted prices in this region. Add at most 20 MW across two lines, and show how prices elsewhere change.

The goal interpretation resolves the region to equipment identities and weights. The user can inspect and edit that interpretation before exploration. A combined implicit derivative ranks feasible interventions, and exact solves determine whether a trial improves the objective. The Study preserves accepted, rejected and failed trials, constraint changes and the reason exploration stopped. Prediction error belongs in the expandable evidence beside those trials.

One Study across interfaces

The browser controls and WebMCP call the same Study controller. Both can create a Study, inspect its history, revise a goal, branch from a saved state, compare candidates, propose interventions and attach evidence. Capacity-tool compatibility adapters create the same persistent Studies. Native Rust and CLI operations use the same generated contracts; PowerMCP invokes the CLI directly.

Three pointers keep navigation clear: the inspected state, the recommended state and the applied state. Viewing an alternative does not apply it. Human approval binds a particular proposal, starting state and goal. A revised goal or changed proposal expires that approval.

Electrical inputs, instances and solutions use PowerIO generation-2 IR. Study semantics live in a separate document with hashed, deduplicated artifacts. Browser IndexedDB and atomic filesystem storage preserve completed operations. A portable bundle can move from a browser to a headless agent and back. Import validates identities, hashes and references, and restores no approvals.

Showcase sequence

  1. Open a congested case and inspect dispatch, prices and limiting branches.
  2. State the regional objective, resolve its weights and review the intervention budget.
  3. Ask for a proposal. Inspect exact improvements and consequences elsewhere.
  4. Select a rejected candidate in the branching history to explain the explored alternative.
  5. Revise the goal to conserve total demand and redistribute it among selected buses.
  6. Compare candidates under the chosen goal revision. Expand a trial’s numerical evidence.
  7. Apply the reviewed recommendation through the explicit user control.
  8. Export, reload and resume the Study with another agent.

A small AC power-flow example instead minimizes squared voltage-target error through demand transfers. This uses the existing AC power-flow solver. Nonlinear AC OPF and multiconductor solving are outside Tellegen’s supported calculations.

Reproducible evidence

Studies describes the document and operations. The checked-in Study declarations and runner record executable and input hashes, exact trial outcomes, solve budgets and numerical tolerances. Browser tests run the same declared Study through native and WASM implementations and compare semantic results.

The Texas7k example explicitly records a lower-convex-cost scenario because the original input contains nonconvex piecewise cost curves. Its results describe that scenario, not an optimum for the original economic model.

The earlier capacity demonstration retains its own call records, revisions and hashes. Fresh native WebMCP evidence accompanies the persistent Study release. The showcase can be reproduced independently of a contest submission.

Verified examples

Fresh result records include the declared tolerances and all attempted trials. The values below are the outer objectives specified by each declaration; capacity and demand examples sum selected nodal prices, while the AC example measures squared voltage error.

StudyStarting objectiveRecommended objectivePlanning solves
CATS capacity76.1190012753.616319346
CATS demand redistribution76.1190012774.259366755
Texas7k convex-cost scenario, capacity87.0811794385.747739208
Texas7k convex-cost scenario, redistribution87.0811794387.081179433
Three-bus AC voltage target0.000437502350.000406782966

Each Study also has one creation solve. The Texas7k redistribution search retains its starting state: the tested changes are below its recorded improvement tolerance. The planner reports that limit instead of presenting numerical noise as a gain. None of these searches applies its recommendation automatically.

The native WebMCP demonstration records all seven Study tools, reload, branching, goal revision and a rejected stale request. Application and stale approvals have separate browser tests. On its synthetic three-bus case, a 5 MW capacity increase lowers the target bus price but raises the price at another bus. A second goal explores demand transfers from the original starting state. The inspected capacity choice, demand recommendation and applied starting point remain distinct.

Native Study comparison showing the target improvement and prices elsewhere

References

Challenge Evidence

The checked in harness under evidence/webmcp produces the large case records. CATS and Texas7k each have one complete invocation spec. The runner parses the named MATPOWER source through the PowerIO revision in Cargo.lock, invokes tellegen plan, checks its solve accounting, and writes one machine readable result.

Each result records:

  • the SHA256 digests of the MATPOWER source, invocation spec, and lockfile;
  • the exact Tellegen commit and tree and the reviewed PowerIO commit;
  • the complete CapacityPlanSpec request;
  • for success, the exact baseline and proposed summaries, every accepted and rejected trial, plus the kind, termination, objective, and canonical JSON digest of the final generation-2 pio-ir solution on the amended instance.

A spec can name one enumerated preparation failure. A matching clean run records a typed error, its preparation stage, and zero completed exact solves. Any other failure exits nonzero and writes nothing.

Run from a clean repository checkout:

node evidence/webmcp/run.mjs evidence/webmcp/specs/cats.json evidence/webmcp/results/cats.json
node evidence/webmcp/run.mjs evidence/webmcp/specs/texas7k.json evidence/webmcp/results/texas7k.json

The runner refuses tracked or untracked changes other than earlier generated JSON files in results/, and it refuses to replace an existing result. --allow-dirty is only a harness smoke test; its output always says reproducible: false and is not submission evidence. A clean run can write only the named result path for its case.

During review, the lock resolves every PowerIO component from one Git commit. After publication, it records the common version and each registry checksum; evidence/webmcp/powerio-releases.json then supplies the checked release tag to commit mapping. Both paths retain powerio_revision in the result.

Publish a numerical case claim only when its result file comes from a clean run and validates against evidence/webmcp/result.schema.json.

Native browser record

evidence/webmcp/native/in-app-browser.json records the native WebMCP calls, transaction checks, browser session change, and artifact hashes. Its screenshots show the pending and applied proposal states. video-run.json records the calls shown in the native browser video, ending with a second proposal expired by a committed edit. The manifests name the exact Tellegen tree and PowerIO commit.

Engine API Reference

@tellegen/engine is the public browser engine package. It is independent of SvelteKit and the hosted demo.

Constants

  • CONTRACT_VERSION: the public TypeScript contract version. It matches the package version.
  • CONTRACT_SOURCE_SHA256: the crates/tellegen/src/api.rs hash used to generate the TypeScript contracts.
  • FORMULATION_IDS and SOLVE_STATUSES: generated enum tags from the Rust API layer.
  • FORMULATIONS and DEFAULT_FORMULATION: browser UI formulation list and default formulation.

Browser Wasm Transport

  • browserWasmTransport: object facade for the browser wasm transport.
  • createBrowserWasmTransport(): returns the browser wasm transport facade.
  • preloadEngine(): initializes the wasm package.

The facade has the same methods as the direct exports:

  • classifyJson(bytes)
  • ingestJsonDrop(bytes)
  • ingestCase(bytes, format)
  • parseDisplay(bytes)
  • capabilities()
  • solveModule(moduleJson, request)
  • createStudy(moduleJson, formulation)

Case And Display Helpers

  • formatOf(name): returns the powerio format token for a supported case name.
  • isDisplayFile(name): returns true for PowerWorld .pwd display files.
  • classifyJson(bytes): asynchronously classifies JSON bytes and returns { kind, format }.
  • ingestJsonDrop(bytes): classifies and parses a JSON drop in one call.
  • ingestCase(bytes, format): parses case bytes and returns a retained PowerIO module plus derived display data, summary, and topology.
  • parseDisplay(bytes): parses PowerWorld display data for diagram overlays.

ingestJsonDrop returns a discriminated IngestedJsonDrop union:

kindformatpayload
modulenullbalanced or multiconductor payload
transmissionstringIngestedCase
distributionstringIngestedDistCase
ambiguous or unknownnullnull

Use the kind discriminant before reading payload. format carries the reader selected for transmission and distribution documents.

Solves And Studies

  • capabilities(): returns available formulations, operands, and parameters.
  • solveModule(moduleJson, request): stateless solve from a PowerIO module.
  • createStudy(moduleJson, formulation): builds a browser Study from a PowerIO module and solves its declared problem instance.
  • Study / BrowserStudy: browser handle with:
    • currentSolution()
    • preview(deltas, rates?)
    • commit(caseId, deltas, rates, target)
    • sensitivity(caseId, deltas, rates, target)
    • saveModule()
    • saveSolutionModule() for an exact DC OPF result
    • plan(spec, signal?) for a read only, bounded capacity planning run on a disposable Study clone
    • export(format)
    • free()

deltas are demand deltas in MW keyed by bus; rates are thermal rating deltas in MW keyed by branch. A key is the original numeric id (bus id, 1-based branch position) or a PowerIO row uid string when the source carries one. ingestCase returns null for topology and view UIDs when the source has none; solve responses omit absent UIDs. Three-winding transformers remain typed in the retained module, while topology and view payloads include their lowered star rows so the rendered graph matches the solver. Those display-only rows have editable: false; persist edits only on canonical rows. Closed transmission switches, in-service storage, and in-service HVDC links are rejected until their solver models are implemented. n_bus and n_branch count canonical typed rows; n_analysis_bus and n_analysis_branch count the lowered topology rows. The analysis counts are optional in TypeScript so clients remain compatible with older engine builds. target is { bus } for the LMP/demand column, { branch } for the LMP/rating column (nonzero only on binding lines), or null for no column.

Call free() when a host app discards a study.

CapacityPlanSpecJson accepts a weighted LMP objective, canonical candidate branch identities, MW bounds and increments, a final line count, and an exact solve budget. BrowserStudy.plan materializes the committed PowerIO module onto a disposable host, so the returned CapacityPlanOutcomeJson is an unapplied proposal and cancellation does not invalidate the retained interactive Study.

Migrating To 0.2

  • ingestCase now takes Uint8Array, not a string. Encode an in-memory string with new TextEncoder().encode(text); use new Uint8Array(await file.arrayBuffer()) for a browser File.
  • classifyJson now takes bytes, is asynchronous, and returns { kind, format }: const { kind, format } = await classifyJson(bytes).
  • @tellegen/svelte no longer exports isStudyPackageText. For classification only, check (await classifyJson(bytes)).kind === "module". To identify the module value family and parse the drop, use await ingestJsonDrop(bytes).
  • JsonDropKind now uses transmission and distribution with a separate format, plus ambiguous and unknown. The former bmopf and pmd kinds are distribution formats; not-json is now unknown.

Types

Public types include:

  • SolveRequest, SolveResponse, ProblemCaps
  • SensRequest, SensitivityMatrix, SensitivityColumn
  • Network, NetworkBus, NetworkBranch
  • Solution, SolveIteration, DemandDeltas
  • ImplicitObjectiveJson, CapacityPlanSpecJson, CapacityPlanOutcomeJson
  • IngestedJsonDrop, JsonDropClassification, JsonDropKind
  • BrowserFormulation, FormulationId, SolveStatus

The generated file is committed at packages/engine/src/generated/contracts.ts and checked in CI.

Transports

The transport boundary is the line between a host app and the tellegen engine.

Browser Wasm

@tellegen/engine ships the browser wasm transport today. It wraps the wasm-pack output from crates/tellegen-wasm and exposes direct functions plus browserWasmTransport.

Use this transport when cases should stay local to the browser. Dropped case files are parsed in WebAssembly, and solves, Study.preview, Study.commit, and sensitivity requests run there as well. No case text or PowerIO module leaves the browser unless the host app sends it.

The wasm instance lives in a dedicated Web Worker, so a multi-second solve never blocks the page. When the browser cannot run a module worker the same requests execute on the calling thread instead; the API is identical either way.

The transport has one wasm package carrying parsing, all solves, Study, capabilities, and generalized sensitivity requests.

The loader is lazy. Host apps can call preloadEngine() to control when the browser downloads and initializes wasm.

HTTP

The hosted demo also uses HTTP for bundled case metadata and native server fallback paths. That server is a demo consumer, not a requirement for using @tellegen/engine.

An HTTP transport can implement the same shape as EngineTransport: parse or fetch a PowerIO module, call a native module solve endpoint, keep a server side study handle, and return the same generated TypeScript contract shapes.

That transport is optional for apps that need server sized cases, audit logs, or native deployment.

Tauri

The desktop and mobile path can use the same transport contract with a Tauri command layer. The UI keeps the Study workflow; only the call boundary changes from browser wasm to native commands.

The Rust contract stays the source of truth. New transports should return the generated SolveResponse, ProblemCaps, and sensitivity matrix shapes instead of defining parallel types.

Getting Started

Repository Layout

  • apps/web/: private SvelteKit hosted demo
  • crates/: the tellegen engine and its wasm, server, CLI, and benchmark adapters
  • packages/engine/: public @tellegen/engine browser package
  • packages/svelte/: public @tellegen/svelte component package
  • examples/browser-minimal/: minimal downstream Vite example
  • examples/svelte-minimal/: minimal Svelte example using the component package
  • scripts/: data staging and docs build helpers
  • deploy/: deployment compose files and proxy notes
  • docs/src/: mdBook documentation source

Prerequisites

  • Rust from rust-toolchain.toml, including rustfmt, clippy, and the wasm32-unknown-unknown target
  • Node.js 22 or newer
  • wasm-pack 0.15.x for browser WebAssembly builds
  • mdBook 0.5.x for local documentation builds

tellegen backend

cargo run -p tellegen-server

Set TELLEGEN_ALLOW_FALLBACK=1 to run without staged demo data:

TELLEGEN_ALLOW_FALLBACK=1 cargo run -p tellegen-server

WebAssembly Module

npm ci
npm run wasm
npm run build:engine
npm run build:svelte

tellegen frontend demo

npm ci
npm run wasm
npm run build:engine
npm run build:svelte
npm --workspace tellegen-frontend run dev

The Vite dev server proxies /api to http://localhost:8000. apps/web resolves @tellegen/svelte through its built dist/, so build:svelte must run before the dev server starts.

Framework Packages

Preview the package contents before publishing:

npm run pack:engine
npm run pack:svelte

Use @tellegen/svelte for the map, panels, local file flow, and solve card. Use @tellegen/engine for apps that want case parsing and browser solves without the tellegen UI. The hosted app is a private demo workspace that consumes @tellegen/svelte like another Svelte app would.

Data

The ACTIVSg and CATS distributions are downloaded by the operator and are not vendored. With the distributions under ~/Datasets:

scripts/stage-data.sh ~/Datasets

The script stages any complete case pairs it finds into data/. The backend serves the staged subset; if nothing is staged, it exits unless TELLEGEN_ALLOW_FALLBACK=1 is set. That fallback serves two pglib cases with synthetic coordinates for CI and local smoke checks.

Docs

Install mdBook, then build the public docs:

scripts/build-docs.sh

CI pins mdBook to v0.5.3. For local work, any recent mdBook 0.5.x release renders the book.

Local Case Files

Dropped case files stay in the browser. The current public demo does not upload local .m, .raw, .aux, .epc, .pwb, .dss, .pwd, .csv, .json, or .geojson files.

MATPOWER .m, PSS/E .raw, PowerWorld .aux/.pwb, and PSLF .epc files describe balanced network topology; OpenDSS .dss opens the multiconductor viewer. If a balanced case includes complete coordinates, tellegen draws it directly. Otherwise it creates a local synthetic layout and asks the user to place it on the map. PowerIO classifies dropped JSON by content. A stored generation-2 pio-ir document opens by its value type: a balanced network or DC OPF instance becomes a case, while supported multiconductor networks, instances, and solutions open in the viewer. AC PF and AC OPF instance modules remain unopened until the browser exposes those formulation choices. Transmission exchange JSON opens as a balanced case, and BMOPF or PowerModelsDistribution documents open the multiconductor viewer. The retired bare model-JSON shape is unrecognized; regenerate it from source data as pio-ir. Tellegen does not define a separate saved case format. Other unrecognized JSON falls through to the geographic reader.

After a parsed local case has coordinates, either from the file, a geographic file, or manual placement, tellegen solves the selected formulation in browser WebAssembly. Local case files do not call the tellegen backend solve endpoints.

Manual Placement

For case files with no coordinates, such as a plain case14.m, tellegen computes a deterministic topology layout from buses and in-service branches. The user then clicks the map to center that synthetic layout at the chosen location.

The placed local case can be moved later with the case panel move action. The first version uses explicit click placement rather than drag movement.

After placement, the local case enters the same bus selection and demand slider workflow as the bundled demo cases. The solve card reports the browser solve time and does not show backend iterations.

Geographic Files

Some case files contain network topology but keep map coordinates in separate GIS files. tellegen accepts those files as local geographic files: drop the case file with one or more .csv, .json, or .geojson files, or drop the geographic files after selecting a parsed local case.

All files stay in the browser. The tellegen backend does not receive dropped case files or geographic files.

Parsing is powerio’s GeoLayer tolerant reader, running in WebAssembly: it accepts headerless OpenDSS buscoords CSV, CSV and JSON records with the aliased field names below, and GeoJSON Point/LineString features, and it rejects input carrying no usable coordinates. Applied coordinates land on the network itself (Bus.location, Branch.route), so a saved PowerIO case module or an exported case carries the placement on screen. Loading that module starts a fresh runtime Study at the saved point. Its history records transformations applied to the stored value. The case panel can also download the current layout as a .geo.json layer that PowerIO and Tellegen read back.

Bus Coordinates

The geographic file must identify buses by the same ids used in the case file. CSV and JSON records can use these field names:

MeaningAccepted fields
Bus idbus_i, bus, bus_id, bus number, number, id
Latitudelat, latitude, y
Longitudelon, lng, longitude, x

Example:

bus_i,Lat,Lon
1,37.77243572,-122.2429162
2,37.77848161,-121.6259513

A geographic file does not need to cover every bus: matched buses place, and the panel reports the matched and unmatched counts. Buses left without coordinates are omitted from the map with a warning; a file that matches nothing is rejected and the case stays placeable. Records can also match by powerio row uid (buses:3) or by case insensitive bus name.

Branch Paths

Branch geometry is optional. Without branch paths, tellegen draws straight segments between placed buses.

CSV and JSON branch records can use:

MeaningAccepted fields
Branch idbranch, branch_id, branch number, cats_id, id
From busf_bus, from, from_bus
To bust_bus, to, to_bus
Endpoint coordinatesLat1, Lon1, Lat2, Lon2 and lowercase variants

GeoJSON LineString features are also accepted. The reader matches a route by uid or branch id when present, then by the unordered from/to bus pair. A LineString endpoint can also provide bus coordinates for its f_bus and t_bus properties. Applied routes land in Branch.route and render as polylines instead of straight segments.

Piecewise Costs

A dropped case with MATPOWER model 1 piecewise linear generator costs solves the declared convex curve exactly (formulations). Malformed rows and rows with decreasing segment slopes are rejected before a solve.

Display Files

PowerWorld .pwd files are display overlays. Dropped alone, they show substation symbols at approximate projected positions. Dropped alongside a case file that has no coordinates, the substation points join onto buses through the SubNum field on the bus rows and fill the case’s positions; a .pwd whose numbers match nothing stays a separate overlay entry.

Data Provenance

The demo cases are synthetic grids from TAMU ACTIVSg and CATS. They are fictional networks built on geographic footprints. ACTIVSg200 and ACTIVSg500 coordinates come from PowerWorld aux exports. ACTIVSg7000 and CATS coordinates come from GIS bus CSVs. These positions are not surveyed infrastructure locations.

caseterritorybusesbranchesfiles
ACTIVSg200central Illinois200245case_ACTIVSg200.m + ACTIVSg200.aux
ACTIVSg500South Carolina500597case_ACTIVSg500.m + ACTIVSg500.aux
ACTIVSg7000Texas67179140Texas7k_20210804.m + Texas7k_lat_long.csv
CATSCalifornia887010823CaliforniaTestSystem.m + CATS_buses.csv + CATS_lines.json

The MATPOWER file feeds the DC OPF. For ACTIVSg200 and ACTIVSg500, the aux file supplies the coordinates. For ACTIVSg7000, the bus coordinate CSV supplies latitude and longitude. Operators download the ACTIVSg distributions from electricgrids.engr.tamu.edu and stage them with scripts/stage-data.sh; the repository does not vendor them.

CATS comes from the WISPO POP CATS repository. The server reads CaliforniaTestSystem.m for the network, CATS_buses.csv for bus latitude and longitude, and CATS_lines.json for branch paths. The staging script also copies CATS_gens.csv when present; the current map does not render a separate generator geometry layer.

Aux coordinate forms

PowerWorld aux exports have used two coordinate layouts:

  • ACTIVSg complete case exports repeat substation latitude and longitude on each bus row in Latitude:1 and Longitude:1.
  • Later exports can leave the bus latitude and longitude columns empty and reference the Substation table through SubNumber.

powerio promotes the bus row form (Latitude:1/Longitude:1) into the typed Bus.location at parse; tellegen keeps the two fallbacks upstream does not cover — bare Latitude/Longitude bus columns and the Substation table join — so dropped files of either form resolve when the data is present.

Buses Sharing Coordinates

Multiple buses can share one substation coordinate. tellegen spreads each group on a deterministic ring of about 400 m around the substation point, ordered by bus id. The group remains visually associated with the substation at network zoom, and individual buses remain hoverable at street zoom.

Explicit Fallback

The embedded fallback (deployment) serves two PGLib cases whose coordinates are synthetic and labeled as synthetic.

Display Data

Case files and display files are separate inputs. MATPOWER .m, PSS/E .raw, and PowerWorld .aux files describe the network. JSON files are accepted as geographic files in this release. PowerWorld .pwd files describe a one-line diagram. tellegen reads coordinates from case files when they exist and reads diagram positions from display files when they are dropped.

PowerIO 0.11 routes display data through its universal parser. A .pwd source passed to powerio::parse returns a generation-2 PioModule whose value is a PioValue::GeoLayer; the older parse_display, parse_display_file, and DisplayData::PowerWorld(PwdDisplay) integration surface no longer exists.

Reading .pwd

crates/tellegen-wasm/src/lib.rs retains its browser-facing parse_display(bytes, format) wrapper, but implements it with the universal powerio::parse entry point and narrows the returned module to GeoLayer. The frontend reads .pwd files with arrayBuffer() and passes a Uint8Array to the WebAssembly module.

A dropped .pwd creates a local display entry with substation points only. It does not create buses, branches, or a solvable case.

Projecting .pwd coordinates

PowerWorld .pwd coordinates are diagram coordinates, not latitude and longitude. TAMU-generated diagrams use Web Mercator scaled by one constant, K = 535.81608, with both axes expressed in degrees:

x = K * lon
y = K * mercdeg(lat)
mercdeg(lat) = (180 / pi) * ln(tan(pi / 4 + lat * pi / 360))

The inverse transform is PowerIO’s to_lonlat_from_pwd_mercator; the wasm parse_display export returns each substation with its projected lon/lat alongside the raw diagram coordinates, so the frontend never reimplements the constant. This places checked ACTIVSg diagrams within about 0.02 degrees of their corresponding named cities. Hand-edited diagrams can differ, so tellegen labels these positions as approximate.

The canonical geographic document

PowerIO’s standalone geographic document (GeoLayer) carries element points and branch routes in one coordinate space, keyed by uid, external id, name, or the branch endpoint pair, written as a GeoJSON FeatureCollection with the powerio_geo foreign member (.geo.json). tellegen consumes it rather than defining a separate format: dropped geographic sidecars parse through its tolerant reader, applied coordinates land on the network (Bus.location, Branch.route), and the case panel exports the current layout as a .geo.json layer with provenance stamped.

On top of it, a dropped .pwd sibling already parses to GeoLayer and fills missing case coordinates through the SubNum join (apply_substation_points, projected by to_lonlat_from_pwd_mercator), and layouts computed by tellegen export with synthetic/manual provenance.

Synthetic Layout

Most public OPF test cases do not include geographic coordinates. tellegen uses synthetic topology layouts in two places: the explicit pglib dev fallback, and local files parsed in the browser that the user places on the map. The tellegen backend API marks fallback coordinates with synthetic_coords: true; local files are labeled in the panel as synthetic layouts.

Tree Short-Circuit for Radial Networks

Before the force pass, the browser layout counts independent cycles on the deduplicated graph (open switches are already excluded). When the count is at most max(2, ceil(0.02 * n)), the graph is a tree or nearly one — the shape of a distribution feeder — and it is drawn as a tidy tree instead: depth grows along one axis from the root, and each subtree occupies its own contiguous band of leaf slots on the other, so the drawing is planar and the longest chain renders as a straight trunk. The root is a source bus when the case identifies one (multiconductor cases pass their sources as hints), else a BFS diameter endpoint. Chords of a near-tree draw as plain segments between their placed endpoints. Meshed networks fall through to the force pass below.

Deterministic Seed

The layout starts from a golden angle spiral on the unit square. A deterministic jitter of about 1e-4, computed from bus index sines, breaks exact ties. No random number generator is used.

Force Refinement

tellegen refines the seed with a Fruchterman–Reingold force pass on the unit square:

  • repulsion k^2 / d between each bus pair, with k = sqrt(1 / n);
  • attraction d^2 / k along branches;
  • capped displacement with a linearly cooled temperature.

The force pass is O(iterations * n^2). In the tellegen backend it runs once at boot and the resulting network payload is cached. In the browser, local dropped files use the same deterministic force idea with a lighter seed and iteration count, then scale into a small footprint around the user’s chosen map point. The fit into that footprint uses one uniform scale for both axes, so a long feeder keeps its aspect ratio instead of stretching to a square. Once placed, the local network solves in browser WebAssembly, and the layout is stamped into the retained PowerIO module (Bus.location, provenance synthetic), so saved modules and case exports carry the placement and the panel can download it as a .geo.json layer.

Determinism

The pipeline uses source order, fixed jitter, and fixed iteration counts. Identical input files produce identical coordinates across boots.

Tests

Rust tests check that:

  • every coordinate lies inside the bounding box;
  • no two buses remain stacked in the test fixture.

References

  • T. M. J. Fruchterman and E. M. Reingold, “Graph drawing by force-directed placement,” Software: Practice and Experience, 1991.

Architecture

tellegen is a differentiable power flow and optimal power flow engine written in Rust for native targets and WebAssembly. The public browser packages are @tellegen/engine, @tellegen/svelte, and @tellegen/webmcp; the SvelteKit hosted demo is one private consumer.

Repository layout

A Cargo workspace and a web app, side by side.

  • crates/tellegen: the engine. It parses a case through powerio and solves the requested supported formulation. One result envelope carries the fields and sensitivities that formulation defines.
  • crates/tellegen-wasm: the wasm-bindgen adapter that exposes the engine to the browser, built with wasm-pack.
  • crates/tellegen-server: a native HTTP server that serves the bundled cases and the static app.
  • crates/tellegen-cli: a command line front end over the engine’s JSON API.
  • crates/benchmarks: a private harness that runs the PGLib-OPF corpus for validation and timing.
  • packages/engine: the public browser engine package, generated TypeScript contracts, and browser wasm transport.
  • packages/svelte: the public Svelte component package for maps, panels, local case files, and browser solves.
  • packages/webmcp: WebMCP tool definitions, validation, and registration lifecycle.
  • apps/web: the private SvelteKit hosted demo that consumes @tellegen/svelte.
  • examples/browser-minimal: a minimal downstream app that imports @tellegen/engine directly.
  • examples/svelte-minimal: a minimal downstream app that imports @tellegen/svelte.

powerio owns parsing and the network and display formats; the engine and the app depend on it.

The engine

crates/tellegen solves four formulations through one interface:

  • DC power flow and DC OPF: a B–θ linear/quadratic program;
  • AC power flow: a polar Newton solve; and
  • SOCWR: the Jabr second-order cone relaxation of AC OPF, in W-space.

The formulations share one result envelope, but they do not claim the same quantities. DC power flow returns angles and branch flows without prices, optimized dispatch, or sensitivities. DC OPF adds dispatch, objective value, LMPs, and KKT sensitivities. AC power flow returns voltages and nodal injections with Newton sensitivities. SOCWR exposes the quantities and conic KKT sensitivities defined by the relaxation. Formulations that implement the Differentiable contract accept an output Operand and input Parameter; the common driver solves the retained KKT or Newton system for the requested forward or adjoint columns.

The engine is Rust and compiles to WebAssembly, so the same code runs natively and in the browser. The convex solves use Clarabel; the sensitivities use faer. The full nonlinear AC OPF is on the desktop and mobile roadmap. Its planned interior point solver uses threads; the current browser solver build is single threaded.

The two API faces

One numerical core, two faces that share a driver and a result type:

  • Stateless: solve_module(module, request) and capabilities_json(). Each call reads a PowerIO module, solves its declared problem instance, and returns.
  • Stateful: the Study. It starts from a PowerIO module and builds the private solver workspace once. commit applies a set of NetworkEdits and re-solves exactly, optionally returning the requested sensitivity columns in the same solve; preview returns a first order update at the committed point with no re-solve.

Browser packages

packages/engine is the reusable package surface. It exports generated contracts, case and display parsing helpers, module based solves, capabilities, the browser Study, and the browser wasm transport. It has no SvelteKit dependency.

packages/svelte consumes @tellegen/engine and exports the map, panels, local file flow, solve card, state provider, and full viewer as Svelte components.

packages/webmcp exports tool definitions, runtime validation, and registration through an adapter that does not depend on Svelte or the engine package.

apps/web consumes the Svelte package and keeps demo concerns: routes, SEO, credits, privacy, deployment, and bundled case pages.

In the browser

@tellegen/engine ships one wasm package built from crates/tellegen-wasm (the conic feature): DC power flow, DC OPF, AC power flow, SOCWR, the Study, and the sensitivity columns. A browser that cannot load it does not solve; the hosted demo shows a notice, and the server’s compute endpoints exist as an opt-in fallback (TELLEGEN_SERVER_COMPUTE).

The Svelte package and the hosted app use Study for DC OPF, AC power flow, and SOCWR. preview returns a first order update for the quantities the chosen formulation defines; commit performs an exact re-solve and can return the displayed sensitivity column. DC power flow uses the stateless solve path, and full nonlinear AC OPF is unavailable. Supported solvable case files run in the browser and are not uploaded.

Sources

  • Rust to WebAssembly: wasm-bindgen, wasm-pack
  • Solvers and linear algebra: Clarabel.rs, faer
  • Convex relaxation: R. A. Jabr, “Radial distribution load flow using conic programming,” IEEE Transactions on Power Systems, 21(3), 2006.
  • Svelte: $state

Release Architecture

The public browser surfaces are @tellegen/engine, @tellegen/svelte, and @tellegen/webmcp.

Stable release surfaces:

  • crates/tellegen Rust API layer, including the serde request and response shapes in src/api.rs;
  • crates/tellegen-wasm wasm adapter;
  • packages/engine TypeScript package, generated TypeScript types, and browser wasm entry points;
  • packages/svelte Svelte component package;
  • packages/webmcp WebMCP tool definitions and runtime validation without a UI framework dependency;
  • examples under examples/.

The hosted demo under apps/web is one consumer of the packages. It keeps routes, SEO, credits, privacy, and deployment specific behavior.

JavaScript Workspace

The repository uses npm workspaces with one root package-lock.json for:

  • packages/engine;
  • packages/webmcp;
  • packages/svelte;
  • apps/web;
  • examples/browser-minimal; and
  • examples/svelte-minimal.

Install JavaScript dependencies from the repository root:

npm ci

Root scripts define the package order:

  • npm run wasm builds both wasm packages into packages/engine;
  • npm run build:engine builds @tellegen/engine;
  • npm run build:webmcp builds @tellegen/webmcp;
  • npm run build:svelte builds @tellegen/svelte;
  • npm run build:example builds both downstream examples;
  • npm run check:web, npm run build:web, and npm run smoke:web gate the hosted demo;
  • npm run pack:engine previews the engine npm package contents;
  • npm run pack:webmcp previews the WebMCP npm package contents;
  • npm run pack:svelte previews the Svelte npm package contents.

Versioning

@tellegen/engine, @tellegen/svelte, and @tellegen/webmcp use independent package versions.

Before 1.0, releases can refine public APIs while preserving the examples and hosted demo behavior. After 1.0, breaking public TypeScript, Svelte prop, or Rust API changes require a semver major version.

Examples of breaking changes after 1.0:

  • removing or renaming public exports;
  • removing or renaming request or response fields;
  • changing enum tags, formulation ids, solve status tags, operand tags, or parameter tags;
  • changing field units or meanings;
  • tightening optional fields to required fields; and
  • changing serialized request or response shapes.

Nonbreaking changes can ship in a minor version:

  • adding optional fields;
  • adding formulations, operands, parameters, statuses, or helper exports while preserving existing meanings; and
  • adding component props with defaults.

Patch versions are for bug fixes and docs that do not change public APIs.

Saved cases

Tellegen saves the materialized network as a stored PowerIO module. The module keeps applicable records, severs source linkage invalidated by edits, and adds descriptive edit history. Loading it starts a fresh runtime Study at the saved point. Its history records transformations applied to the stored value.

Exact OPF results are stored as PowerIO solution modules containing the instance that was solved.

Releasing

Package versions and the crate version move independently, so two release bots drive the pipeline. A release that touches one surface publishes that surface only.

Nobody cuts a tag by hand. This repository stores no registry token. Both registries authenticate with OIDC, and each exchange runs in a deployment environment (crates-io, npm). Reviewing and merging the generated version pull request is the final human publication gate. Publication after that merge is unattended.

Each environment allows deployments from main only and has no required reviewer. Set the same environment on the registry’s trusted publisher, along with the repository and workflow filename. The workflow also rejects non-main refs; the publisher binding makes the registry enforce that identity.

Enabling the pipeline

The release workflows do nothing until the TELLEGEN_RELEASE_ENABLED repository variable is true. What they need lives outside the repository. Set the variable after you make all of these:

  1. a crates-io environment and an npm environment, each with a deployment branch rule limited to main and no required reviewers;
  2. a crates.io trusted publisher for tellegen, set to this repository, release-crate.yml, and the crates-io environment;
  3. an npm trusted publisher for @tellegen/engine, @tellegen/svelte, and @tellegen/webmcp, each set to this repository, release-npm.yml, and the npm environment. The publisher binds to the workflow filename. If you rename the workflow, publishing stops until you change the publisher;
  4. a GitHub App on this repository with contents: write and pull-requests: write. Put its id in the RELEASE_PLZ_APP_ID variable and its private key in the RELEASE_PLZ_APP_PRIVATE_KEY secret. Both release bots use it so their version pull requests start CI. Pull requests opened by GITHUB_TOKEN do not start workflows.

Protect main with a repository ruleset that requires pull requests, at least one approval, approval of the most recent reviewable push (or dismissal of stale approvals), an up-to-date branch, and the CI checks before merge. Block force pushes and deletion. Do not give the Release App a ruleset bypass. Those protections ensure an approval cannot survive a bot refresh with different release contents and make the version pull-request merge the publication gate.

If a package or crate does not yet exist at its registry, publish its first version manually before configuring the trusted publisher. Later releases use this pipeline end to end.

Delete any NPM_TOKEN or CARGO_REGISTRY_TOKEN repository secrets. The workflows do not read long-lived registry credentials.

Packages

@tellegen/engine, @tellegen/svelte, and @tellegen/webmcp release through changesets.

A change to any of these packages needs a changeset. Run npm run changeset and commit the file it writes. See .changeset/README.md.

On a push to main, .github/workflows/release-npm.yml keeps a “Release packages” pull request open. That pull request applies every pending changeset: version bumps, changelog entries, the engine CONTRACT_VERSION, and the lockfile. Merge it to run the gates and publish. Tags take the form @tellegen/<name>@X.Y.Z.

The workflow selects version or publish mode before it requests privileged permissions. Versioning runs without the GitHub App, validates an exact output allowlist, and passes a SHA-256-verified patch to a fresh runner. That runner revalidates and commits the patch before minting the App token; only git and gh run afterward. Publishing builds immutable tarballs in an unprivileged job, then gives only the final npm job the npm environment and OIDC permission. Merging the version pull request is therefore the only manual release action.

@tellegen/svelte resolves @tellegen/engine from the registry, so a release that moves both publishes the engine first.

Crate

tellegen is the only crate that publishes to crates.io. tellegen-wasm, tellegen-server, tellegen-cli, and benchmarks carry publish = false, and the release-plz workspace defaults to release = false. A new crate needs an explicit package opt-in.

On a push to main, .github/workflows/release-crate.yml keeps a pull request open that bumps the version. Merge it to run the gates, continue the existing vX.Y.Z tag series, make the GitHub release, and publish. The configuration sets release_always = false, limiting publication to a merged release-plz pull request. The unprivileged gate runs cargo package --locked; the OIDC-enabled release uses the ordinary verified Cargo publish path against that exact commit. release-plz obtains its short-lived crates.io credential directly from OIDC.

The crate update uses the same sealed-patch privilege boundary as package versioning. Its fixed release-plz-main branch is intentional: with release_always = false, release-plz recognizes a release commit by the release-plz- head prefix. Merge this generated pull request with a normal merge commit, not squash, so its release commit remains unambiguous if another change reaches main nearby.

An emergency repair to a generated crate release must retain its release-plz-* branch name; that is how release_always = false recognizes the merged release commit.

Inspecting an artifact

.github/workflows/package-inspect.yml is a manual run. It builds the artifacts a release would upload, cargo package and all three npm pack tarballs, and attaches them to the run. It publishes nothing.

Changelogs

Each surface keeps its own generated changelog: crates/tellegen/CHANGELOG.md, packages/engine/CHANGELOG.md, and packages/svelte/CHANGELOG.md, and packages/webmcp/CHANGELOG.md. The root CHANGELOG.md is the curated view across all four and the only record for releases before the split.

CI Gates

.github/workflows/gates-rust.yml and .github/workflows/gates-js.yml define what green means. CI calls both on every pull request. The crate release calls the Rust gates, and the npm release calls both. Add a gate to those files, not to a caller.

The Rust gates are cargo fmt --check, clippy with warnings denied on the shipping crates, cargo-deny, the EPL guard, the workspace tests, the engine’s conic path, tellegen-wasm built with conic, and cargo package --locked for the published crate. The wasm test matters because tellegen-wasm declares default = [], so the workspace run skips the tests that assert an untrusted package or case rejects rather than panicking.

The JavaScript gates install once from the root lockfile, check and pack packages/webmcp, build and check packages/engine before packages/svelte, run the engine import smoke test, check the Svelte package through a packed temporary consumer, build the hosted demo, and run its browser tests. just ci runs everything locally.

Formulations

Tellegen solves DC power flow, DC OPF, AC power flow, and the Jabr SOCWR relaxation through the PowerIO module boundary. The shared response has optional fields for the quantities each formulation produces. Economic fields are present only when the declared objective gives them that interpretation. Supported implicit derivatives use the contract described in the sensitivity contract.

DC power flow and DC OPF (B–θ)

The linearized power flow couples bus angles $\theta$ to injections through the susceptance-weighted graph Laplacian

$$ B = A^\top\operatorname{diag}(b)A, \qquad B\theta = p, $$

where $A$ is the branch by bus incidence and $b$ contains positive solver weights derived from the public branch susceptances. The OPF minimizes generation cost subject to the network balance and the thermal and generation limits; it is a convex quadratic program solved with Clarabel. solve_module_json is the portable entry. Rust callers that already own a DcOpfInstance can use solve_instance.

MATPOWER model 2 quadratic generator costs are read directly. Convex model 1 piecewise linear costs use one epigraph variable and one inequality per segment, so dispatch, objective values, prices, and supported implicit derivatives refer to the declared curve. Malformed and nonconvex model 1 rows are rejected before the program is assembled. At a breakpoint or an active set change, the marginal value or its derivative need not be unique; the sensitivity API reports the local KKT linearization and numerical checks identify stencils that cross a different active set.

Branch angle-difference bounds are enforced in radians after normalization. MATPOWER’s unconstrained -360/360 spelling and an unset 0/0 pair become exactly -60/+60 degrees. When a branch has no thermal rating, Tellegen synthesizes its fallback rating from that same 60 degree window and the terminal voltage bands. Explicit tighter source bounds are preserved.

AC power flow (polar)

The nodal power balance in polar coordinates,

$$ S_i = V_i \sum_j \overline{Y_{ij}} \overline{V_j}, $$

is solved by Newton–Raphson on the reduced system $\partial(P, Q)/\partial(\theta, V_m)$. Buses are typed slack / PV / PQ (PV and slack buses hold the generator voltage setpoint; PQ buses solve for both angle and magnitude), and the solve takes damped steps with a backtracking line search from the setpoint start plus a few perturbations, keeping the lowest-residual result. Select acpf in solve_module_json.

Conic SOCWR (Jabr)

The Jabr second-order cone relaxation lifts the voltage product to W-space variables $w_i = |V_i|^2$, $w^r_{ij} = \Re(V_i \overline{V_j})$, $w^i_{ij} = \Im(V_i \overline{V_j})$, with the rotated cone coupling

$$ (w^r_{ij})^2 + (w^i_{ij})^2 \le w_i w_j. $$

The relaxation is a convex lower bound on AC OPF, solved with Clarabel’s second-order cone support. It uses the same exact quadratic and convex piecewise linear generator cost representation as DC OPF. Select socwr in solve_module_json.

These formulations compile to native Rust and WebAssembly.

The sensitivity contract

Every sensitivity comes from a converged solution by the implicit function theorem on a residual $K(z, p) = 0$:

$$ \frac{dz}{dp} = -\left(\frac{\partial K}{\partial z}\right)^{-1} \frac{\partial K}{\partial p}. $$

Operand / Parameter vocabulary

A sensitivity is named for the physical quantities it relates, not a formulation’s internal variables. The operand (what the derivative is of) and parameter (what it is taken with respect to) are the orthogonal sub-axes (active/reactive, from/to, voltage representation):

  • Operand: Price, Dispatch, Flow { power, end }, Voltage(kind).
  • Parameter: Demand, Cost, LineLimit, SeriesAdmittance, ShuntAdmittance, VoltageBound, GenBound, Transformer, Switching.

Each formulation maps a request to its own KKT rows or reports the combination as unsupported.

The object-safe Differentiable trait

One trait exposes the pieces the shared driver needs: the system Jacobian $K$, the parameter column $\partial K/\partial p$, the operand selector $S$, and the per-formulation regularization. It is object safe by construction (&dyn Differentiable), so the DC KKT, the AC Newton system (AcNewton), and the conic KKT (ConicKkt) all plug into one driver.

Forward and adjoint

The single driver runs whichever direction is cheaper:

  • forward solves $K X = \partial K/\partial p$ once per parameter, reads the operand rows;
  • adjoint solves $K^\top Y = S^\top$ once per operand, contracts with $\partial K/\partial p$.

The two are algebraically identical; Mode::Auto picks the smaller dimension.

Per-cell parity classes

Finite differences validate the analytic columns per cell:

  • clean: cells routed through active power, relative error $< 10^{-3}$.
  • Jabr-coupled / soft: squared-voltage or reactive cells, looser (the cone’s degenerate directions).
  • norm-floor skip: columns below the regularization floor carry no resolvable derivative and are not compared.

See Validation for how these classes are checked, and Methodology for how the figures are produced.

Methodology

The benchmarks crate is a private workspace member that drives tellegen’s public API over the PGLib-OPF corpus for validation and timing. It never vendors the corpus and reads it from $PGLIB_OPF_PATH, skipping when the path is absent.

Corpus

PGLib-OPF v23.07 at $PGLIB_OPF_PATH: 66 base, 66 congested (api/), and 66 small angle (sad/) MATPOWER files, spanning a range of bus counts at baseMVA 100. PGLib data is CC BY 4.0 (see References).

What is driven

Per (case, variant), the harness parses a PowerIO module and declares the calculation as DcOpfInstance, AcOpfInstance, or AcPfInstance. It then drives the same typed facade available to downstream consumers:

  • DC OPF: solve_instance (objective, dispatch, LMPs);
  • conic SOCWR: solve_ac_instance (objective, gap, W-space primals);
  • AC power flow: solve_ac_pf_instance (convergence, residual);
  • sensitivities: the same typed solve entries in forward and adjoint modes.

Timing

The engine exposes iteration traces and residuals rather than wall time, so the harness times the public calls itself, per stage (parse / build / solve / sensitivity). Solves run on one thread for clean timing.

Metrics

  1. OPF correctness vs the published reference: the DC objective, the SOCWR relaxation lower bound, and the SOC gap, rolled up into a per-case reproduction verdict (see Validation).
  2. Sensitivity parity: adjoint equals forward, and central finite differences against the analytic columns, classified per parity class (see the sensitivity contract).
  3. Feasibility / convergence: per-case status, interior-point iterations, residuals.
  4. Performance: wall time per stage, and a scaling curve against bus count.
  5. Coverage: a per-case status table, including the size caps applied by the harness flags.

Baselines

Correctness is checked against two independent baselines:

  • the published PGLib reference solves (PowerModels.jl with IPOPT), tabulated per case and variant in $PGLIB_OPF_PATH/BASELINE.md; and
  • finite differences, which validate the analytic sensitivity columns by perturbing the public network fields.

Reproducibility

The harness records the source revision, toolchain, dependency versions, host, and invocation used for each run. It writes results.json (one record per (case, variant)) and results.csv. With its book flag it also writes the markdown snapshot to docs/src/benchmark-results.md. The repository keeps that generated snapshot, and a rerun can be compared with its recorded provenance:

cargo run -p benchmarks --release -- [flags]

Validation

tellegen is checked against the published PGLib reference solves. For each case and variant, $PGLIB_OPF_PATH/BASELINE.md tabulates the PowerModels.jl with IPOPT reference: DC ($/h), AC ($/h), QC Gap (%), and SOC Gap (%). The Methodology chapter describes how the harness produces the comparison; this chapter records what each comparison asserts. The measured figures are in benchmark results, a committed snapshot of a full harness run with its provenance.

DC objective

tellegen’s declared DC objective, including its constant cost term, is compared against the published DC baseline. Both values come from the same PGLib cost data. Per-unit power scaling cancels; tellegen does not assign a currency to objectives from other sources.

Relaxation lower bound

The SOCWR objective (socwr_opf objective) must be a lower bound on the published AC optimum (AC ($/h)):

$$ \text{socwr} \le \text{AC} + \text{tol}. $$

A bound violation is a correctness failure.

SOC gap

The gap is

$$ \text{gap} = \frac{\text{AC} - \text{socwr}}{\text{AC}} \cdot 100, $$

compared against the baseline SOC Gap (%). tellegen’s SOCWR is the Jabr SOC relaxation, the same family as the baseline SOC column, so a near-zero difference in gap is the expected result. The published SOC bound is recovered as $\text{AC} \cdot (1 - \text{SOC gap}/100)$.

All three variants are exercised: typical, congested (API), and small-angle (SAD). tellegen’s AcNetwork carries the angle difference limits and the SOCWR enforces them in W-space, so the SAD relaxation tracks the published SAD SOC. Where a case shows a large SOC gap, that measures relaxation quality, not a failure.

Benchmark results

Generated by the benchmarks harness over PGLib-OPF v23.07. Regenerate with cargo run -p benchmarks --release -- --out <dir> and copy the snapshot here. The provenance table records the toolchain used for this run.

Provenance

tellegenrustcclarabelfaerpowerioos/arch
178da991.96.0 (ac68faa20 2026-05-25)0.11.10.24.40.7.1macos/aarch64

PGLib v23.07 (arXiv:1908.02788, CC BY 4.0). Command: ./target/release/pglib-bench --out target/pglib-bench-20260720 --book.

Coverage summary

198 (case, variant) rows.

statuscount
caveat50
failed2
skipped3
solved143

Reproduction of PGLib

Corpus roll-up of the per-case marks in the table below. “Reproduced” is an objective match (DC within 1%) or, for the SOCWR relaxation, a valid lower bound whose gap matches the published SOC gap. “Consistent” is an acceptable non-exact agreement: a DC the baseline also reports infeasible, or a SOCWR lower bound with a looser gap. “Mismatch” is a differing objective or a lower-bound violation.

formulationreproducedconsistentmismatchwith baseline
DC OPF148450193
SOCWR (lower bound)18360189

OPF correctness vs PGLib BASELINE

Tellegen’s objective against the published value, per formulation: the DC OPF (constant cost included) vs the published DC, and the SOCWR relaxation (a lower bound on AC) vs the published AC. Δgap is Tellegen’s SOC gap minus the published SOC gap. The reproduces column is DC·SOC: ✓ matches the published objective (DC within 1%), inf✓ infeasible consistent with the published inf., ✓lb a valid lower bound whose gap matches the published SOC, lb a valid bound with a looser gap, ✗ a mismatch (a converged but differing objective), — no baseline.

casevarbusesDC objectivePGLib DCSOCWR objectivePGLib ACΔgapreproduces
case3_lmbdapi310444.410444.010194.911242.0-0.01✓·✓lb
case3_lmbdsad35856.05856.05736.25959.3-0.01✓·✓lb
case3_lmbdtyp35695.95695.95736.25812.6-0.01✓·✓lb
case5_pjmapi578025.278025.077571.478950.0-0.00✓·✓lb
case5_pjmsad525164.926109.0-0.00inf✓·✓lb
case5_pjmtyp517479.917480.014999.717552.0-0.01✓·✓lb
case14_ieeeapi144797.64797.65691.85999.4-0.00✓·✓lb
case14_ieeesad142179.22776.8-0.01inf✓·✓lb
case14_ieeetyp142051.52051.52175.72178.1-0.00✓·✓lb
case24_ieee_rtsapi24148845.5148850.0149168.3161220.0-0.00✓·✓lb
case24_ieee_rtssad2478122.578122.069578.976918.0-0.01✓·✓lb
case24_ieee_rtstyp2461001.261001.063344.663352.0-0.01✓·✓lb
case30_asapi303092.13092.12767.84996.2-0.01✓·✓lb
case30_assad30825.9897.40.08inf✓·✓lb
case30_astyp30767.6767.6802.7803.1-0.00✓·✓lb
case30_ieeeapi3016145.116145.017057.818037.0-0.00✓·✓lb
case30_ieeesad307412.68208.5-0.00inf✓·✓lb
case30_ieeetyp307472.87472.86662.28208.5-0.00✓·✓lb
case39_epriapi39252754.6252750.0253130.4256770.0-0.00✓·✓lb
case39_eprisad39150669.9150670.0147360.5148340.0-0.01✓·✓lb
case39_eprityp39136889.7136890.0137654.1138420.0-0.01✓·✓lb
case57_ieeeapi5734081.134081.033271.636242.0-0.00✓·✓lb
case57_ieeesad5738391.438663.0-0.01inf✓·✓lb
case57_ieeetyp5734773.034773.037529.737589.0-0.00✓·✓lb
case60_capi60176378.6176380.0181189.2185000.0-0.01✓·✓lb
case60_csad60108485.3113500.00.05inf✓·✓lb
case60_ctyp6090700.190700.092637.092694.0-0.01✓·✓lb
case73_ieee_rtsapi73472183.2472180.0488422.7509850.0-0.01✓·✓lb
case73_ieee_rtssad73232679.1232680.0212280.3227600.00.00✓·✓lb
case73_ieee_rtstyp73183003.7183000.0189706.1189760.0-0.01✓·✓lb
case89_pegaseapi89118452.5118630.0113367.9129570.0-0.01✓·✓lb
case89_pegasesad89106491.2107290.00.01inf✓·✓lb
case89_pegasetyp89104919.0105040.0106480.8107290.00.00✓·✓lb
case118_ieeeapi118231291.9231290.0184303.2249610.0-0.01✓·✓lb
case118_ieeesad11896535.5105160.00.03inf✓·✓lb
case118_ieeetyp11893100.793101.096334.797214.0-0.01✓·✓lb
case162_ieee_dtcapi162111570.9111570.0115655.9120880.0-0.01✓·✓lb
case162_ieee_dtcsad162106287.8106290.0101655.6108690.0-0.01✓·✓lb
case162_ieee_dtctyp162101462.3101460.0101654.9108080.0-0.01✓·✓lb
case179_gocapi1791813209.81813200.01727618.51883400.00.01✓·✓lb
case179_gocsad179754005.8762530.0-0.00inf✓·✓lb
case179_goctyp179751881.6751880.0753089.1754270.0-0.00✓·✓lb
case197_snemapi19715689.715690.016203.016363.0-0.00✓·✓lb
case197_snemsad1971.51.50.03inf✓·✓lb
case197_snemtyp1971.51.51.51.50.02✓·✓lb
case200_activapi20040129.840130.040692.940700.0-0.00✓·✓lb
case200_activsad20027556.627558.0-0.01inf✓·✓lb
case200_activtyp20027479.627480.027556.627558.0-0.01✓·✓lb
case240_psercapi2404624856.04624800.04636792.94692200.00.00✓·✓lb
case240_psercsad2403237213.73405400.00.01inf✓·✓lb
case240_pserctyp2403271442.23271400.03237193.63329700.0-0.00✓·✓lb
case300_ieeeapi300659786.2659840.0679566.9686040.0-0.01✓·✓lb
case300_ieeesad300527243.9527290.0550597.4565700.00.06✓·✓lb
case300_ieeetyp300517802.2517850.0550393.8565220.0-0.01✓·✓lb
case500_gocapi500646871.6646870.0663185.9688290.0-0.00✓·✓lb
case500_gocsad500454907.9487400.0-0.00inf✓·✓lb
case500_goctyp500440548.6440550.0453838.5454950.0-0.01✓·✓lb
case588_sdetapi588392953.8392950.0387619.1398760.0-0.01✓·✓lb
case588_sdetsad588307081.1329360.00.09inf✓·✓lb
case588_sdettyp588310125.6310130.0306447.9313140.0-0.00✓·✓lb
case793_gocapi793372180.2372180.0321148.8379800.0-0.01✓·✓lb
case793_gocsad793263030.4285800.0-0.00inf✓·✓lb
case793_goctyp793258308.0258310.0256757.9260200.0-0.01✓·✓lb
case1354_pegaseapi13541558525.91558500.01578360.11608200.00.01✓·✓lb
case1354_pegasesad13541236022.11258800.00.24inf✓·✓lb
case1354_pegasetyp13541218182.91218200.01235968.21258800.00.24✓·✓lb
case1803_snemapi180362064.261723.046102.380240.01.70✓·lb
case1803_snemsad180391290.5106340.00.16inf✓·✓lb
case1803_snemtyp180387706.887696.090240.498335.00.20✓·✓lb
case1888_rteapi18881961569.91961600.02013300.12019700.0-0.00✓·✓lb
case1888_rtesad18881353179.31353200.01373847.51413900.00.01✓·✓lb
case1888_rtetyp18881352872.61352900.01373847.41402500.0-0.01✓·✓lb
case1951_rteapi19512411505.22411500.02478404.22490300.0-0.00✓·✓lb
case1951_rtesad19512082719.52092400.00.00inf✓·✓lb
case1951_rtetyp19512031629.32031600.02082711.62085600.0-0.00✓·✓lb
case2000_gocapi20001409993.71410000.01441231.61483900.00.01✓·✓lb
case2000_gocsad2000977751.3992880.00.00inf✓·✓lb
case2000_goctyp2000943042.6943040.0970446.9973430.0-0.00✓·✓lb
case2312_gocapi2312615216.3615220.0546946.8663440.00.03✓·✓lb
case2312_gocsad2312444178.0462350.00.00inf✓·✓lb
case2312_goctyp2312440328.5440330.0432953.7441330.0-0.00✓·✓lb
case2383wp_kapi2383279125.8279130.0279125.8279130.0-0.01✓·✓lb
case2383wp_ksad23831856608.11911200.0-0.00inf✓·✓lb
case2383wp_ktyp23831804090.41804100.01848908.71868200.0-0.01✓·✓lb
case2736sp_kapi2736977821.5977820.0939178.21017800.0-0.01✓·✓lb
case2736sp_ksad27361305707.81326600.0-0.01inf✓·✓lb
case2736sp_ktyp27361276033.71276000.01303994.51308000.0-0.00✓·✓lb
case2737sop_kapi2737755318.3755320.0733986.1788310.0-0.01✓·✓lb
case2737sop_ksad2737776126.6790950.0-0.01inf✓·✓lb
case2737sop_ktyp2737764009.8764010.0775689.9777730.0-0.01✓·✓lb
case2742_gocapi2742505399.5505400.0471741.7609960.0-0.01✓·✓lb
case2742_gocsad2742259696.6259700.0271971.8275710.0-0.00✓·✓lb
case2742_goctyp2742259696.6259700.0271999.6275710.0-0.00✓·✓lb
case2746wop_kapi2746531726.6531550.0511658.6550480.0-0.01✓·✓lb
case2746wop_ksad27461205170.11233700.0-0.01inf✓·✓lb
case2746wop_ktyp27461178363.81178200.01203861.31208300.0-0.00✓·✓lb
case2746wp_kapi2746581827.9581830.0581825.1581830.0-0.01✓·✓lb
case2746wp_ksad27461630588.41666900.0-0.01inf✓·✓lb
case2746wp_ktyp27461581425.11581400.01626493.51631700.0-0.01✓·✓lb
case2848_rteapi28481488985.31489000.01527378.91531100.0-0.01✓·✓lb
case2848_rtesad28481285682.11289000.00.01inf✓·✓lb
case2848_rtetyp28481267732.31267700.01285034.31286600.0-0.01✓·✓lb
case2853_sdetapi28532456065.62456100.02420923.42484300.00.01✓·✓lb
case2853_sdetsad28532033761.72069200.00.01inf✓·✓lb
case2853_sdettyp28532036958.32037000.02033582.32052400.00.01✓·✓lb
case2868_rteapi28682277460.82277500.02334871.02343900.00.01✓·✓lb
case2868_rtesad28682009425.42021300.0-0.00inf✓·✓lb
case2868_rtetyp28681966685.01966700.02007570.82009600.00.00✓·✓lb
case2869_pegaseapi28692966298.32966600.03026118.53063000.00.02✓·✓lb
case2869_pegasesad28692438499.12468700.00.10inf✓·✓lb
case2869_pegasetyp28692386115.82386400.02435400.72462800.00.10✓·✓lb
case3012wp_kapi3012851750.5851750.0728874.0914590.0-0.00✓·✓lb
case3012wp_ksad30122578747.02619500.0-0.00inf✓·✓lb
case3012wp_ktyp30122509001.52509000.02574297.72600800.0-0.01✓·✓lb
case3022_gocapi3022666194.7666190.0594789.6687360.00.02✓·✓lb
case3022_gocsad3022599221.5599220.0584763.7601430.00.00✓·✓lb
case3022_goctyp3022599221.5599220.0584742.8601380.0-0.00✓·✓lb
case3120sp_kapi31201326454.41326500.01178608.61403500.0-0.01✓·✓lb
case3120sp_ksad31202141758.02174900.00.00inf✓·✓lb
case3120sp_ktyp31202087974.92088000.02135975.32148000.0-0.00✓·✓lb
case3375wp_kapi33746272121.16272100.05865209.86364100.0-0.00✓·✓lb
case3375wp_ksad33747319637.37319600.07397595.47438200.0-0.00✓·✓lb
case3375wp_ktyp33747317011.97317000.07397405.57438200.0-0.00✓·✓lb
case3970_gocapi39701227776.41227800.01070893.91749400.0-0.00✓·✓lb
case3970_gocsad3970958294.5965550.0-0.01inf✓·✓lb
case3970_goctyp3970934219.4934220.0957739.2960990.0-0.00✓·✓lb
case4020_gocapi40201082422.11082400.01062324.81281700.0-0.00✓·✓lb
case4020_gocsad4020812671.5889690.0-0.00inf✓·✓lb
case4020_goctyp4020795061.9795060.0812181.5822250.0-0.01✓·✓lb
case4601_gocapi4601796409.8796410.0687104.5871240.0-0.01✓·✓lb
case4601_gocsad46011195553.71195500.0822282.0878180.0-0.00✓·✓lb
case4601_goctyp4601793813.9793810.0821853.5826240.0-0.01✓·✓lb
case4619_gocapi46191010784.91010800.0971704.91068800.0-0.01✓·✓lb
case4619_gocsad4619474687.4484350.0-0.01inf✓·✓lb
case4619_goctyp4619457436.6457440.0472392.3476700.0-0.01✓·✓lb
case4661_sdetapi46612670623.12670600.02623054.32731500.00.00✓·✓lb
case4661_sdetsad46612216647.82261000.00.00inf✓·✓lb
case4661_sdettyp46612216303.92216300.02206725.82251300.0-0.01✓·✓lb
case4837_gocapi48371209606.31209600.01200021.11291900.0-0.01✓·✓lb
case4837_gocsad4837869666.7877120.0-0.00inf✓·✓lb
case4837_goctyp4837850397.1850400.0868168.2872260.0-0.00✓·✓lb
case4917_gocapi49171703464.01703500.01434729.51717400.00.08✓·✓lb
case4917_gocsad49171384325.41384300.01354002.91389000.0-0.00✓·✓lb
case4917_goctyp49171383656.11383700.01353066.31387800.00.00✓·✓lb
case5658_epigridsapi56581306345.01306300.01318822.61326800.00.00✓·✓lb
case5658_epigridssad56581206615.61235800.00.00inf✓·✓lb
case5658_epigridstyp56581195466.51195500.01205418.31207300.0-0.00✓·✓lb
case6468_rteapi64682343290.42343300.02436575.42452700.00.01✓·✓lb
case6468_rtesad64681982819.81982800.02046409.52069700.00.01✓·✓lb
case6468_rtetyp64681982819.81982800.02046150.92069700.00.01✓·✓lb
case6470_rteapi64702604137.72604100.02685118.42720900.00.01✓·✓lb
case6470_rtesad64702139078.92139100.02198690.42241600.00.00✓·✓lb
case6470_rtetyp64702136096.92136100.02198106.42237600.00.01✓·✓lb
case6495_rteapi64952924495.62924500.03015652.03130400.00.01✓·✓lb
case6495_rtesad64952561805.32561800.02604047.93067800.00.01✓·✓lb
case6495_rtetyp64952561788.82561800.02604047.93067800.00.01✓·✓lb
case6515_rteapi65152959744.82959700.03057632.13129200.00.01✓·✓lb
case6515_rtesad65152559524.42559500.02644411.02869800.00.00✓·✓lb
case6515_rtetyp65152559330.72559300.02644404.02825500.00.01✓·✓lb
case7336_epigridsapi73361980097.51980100.01998086.32035500.0-0.00✓·✓lb
case7336_epigridssad73361878684.11888400.0-0.01inf✓·✓lb
case7336_epigridstyp73361855900.11855900.01878683.51882400.0-0.00✓·✓lb
case8387_pegaseapi83874974002.44975900.04637519.25242800.01.42✓·lb
case8387_pegasesad83872595938.72597700.0977279.02803900.05.43✓·lb
case8387_pegasetyp83872501101.02502800.0822867.92771400.06.20✓·lb
case9241_pegaseapi92416814526.06816300.06840605.97067900.00.27✓·✓lb
case9241_pegasesad92416084311.76318500.01.24inf✓·lb
case9241_pegasetyp92416027173.86028700.06075800.76243100.00.14✓·✓lb
case9591_gocapi95911470245.01470200.01346495.01570300.0-0.01✓·✓lb
case9591_gocsad95911055855.41167400.0-0.01inf✓·✓lb
case9591_goctyp95911030938.81030900.01055119.51061700.0-0.00✓·✓lb
case10000_gocapi100002499133.42499100.0✓·—
case10000_gocsad10000inf✓·—
case10000_goctyp100001346114.31346100.0✓·—
case10192_epigridsapi101891856478.61856500.01849684.21977700.0-0.01✓·✓lb
case10192_epigridssad101891673015.61720200.0-0.01inf✓·✓lb
case10192_epigridstyp101891665551.11665600.01672591.51686900.0-0.00✓·✓lb
case10480_gocapi104802712412.52712400.02709085.22863500.0-0.01✓·✓lb
case10480_gocsad104802286465.72314700.0-0.01inf✓·✓lb
case10480_goctyp104802215826.22215800.02286386.92314600.0-0.01✓·✓lb
case13659_pegaseapi136599117036.99126300.09198494.69385800.00.15✓·✓lb
case13659_pegasesad136598829708.19042200.00.67inf✓·lb
case13659_pegasetyp136598763111.48769900.08817544.38948000.00.07✓·✓lb
case19402_gocapi194022458064.02458100.02449028.32583700.0-0.01✓·✓lb
case19402_gocsad194021910224.61910200.01954400.71983800.0-0.01✓·✓lb
case19402_goctyp194021897794.81897800.01954308.11977800.0-0.00✓·✓lb
case20758_epigridsapi207583034846.93034800.03042940.73126500.0-0.01✓·✓lb
case20758_epigridssad207582612726.12638200.0-0.00inf✓·✓lb
case20758_epigridstyp207582572328.12572300.02608669.62618600.0-0.01✓·✓lb
case24464_gocapi244642560710.22684000.0-0.01—·✓lb
case24464_gocsad244642605377.12654000.0-0.01—·✓lb
case24464_goctyp244642512811.82512800.02604171.32629500.0-0.01✓·✓lb
case30000_gocapi300001700874.21700900.0✓·—
case30000_gocsad300001297528.41297500.0✓·—
case30000_goctyp300001092116.11092100.0✓·—

Sensitivity parity (finite differences)

Per size band. adj−fwd is the largest observed difference between the two implicit solve directions. FdClean and coupled are central difference relative errors per parity class, shown as median/worst. Coupled cells contain reactive quantities, squared voltage, or conductance. A central difference that crosses an active set boundary measures two local regimes, so the table keeps both median and worst values. Columns below the stated numerical floor are skipped.

bandformulationcasescellsFD colsworst adj−fwdFdClean med/worstcoupled med/worst
100–1kac30512947.4898e-148.4965e-9 / 8.7898e-7
100–1kdc3022162.6468e-134.4498e-2 / 9.2257e-1
100–1ksocwr3030011271.6558e-72.6745e-3 / 5.1572e58.4129e-2 / 4.1153e2
1k–10kac36361.6549e-157.1479e-9 / 9.2028e1
1k–10kdc3218.6689e-218.0256e-5 / 8.0256e-5
1k–10ksocwr3301161.3965e-101.9303e-1 / 1.3502e01.7219e0 / 6.1760e0
<100ac33783291.1102e-151.4183e-8 / 2.6527e-6
<100dc3326481.6409e-104.0039e-6 / 1.1623e1
<100socwr333309999.8460e-74.9246e-5 / 3.4204e13.9313e-3 / 2.8670e2

Performance (wall time per stage)

Median milliseconds per stage, by size band. Solves are single-threaded.

bandcasesparsebuildDCSOCWRAC PFsens
<100330.080.040.744.730.22385.35
100–1k300.280.187.6465.459.606125.20
1k–10k1082.812.13165.681444.30507.970.00
>10k2417.7821.601297.5610187.043892.620.00

Limits and skips

Every capped, failed, or caveated case, with its reason (no silent truncation).

casevarbusesstatusnotes
case5_pjmsad5caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case14_ieeesad14caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case30_assad30caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case30_ieeesad30caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case57_ieeesad57caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case60_csad60caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case89_pegasesad89caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case118_ieeesad118caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case179_gocsad179caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 4.857e1 over 5 starts
case197_snemsad197caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case200_activsad200caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case240_psercsad240caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 7.232e1 over 5 starts
case500_gocsad500caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case588_sdetsad588caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case793_gocsad793caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case1354_pegasesad1354caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.
case1803_snemsad1803caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 1.519e0 over 5 starts; sensitivity skipped: buses 1803 exceed –max-sens-bus 1500
case1951_rtesad1951caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 5.113e1 over 5 starts; sensitivity skipped: buses 1951 exceed –max-sens-bus 1500
case2000_gocsad2000caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 1.828e0 over 5 starts; sensitivity skipped: buses 2000 exceed –max-sens-bus 1500
case2312_gocsad2312caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2312 exceed –max-sens-bus 1500
case2383wp_ksad2383caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2383 exceed –max-sens-bus 1500
case2736sp_ksad2736caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2736 exceed –max-sens-bus 1500
case2737sop_ksad2737caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2737 exceed –max-sens-bus 1500
case2746wop_ksad2746caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2746 exceed –max-sens-bus 1500
case2746wp_ksad2746caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2746 exceed –max-sens-bus 1500
case2848_rtesad2848caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 5.923e-1 over 5 starts; sensitivity skipped: buses 2848 exceed –max-sens-bus 1500
case2853_sdetsad2853caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 1.030e1 over 5 starts; sensitivity skipped: buses 2853 exceed –max-sens-bus 1500
case2868_rtesad2868caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 3.169e1 over 5 starts; sensitivity skipped: buses 2868 exceed –max-sens-bus 1500
case2869_pegasesad2869caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2869 exceed –max-sens-bus 1500
case3012wp_ksad3012caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 1.335e0 over 5 starts; sensitivity skipped: buses 3012 exceed –max-sens-bus 1500
case3120sp_ksad3120caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 3120 exceed –max-sens-bus 1500
case3970_gocsad3970caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 3970 exceed –max-sens-bus 1500
case4020_gocsad4020caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 2.905e0 over 5 starts; sensitivity skipped: buses 4020 exceed –max-sens-bus 1500
case4619_gocsad4619caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 4619 exceed –max-sens-bus 1500
case4661_sdetsad4661caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 6.734e-1 over 5 starts; sensitivity skipped: buses 4661 exceed –max-sens-bus 1500
case4837_gocsad4837caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 8.525e0 over 5 starts; sensitivity skipped: buses 4837 exceed –max-sens-bus 1500
case5658_epigridssad5658caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 5658 exceed –max-sens-bus 1500
case7336_epigridssad7336caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 7336 exceed –max-sens-bus 1500
case9241_pegasesad9241caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 6.289e1 over 5 starts; sensitivity skipped: buses 9241 exceed –max-sens-bus 1500
case9591_gocsad9591caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 7.825e0 over 5 starts; sensitivity skipped: buses 9591 exceed –max-sens-bus 1500
case10000_gocapi10000caveatSOCWR solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 5.306e0 over 5 starts; sensitivity skipped: buses 10000 exceed –max-sens-bus 1500
case10000_gocsad10000caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; SOCWR solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 1.452e1 over 5 starts; sensitivity skipped: buses 10000 exceed –max-sens-bus 1500
case10000_goctyp10000caveatSOCWR solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 1.452e1 over 5 starts; sensitivity skipped: buses 10000 exceed –max-sens-bus 1500
case10192_epigridssad10189caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; bus count 10189 differs from BASELINE nodes 10192; AC power flow did not converge: best mismatch 1.441e1 over 5 starts; sensitivity skipped: buses 10192 exceed –max-sens-bus 1500
case10480_gocsad10480caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 5.689e0 over 5 starts; sensitivity skipped: buses 10480 exceed –max-sens-bus 1500
case13659_pegasesad13659caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 2.786e1 over 5 starts; sensitivity skipped: buses 13659 exceed –max-sens-bus 1500
case20758_epigridssad20758caveatDC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 2.751e-1 over 5 starts; sensitivity skipped: buses 20758 exceed –max-sens-bus 1500
case24464_gocapi24464failedDC OPF solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 1.699e1 over 5 starts; sensitivity skipped: buses 24464 exceed –max-sens-bus 1500
case24464_gocsad24464failedDC OPF solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 2.075e1 over 5 starts; sensitivity skipped: buses 24464 exceed –max-sens-bus 1500
case30000_gocapi30000caveatSOCWR solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 3.598e0 over 5 starts; sensitivity skipped: buses 30000 exceed –max-sens-bus 1500
case30000_gocsad30000caveatSOCWR solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 1.198e1 over 5 starts; sensitivity skipped: buses 30000 exceed –max-sens-bus 1500
case30000_goctyp30000caveatSOCWR solve failed: DC OPF solve did not converge: NumericalError; AC power flow did not converge: best mismatch 1.198e1 over 5 starts; sensitivity skipped: buses 30000 exceed –max-sens-bus 1500
case78484_epigridsapi78484skippedtimed out after 180s
case78484_epigridssad78484skippedtimed out after 180s
case78484_epigridstyp78484skippedtimed out after 180s

198 rows; 55 skipped/failed/caveat.

References

PGLib-OPF

The benchmark corpus and the reference solves.

  • IEEE PES Power Grid Library — Optimal Power Flow, v23.07. https://github.com/power-grid-lib/pglib-opf
  • Archive report: S. Babaeinejadsarookolaee et al., The Power Grid Library for Benchmarking AC Optimal Power Flow Algorithms, arXiv:1908.02788.
  • License: the PGLib data is CC BY 4.0; the software is MIT. The corpus is read from $PGLIB_OPF_PATH and never vendored. Per-file header attribution is preserved for any case quoted in these docs.
  • Reference solves: $PGLIB_OPF_PATH/BASELINE.md (PowerModels.jl with IPOPT).

Formulation references

  • Jabr SOCWR: R. A. Jabr, Radial distribution load flow using conic programming, IEEE Transactions on Power Systems, 21(3), 2006. The paper gives the W-space second-order cone relaxation, also implemented in PowerModels.jl (BSD-3-Clause); the conic implementation in tellegen is independent.

Solvers and linear algebra

  • Clarabel.rs: the convex conic and quadratic program solver.
  • faer: the dense linear algebra used by the sensitivity driver.

PowerIO 0.11 consumer review

PowerIO v0.11.0 is the baseline for this integration. The release preparation merged in powerio#482, with dependency maintenance in powerio#485. The final documentation and release checks are in powerio#491. Tellegen pins merge revision ca8cfcec8bdc35d083dfc91b0bb9025ac8bb7507 until the component crates are published. The earlier 1.0 candidate in powerio#454 is no longer the release target.

Every pull request that changes the temporary pin runs the PowerIO Candidate workflow at the checked-in revision. Once the workflow is on main, it can also be dispatched with a proposed full PowerIO commit SHA before a PowerIO release. The isolated runner resolves all six PowerIO components together, then exercises the Rust, WebAssembly, WebMCP package, and browser integration. Normal CI also rejects a manifest and lockfile that name different PowerIO revisions. After publication, the same check accepts unpatched crates.io dependencies only when all six components have one version and registry checksums. A manual candidate run can add temporary Git patches to that published baseline.

Tellegen consumes PowerIO modules at its public entry points. DcNetwork and AcNetwork are private solver workspaces built from a PowerIO problem instance. The browser and CLI save PowerIO case and solution modules; Tellegen uses PowerIO IR for portable networks and solutions. Its optional experiment journal records browser tool activity and never replaces the electrical module. PowerIO IR text is written and read through tellegen::ir, which calls powerio::serialize and powerio::deserialize.

Release, IR, and ABI versions

The PowerIO release, stored IR generation, and C ABI are independent:

ConcernCurrent valueTellegen integration
Rust crate release0.11.0dependency requirement 0.11
Stored IR"schema": "pio-ir", "version": 2sole durable browser and CLI JSON boundary
Producerpowerio 0.11.0records which release wrote an IR document
C ABI7unchanged and not used as an IR or crate version

The historical powerio.module version 1 document and bare balanced-network model JSON are not current input formats. Regenerate those documents from their original case data. Checked-in evidence produced with the old candidate is historical and must be rerun before it is cited for this pin.

Current module API

Tellegen uses the v0.11 facade directly:

  • powerio::parse(input) for automatic routing and powerio::parse_with_options(input, &ParseOptions) for an explicit format;
  • powerio::serialize and powerio::deserialize for .pio.json documents;
  • PioModule::value() and diagnostics() for reads, with value_mut() for edits;
  • PioModule::try_map_value for typed narrowing;
  • PioValue::type_name() for canonical structural type names;
  • powerio::emit for grid exchange formats.

Calling value_mut() is material to correctness: PowerIO drops retained source bytes and severs value source-map targets before an in-place edit. Tellegen therefore applies geographic data and auxiliary substation locations through the retained module, then serializes that updated module back to the browser.

PowerIO JSON classification now has five families: module, transmission, distribution, ambiguous, and unknown. The removed model-json family is not recreated in Tellegen. A PowerWorld .pwd display also follows the universal parse route and narrows to powerio.GeoLayer.

Browser boundary

Every solvable browser payload carries module_json. That generation-2 IR is used for Study construction, geographic transforms, saved cases, and exact solution modules. Counts, topology, and map views remain derived response data; there is no separately serialized network_json integration point.

This keeps provenance and mutation behavior intact across the whole browser flow. It also prevents a stale generation-1 shape from being accepted by a display helper while the solver receives a different module.

Preparation and results

Tellegen continues to consume typed PowerIO problem instances and shared numerical preparation. Objective and constraint selections, persistent identities, analysis-to-source row mappings, three-winding lowering, and declared thermal limits come from PowerIO. DC and AC solution modules retain convention-neutral demand marginals and terminal multipliers.

When Tellegen commits edits, it retains valid module diagnostics, history, extensions, and producer data; replaces the module value with the committed network; and severs obsolete source targets. A saved exact solution contains the amended OPF instance that was solved and is emitted as generation-2 pio-ir with value type powerio.DcOpfSolution.

HTTP API

The demo server’s surface. Data endpoints are always served; the compute endpoints ship disabled and answer 403 unless TELLEGEN_SERVER_COMPUTE=1 (see Deployment).

Data

  • GET /api/health — liveness and the served case ids.
  • GET /api/compute{"enabled": bool}, whether the compute endpoints are on.
  • GET /api/cases — case summaries.
  • GET /api/cases/{id}/case — the raw powerio network JSON the browser engine consumes.
  • GET /api/cases/{id}/network — the map view (buses, branches, coordinates).
  • GET /api/cases/{id}/solution — the cached base DC OPF solution, computed once at startup.

Case summaries report canonical PowerIO row counts as n_bus/n_branch and rendered, three-winding-lowered counts as n_analysis_bus/n_analysis_branch. Older servers may omit the analysis counts.

Compute

  • GET /api/cases/{id}/sensitivity/lmp/d/{bus} — the ∂LMP/∂demand column at a bus.
  • GET /api/cases/{id}/sensitivity/lmp/fmax/{branch} — the ∂LMP/∂rating column at a branch.
  • GET /api/cases/{id}/solve — a DC OPF solve streamed over server-sent events: status, solution, optional sensitivity, and done.

The sensitivity and solve endpoints accept ?d=bus:mw,bus:mw, each value a MW delta from the base case.

Limits

Solve work is bounded by TELLEGEN_SOLVER_CONCURRENCY (default 2) and TELLEGEN_SOLVER_TIMEOUT_SECS (default 30). Compute routes are rate limited per client: 5 solve and 25 sensitivity requests per 10 seconds by default, tuned with TELLEGEN_RATE_LIMIT_WINDOW_SECS, TELLEGEN_SOLVE_RATE_LIMIT_EVENTS, and TELLEGEN_SENSITIVITY_RATE_LIMIT_EVENTS.

Deployment

tellegen deploys as one container: the tellegen backend with the built tellegen frontend copied into the image. Production can run behind an existing Caddy edge proxy that owns ports 80 and 443.

Requirements

  • Docker Engine with the Compose plugin
  • flock (normally provided by util-linux)
  • 4 GB RAM minimum; 8 GB recommended for the bundled cases
  • external Docker network edge, owned by the Caddy edge stack
  • staged demo data under the deploy path’s data/ directory
  • For manual deployment, GHCR pull access on the host or a public ghcr.io/eigenergy/tellegen package; the workflow authenticates its own pulls

Shared Edge Layout

On a host with an existing reverse proxy, tellegen joins external Docker network edge under container name tellegen. The proxy should route the public hostname to tellegen:8000. The tellegen workflow does not mutate the proxy config; it only deploys the app container and ensures that container joins edge.

The staged case data should live under the deploy path, for example:

$TELLEGEN_DEPLOY_PATH/data

For the full demo geometry, stage these files:

ACTIVSg200/case_ACTIVSg200.m
ACTIVSg200/ACTIVSg200.aux
ACTIVSg500/case_ACTIVSg500.m
ACTIVSg500/ACTIVSg500.aux
ACTIVSg7000/Texas7k_20210804.m
ACTIVSg7000/Texas7k_lat_long.csv
CATS/CaliforniaTestSystem.m
CATS/CATS_buses.csv
CATS/CATS_lines.json

The server serves the staged subset. If no complete case pair is staged, the container exits unless TELLEGEN_ALLOW_FALLBACK=1 is set for a CI or local smoke check. Production deploys should stage the intended public case set before enabling the workflow.

CATS/CATS_gens.csv is also staged when available. It is source metadata for generator locations; the current map does not draw a separate generator layer.

Treat every staged case as public. The browser fetches the full staged network JSON through /api/cases/{id}/case so it can build browser studies and exact solves locally.

Server Compute

The compute endpoints (/api/cases/{id}/solve over SSE and the /api/cases/{id}/sensitivity/... routes) ship disabled and answer 403 server compute is disabled. Set TELLEGEN_SERVER_COMPUTE=1 to enable them as the fallback for browsers that cannot run the WebAssembly engine; the rate limits and solver concurrency caps then apply. /api/compute reports the gate ({"enabled": bool}) so the frontend picks honest fallback copy and skips requests that would 403. The data endpoints, including the cached base /solution, are always served.

Local Build Deploy

For a host that builds from source:

git clone <repo> /opt/tellegen
cd /opt/tellegen
scripts/stage-data.sh /path/to/datasets
docker compose -f docker-compose.yml -f deploy/docker-compose.edge.yml up -d --build

docker-compose.yml binds the service to 127.0.0.1:8000. The edge overlay also joins the app to Docker network edge under container name tellegen, so the shared proxy can route to it. Set TELLEGEN_ALLOW_FALLBACK=1 only for CI or local smoke checks that intentionally use the two pglib cases with synthetic coordinates.

Image Deploy

The production compose file consumes an image built by GitHub Actions:

TELLEGEN_IMAGE=ghcr.io/eigenergy/tellegen@sha256:<digest>
TELLEGEN_DATA_DIR=/opt/tellegen/data
docker compose -p tellegen --env-file .env \
  -f deploy/docker-compose.prod.yml \
  -f deploy/docker-compose.edge.yml up -d

The production compose file binds 127.0.0.1:8000, mounts staged data read only, runs with a read only root filesystem, drops Linux capabilities, blocks new privileges, caps process count, and sets the memory limit. The edge overlay adds the fixed container name and external edge network membership needed by the Caddy route. The fixed Compose project name matters because the shared Caddy edge stack is a separate project.

Use the host deploy script for normal deploys and rollbacks:

bash deploy/remote-deploy.sh \
  ghcr.io/eigenergy/tellegen@sha256:<digest> \
  "$TELLEGEN_DEPLOY_PATH/data"

The script validates Docker, Compose, the external edge network, that at least one case directory exists below the canonical deploy root, and the compose config. A host flock serializes workflow and operator invocations. The script pulls the selected digest before recreating the container, then waits for Docker health and /api/health. It does not use --remove-orphans; the shared edge proxy is owned by a separate stack.

Before changing the container, the script records a pending snapshot containing the digest, data path, and exact two Compose files. Promotion atomically makes that snapshot the last known good state. Rollback therefore restores the prior Compose configuration as well as its image, and a later invocation recovers an unpromoted candidate left by runner cancellation or SSH loss. The first run migrates a healthy existing deployment from Docker’s Compose file labels.

GitHub Actions Deploy

.github/workflows/deploy.yml runs after a successful CI workflow for a push to main, or by workflow_dispatch from main. Every job is gated by the repository variable:

TELLEGEN_DEPLOY_ENABLED=true

Leave that variable unset until the host has staged data and an edge network. Once enabled, the workflow builds and smoke-tests the exact commit whose CI passed, then checks that it is still the latest successful CI push for main. Superseded runs stop without publishing; a newer failing or in-progress commit does not suppress the last green deployment. The current run pushes the already smoke-tested local image, resolves its immutable registry digest, and repeats the latest-green check after any environment wait. It then copies the deploy files into a unique per-run bundle, invokes the locked host deploy with that bundle and digest, and checks ${TELLEGEN_DEMO_URL}/api/health. A manual run redeploys the latest green commit. It checks latest-green state again immediately before promotion; a candidate superseded during health checks is rolled back.

All demo runs share one non-canceling workflow mutex with the maximum pending queue. Dispatch and start order are not a publication guarantee, so the repeated latest-green checks remain the source of truth for which commit may publish or reach the host. Per-run bundles keep an interrupted remote process from observing files uploaded by its successor; the host lock serializes their use. After a healthy deploy, bundles older than seven days are removed.

Required repository secrets:

  • TELLEGEN_DEPLOY_HOST
  • TELLEGEN_DEPLOY_USER
  • TELLEGEN_DEPLOY_SSH_KEY
  • TELLEGEN_DEPLOY_PATH, for example /opt/tellegen
  • TELLEGEN_DEMO_URL, for example https://tellegen.dev
  • TELLEGEN_DEPLOY_KNOWN_HOSTS: the pinned SSH host key for the deploy host, the verified output of ssh-keyscan -H <host> or an equivalent known hosts entry. The workflow refuses to deploy without this secret.

The deployment job has read-only package access. It sends its temporary GITHUB_TOKEN through SSH stdin for docker login, with a private temporary Docker configuration used only by that deployment or rollback command. The helper removes the configuration on success, failure, or a handled signal, and the job token expires when the job finishes. Existing host credentials remain untouched. No registry token needs to be stored as a repository secret or kept on the host.

Preflight

Read only checks for a configured host:

ssh "$DEPLOY_HOST" docker network inspect edge
ssh "$DEPLOY_HOST" find "$TELLEGEN_DEPLOY_PATH/data" -maxdepth 2 -type f
ssh "$DEPLOY_HOST" curl -fsS http://127.0.0.1:8000/api/health

After a deploy:

ssh "$DEPLOY_HOST" docker inspect tellegen --format '{{if index .NetworkSettings.Networks "edge"}}edge{{end}}'
curl -fsS "${TELLEGEN_DEMO_URL%/}/api/health"

Both health checks should report status: "ok" and a nonempty cases array. For the full public demo, that array should contain case200, case500, case7000, and cats.

Reverse Proxy

deploy/Caddyfile is a sample route for a shared edge proxy. Tellegen CI does not mutate proxy config files.

The sample route sets HSTS, CSP, permissions policy, referrer policy, frame ancestor denial, and content type sniffing protection. The CSP allows the current SvelteKit bootstrap, WebAssembly compilation, MapLibre blob workers, and CARTO tile images.

A Caddy image with github.com/mholt/caddy-ratelimit is needed for the sample rate limits; stock Caddy does not include that module. The tellegen backend also enforces solve and sensitivity rate limits with the same defaults, so the container has a guard even when the proxy module is absent. Keep solve endpoints compatible with SSE: do not buffer responses, and do not use short write timeouts on /api/cases/*/solve.

Capacity

Staged cases are parsed at boot, and the base DC OPF solution is cached for each case. Browser WebAssembly handles the normal exact solve path; the tellegen backend recomputes fallback solves on demand. Read endpoints serve prebuilt case payloads.

Public Hardening

  • Keep the Caddy security headers and rate limits on solve and sensitivity endpoints.
  • Keep the backend rate limits enabled. Defaults are 5 solve requests and 25 sensitivity requests per client per 10 seconds. Set TELLEGEN_SOLVE_RATE_LIMIT_EVENTS=0 or TELLEGEN_SENSITIVITY_RATE_LIMIT_EVENTS=0 only for local debugging.
  • Keep the image running as the bundled unprivileged user and keep the compose read only filesystem, dropped capabilities, no new privileges, process cap, and memory cap.
  • Keep staged data mounted read only.
  • Stage only cases that are cleared for public distribution; the demo serves the full network JSON for each staged case.
  • Add request body limits before adding any tellegen backend upload endpoint.
  • Current file drop parsing runs in the browser and does not reach the tellegen backend.

Privacy

tellegen parses dropped case files in your browser. The current public demo does not upload those files to the tellegen backend.

Current Demo

  • Dropped .m, .raw, .aux, .epc, .pwb, .dss, .pwd, .csv, .json, and .geojson files stay on your device.
  • The browser uses local file contents to draw the map and run DC solves.
  • The tellegen backend receives ordinary page and API requests for the bundled demo cases.
  • There is no analytics product wired to uploaded case contents.
  • The map basemap comes from CARTO (*.basemaps.cartocdn.com). Your browser requests those tiles directly, so CARTO receives your IP address, your user agent, and the map area you view. Your case file is never sent, but the map area shows where a locally parsed case sits. Nothing else on the page loads from a third party.

Future Opt In Sharing

If tellegen adds a sharing feature, it will be explicit. The action will say what will be sent and will require a separate confirmation before upload.

Planned defaults for shared files:

  • Raw shared files will be retained for 180 days.
  • Derived aggregate statistics may be retained without a fixed end date.
  • Each upload will return a share id that can be used to request deletion.
  • Shared files will not be sold or used for third party advertising.

The public demo is intended to run on Hetzner infrastructure. Before accepting uploaded user files, the operator will conclude Hetzner’s Data Processing Agreement and publish controller contact details.

Third Party Notices

This page records attribution sources for the repository and public demo. It does not replace the license metadata in Cargo.lock, package-lock.json, or the source distributions for each dependency.

Project Code

The project, including the Rust crates and the npm packages @tellegen/engine and @tellegen/svelte, is MIT licensed. See LICENSE.

The engine uses the W space SOCWR formulation as implemented in PowerModels.jl as a formulation reference. PowerModels.jl is BSD 3-Clause licensed. The tellegen implementation is independent.

Direct Software Dependencies

Rust dependencies are resolved by Cargo and governed by deny.toml. Direct runtime dependencies include powerio, clarabel, faer, num-complex, serde, serde_json, wasm-bindgen, console_error_panic_hook, axum, tokio, tokio-stream, tower-http, tracing, and tracing-subscriber.

The web app dependencies are resolved by npm. Direct dependencies include SvelteKit, Svelte, Vite, TypeScript, deck.gl, MapLibre GL JS, Prettier, @fontsource-variable/bricolage-grotesque, and @fontsource/ibm-plex-mono.

The repository does not modify or vendor those dependency sources. License and notice files from dependencies are distributed through their normal package archives.

Demo Case Data

The demo data is staged by the operator and is not vendored in this repository.

ACTIVSg synthetic grids come from the Texas A&M Electric Grid Test Case Repository. The case file headers request this citation:

A. B. Birchfield, T. Xu, K. M. Gegner, K. S. Shetye, and T. J. Overbye, “Grid Structural Characteristics as Validation Criteria for Synthetic Networks,” IEEE Transactions on Power Systems, 2017.

CATS comes from the WISPO POP CATS California Test System repository and is BSD 3 Clause licensed in that repository. The source repository requests this citation:

S. Taylor, A. Rangarajan, N. Rhodes, J. Snodgrass, B. C. Lesieutre, and L. A. Roald, “California Test System (CATS): A Geographically Accurate Test System Based on the California Grid,” IEEE Transactions on Energy Markets, Policy and Regulation, vol. 2, no. 1, pp. 107-118, 2024. doi:10.1109/TEMPR.2023.3338568.

Embedded fallback cases are from PGLib OPF and are used only when TELLEGEN_ALLOW_FALLBACK=1 is set. PGLib OPF data is CC BY 4.0; the PGLib software is MIT licensed.

Maps

The web app uses CARTO map tiles with OpenStreetMap attribution. Keep the map attribution visible in any hosted demo or derivative deployment.

Desktop and mobile (planned)

None of this is built yet. This page records the plan for native desktop and mobile builds.

tellegen runs in the browser today. A native build adds three things the browser cannot:

  • Cross-platform reach. Tauri 2 builds desktop (macOS, Windows, Linux) and mobile (iOS, Android) from one Rust and web codebase. Phones and tablets get an installable app, not a tab.
  • The full nonlinear AC OPF. The browser solves DC OPF, AC power flow, and the SOCWR relaxation. The interior point AC OPF parallelizes across threads, which the browser does not have, so it runs natively, on the device, with no server.
  • Offline and local. Cases stay on the device and solve in process.

The pieces already fit. The SvelteKit app is a static single-page app and the engine is a Rust crate, so the native build reuses both:

  • apps/desktop: the Tauri shell. It loads the same apps/web build.
  • crates/tellegen-tauri: a workspace member that holds a native Study and exposes it through #[tauri::command]. Built natively, it adds the interior point AC OPF backend.

One UI, three transports: the in-browser Study, the HTTP server, and the Tauri bridge, chosen at runtime. The Study contract is the same across all three; only the transport differs.