@tellegen/svelte 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/dist/TellegenMap.svelte +47 -13
- package/dist/TellegenMap.svelte.d.ts +3 -1
- package/dist/antimeridian.d.ts +21 -0
- package/dist/antimeridian.js +40 -0
- package/dist/basemap.d.ts +10 -0
- package/dist/basemap.js +20 -0
- package/dist/bus-kinds.d.ts +9 -0
- package/dist/bus-kinds.js +33 -0
- package/dist/components/DiagramCanvas.svelte +5 -1
- package/dist/components/McModelQualification.svelte +67 -0
- package/dist/components/McModelQualification.svelte.d.ts +8 -0
- package/dist/components/McNetworkScope.svelte +90 -0
- package/dist/components/McNetworkScope.svelte.d.ts +9 -0
- package/dist/components/McPfSettings.svelte +158 -0
- package/dist/components/McPfSettings.svelte.d.ts +9 -0
- package/dist/components/McResults.svelte +16 -1
- package/dist/components/McResults.svelte.d.ts +1 -0
- package/dist/components/McWorkbench.svelte +2 -0
- package/dist/components/MulticonductorDetails.svelte +25 -6
- package/dist/components.d.ts +3 -0
- package/dist/components.js +3 -0
- package/dist/controller.svelte.d.ts +9 -5
- package/dist/controller.svelte.js +83 -18
- package/dist/diagram/schematic-equipment.d.ts +4 -3
- package/dist/diagram/schematic-equipment.js +13 -1
- package/dist/diagram/snapshot.d.ts +6 -3
- package/dist/diagram/snapshot.js +56 -35
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/map-bounds.d.ts +8 -0
- package/dist/map-bounds.js +21 -8
- package/dist/multiconductor.js +38 -4
- package/dist/state.svelte.d.ts +33 -8
- package/dist/state.svelte.js +35 -5
- package/package.json +10 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DistAttachmentKind, DistConnection, DistGraph } from '@tellegen/engine';
|
|
1
|
+
import type { DistAttachmentKind, DistConnection, DistGraph, McPfResultTables } from '@tellegen/engine';
|
|
2
2
|
import type { SchematicGeometry } from './schematic-layout.js';
|
|
3
3
|
/** A stroked glyph: polylines in symbol units, x along the connection, y down. */
|
|
4
4
|
export type Glyph = [number, number][][];
|
|
@@ -108,8 +108,9 @@ export declare function flowArrows(symbols: EquipmentSymbols, ends: ({
|
|
|
108
108
|
/** Complex power into each element (loads, generators, IBRs, shunts) by element id, and for
|
|
109
109
|
* sources the power they draw (the negative of what they inject), so every attachment reads with
|
|
110
110
|
* the same convention: positive is consumption. Branch ports are left out: names are unique only
|
|
111
|
-
* within a family, and a line sharing a load's name must not add to it.
|
|
112
|
-
|
|
111
|
+
* within a family, and a line sharing a load's name must not add to it. Reads a full result's
|
|
112
|
+
* objects or its columns. */
|
|
113
|
+
export declare function elementPowers(result: McPfResultTables | {
|
|
113
114
|
element_ports: {
|
|
114
115
|
element: string;
|
|
115
116
|
kind?: string;
|
|
@@ -620,7 +620,8 @@ const BRANCH_PORT_KINDS = new Set(['line', 'switch', 'transformer']);
|
|
|
620
620
|
/** Complex power into each element (loads, generators, IBRs, shunts) by element id, and for
|
|
621
621
|
* sources the power they draw (the negative of what they inject), so every attachment reads with
|
|
622
622
|
* the same convention: positive is consumption. Branch ports are left out: names are unique only
|
|
623
|
-
* within a family, and a line sharing a load's name must not add to it.
|
|
623
|
+
* within a family, and a line sharing a load's name must not add to it. Reads a full result's
|
|
624
|
+
* objects or its columns. */
|
|
624
625
|
export function elementPowers(result) {
|
|
625
626
|
const out = new Map();
|
|
626
627
|
const add = (id, p, q) => {
|
|
@@ -629,6 +630,17 @@ export function elementPowers(result) {
|
|
|
629
630
|
row.q += q;
|
|
630
631
|
out.set(id, row);
|
|
631
632
|
};
|
|
633
|
+
if ('strings' in result) {
|
|
634
|
+
const names = result.strings;
|
|
635
|
+
const power = result.portPower;
|
|
636
|
+
for (let k = 0; k < result.portElement.length; k++)
|
|
637
|
+
if (!BRANCH_PORT_KINDS.has(names[result.portKind[k]]))
|
|
638
|
+
add(names[result.portElement[k]], power[2 * k], power[2 * k + 1]);
|
|
639
|
+
const source = result.sourcePower;
|
|
640
|
+
for (let k = 0; k < result.sourceName.length; k++)
|
|
641
|
+
add(names[result.sourceName[k]], -source[2 * k], -source[2 * k + 1]);
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
632
644
|
for (const port of result.element_ports)
|
|
633
645
|
if (port.kind === undefined || !BRANCH_PORT_KINDS.has(port.kind))
|
|
634
646
|
add(port.element, port.power_into_element.re, port.power_into_element.im);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type McComplex, type McPfResult, type McPfResultTables } from '@tellegen/engine';
|
|
2
2
|
/** Which reference a pair voltage is measured against. */
|
|
3
3
|
export type PairZone = 'phase–neutral' | 'phase–phase' | 'phase–ground';
|
|
4
4
|
/** The second member of a phase–ground pair. */
|
|
@@ -52,6 +52,8 @@ export interface BranchSnapshot {
|
|
|
52
52
|
export interface ResultSnapshot {
|
|
53
53
|
kind: 'solved' | 'stress';
|
|
54
54
|
label: string;
|
|
55
|
+
/** Qualification and effective settings retained by CSV exports. */
|
|
56
|
+
provenance?: string[];
|
|
55
57
|
iterations: number | null;
|
|
56
58
|
buses: BusSnapshot[];
|
|
57
59
|
branches: BranchSnapshot[];
|
|
@@ -107,9 +109,10 @@ export interface SnapshotBranch {
|
|
|
107
109
|
/** Voltage unbalance factor |V2| / |V1| in percent, from three phasors in phase order. The ratio
|
|
108
110
|
* is the same for phase–neutral and phase–phase voltages, so either reference works. */
|
|
109
111
|
export declare function sequenceUnbalance(a: McComplex, b: McComplex, c: McComplex): number | null;
|
|
110
|
-
/** Project a full multiconductor power-flow result onto the drawing's bus and branch order
|
|
112
|
+
/** Project a full multiconductor power-flow result onto the drawing's bus and branch order, from
|
|
113
|
+
* its object form or its columns (`McPfResultTables`, what a live session transfers).
|
|
111
114
|
* Missing ports leave an end `null`; nothing is interpolated. */
|
|
112
|
-
export declare function snapshotFromMcPf(result: McPfResult, buses: SnapshotBus[], branches: SnapshotBranch[], label?: string): ResultSnapshot;
|
|
115
|
+
export declare function snapshotFromMcPf(result: McPfResult | McPfResultTables, buses: SnapshotBus[], branches: SnapshotBranch[], label?: string): ResultSnapshot;
|
|
113
116
|
/** Bus results from a live session's transferred terminal arrays (`[re, im]` interleaved), for when
|
|
114
117
|
* the full result with element ports has not been fetched. Branches are left without results. */
|
|
115
118
|
export declare function snapshotBusesFromTerminals(ids: ArrayLike<[string, string]>, voltages: ArrayLike<number>, buses: SnapshotBus[]): BusSnapshot[];
|
package/dist/diagram/snapshot.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mcPfResultTables, mcLoadModelLabel, mcNetworkScopeLabel } from '@tellegen/engine';
|
|
1
2
|
import { neutralTerminal } from '../multiconductor.js';
|
|
2
3
|
/** The second member of a phase–ground pair. */
|
|
3
4
|
export const GROUND = 'ground';
|
|
@@ -89,27 +90,34 @@ function busSnapshot(bus, voltage) {
|
|
|
89
90
|
const vuf = phasors.length === 3 ? sequenceUnbalance(phasors[0], phasors[1], phasors[2]) : null;
|
|
90
91
|
return { pairs, unbalance: vuf === null ? null : { percent: vuf, definition: 'VUF' } };
|
|
91
92
|
}
|
|
92
|
-
/** Project a full multiconductor power-flow result onto the drawing's bus and branch order
|
|
93
|
+
/** Project a full multiconductor power-flow result onto the drawing's bus and branch order, from
|
|
94
|
+
* its object form or its columns (`McPfResultTables`, what a live session transfers).
|
|
93
95
|
* Missing ports leave an end `null`; nothing is interpolated. */
|
|
94
96
|
export function snapshotFromMcPf(result, buses, branches, label = 'Tellegen multiconductor power flow') {
|
|
95
|
-
|
|
97
|
+
const t = 'strings' in result ? result : mcPfResultTables(result);
|
|
98
|
+
const names = t.strings;
|
|
99
|
+
// Nested maps keyed by bus, then terminal or element, holding row numbers into the tables: no
|
|
100
|
+
// composite string keys and no object per row on a 100k-bus result.
|
|
96
101
|
const voltages = new Map();
|
|
97
|
-
for (
|
|
98
|
-
|
|
102
|
+
for (let k = 0; k < t.terminalBus.length; k++) {
|
|
103
|
+
const id = names[t.terminalBus[k]];
|
|
104
|
+
let bus = voltages.get(id);
|
|
99
105
|
if (!bus)
|
|
100
|
-
voltages.set(
|
|
101
|
-
bus.set(t.
|
|
106
|
+
voltages.set(id, (bus = new Map()));
|
|
107
|
+
bus.set(names[t.terminalName[k]], k);
|
|
102
108
|
}
|
|
103
109
|
const ports = new Map();
|
|
104
|
-
for (
|
|
105
|
-
|
|
110
|
+
for (let k = 0; k < t.portElement.length; k++) {
|
|
111
|
+
const id = names[t.portElement[k]];
|
|
112
|
+
let element = ports.get(id);
|
|
106
113
|
if (!element)
|
|
107
|
-
ports.set(
|
|
108
|
-
const
|
|
114
|
+
ports.set(id, (element = new Map()));
|
|
115
|
+
const bus = names[t.portBus[k]];
|
|
116
|
+
const rows = element.get(bus);
|
|
109
117
|
if (rows)
|
|
110
|
-
rows.push(
|
|
118
|
+
rows.push(k);
|
|
111
119
|
else
|
|
112
|
-
element.set(
|
|
120
|
+
element.set(bus, [k]);
|
|
113
121
|
}
|
|
114
122
|
/** The element's port rows, of its own kind when a line and a switch share a name. A closed
|
|
115
123
|
* switch has no ports of its own (the solver merges its buses), so it must not borrow another
|
|
@@ -118,8 +126,10 @@ export function snapshotFromMcPf(result, buses, branches, label = 'Tellegen mult
|
|
|
118
126
|
const all = ports.get(branch.id)?.get(bus);
|
|
119
127
|
if (!all || branch.kind === undefined)
|
|
120
128
|
return all;
|
|
121
|
-
return all.filter((
|
|
129
|
+
return all.filter((k) => names[t.portKind[k]] === branch.kind);
|
|
122
130
|
};
|
|
131
|
+
const power = t.portPower;
|
|
132
|
+
const current = t.portCurrent;
|
|
123
133
|
const seen = new Set();
|
|
124
134
|
const branchSnapshots = branches.map((branch) => {
|
|
125
135
|
const key = `${branch.kind ?? ''}\u0000${branch.id}`;
|
|
@@ -132,33 +142,44 @@ export function snapshotFromMcPf(result, buses, branches, label = 'Tellegen mult
|
|
|
132
142
|
let p = 0;
|
|
133
143
|
let q = 0;
|
|
134
144
|
const byTerminal = new Map();
|
|
135
|
-
for (const
|
|
136
|
-
p +=
|
|
137
|
-
q +=
|
|
138
|
-
byTerminal.set(
|
|
145
|
+
for (const k of rows) {
|
|
146
|
+
p += power[2 * k];
|
|
147
|
+
q += power[2 * k + 1];
|
|
148
|
+
byTerminal.set(names[t.portTerminal[k]], k);
|
|
139
149
|
}
|
|
140
150
|
const currents = [];
|
|
141
151
|
for (const terminal of side ? branch.toTerminals : branch.fromTerminals) {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
currents.push({ terminal, amps:
|
|
152
|
+
const k = byTerminal.get(terminal);
|
|
153
|
+
if (k !== undefined)
|
|
154
|
+
currents.push({ terminal, amps: Math.hypot(current[2 * k], current[2 * k + 1]) });
|
|
145
155
|
}
|
|
146
156
|
return { p: p / 1000, q: q / 1000, currents };
|
|
147
157
|
});
|
|
148
158
|
return {
|
|
149
159
|
ends,
|
|
150
|
-
...loadingOf(branch, ends, elementPorts),
|
|
160
|
+
...loadingOf(branch, ends, elementPorts, power),
|
|
151
161
|
primary,
|
|
152
|
-
loss: primary ? equipmentLoss(branch, elementPorts) : null
|
|
162
|
+
loss: primary ? equipmentLoss(branch, elementPorts, power) : null
|
|
153
163
|
};
|
|
154
164
|
});
|
|
165
|
+
const voltage = t.terminalVoltage;
|
|
155
166
|
return {
|
|
156
167
|
kind: 'solved',
|
|
157
|
-
label
|
|
158
|
-
|
|
168
|
+
label: `${label}: ${mcLoadModelLabel(t.load_model)}; ${mcNetworkScopeLabel(t.network_scope)}`,
|
|
169
|
+
provenance: [
|
|
170
|
+
JSON.stringify({
|
|
171
|
+
load_model: t.load_model ?? null,
|
|
172
|
+
network_scope: t.network_scope ?? null,
|
|
173
|
+
excluded_islands: t.excluded_islands ?? []
|
|
174
|
+
})
|
|
175
|
+
],
|
|
176
|
+
iterations: t.iterations,
|
|
159
177
|
buses: buses.map((bus) => {
|
|
160
178
|
const terminals = voltages.get(bus.id);
|
|
161
|
-
return busSnapshot(bus, (
|
|
179
|
+
return busSnapshot(bus, (terminal) => {
|
|
180
|
+
const k = terminals?.get(terminal);
|
|
181
|
+
return k === undefined ? undefined : { re: voltage[2 * k], im: voltage[2 * k + 1] };
|
|
182
|
+
});
|
|
162
183
|
}),
|
|
163
184
|
branches: branchSnapshots
|
|
164
185
|
};
|
|
@@ -175,18 +196,18 @@ export function snapshotBusesFromTerminals(ids, voltages, buses) {
|
|
|
175
196
|
}));
|
|
176
197
|
}
|
|
177
198
|
/** Power summed over a set of port rows, in kW and kvar. */
|
|
178
|
-
function portPower(rows) {
|
|
199
|
+
function portPower(rows, power) {
|
|
179
200
|
let p = 0;
|
|
180
201
|
let q = 0;
|
|
181
|
-
for (const
|
|
182
|
-
p +=
|
|
183
|
-
q +=
|
|
202
|
+
for (const k of rows) {
|
|
203
|
+
p += power[2 * k];
|
|
204
|
+
q += power[2 * k + 1];
|
|
184
205
|
}
|
|
185
206
|
return { p: p / 1000, q: q / 1000 };
|
|
186
207
|
}
|
|
187
208
|
/** Both ends' ports are signed into the equipment, so their sum over every bus it connects is what
|
|
188
209
|
* it consumes. Each bus counts once, however many winding pairs join it. */
|
|
189
|
-
function equipmentLoss(branch, rows) {
|
|
210
|
+
function equipmentLoss(branch, rows, power) {
|
|
190
211
|
if (!branch.inService)
|
|
191
212
|
return null;
|
|
192
213
|
const buses = new Set([branch.from, branch.to]);
|
|
@@ -199,13 +220,13 @@ function equipmentLoss(branch, rows) {
|
|
|
199
220
|
const at = rows(branch, bus);
|
|
200
221
|
if (!at?.length)
|
|
201
222
|
return null;
|
|
202
|
-
const
|
|
203
|
-
p +=
|
|
204
|
-
q +=
|
|
223
|
+
const sum = portPower(at, power);
|
|
224
|
+
p += sum.p;
|
|
225
|
+
q += sum.q;
|
|
205
226
|
}
|
|
206
227
|
return { p, q };
|
|
207
228
|
}
|
|
208
|
-
function loadingOf(branch, ends, rows) {
|
|
229
|
+
function loadingOf(branch, ends, rows, power) {
|
|
209
230
|
const rating = branch.rating;
|
|
210
231
|
if (!rating || !ends[0] || !ends[1])
|
|
211
232
|
return { loading: null, basis: null };
|
|
@@ -228,7 +249,7 @@ function loadingOf(branch, ends, rows) {
|
|
|
228
249
|
const at = rows(branch, bus);
|
|
229
250
|
if (!(va > 0) || !at?.length)
|
|
230
251
|
continue;
|
|
231
|
-
const { p, q } = portPower(at);
|
|
252
|
+
const { p, q } = portPower(at, power);
|
|
232
253
|
const percent = (100 * Math.hypot(p, q) * 1000) / va;
|
|
233
254
|
if (loading === null || percent > loading)
|
|
234
255
|
loading = percent;
|
package/dist/index.d.ts
CHANGED
|
@@ -30,3 +30,6 @@ export type { MultiView, PlacedMultiBus, PlacedMultiEdge } from './multiconducto
|
|
|
30
30
|
export type { CaseFileSummary, DistAttachmentKind, DistEdgeKind, DistGraph, DistGraphAttachment, DistGraphBus, DistGraphEdge, DisplayPreview, Formulation, IngestedCase, IngestedDistCase, IngestedJsonDrop, JsonDropClassification, JsonDropKind, CapacityPlanBusWeightJson, CapacityPlanIterationJson, CapacityPlanOutcomeJson, CapacityPlanRatingChangeJson, CapacityPlanResultSummaryJson, CapacityPlanSpecJson, ImplicitObjectiveJson, SensTarget, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
|
|
31
31
|
export { default as McResults } from './components/McResults.svelte';
|
|
32
32
|
export { default as McWorkbench } from './components/McWorkbench.svelte';
|
|
33
|
+
export { default as McPfSettings } from './components/McPfSettings.svelte';
|
|
34
|
+
export { default as McModelQualification } from './components/McModelQualification.svelte';
|
|
35
|
+
export { default as McNetworkScope } from './components/McNetworkScope.svelte';
|
package/dist/index.js
CHANGED
|
@@ -19,3 +19,6 @@ export { caseDeltas, caseRatings, displayMetaFor, displaySeriesFor } from './dis
|
|
|
19
19
|
export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, classifyJson, ingestJsonDrop, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveModule } from '@tellegen/engine';
|
|
20
20
|
export { default as McResults } from './components/McResults.svelte';
|
|
21
21
|
export { default as McWorkbench } from './components/McWorkbench.svelte';
|
|
22
|
+
export { default as McPfSettings } from './components/McPfSettings.svelte';
|
|
23
|
+
export { default as McModelQualification } from './components/McModelQualification.svelte';
|
|
24
|
+
export { default as McNetworkScope } from './components/McNetworkScope.svelte';
|
package/dist/map-bounds.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export type MapBounds = [[number, number], [number, number]];
|
|
2
|
+
/** Whether a point can be framed on a Web Mercator map: finite, longitude in
|
|
3
|
+
* [-180, 180], latitude inside the projection's ±85.05° limit. */
|
|
4
|
+
export declare function isMapPoint([lon, lat]: readonly [number, number]): boolean;
|
|
2
5
|
/** Fold finite Web Mercator coordinates into map bounds. Invalid points are
|
|
3
6
|
* ignored so one malformed row cannot hide an otherwise usable network, and a
|
|
4
7
|
* single surviving point still yields a box `fitBounds` can frame. */
|
|
5
8
|
export declare function foldMapBounds(points: Iterable<readonly [number, number]>): MapBounds | null;
|
|
9
|
+
/** The box around points already checked, or `null` when there are none. An
|
|
10
|
+
* axis that collapsed to a single value is padded so `fitBounds` can frame
|
|
11
|
+
* it. Longitudes are taken as they are, so an unwrapped route may give a west
|
|
12
|
+
* edge below -180 or an east edge above 180. */
|
|
13
|
+
export declare function spanBounds(points: Iterable<readonly [number, number]>): MapBounds | null;
|
package/dist/map-bounds.js
CHANGED
|
@@ -2,23 +2,36 @@
|
|
|
2
2
|
* zero-span box makes `fitBounds` divide by zero, clamp to the map's maximum
|
|
3
3
|
* zoom, and slam the camera onto one point instead of framing it. */
|
|
4
4
|
const DEGENERATE_PAD_DEGREES = 0.005;
|
|
5
|
+
/** Whether a point can be framed on a Web Mercator map: finite, longitude in
|
|
6
|
+
* [-180, 180], latitude inside the projection's ±85.05° limit. */
|
|
7
|
+
export function isMapPoint([lon, lat]) {
|
|
8
|
+
return (Number.isFinite(lon) &&
|
|
9
|
+
Number.isFinite(lat) &&
|
|
10
|
+
lon >= -180 &&
|
|
11
|
+
lon <= 180 &&
|
|
12
|
+
lat >= -85.05 &&
|
|
13
|
+
lat <= 85.05);
|
|
14
|
+
}
|
|
5
15
|
/** Fold finite Web Mercator coordinates into map bounds. Invalid points are
|
|
6
16
|
* ignored so one malformed row cannot hide an otherwise usable network, and a
|
|
7
17
|
* single surviving point still yields a box `fitBounds` can frame. */
|
|
8
18
|
export function foldMapBounds(points) {
|
|
19
|
+
const kept = [];
|
|
20
|
+
for (const point of points)
|
|
21
|
+
if (isMapPoint(point))
|
|
22
|
+
kept.push(point);
|
|
23
|
+
return spanBounds(kept);
|
|
24
|
+
}
|
|
25
|
+
/** The box around points already checked, or `null` when there are none. An
|
|
26
|
+
* axis that collapsed to a single value is padded so `fitBounds` can frame
|
|
27
|
+
* it. Longitudes are taken as they are, so an unwrapped route may give a west
|
|
28
|
+
* edge below -180 or an east edge above 180. */
|
|
29
|
+
export function spanBounds(points) {
|
|
9
30
|
let minLon = Infinity;
|
|
10
31
|
let minLat = Infinity;
|
|
11
32
|
let maxLon = -Infinity;
|
|
12
33
|
let maxLat = -Infinity;
|
|
13
34
|
for (const [lon, lat] of points) {
|
|
14
|
-
if (!Number.isFinite(lon) ||
|
|
15
|
-
!Number.isFinite(lat) ||
|
|
16
|
-
lon < -180 ||
|
|
17
|
-
lon > 180 ||
|
|
18
|
-
lat < -85.05 ||
|
|
19
|
-
lat > 85.05) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
35
|
minLon = Math.min(minLon, lon);
|
|
23
36
|
minLat = Math.min(minLat, lat);
|
|
24
37
|
maxLon = Math.max(maxLon, lon);
|
package/dist/multiconductor.js
CHANGED
|
@@ -110,6 +110,43 @@ function withRoutes(view, layer) {
|
|
|
110
110
|
return view;
|
|
111
111
|
const routes = new Map();
|
|
112
112
|
const same = (a, b) => typeof a === 'string' && a.toLowerCase() === b.toLowerCase();
|
|
113
|
+
// Index the edges once by lowercased id and by lowercased directed endpoint
|
|
114
|
+
// pair. A city case carries tens of thousands of routes and edges, so a scan
|
|
115
|
+
// of every edge per feature is quadratic.
|
|
116
|
+
const byId = new Map();
|
|
117
|
+
const byEnds = new Map();
|
|
118
|
+
for (const edge of view.edges) {
|
|
119
|
+
const id = edge.id.toLowerCase();
|
|
120
|
+
const from = edge.from.toLowerCase();
|
|
121
|
+
const to = edge.to.toLowerCase();
|
|
122
|
+
const withId = byId.get(id);
|
|
123
|
+
if (withId)
|
|
124
|
+
withId.push(edge);
|
|
125
|
+
else
|
|
126
|
+
byId.set(id, [edge]);
|
|
127
|
+
let fromEnds = byEnds.get(from);
|
|
128
|
+
if (!fromEnds)
|
|
129
|
+
byEnds.set(from, (fromEnds = new Map()));
|
|
130
|
+
const between = fromEnds.get(to);
|
|
131
|
+
if (between)
|
|
132
|
+
between.push(edge);
|
|
133
|
+
else
|
|
134
|
+
fromEnds.set(to, [edge]);
|
|
135
|
+
}
|
|
136
|
+
const directed = (from, to) => byEnds.get(from)?.get(to) ?? [];
|
|
137
|
+
// The edges `same` would match: by id when the feature names one, otherwise
|
|
138
|
+
// by its endpoints in either direction. A self loop's two directions are one
|
|
139
|
+
// set, so it is not counted twice.
|
|
140
|
+
const matching = (id, from, to) => {
|
|
141
|
+
if (id !== undefined)
|
|
142
|
+
return typeof id === 'string' ? (byId.get(id.toLowerCase()) ?? []) : [];
|
|
143
|
+
if (typeof from !== 'string' || typeof to !== 'string')
|
|
144
|
+
return [];
|
|
145
|
+
const f = from.toLowerCase();
|
|
146
|
+
const t = to.toLowerCase();
|
|
147
|
+
const forward = directed(f, t);
|
|
148
|
+
return f === t ? forward : [...forward, ...directed(t, f)];
|
|
149
|
+
};
|
|
113
150
|
for (const feature of features) {
|
|
114
151
|
const props = feature?.properties;
|
|
115
152
|
const geometry = feature?.geometry;
|
|
@@ -126,10 +163,7 @@ function withRoutes(view, layer) {
|
|
|
126
163
|
const from = props.from ?? props.bus_from;
|
|
127
164
|
const to = props.to ?? props.bus_to;
|
|
128
165
|
const id = props.id ?? props.uid;
|
|
129
|
-
const matches =
|
|
130
|
-
? same(id, edge.id)
|
|
131
|
-
: (same(from, edge.from) && same(to, edge.to)) ||
|
|
132
|
-
(same(from, edge.to) && same(to, edge.from)));
|
|
166
|
+
const matches = matching(id, from, to);
|
|
133
167
|
if (matches.length !== 1)
|
|
134
168
|
continue;
|
|
135
169
|
const edge = matches[0];
|
package/dist/state.svelte.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration } from './api.js';
|
|
2
|
-
import { type StudyView, type BranchRatingDeltas, type BrowserMcPfSession, type CaseFileSummary, type DistGraph, type Formulation, type IngestedDistCase, type McPfCallTiming, type McPfResult, type McPfSummary, type McLoadBranchState, type McLoadPowerEdit, type McStudySnapshot, type SensTarget, type Topology } from '@tellegen/engine';
|
|
2
|
+
import { type StudyView, type BranchRatingDeltas, type BrowserMcPfSession, type CaseFileSummary, type DistGraph, type Formulation, type IngestedDistCase, type McPfCallTiming, type McPfOptions, type McPfResult, type McPfResultTables, type McPfSummary, type McLoadBranchState, type McLoadPowerEdit, type McModuleInput, type McStudySnapshot, type SensTarget, type Topology } from '@tellegen/engine';
|
|
3
3
|
import type { MultiView } from './multiconductor.js';
|
|
4
4
|
import { DiagnosticLog } from './log.svelte.js';
|
|
5
5
|
import { type GraphInputs } from './diagram/graph-inputs.js';
|
|
@@ -95,6 +95,11 @@ export declare class LocalCase {
|
|
|
95
95
|
coordsKind: CoordsKind | undefined;
|
|
96
96
|
/** Map geometry when the file carried or received coordinates. */
|
|
97
97
|
view: LocalView | null;
|
|
98
|
+
/** Buses a generated grid marks as line taps. The map draws no marker for
|
|
99
|
+
* them; the bus picker and the selection still reach them. */
|
|
100
|
+
readonly tapBuses: ReadonlySet<number>;
|
|
101
|
+
/** `view.buses` less the line taps: the buses the map draws as markers. */
|
|
102
|
+
readonly markerBuses: NetworkBus[];
|
|
98
103
|
syntheticCenter: {
|
|
99
104
|
lon: number;
|
|
100
105
|
lat: number;
|
|
@@ -181,9 +186,10 @@ export declare class CaseState {
|
|
|
181
186
|
}
|
|
182
187
|
/** A case the solver can run: a server-backed case or a browser-parsed local case. */
|
|
183
188
|
export type SolvableCase = CaseState | LocalCase;
|
|
184
|
-
/** The multiconductor ingest payload without the graph
|
|
185
|
-
* connected load/generation, coordinate
|
|
186
|
-
|
|
189
|
+
/** The multiconductor ingest payload without the graph or the retained
|
|
190
|
+
* input: the summary counts, connected load/generation, coordinate
|
|
191
|
+
* provenance, and diagnostics. */
|
|
192
|
+
export type MultiCaseSummary = Omit<IngestedDistCase, 'graph' | 'input'>;
|
|
187
193
|
/** Geographic coordinates use the map; other positions use the diagram canvas. */
|
|
188
194
|
export type MultiCoordsKind = 'geographic' | 'planar' | 'synthetic';
|
|
189
195
|
/** Where one interactive load edit spent its time, in milliseconds: engine
|
|
@@ -197,7 +203,10 @@ export declare class MulticonductorCase {
|
|
|
197
203
|
readonly id: string;
|
|
198
204
|
readonly label: string;
|
|
199
205
|
readonly fileName: string;
|
|
200
|
-
|
|
206
|
+
/** The electrical input calculations start from: the module the engine
|
|
207
|
+
* keeps from the import, or PowerIO IR text (a saved or re-placed module).
|
|
208
|
+
* A kept module is freed when it is replaced or the case is removed. */
|
|
209
|
+
mcInput: McModuleInput | null;
|
|
201
210
|
geoLayer: string | null;
|
|
202
211
|
geoWarnings: string[];
|
|
203
212
|
/** Summary of the displayed operating point: from the live session after
|
|
@@ -208,6 +217,15 @@ export declare class MulticonductorCase {
|
|
|
208
217
|
mcFullResult: McPfResult | null;
|
|
209
218
|
/** Latency breakdown of the latest load-edit solve. */
|
|
210
219
|
mcTiming: McEditTiming | null;
|
|
220
|
+
/** Explicit selection for the next solve. The graph and module retain every bus. */
|
|
221
|
+
mcEnergizedOnly: boolean;
|
|
222
|
+
/** Successful explicit solve to reveal in Studies; load edits do not advance it. */
|
|
223
|
+
mcCompletedSolveSeq: number;
|
|
224
|
+
/** Detected on import, revalidated when preparing a solve; never removed from the graph. */
|
|
225
|
+
mcExcludedIslands: import('@tellegen/engine').McExcludedIsland[];
|
|
226
|
+
mcPreparedScope: import('@tellegen/engine').McNetworkScope | null;
|
|
227
|
+
/** Selected settings for the next explicit solve; results carry their own effective settings. */
|
|
228
|
+
mcOptions: McPfOptions;
|
|
211
229
|
mcSnapshot: McStudySnapshot | null;
|
|
212
230
|
mcSession: BrowserMcPfSession | null;
|
|
213
231
|
mcLoadBranches: McLoadBranchState[];
|
|
@@ -248,9 +266,10 @@ export declare class MulticonductorCase {
|
|
|
248
266
|
/** Bumped to ask the views to bring the current selection into view again (an issue clicked
|
|
249
267
|
* that is already selected). */
|
|
250
268
|
revealSeq: number;
|
|
251
|
-
/** The full operating point read for whole-network views (overview, schematic)
|
|
252
|
-
* and cleared when a large case's
|
|
253
|
-
|
|
269
|
+
/** The full operating point read for whole-network views (overview, schematic): a stored
|
|
270
|
+
* result, or a live session's as columns. Null until read, and cleared when a large case's
|
|
271
|
+
* operating point changes. */
|
|
272
|
+
overviewResult: McPfResult | McPfResultTables | null;
|
|
254
273
|
overviewLoading: boolean;
|
|
255
274
|
/** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
|
|
256
275
|
* graph is replaced, never after a solve. Both views read this one copy. */
|
|
@@ -270,11 +289,17 @@ export declare class MulticonductorCase {
|
|
|
270
289
|
label: string;
|
|
271
290
|
fileName: string;
|
|
272
291
|
summary: MultiCaseSummary;
|
|
292
|
+
/** The module the engine kept from the import, in place of the
|
|
293
|
+
* summary's `module_json`. */
|
|
294
|
+
input?: McModuleInput | null;
|
|
273
295
|
graph: DistGraph;
|
|
274
296
|
coordsKind: MultiCoordsKind;
|
|
275
297
|
view?: MultiView | null;
|
|
276
298
|
});
|
|
277
299
|
get mcPfReason(): string | null;
|
|
300
|
+
/** Import detects scope; only a solve action opts into the selection. */
|
|
301
|
+
get mcRequiresEnergizedSolve(): boolean;
|
|
302
|
+
get mcSolveLabel(): string;
|
|
278
303
|
get mcPfSupported(): boolean;
|
|
279
304
|
/** Whether the case is placed and ready to render. */
|
|
280
305
|
get placed(): boolean;
|
package/dist/state.svelte.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_MC_PF_OPTIONS, summarizeMcExcludedIslands } from '@tellegen/engine';
|
|
2
|
+
import { tapBusIds, withoutTaps } from './bus-kinds.js';
|
|
1
3
|
import { DEFAULT_FORMULATION } from '@tellegen/engine';
|
|
2
4
|
import { DiagnosticLog, errorEntry } from './log.svelte.js';
|
|
3
5
|
import { graphInputs } from './diagram/graph-inputs.js';
|
|
@@ -26,6 +28,11 @@ export class LocalCase {
|
|
|
26
28
|
coordsKind = $state.raw(undefined);
|
|
27
29
|
/** Map geometry when the file carried or received coordinates. */
|
|
28
30
|
view = $state.raw(null);
|
|
31
|
+
/** Buses a generated grid marks as line taps. The map draws no marker for
|
|
32
|
+
* them; the bus picker and the selection still reach them. */
|
|
33
|
+
tapBuses = $derived(tapBusIds(this.studyInputJson));
|
|
34
|
+
/** `view.buses` less the line taps: the buses the map draws as markers. */
|
|
35
|
+
markerBuses = $derived(withoutTaps(this.view?.buses ?? [], this.tapBuses));
|
|
29
36
|
syntheticCenter = $state.raw(undefined);
|
|
30
37
|
diagram = $state.raw(null);
|
|
31
38
|
displayMode = $state('geographic');
|
|
@@ -127,7 +134,10 @@ export class MulticonductorCase {
|
|
|
127
134
|
id;
|
|
128
135
|
label;
|
|
129
136
|
fileName;
|
|
130
|
-
|
|
137
|
+
/** The electrical input calculations start from: the module the engine
|
|
138
|
+
* keeps from the import, or PowerIO IR text (a saved or re-placed module).
|
|
139
|
+
* A kept module is freed when it is replaced or the case is removed. */
|
|
140
|
+
mcInput = $state.raw(null);
|
|
131
141
|
geoLayer = $state.raw(null);
|
|
132
142
|
geoWarnings = $state.raw([]);
|
|
133
143
|
/** Summary of the displayed operating point: from the live session after
|
|
@@ -138,6 +148,15 @@ export class MulticonductorCase {
|
|
|
138
148
|
mcFullResult = $state.raw(null);
|
|
139
149
|
/** Latency breakdown of the latest load-edit solve. */
|
|
140
150
|
mcTiming = $state.raw(null);
|
|
151
|
+
/** Explicit selection for the next solve. The graph and module retain every bus. */
|
|
152
|
+
mcEnergizedOnly = $state(false);
|
|
153
|
+
/** Successful explicit solve to reveal in Studies; load edits do not advance it. */
|
|
154
|
+
mcCompletedSolveSeq = $state(0);
|
|
155
|
+
/** Detected on import, revalidated when preparing a solve; never removed from the graph. */
|
|
156
|
+
mcExcludedIslands = $state.raw([]);
|
|
157
|
+
mcPreparedScope = $state.raw(null);
|
|
158
|
+
/** Selected settings for the next explicit solve; results carry their own effective settings. */
|
|
159
|
+
mcOptions = $state({ ...DEFAULT_MC_PF_OPTIONS });
|
|
141
160
|
mcSnapshot = $state.raw(null);
|
|
142
161
|
mcSession = $state.raw(null);
|
|
143
162
|
mcLoadBranches = $state.raw([]);
|
|
@@ -172,8 +191,9 @@ export class MulticonductorCase {
|
|
|
172
191
|
/** Bumped to ask the views to bring the current selection into view again (an issue clicked
|
|
173
192
|
* that is already selected). */
|
|
174
193
|
revealSeq = $state(0);
|
|
175
|
-
/** The full operating point read for whole-network views (overview, schematic)
|
|
176
|
-
* and cleared when a large case's
|
|
194
|
+
/** The full operating point read for whole-network views (overview, schematic): a stored
|
|
195
|
+
* result, or a live session's as columns. Null until read, and cleared when a large case's
|
|
196
|
+
* operating point changes. */
|
|
177
197
|
overviewResult = $state.raw(null);
|
|
178
198
|
overviewLoading = $state(false);
|
|
179
199
|
/** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
|
|
@@ -196,14 +216,17 @@ export class MulticonductorCase {
|
|
|
196
216
|
this.label = init.label;
|
|
197
217
|
this.fileName = init.fileName;
|
|
198
218
|
this.summary = init.summary;
|
|
199
|
-
this.
|
|
219
|
+
this.mcInput = init.input ?? init.summary.module_json ?? null;
|
|
220
|
+
this.mcEnergizedOnly = init.summary.mc_energized_only ?? false;
|
|
221
|
+
this.mcExcludedIslands = init.summary.mc_excluded_islands ?? [];
|
|
222
|
+
this.mcPreparedScope = summarizeMcExcludedIslands(this.mcExcludedIslands);
|
|
200
223
|
this.geoLayer = init.summary.geo_layer ?? null;
|
|
201
224
|
this.graph = init.graph;
|
|
202
225
|
this.coordsKind = init.coordsKind;
|
|
203
226
|
this.view = init.view ?? null;
|
|
204
227
|
}
|
|
205
228
|
get mcPfReason() {
|
|
206
|
-
if (!this.
|
|
229
|
+
if (!this.mcInput)
|
|
207
230
|
return 'This case has no retained electrical input';
|
|
208
231
|
if (this.summary?.mc_pf_enabled !== true)
|
|
209
232
|
return 'AC power flow is unavailable in this build';
|
|
@@ -211,6 +234,13 @@ export class MulticonductorCase {
|
|
|
211
234
|
this.summary.mc_pf_unavailable_reason ??
|
|
212
235
|
(this.summary.mc_pf_supported !== true ? 'AC power flow is unavailable for this case' : null));
|
|
213
236
|
}
|
|
237
|
+
/** Import detects scope; only a solve action opts into the selection. */
|
|
238
|
+
get mcRequiresEnergizedSolve() {
|
|
239
|
+
return this.mcExcludedIslands.length > 0;
|
|
240
|
+
}
|
|
241
|
+
get mcSolveLabel() {
|
|
242
|
+
return this.mcRequiresEnergizedSolve ? 'Solve energized network' : 'Run power flow';
|
|
243
|
+
}
|
|
214
244
|
get mcPfSupported() {
|
|
215
245
|
return this.mcPfReason === null;
|
|
216
246
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellegen/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Svelte components for tellegen power system maps, local case files, and browser solves.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"pack:dry-run": "npm pack --dry-run --cache ../../node_modules/.cache/npm-pack"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@tellegen/engine": "^0.
|
|
70
|
+
"@tellegen/engine": "^0.5.1"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"@deck.gl/core": "^9.4.0",
|
|
@@ -77,16 +77,16 @@
|
|
|
77
77
|
"svelte": ">=5.30 <6"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@deck.gl/core": "^9.4.
|
|
81
|
-
"@deck.gl/layers": "^9.4.
|
|
82
|
-
"@deck.gl/maplibre": "^9.4.
|
|
83
|
-
"@sveltejs/package": "^3.0.
|
|
80
|
+
"@deck.gl/core": "^9.4.1",
|
|
81
|
+
"@deck.gl/layers": "^9.4.1",
|
|
82
|
+
"@deck.gl/maplibre": "^9.4.1",
|
|
83
|
+
"@sveltejs/package": "^3.0.2",
|
|
84
84
|
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
85
|
-
"maplibre-gl": "^6.
|
|
86
|
-
"svelte": "^5.57.
|
|
87
|
-
"svelte-check": "^4.7.
|
|
85
|
+
"maplibre-gl": "^6.16.0",
|
|
86
|
+
"svelte": "^5.57.3",
|
|
87
|
+
"svelte-check": "^4.7.8",
|
|
88
88
|
"typescript": "^6.0.3",
|
|
89
89
|
"vite": "^8.3.1",
|
|
90
|
-
"vitest": "^5.0
|
|
90
|
+
"vitest": "^5.1.0"
|
|
91
91
|
}
|
|
92
92
|
}
|