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:
| case | territory | buses | branches |
|---|---|---|---|
| ACTIVSg200 | central Illinois | 200 | 245 |
| ACTIVSg500 | South Carolina | 500 | 597 |
| ACTIVSg7000 | Texas | 6717 | 9140 |
| CATS | California | 8870 | 10823 |
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/healthGET /api/casesGET /api/cases/{id}/caseGET /api/cases/{id}/networkGET /api/cases/{id}/solutionGET /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.rawon 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
- Rust to wasm toolchain: wasm-bindgen, wasm-pack (both active in 2026)
- wasm features and browser support: caniuse wasm-simd, wasm-threads, COOP/COEP
- In-browser solving: Clarabel.rs (wasm support, issue #133), faer (wasm sparse caveat)
- Rendering: deck.gl performance, deck.gl WebGPU status
- Svelte: $state, packaging
- Julia to wasm status: julia-wasm (dormant), WasmTarget.jl (early)
- Landscape: GridSuite, GridStatus, Electrisim, PowerPlots.jl
Framework Quickstart
There are two integration references:
examples/svelte-minimalimports@tellegen/svelteand renders the full viewer for local files only.examples/browser-minimalimports@tellegen/enginedirectly and builds its own simple UI.
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} />
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 parsed = await ingestCase(caseText, format);
const study = await createStudy(parsed.network_json, "dcopf");
try {
const preview = study.preview({ 3: 25 });
const committed = study.commit(parsed.name, { 3: 25 }, {}, { bus: 3 });
console.log(preview.objectiveDelta, committed.sensitivity);
} finally {
study.free();
}
The call sequence is:
- detect the case format;
- parse the case in browser WebAssembly;
- create a
Study; - preview a demand edit without a solve;
- commit the edit with a sensitivity request; and
- free the
Study.
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/apiloadDefaultCases, defaulttruedocsHreforgHreforgLabelshowFooter, defaulttrue
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:
TellegenMapAppState,CaseState,LocalCase, andcreateAppStateControllerandcreateControllercreateApiClient- 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, solveJson } from "@tellegen/engine";
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-minimalimports@tellegen/svelteand runs withloadDefaultCases={false}for local files only.examples/browser-minimalimports@tellegen/enginedirectly and has no map stack.
Downstream apps should not import from apps/web/src/lib or from generated wasm
folders.
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: thecrates/tellegen/src/api.rshash used to generate the TypeScript contracts.FORMULATION_IDSandSOLVE_STATUSES: generated enum tags from the Rust API layer.FORMULATIONSandDEFAULT_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:
ingestCase(text, format)parseDisplay(bytes)capabilities()solveJson(networkJson, request)createStudy(networkJson, formulation)
Case And Display Helpers
formatOf(name): returnsm,raw, orauxfor supported case names.isDisplayFile(name): returns true for PowerWorld.pwddisplay files.ingestCase(text, format): parses a case and returns a network JSON payload plus summary and topology.parseDisplay(bytes): parses PowerWorld display data for diagram overlays.
Solves And Studies
capabilities(): returns available formulations, operands, and parameters.solveJson(networkJson, request): stateless solve over the generalized Rust API.createStudy(networkJson, formulation): builds a browserStudy.Study/BrowserStudy: browser handle with:currentSolution()preview(deltas, rates?)commit(caseId, deltas, rates, target)sensitivity(caseId, deltas, rates, target)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 the powerio row uid string ("buses:1", "branches:2")
stamped at ingest — ingestCase payloads carry the uid on every topology and
view element, and solve responses echo it on bus and branch scalars.
target is { bus } for the ∂LMP/∂d 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.
Types
Generated public types include:
SolveRequest,SolveResponse,ProblemCapsSensRequest,SensitivityMatrix,SensitivityColumnNetwork,NetworkBus,NetworkBranchSolution,SolveIteration,DemandDeltasBrowserFormulation,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 network JSON 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 network, call a native solve_json 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 democrates/: the tellegen engine and its wasm, server, CLI, and benchmark adapterspackages/engine/: public@tellegen/enginebrowser packagepackages/svelte/: public@tellegen/sveltecomponent packageexamples/browser-minimal/: minimal downstream Vite exampleexamples/svelte-minimal/: minimal Svelte example using the component packagescripts/: data staging and docs build helpersdeploy/: deployment compose files and proxy notesdocs/src/: mdBook documentation source
Prerequisites
- Rust from
rust-toolchain.toml, includingrustfmt,clippy, and thewasm32-unknown-unknowntarget - Node.js 22 or newer
wasm-pack0.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, .pwd, .csv, .json, or .geojson files.
MATPOWER .m, PSS/E .raw, and PowerWorld .aux files describe network
topology. If a case file includes complete coordinates, tellegen draws it
directly. If coordinates are missing, tellegen creates a local synthetic layout
and asks the user to place it on the map. Dropped JSON is content sniffed: a
.pio.json study package restores, a BMOPF or PowerModelsDistribution document
opens the multiconductor viewer, and anything else is read as a geographic
file.
After a parsed local case has coordinates, either from the file, a geographic file, or manual placement, tellegen solves the DC OPF 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 study package or an
exported case carries exactly the placement on screen. The case panel can also
download the current layout as a .geo.json layer — canonical GeoJSON with
provenance stamped (synthetic or manual for layouts) 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:
| Meaning | Accepted fields |
|---|---|
| Bus id | bus_i, bus, bus_id, bus number, number, id |
| Latitude | lat, latitude, y |
| Longitude | lon, 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:
| Meaning | Accepted fields |
|---|---|
| Branch id | branch, branch_id, branch number, cats_id, id |
| From bus | f_bus, from, from_bus |
| To bus | t_bus, to, to_bus |
| Endpoint coordinates | Lat1, 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 against a least squares quadratic fit of its breakpoints (formulations), so its objective and prices differ from a solver that carries the piecewise curve exactly.
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.
| case | territory | buses | branches | files |
|---|---|---|---|---|
| ACTIVSg200 | central Illinois | 200 | 245 | case_ACTIVSg200.m + ACTIVSg200.aux |
| ACTIVSg500 | South Carolina | 500 | 597 | case_ACTIVSg500.m + ACTIVSg500.aux |
| ACTIVSg7000 | Texas | 6717 | 9140 | Texas7k_20210804.m + Texas7k_lat_long.csv |
| CATS | California | 8870 | 10823 | CaliforniaTestSystem.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:1andLongitude:1. - Later exports can leave the bus latitude and longitude columns empty and
reference the
Substationtable throughSubNumber.
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.2.2 added a display API separate from network parsing.
parse_display_bytes and parse_display_file return
DisplayData::PowerWorld(PwdDisplay) for .pwd inputs. parse_file does not
accept .pwd.
Reading .pwd
crates/tellegen-wasm/src/lib.rs exports parse_display(bytes, format) over
powerio::parse_display_bytes. 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 pwd_mercator_to_lonlat; 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 0.7.1 ships the standalone geographic document (GeoLayer): 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 fills missing case coordinates through
the SubNum join (geo_layer_from_pwd + apply_substation_points, projected
by pwd_mercator_to_lonlat), 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 / dbetween each bus pair, withk = sqrt(1 / n); - attraction
d^2 / kalong 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 network payload (Bus.location, provenance synthetic), so
saved study packages and 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 and compiled to both native targets and WebAssembly. The public browser framework packages are @tellegen/engine and @tellegen/svelte; the SvelteKit hosted demo is one private consumer of them.
Repository layout
A Cargo workspace and a web app, side by side.
crates/tellegen: the engine. It parses a case (through powerio) and solves any formulation, returning a formulation-agnostic result and analytical sensitivities.crates/tellegen-wasm: thewasm-bindgenadapter that exposes the engine to the browser, built withwasm-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 non-shipping 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.apps/web: the private SvelteKit hosted demo that consumes@tellegen/svelte.examples/browser-minimal: a minimal downstream app that imports@tellegen/enginedirectly.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.
Every formulation returns the same result shape (locational marginal prices, voltages, branch flows, and dispatch) and exposes analytical sensitivities of any output (an Operand) with respect to any input (a Parameter) through one implicit differentiation contract, Differentiable. Each solved formulation builds its KKT or Newton system; the sensitivity driver solves that system, forward or adjoint, for the requested columns. Adding a formulation, operand, or parameter means implementing the contract, not special-casing the callers.
The whole engine is pure 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 (an interior-point program) is on the desktop and mobile roadmap: it parallelizes across threads, which the browser does not have.
The two API faces
One numerical core, two faces that share a driver and a result type:
- Stateless:
solve_json(network, request)andcapabilities_json(). Each call parses, solves, and returns. This is the face for one-shot callers: the HTTP server, the CLI, fixtures, and the initial case load. - Stateful: the
Study. It builds the model once.commitapplies a set ofNetworkEdits and re-solves exactly, optionally returning the requested sensitivity columns in the same solve;previewreturns a first-order update at the committed point with no re-solve. This is the reactive hot path: a demand drag previews and commits without re-parsing the network every frame.
Browser framework packages
packages/engine is the reusable package surface. It exports generated
contracts, case and display parsing helpers, stateless solve calls, 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.
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 the same Study loop: a drag calls preview (a first-order LMP and objective update, in WebAssembly, no server round trip) and release calls commit (an exact re-solve that also returns the displayed sensitivity column). Every formulation solves in the browser; dropped-in case files solve there too and are never 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 framework surfaces are @tellegen/engine and @tellegen/svelte.
Stable release surfaces:
crates/tellegenRust API layer, including the serde request and response shapes insrc/api.rs;crates/tellegen-wasmwasm adapter;packages/engineTypeScript package, generated TypeScript types, and browser wasm entry points;packages/svelteSvelte component package; and- 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/svelte;apps/web;examples/browser-minimal; andexamples/svelte-minimal.
Install JavaScript dependencies from the repository root:
npm ci
Root scripts define the package order:
npm run wasmbuilds both wasm packages intopackages/engine;npm run build:enginebuilds@tellegen/engine;npm run build:sveltebuilds@tellegen/svelte;npm run build:examplebuilds both downstream examples;npm run check:web,npm run build:web, andnpm run smoke:webgate the hosted demo;npm run pack:enginepreviews the engine npm package contents; andnpm run pack:sveltepreviews the Svelte npm package contents.
Versioning
@tellegen/engine and @tellegen/svelte start at 0.1.0.
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.
Package Release
The package publish workflow is .github/workflows/npm-publish.yml. Manual runs
build wasm, build packages, run downstream import checks, run packed Svelte
consumer smoke tests, run npm pack --dry-run, and upload packed .tgz
artifacts for inspection.
Publishing is gated by tags named engine-vX.Y.Z for @tellegen/engine and
svelte-vX.Y.Z for @tellegen/svelte. The workflow checks that each tag
version matches its package metadata, then publishes with npm provenance.
Publishing requires either npm trusted publishing for this repository or an
NPM_TOKEN secret with publish access.
CI Gates
CI installs JavaScript dependencies once from the root lockfile, builds
packages/engine before packages/svelte, builds the hosted demo, builds both
examples, installs the packed Svelte tarball into a temporary downstream
consumer, and runs a browser test against the hosted demo shell. The root
ci:js script covers JS checks, builds, the Svelte package dry run, hosted demo
smoke, and downstream smoke tests.
Formulations
tellegen solves the DC power flow and DC OPF, the AC power flow, and the Jabr SOCWR relaxation through one interface, each driven from the engine’s public API. Every formulation returns the same result shape (locational marginal prices, voltages, branch flows, and generator dispatch) and exposes analytical sensitivities through 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\operatorname{diag}(b)A^\top, \qquad B\theta = p, $$
where $A$ is the branch–bus incidence and $b$ the 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. Entry
point: solve_network (or solve_prebuilt over a prebuilt DcNetwork).
Generator costs are quadratic. MATPOWER model 2 polynomial rows are read directly; model 1 piecewise linear rows are projected onto a least squares quadratic (a least squares line when the quadratic fit is rejected), so objectives and prices for such cases are for the fitted curve, not the piecewise original.
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.
Entry point: ac_pf.
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. Entry point: socwr_opf.
Every formulation is pure Rust and compiles to WebAssembly, so the same code runs on a server and in the browser. The full nonlinear AC OPF (an interior-point program) is on the roadmap; it runs natively, where it can use threads.
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 non-shipping 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): parse (through powerio), build DcNetwork / AcNetwork,
then
- DC OPF:
solve_prebuilt(objective, dispatch, LMPs); - conic SOCWR:
socwr_opf(objective, gap, W-space primals); - AC power flow:
ac_pf(convergence, residual); - sensitivities: the typed engines (
AcNewton/ConicKktwithsensitivity) and thesolve_jsonfront door, which carries the DC sensitivities.
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 single-threaded for clean timing.
Metrics
- 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).
- Sensitivity parity: adjoint equals forward, and central finite differences against the analytic columns, classified per parity class (see the sensitivity contract).
- Feasibility / convergence: per-case status, interior-point iterations, residuals.
- Performance: wall time per stage, and a scaling curve against bus count.
- 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 solves are deterministic, so the figures reproduce on a fixed toolchain. The
harness 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. That file is generated by running the harness; it
is not checked in, and the published figures are whatever the current run
produces. To regenerate it:
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 DC objective (constant cost term included) is compared against the
published DC ($/h). The per-unit cost scaling cancels exactly, so the comparison
is in dollars per hour directly.
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 solves are deterministic; the numbers reproduce on the recorded toolchain.
Provenance
| tellegen | rustc | clarabel | faer | powerio | os/arch |
|---|---|---|---|---|---|
178da99 | 1.96.0 (ac68faa20 2026-05-25) | 0.11.1 | 0.24.4 | 0.7.1 | macos/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.
| status | count |
|---|---|
| caveat | 50 |
| failed | 2 |
| skipped | 3 |
| solved | 143 |
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.
| formulation | reproduced | consistent | mismatch | with baseline |
|---|---|---|---|---|
| DC OPF | 148 | 45 | 0 | 193 |
| SOCWR (lower bound) | 183 | 6 | 0 | 189 |
OPF correctness vs PGLib BASELINE
tellegen’s objective against the published value, per formulation: the DC OPF ($/h, 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; near zero is the steelman result (same Jabr family). 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.
| case | var | buses | DC $/h | PGLib DC | SOCWR $/h | PGLib AC | Δgap | reproduces |
|---|---|---|---|---|---|---|---|---|
| case3_lmbd | api | 3 | 10444.4 | 10444.0 | 10194.9 | 11242.0 | -0.01 | ✓·✓lb |
| case3_lmbd | sad | 3 | 5856.0 | 5856.0 | 5736.2 | 5959.3 | -0.01 | ✓·✓lb |
| case3_lmbd | typ | 3 | 5695.9 | 5695.9 | 5736.2 | 5812.6 | -0.01 | ✓·✓lb |
| case5_pjm | api | 5 | 78025.2 | 78025.0 | 77571.4 | 78950.0 | -0.00 | ✓·✓lb |
| case5_pjm | sad | 5 | — | — | 25164.9 | 26109.0 | -0.00 | inf✓·✓lb |
| case5_pjm | typ | 5 | 17479.9 | 17480.0 | 14999.7 | 17552.0 | -0.01 | ✓·✓lb |
| case14_ieee | api | 14 | 4797.6 | 4797.6 | 5691.8 | 5999.4 | -0.00 | ✓·✓lb |
| case14_ieee | sad | 14 | — | — | 2179.2 | 2776.8 | -0.01 | inf✓·✓lb |
| case14_ieee | typ | 14 | 2051.5 | 2051.5 | 2175.7 | 2178.1 | -0.00 | ✓·✓lb |
| case24_ieee_rts | api | 24 | 148845.5 | 148850.0 | 149168.3 | 161220.0 | -0.00 | ✓·✓lb |
| case24_ieee_rts | sad | 24 | 78122.5 | 78122.0 | 69578.9 | 76918.0 | -0.01 | ✓·✓lb |
| case24_ieee_rts | typ | 24 | 61001.2 | 61001.0 | 63344.6 | 63352.0 | -0.01 | ✓·✓lb |
| case30_as | api | 30 | 3092.1 | 3092.1 | 2767.8 | 4996.2 | -0.01 | ✓·✓lb |
| case30_as | sad | 30 | — | — | 825.9 | 897.4 | 0.08 | inf✓·✓lb |
| case30_as | typ | 30 | 767.6 | 767.6 | 802.7 | 803.1 | -0.00 | ✓·✓lb |
| case30_ieee | api | 30 | 16145.1 | 16145.0 | 17057.8 | 18037.0 | -0.00 | ✓·✓lb |
| case30_ieee | sad | 30 | — | — | 7412.6 | 8208.5 | -0.00 | inf✓·✓lb |
| case30_ieee | typ | 30 | 7472.8 | 7472.8 | 6662.2 | 8208.5 | -0.00 | ✓·✓lb |
| case39_epri | api | 39 | 252754.6 | 252750.0 | 253130.4 | 256770.0 | -0.00 | ✓·✓lb |
| case39_epri | sad | 39 | 150669.9 | 150670.0 | 147360.5 | 148340.0 | -0.01 | ✓·✓lb |
| case39_epri | typ | 39 | 136889.7 | 136890.0 | 137654.1 | 138420.0 | -0.01 | ✓·✓lb |
| case57_ieee | api | 57 | 34081.1 | 34081.0 | 33271.6 | 36242.0 | -0.00 | ✓·✓lb |
| case57_ieee | sad | 57 | — | — | 38391.4 | 38663.0 | -0.01 | inf✓·✓lb |
| case57_ieee | typ | 57 | 34773.0 | 34773.0 | 37529.7 | 37589.0 | -0.00 | ✓·✓lb |
| case60_c | api | 60 | 176378.6 | 176380.0 | 181189.2 | 185000.0 | -0.01 | ✓·✓lb |
| case60_c | sad | 60 | — | — | 108485.3 | 113500.0 | 0.05 | inf✓·✓lb |
| case60_c | typ | 60 | 90700.1 | 90700.0 | 92637.0 | 92694.0 | -0.01 | ✓·✓lb |
| case73_ieee_rts | api | 73 | 472183.2 | 472180.0 | 488422.7 | 509850.0 | -0.01 | ✓·✓lb |
| case73_ieee_rts | sad | 73 | 232679.1 | 232680.0 | 212280.3 | 227600.0 | 0.00 | ✓·✓lb |
| case73_ieee_rts | typ | 73 | 183003.7 | 183000.0 | 189706.1 | 189760.0 | -0.01 | ✓·✓lb |
| case89_pegase | api | 89 | 118452.5 | 118630.0 | 113367.9 | 129570.0 | -0.01 | ✓·✓lb |
| case89_pegase | sad | 89 | — | — | 106491.2 | 107290.0 | 0.01 | inf✓·✓lb |
| case89_pegase | typ | 89 | 104919.0 | 105040.0 | 106480.8 | 107290.0 | 0.00 | ✓·✓lb |
| case118_ieee | api | 118 | 231291.9 | 231290.0 | 184303.2 | 249610.0 | -0.01 | ✓·✓lb |
| case118_ieee | sad | 118 | — | — | 96535.5 | 105160.0 | 0.03 | inf✓·✓lb |
| case118_ieee | typ | 118 | 93100.7 | 93101.0 | 96334.7 | 97214.0 | -0.01 | ✓·✓lb |
| case162_ieee_dtc | api | 162 | 111570.9 | 111570.0 | 115655.9 | 120880.0 | -0.01 | ✓·✓lb |
| case162_ieee_dtc | sad | 162 | 106287.8 | 106290.0 | 101655.6 | 108690.0 | -0.01 | ✓·✓lb |
| case162_ieee_dtc | typ | 162 | 101462.3 | 101460.0 | 101654.9 | 108080.0 | -0.01 | ✓·✓lb |
| case179_goc | api | 179 | 1813209.8 | 1813200.0 | 1727618.5 | 1883400.0 | 0.01 | ✓·✓lb |
| case179_goc | sad | 179 | — | — | 754005.8 | 762530.0 | -0.00 | inf✓·✓lb |
| case179_goc | typ | 179 | 751881.6 | 751880.0 | 753089.1 | 754270.0 | -0.00 | ✓·✓lb |
| case197_snem | api | 197 | 15689.7 | 15690.0 | 16203.0 | 16363.0 | -0.00 | ✓·✓lb |
| case197_snem | sad | 197 | — | — | 1.5 | 1.5 | 0.03 | inf✓·✓lb |
| case197_snem | typ | 197 | 1.5 | 1.5 | 1.5 | 1.5 | 0.02 | ✓·✓lb |
| case200_activ | api | 200 | 40129.8 | 40130.0 | 40692.9 | 40700.0 | -0.00 | ✓·✓lb |
| case200_activ | sad | 200 | — | — | 27556.6 | 27558.0 | -0.01 | inf✓·✓lb |
| case200_activ | typ | 200 | 27479.6 | 27480.0 | 27556.6 | 27558.0 | -0.01 | ✓·✓lb |
| case240_pserc | api | 240 | 4624856.0 | 4624800.0 | 4636792.9 | 4692200.0 | 0.00 | ✓·✓lb |
| case240_pserc | sad | 240 | — | — | 3237213.7 | 3405400.0 | 0.01 | inf✓·✓lb |
| case240_pserc | typ | 240 | 3271442.2 | 3271400.0 | 3237193.6 | 3329700.0 | -0.00 | ✓·✓lb |
| case300_ieee | api | 300 | 659786.2 | 659840.0 | 679566.9 | 686040.0 | -0.01 | ✓·✓lb |
| case300_ieee | sad | 300 | 527243.9 | 527290.0 | 550597.4 | 565700.0 | 0.06 | ✓·✓lb |
| case300_ieee | typ | 300 | 517802.2 | 517850.0 | 550393.8 | 565220.0 | -0.01 | ✓·✓lb |
| case500_goc | api | 500 | 646871.6 | 646870.0 | 663185.9 | 688290.0 | -0.00 | ✓·✓lb |
| case500_goc | sad | 500 | — | — | 454907.9 | 487400.0 | -0.00 | inf✓·✓lb |
| case500_goc | typ | 500 | 440548.6 | 440550.0 | 453838.5 | 454950.0 | -0.01 | ✓·✓lb |
| case588_sdet | api | 588 | 392953.8 | 392950.0 | 387619.1 | 398760.0 | -0.01 | ✓·✓lb |
| case588_sdet | sad | 588 | — | — | 307081.1 | 329360.0 | 0.09 | inf✓·✓lb |
| case588_sdet | typ | 588 | 310125.6 | 310130.0 | 306447.9 | 313140.0 | -0.00 | ✓·✓lb |
| case793_goc | api | 793 | 372180.2 | 372180.0 | 321148.8 | 379800.0 | -0.01 | ✓·✓lb |
| case793_goc | sad | 793 | — | — | 263030.4 | 285800.0 | -0.00 | inf✓·✓lb |
| case793_goc | typ | 793 | 258308.0 | 258310.0 | 256757.9 | 260200.0 | -0.01 | ✓·✓lb |
| case1354_pegase | api | 1354 | 1558525.9 | 1558500.0 | 1578360.1 | 1608200.0 | 0.01 | ✓·✓lb |
| case1354_pegase | sad | 1354 | — | — | 1236022.1 | 1258800.0 | 0.24 | inf✓·✓lb |
| case1354_pegase | typ | 1354 | 1218182.9 | 1218200.0 | 1235968.2 | 1258800.0 | 0.24 | ✓·✓lb |
| case1803_snem | api | 1803 | 62064.2 | 61723.0 | 46102.3 | 80240.0 | 1.70 | ✓·lb |
| case1803_snem | sad | 1803 | — | — | 91290.5 | 106340.0 | 0.16 | inf✓·✓lb |
| case1803_snem | typ | 1803 | 87706.8 | 87696.0 | 90240.4 | 98335.0 | 0.20 | ✓·✓lb |
| case1888_rte | api | 1888 | 1961569.9 | 1961600.0 | 2013300.1 | 2019700.0 | -0.00 | ✓·✓lb |
| case1888_rte | sad | 1888 | 1353179.3 | 1353200.0 | 1373847.5 | 1413900.0 | 0.01 | ✓·✓lb |
| case1888_rte | typ | 1888 | 1352872.6 | 1352900.0 | 1373847.4 | 1402500.0 | -0.01 | ✓·✓lb |
| case1951_rte | api | 1951 | 2411505.2 | 2411500.0 | 2478404.2 | 2490300.0 | -0.00 | ✓·✓lb |
| case1951_rte | sad | 1951 | — | — | 2082719.5 | 2092400.0 | 0.00 | inf✓·✓lb |
| case1951_rte | typ | 1951 | 2031629.3 | 2031600.0 | 2082711.6 | 2085600.0 | -0.00 | ✓·✓lb |
| case2000_goc | api | 2000 | 1409993.7 | 1410000.0 | 1441231.6 | 1483900.0 | 0.01 | ✓·✓lb |
| case2000_goc | sad | 2000 | — | — | 977751.3 | 992880.0 | 0.00 | inf✓·✓lb |
| case2000_goc | typ | 2000 | 943042.6 | 943040.0 | 970446.9 | 973430.0 | -0.00 | ✓·✓lb |
| case2312_goc | api | 2312 | 615216.3 | 615220.0 | 546946.8 | 663440.0 | 0.03 | ✓·✓lb |
| case2312_goc | sad | 2312 | — | — | 444178.0 | 462350.0 | 0.00 | inf✓·✓lb |
| case2312_goc | typ | 2312 | 440328.5 | 440330.0 | 432953.7 | 441330.0 | -0.00 | ✓·✓lb |
| case2383wp_k | api | 2383 | 279125.8 | 279130.0 | 279125.8 | 279130.0 | -0.01 | ✓·✓lb |
| case2383wp_k | sad | 2383 | — | — | 1856608.1 | 1911200.0 | -0.00 | inf✓·✓lb |
| case2383wp_k | typ | 2383 | 1804090.4 | 1804100.0 | 1848908.7 | 1868200.0 | -0.01 | ✓·✓lb |
| case2736sp_k | api | 2736 | 977821.5 | 977820.0 | 939178.2 | 1017800.0 | -0.01 | ✓·✓lb |
| case2736sp_k | sad | 2736 | — | — | 1305707.8 | 1326600.0 | -0.01 | inf✓·✓lb |
| case2736sp_k | typ | 2736 | 1276033.7 | 1276000.0 | 1303994.5 | 1308000.0 | -0.00 | ✓·✓lb |
| case2737sop_k | api | 2737 | 755318.3 | 755320.0 | 733986.1 | 788310.0 | -0.01 | ✓·✓lb |
| case2737sop_k | sad | 2737 | — | — | 776126.6 | 790950.0 | -0.01 | inf✓·✓lb |
| case2737sop_k | typ | 2737 | 764009.8 | 764010.0 | 775689.9 | 777730.0 | -0.01 | ✓·✓lb |
| case2742_goc | api | 2742 | 505399.5 | 505400.0 | 471741.7 | 609960.0 | -0.01 | ✓·✓lb |
| case2742_goc | sad | 2742 | 259696.6 | 259700.0 | 271971.8 | 275710.0 | -0.00 | ✓·✓lb |
| case2742_goc | typ | 2742 | 259696.6 | 259700.0 | 271999.6 | 275710.0 | -0.00 | ✓·✓lb |
| case2746wop_k | api | 2746 | 531726.6 | 531550.0 | 511658.6 | 550480.0 | -0.01 | ✓·✓lb |
| case2746wop_k | sad | 2746 | — | — | 1205170.1 | 1233700.0 | -0.01 | inf✓·✓lb |
| case2746wop_k | typ | 2746 | 1178363.8 | 1178200.0 | 1203861.3 | 1208300.0 | -0.00 | ✓·✓lb |
| case2746wp_k | api | 2746 | 581827.9 | 581830.0 | 581825.1 | 581830.0 | -0.01 | ✓·✓lb |
| case2746wp_k | sad | 2746 | — | — | 1630588.4 | 1666900.0 | -0.01 | inf✓·✓lb |
| case2746wp_k | typ | 2746 | 1581425.1 | 1581400.0 | 1626493.5 | 1631700.0 | -0.01 | ✓·✓lb |
| case2848_rte | api | 2848 | 1488985.3 | 1489000.0 | 1527378.9 | 1531100.0 | -0.01 | ✓·✓lb |
| case2848_rte | sad | 2848 | — | — | 1285682.1 | 1289000.0 | 0.01 | inf✓·✓lb |
| case2848_rte | typ | 2848 | 1267732.3 | 1267700.0 | 1285034.3 | 1286600.0 | -0.01 | ✓·✓lb |
| case2853_sdet | api | 2853 | 2456065.6 | 2456100.0 | 2420923.4 | 2484300.0 | 0.01 | ✓·✓lb |
| case2853_sdet | sad | 2853 | — | — | 2033761.7 | 2069200.0 | 0.01 | inf✓·✓lb |
| case2853_sdet | typ | 2853 | 2036958.3 | 2037000.0 | 2033582.3 | 2052400.0 | 0.01 | ✓·✓lb |
| case2868_rte | api | 2868 | 2277460.8 | 2277500.0 | 2334871.0 | 2343900.0 | 0.01 | ✓·✓lb |
| case2868_rte | sad | 2868 | — | — | 2009425.4 | 2021300.0 | -0.00 | inf✓·✓lb |
| case2868_rte | typ | 2868 | 1966685.0 | 1966700.0 | 2007570.8 | 2009600.0 | 0.00 | ✓·✓lb |
| case2869_pegase | api | 2869 | 2966298.3 | 2966600.0 | 3026118.5 | 3063000.0 | 0.02 | ✓·✓lb |
| case2869_pegase | sad | 2869 | — | — | 2438499.1 | 2468700.0 | 0.10 | inf✓·✓lb |
| case2869_pegase | typ | 2869 | 2386115.8 | 2386400.0 | 2435400.7 | 2462800.0 | 0.10 | ✓·✓lb |
| case3012wp_k | api | 3012 | 851750.5 | 851750.0 | 728874.0 | 914590.0 | -0.00 | ✓·✓lb |
| case3012wp_k | sad | 3012 | — | — | 2578747.0 | 2619500.0 | -0.00 | inf✓·✓lb |
| case3012wp_k | typ | 3012 | 2509001.5 | 2509000.0 | 2574297.7 | 2600800.0 | -0.01 | ✓·✓lb |
| case3022_goc | api | 3022 | 666194.7 | 666190.0 | 594789.6 | 687360.0 | 0.02 | ✓·✓lb |
| case3022_goc | sad | 3022 | 599221.5 | 599220.0 | 584763.7 | 601430.0 | 0.00 | ✓·✓lb |
| case3022_goc | typ | 3022 | 599221.5 | 599220.0 | 584742.8 | 601380.0 | -0.00 | ✓·✓lb |
| case3120sp_k | api | 3120 | 1326454.4 | 1326500.0 | 1178608.6 | 1403500.0 | -0.01 | ✓·✓lb |
| case3120sp_k | sad | 3120 | — | — | 2141758.0 | 2174900.0 | 0.00 | inf✓·✓lb |
| case3120sp_k | typ | 3120 | 2087974.9 | 2088000.0 | 2135975.3 | 2148000.0 | -0.00 | ✓·✓lb |
| case3375wp_k | api | 3374 | 6272121.1 | 6272100.0 | 5865209.8 | 6364100.0 | -0.00 | ✓·✓lb |
| case3375wp_k | sad | 3374 | 7319637.3 | 7319600.0 | 7397595.4 | 7438200.0 | -0.00 | ✓·✓lb |
| case3375wp_k | typ | 3374 | 7317011.9 | 7317000.0 | 7397405.5 | 7438200.0 | -0.00 | ✓·✓lb |
| case3970_goc | api | 3970 | 1227776.4 | 1227800.0 | 1070893.9 | 1749400.0 | -0.00 | ✓·✓lb |
| case3970_goc | sad | 3970 | — | — | 958294.5 | 965550.0 | -0.01 | inf✓·✓lb |
| case3970_goc | typ | 3970 | 934219.4 | 934220.0 | 957739.2 | 960990.0 | -0.00 | ✓·✓lb |
| case4020_goc | api | 4020 | 1082422.1 | 1082400.0 | 1062324.8 | 1281700.0 | -0.00 | ✓·✓lb |
| case4020_goc | sad | 4020 | — | — | 812671.5 | 889690.0 | -0.00 | inf✓·✓lb |
| case4020_goc | typ | 4020 | 795061.9 | 795060.0 | 812181.5 | 822250.0 | -0.01 | ✓·✓lb |
| case4601_goc | api | 4601 | 796409.8 | 796410.0 | 687104.5 | 871240.0 | -0.01 | ✓·✓lb |
| case4601_goc | sad | 4601 | 1195553.7 | 1195500.0 | 822282.0 | 878180.0 | -0.00 | ✓·✓lb |
| case4601_goc | typ | 4601 | 793813.9 | 793810.0 | 821853.5 | 826240.0 | -0.01 | ✓·✓lb |
| case4619_goc | api | 4619 | 1010784.9 | 1010800.0 | 971704.9 | 1068800.0 | -0.01 | ✓·✓lb |
| case4619_goc | sad | 4619 | — | — | 474687.4 | 484350.0 | -0.01 | inf✓·✓lb |
| case4619_goc | typ | 4619 | 457436.6 | 457440.0 | 472392.3 | 476700.0 | -0.01 | ✓·✓lb |
| case4661_sdet | api | 4661 | 2670623.1 | 2670600.0 | 2623054.3 | 2731500.0 | 0.00 | ✓·✓lb |
| case4661_sdet | sad | 4661 | — | — | 2216647.8 | 2261000.0 | 0.00 | inf✓·✓lb |
| case4661_sdet | typ | 4661 | 2216303.9 | 2216300.0 | 2206725.8 | 2251300.0 | -0.01 | ✓·✓lb |
| case4837_goc | api | 4837 | 1209606.3 | 1209600.0 | 1200021.1 | 1291900.0 | -0.01 | ✓·✓lb |
| case4837_goc | sad | 4837 | — | — | 869666.7 | 877120.0 | -0.00 | inf✓·✓lb |
| case4837_goc | typ | 4837 | 850397.1 | 850400.0 | 868168.2 | 872260.0 | -0.00 | ✓·✓lb |
| case4917_goc | api | 4917 | 1703464.0 | 1703500.0 | 1434729.5 | 1717400.0 | 0.08 | ✓·✓lb |
| case4917_goc | sad | 4917 | 1384325.4 | 1384300.0 | 1354002.9 | 1389000.0 | -0.00 | ✓·✓lb |
| case4917_goc | typ | 4917 | 1383656.1 | 1383700.0 | 1353066.3 | 1387800.0 | 0.00 | ✓·✓lb |
| case5658_epigrids | api | 5658 | 1306345.0 | 1306300.0 | 1318822.6 | 1326800.0 | 0.00 | ✓·✓lb |
| case5658_epigrids | sad | 5658 | — | — | 1206615.6 | 1235800.0 | 0.00 | inf✓·✓lb |
| case5658_epigrids | typ | 5658 | 1195466.5 | 1195500.0 | 1205418.3 | 1207300.0 | -0.00 | ✓·✓lb |
| case6468_rte | api | 6468 | 2343290.4 | 2343300.0 | 2436575.4 | 2452700.0 | 0.01 | ✓·✓lb |
| case6468_rte | sad | 6468 | 1982819.8 | 1982800.0 | 2046409.5 | 2069700.0 | 0.01 | ✓·✓lb |
| case6468_rte | typ | 6468 | 1982819.8 | 1982800.0 | 2046150.9 | 2069700.0 | 0.01 | ✓·✓lb |
| case6470_rte | api | 6470 | 2604137.7 | 2604100.0 | 2685118.4 | 2720900.0 | 0.01 | ✓·✓lb |
| case6470_rte | sad | 6470 | 2139078.9 | 2139100.0 | 2198690.4 | 2241600.0 | 0.00 | ✓·✓lb |
| case6470_rte | typ | 6470 | 2136096.9 | 2136100.0 | 2198106.4 | 2237600.0 | 0.01 | ✓·✓lb |
| case6495_rte | api | 6495 | 2924495.6 | 2924500.0 | 3015652.0 | 3130400.0 | 0.01 | ✓·✓lb |
| case6495_rte | sad | 6495 | 2561805.3 | 2561800.0 | 2604047.9 | 3067800.0 | 0.01 | ✓·✓lb |
| case6495_rte | typ | 6495 | 2561788.8 | 2561800.0 | 2604047.9 | 3067800.0 | 0.01 | ✓·✓lb |
| case6515_rte | api | 6515 | 2959744.8 | 2959700.0 | 3057632.1 | 3129200.0 | 0.01 | ✓·✓lb |
| case6515_rte | sad | 6515 | 2559524.4 | 2559500.0 | 2644411.0 | 2869800.0 | 0.00 | ✓·✓lb |
| case6515_rte | typ | 6515 | 2559330.7 | 2559300.0 | 2644404.0 | 2825500.0 | 0.01 | ✓·✓lb |
| case7336_epigrids | api | 7336 | 1980097.5 | 1980100.0 | 1998086.3 | 2035500.0 | -0.00 | ✓·✓lb |
| case7336_epigrids | sad | 7336 | — | — | 1878684.1 | 1888400.0 | -0.01 | inf✓·✓lb |
| case7336_epigrids | typ | 7336 | 1855900.1 | 1855900.0 | 1878683.5 | 1882400.0 | -0.00 | ✓·✓lb |
| case8387_pegase | api | 8387 | 4974002.4 | 4975900.0 | 4637519.2 | 5242800.0 | 1.42 | ✓·lb |
| case8387_pegase | sad | 8387 | 2595938.7 | 2597700.0 | 977279.0 | 2803900.0 | 5.43 | ✓·lb |
| case8387_pegase | typ | 8387 | 2501101.0 | 2502800.0 | 822867.9 | 2771400.0 | 6.20 | ✓·lb |
| case9241_pegase | api | 9241 | 6814526.0 | 6816300.0 | 6840605.9 | 7067900.0 | 0.27 | ✓·✓lb |
| case9241_pegase | sad | 9241 | — | — | 6084311.7 | 6318500.0 | 1.24 | inf✓·lb |
| case9241_pegase | typ | 9241 | 6027173.8 | 6028700.0 | 6075800.7 | 6243100.0 | 0.14 | ✓·✓lb |
| case9591_goc | api | 9591 | 1470245.0 | 1470200.0 | 1346495.0 | 1570300.0 | -0.01 | ✓·✓lb |
| case9591_goc | sad | 9591 | — | — | 1055855.4 | 1167400.0 | -0.01 | inf✓·✓lb |
| case9591_goc | typ | 9591 | 1030938.8 | 1030900.0 | 1055119.5 | 1061700.0 | -0.00 | ✓·✓lb |
| case10000_goc | api | 10000 | 2499133.4 | 2499100.0 | — | — | — | ✓·— |
| case10000_goc | sad | 10000 | — | — | — | — | — | inf✓·— |
| case10000_goc | typ | 10000 | 1346114.3 | 1346100.0 | — | — | — | ✓·— |
| case10192_epigrids | api | 10189 | 1856478.6 | 1856500.0 | 1849684.2 | 1977700.0 | -0.01 | ✓·✓lb |
| case10192_epigrids | sad | 10189 | — | — | 1673015.6 | 1720200.0 | -0.01 | inf✓·✓lb |
| case10192_epigrids | typ | 10189 | 1665551.1 | 1665600.0 | 1672591.5 | 1686900.0 | -0.00 | ✓·✓lb |
| case10480_goc | api | 10480 | 2712412.5 | 2712400.0 | 2709085.2 | 2863500.0 | -0.01 | ✓·✓lb |
| case10480_goc | sad | 10480 | — | — | 2286465.7 | 2314700.0 | -0.01 | inf✓·✓lb |
| case10480_goc | typ | 10480 | 2215826.2 | 2215800.0 | 2286386.9 | 2314600.0 | -0.01 | ✓·✓lb |
| case13659_pegase | api | 13659 | 9117036.9 | 9126300.0 | 9198494.6 | 9385800.0 | 0.15 | ✓·✓lb |
| case13659_pegase | sad | 13659 | — | — | 8829708.1 | 9042200.0 | 0.67 | inf✓·lb |
| case13659_pegase | typ | 13659 | 8763111.4 | 8769900.0 | 8817544.3 | 8948000.0 | 0.07 | ✓·✓lb |
| case19402_goc | api | 19402 | 2458064.0 | 2458100.0 | 2449028.3 | 2583700.0 | -0.01 | ✓·✓lb |
| case19402_goc | sad | 19402 | 1910224.6 | 1910200.0 | 1954400.7 | 1983800.0 | -0.01 | ✓·✓lb |
| case19402_goc | typ | 19402 | 1897794.8 | 1897800.0 | 1954308.1 | 1977800.0 | -0.00 | ✓·✓lb |
| case20758_epigrids | api | 20758 | 3034846.9 | 3034800.0 | 3042940.7 | 3126500.0 | -0.01 | ✓·✓lb |
| case20758_epigrids | sad | 20758 | — | — | 2612726.1 | 2638200.0 | -0.00 | inf✓·✓lb |
| case20758_epigrids | typ | 20758 | 2572328.1 | 2572300.0 | 2608669.6 | 2618600.0 | -0.01 | ✓·✓lb |
| case24464_goc | api | 24464 | — | — | 2560710.2 | 2684000.0 | -0.01 | —·✓lb |
| case24464_goc | sad | 24464 | — | — | 2605377.1 | 2654000.0 | -0.01 | —·✓lb |
| case24464_goc | typ | 24464 | 2512811.8 | 2512800.0 | 2604171.3 | 2629500.0 | -0.01 | ✓·✓lb |
| case30000_goc | api | 30000 | 1700874.2 | 1700900.0 | — | — | — | ✓·— |
| case30000_goc | sad | 30000 | 1297528.4 | 1297500.0 | — | — | — | ✓·— |
| case30000_goc | typ | 30000 | 1092116.1 | 1092100.0 | — | — | — | ✓·— |
Sensitivity parity (finite differences)
Per size band. adj−fwd (worst) is the adjoint/forward solve-consistency bound — the analytic guarantee, near machine precision. FdClean and coupled are the central-difference relative errors per parity class (coupled = Jabr-cone soft cells), shown as median/worst. The median is the validation signal: the typical agreement in the smooth interior. The worst is an outlier where the two-sided difference straddles a non-smooth point — an LMP at a congestion boundary, where the sensitivity is genuinely one-sided and a central difference is not a valid check, or a soft Jabr-cone direction. These are finite-difference limitations, not analytic errors; the analytic columns are pinned by adjoint == forward and by the median. Columns below the regularization floor are skipped.
| band | formulation | cases | cells | FD cols | worst adj−fwd | FdClean med/worst | coupled med/worst |
|---|---|---|---|---|---|---|---|
| 100–1k | ac | 30 | 51 | 294 | 7.4898e-14 | 8.4965e-9 / 8.7898e-7 | — |
| 100–1k | dc | 30 | 22 | 16 | 2.6468e-13 | 4.4498e-2 / 9.2257e-1 | — |
| 100–1k | socwr | 30 | 300 | 1127 | 1.6558e-7 | 2.6745e-3 / 5.1572e5 | 8.4129e-2 / 4.1153e2 |
| 1k–10k | ac | 3 | 6 | 36 | 1.6549e-15 | 7.1479e-9 / 9.2028e1 | — |
| 1k–10k | dc | 3 | 2 | 1 | 8.6689e-21 | 8.0256e-5 / 8.0256e-5 | — |
| 1k–10k | socwr | 3 | 30 | 116 | 1.3965e-10 | 1.9303e-1 / 1.3502e0 | 1.7219e0 / 6.1760e0 |
| <100 | ac | 33 | 78 | 329 | 1.1102e-15 | 1.4183e-8 / 2.6527e-6 | — |
| <100 | dc | 33 | 26 | 48 | 1.6409e-10 | 4.0039e-6 / 1.1623e1 | — |
| <100 | socwr | 33 | 330 | 999 | 9.8460e-7 | 4.9246e-5 / 3.4204e1 | 3.9313e-3 / 2.8670e2 |
Performance (wall time per stage)
Median milliseconds per stage, by size band. Solves are single-threaded.
| band | cases | parse | build | DC | SOCWR | AC PF | sens |
|---|---|---|---|---|---|---|---|
| <100 | 33 | 0.08 | 0.04 | 0.74 | 4.73 | 0.22 | 385.35 |
| 100–1k | 30 | 0.28 | 0.18 | 7.64 | 65.45 | 9.60 | 6125.20 |
| 1k–10k | 108 | 2.81 | 2.13 | 165.68 | 1444.30 | 507.97 | 0.00 |
| >10k | 24 | 17.78 | 21.60 | 1297.56 | 10187.04 | 3892.62 | 0.00 |
Limits and skips
Every capped, failed, or caveated case, with its reason (no silent truncation).
| case | var | buses | status | notes |
|---|---|---|---|---|
| case5_pjm | sad | 5 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case14_ieee | sad | 14 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case30_as | sad | 30 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case30_ieee | sad | 30 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case57_ieee | sad | 57 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case60_c | sad | 60 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case89_pegase | sad | 89 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case118_ieee | sad | 118 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case179_goc | sad | 179 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 4.857e1 over 5 starts |
| case197_snem | sad | 197 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case200_activ | sad | 200 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case240_pserc | sad | 240 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; AC power flow did not converge: best mismatch 7.232e1 over 5 starts |
| case500_goc | sad | 500 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case588_sdet | sad | 588 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case793_goc | sad | 793 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case1354_pegase | sad | 1354 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf. |
| case1803_snem | sad | 1803 | caveat | DC 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_rte | sad | 1951 | caveat | DC 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_goc | sad | 2000 | caveat | DC 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_goc | sad | 2312 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2312 exceed –max-sens-bus 1500 |
| case2383wp_k | sad | 2383 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2383 exceed –max-sens-bus 1500 |
| case2736sp_k | sad | 2736 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2736 exceed –max-sens-bus 1500 |
| case2737sop_k | sad | 2737 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2737 exceed –max-sens-bus 1500 |
| case2746wop_k | sad | 2746 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2746 exceed –max-sens-bus 1500 |
| case2746wp_k | sad | 2746 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2746 exceed –max-sens-bus 1500 |
| case2848_rte | sad | 2848 | caveat | DC 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_sdet | sad | 2853 | caveat | DC 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_rte | sad | 2868 | caveat | DC 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_pegase | sad | 2869 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 2869 exceed –max-sens-bus 1500 |
| case3012wp_k | sad | 3012 | caveat | DC 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_k | sad | 3120 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 3120 exceed –max-sens-bus 1500 |
| case3970_goc | sad | 3970 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 3970 exceed –max-sens-bus 1500 |
| case4020_goc | sad | 4020 | caveat | DC 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_goc | sad | 4619 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 4619 exceed –max-sens-bus 1500 |
| case4661_sdet | sad | 4661 | caveat | DC 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_goc | sad | 4837 | caveat | DC 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_epigrids | sad | 5658 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 5658 exceed –max-sens-bus 1500 |
| case7336_epigrids | sad | 7336 | caveat | DC infeasible (DC OPF solve infeasible: PrimalInfeasible); consistent with BASELINE inf.; sensitivity skipped: buses 7336 exceed –max-sens-bus 1500 |
| case9241_pegase | sad | 9241 | caveat | DC 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_goc | sad | 9591 | caveat | DC 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_goc | api | 10000 | caveat | SOCWR 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_goc | sad | 10000 | caveat | DC 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_goc | typ | 10000 | caveat | 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 |
| case10192_epigrids | sad | 10189 | caveat | DC 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_goc | sad | 10480 | caveat | DC 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_pegase | sad | 13659 | caveat | DC 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_epigrids | sad | 20758 | caveat | DC 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_goc | api | 24464 | failed | DC 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_goc | sad | 24464 | failed | DC 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_goc | api | 30000 | caveat | SOCWR 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_goc | sad | 30000 | caveat | SOCWR 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_goc | typ | 30000 | caveat | SOCWR 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_epigrids | api | 78484 | skipped | timed out after 180s |
| case78484_epigrids | sad | 78484 | skipped | timed out after 180s |
| case78484_epigrids | typ | 78484 | skipped | timed 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_PATHand 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.
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.
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, optionalsensitivity, anddone.
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
- 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 - GHCR pull access on the host, or a public
ghcr.io/eigenergy/tellegenpackage
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:<sha>
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:<sha> "$TELLEGEN_DEPLOY_PATH/data"
The script validates Docker, Compose, the external edge network, that at
least one case directory exists, and the compose config. It pulls the selected
image 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.
GitHub Actions Deploy
.github/workflows/deploy.yml runs on push to main and
workflow_dispatch, but every job is gated by repository variable:
TELLEGEN_DEPLOY_ENABLED=true
Leave that variable unset until the host has staged data, an edge network,
and GHCR pull access. Once enabled, the workflow:
- runs the tellegen backend and tellegen frontend checks;
- builds the Docker image;
- starts the image in Actions and checks
/api/healthwith fallback data; - pushes
ghcr.io/eigenergy/tellegen:<sha>andghcr.io/eigenergy/tellegen:main; - copies
docker-compose.prod.yml,docker-compose.edge.yml, andremote-deploy.shto the host; - runs the host deploy script with the immutable SHA image;
- checks
${TELLEGEN_DEMO_URL}/api/healthfrom Actions.
Required repository secrets:
TELLEGEN_DEPLOY_HOSTTELLEGEN_DEPLOY_USERTELLEGEN_DEPLOY_SSH_KEYTELLEGEN_DEPLOY_PATH, for example/opt/tellegenTELLEGEN_DEMO_URL, for examplehttps://tellegen.devTELLEGEN_DEPLOY_KNOWN_HOSTS: the pinned SSH host key for the deploy host, the verified output ofssh-keyscan -H <host>or an equivalent known hosts entry. The workflow refuses to deploy without this secret.
The workflow does not send the GitHub Actions token to the host. The host must already be able to pull the GHCR image, or the GHCR package must be public.
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=0orTELLEGEN_SENSITIVITY_RATE_LIMIT_EVENTS=0only 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,.pwd,.csv,.json, and.geojsonfiles 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.
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.
Hosting And Legal Notes
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 sameapps/webbuild.crates/tellegen-tauri: a workspace member that holds a nativeStudyand 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.