@tellegen/svelte 0.2.0 → 0.3.0
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 +36 -5
- package/dist/TellegenMap.svelte +325 -163
- package/dist/TellegenProvider.svelte +4 -0
- package/dist/TellegenShell.svelte +19 -13
- package/dist/api.d.ts +8 -1
- package/dist/api.js +6 -2
- package/dist/colors.d.ts +7 -7
- package/dist/colors.js +13 -13
- package/dist/components/AppHeader.svelte +23 -11
- package/dist/components/BindingLines.svelte +3 -6
- package/dist/components/BusPicker.svelte +8 -2
- package/dist/components/ControlPanel.svelte +106 -343
- package/dist/components/DemandSlider.svelte +21 -23
- package/dist/components/DiagramCanvas.svelte +486 -0
- package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
- package/dist/components/DisplayControls.svelte +4 -2
- package/dist/components/FormulationSelector.svelte +8 -3
- package/dist/components/LocalCaseDetails.svelte +55 -45
- package/dist/components/McResults.svelte +365 -0
- package/dist/components/McResults.svelte.d.ts +11 -0
- package/dist/components/ModelDetails.svelte +230 -0
- package/dist/components/ModelDetails.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +227 -78
- package/dist/components/NetworkStats.svelte +20 -18
- package/dist/components/NotificationCenter.svelte +243 -0
- package/dist/components/NotificationCenter.svelte.d.ts +3 -0
- package/dist/components/PanelCard.svelte +319 -0
- package/dist/components/PanelCard.svelte.d.ts +9 -0
- package/dist/components/PanelFrame.svelte +46 -0
- package/dist/components/PanelFrame.svelte.d.ts +16 -0
- package/dist/components/PanelHost.svelte +212 -0
- package/dist/components/PanelHost.svelte.d.ts +3 -0
- package/dist/components/PowerFlowDetails.svelte +99 -0
- package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
- package/dist/components/RatingSlider.svelte +5 -9
- package/dist/components/SavedNetworkDetails.svelte +214 -0
- package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
- package/dist/components/SensitivityReadout.svelte +24 -16
- package/dist/components/SolveCard.svelte +46 -49
- package/dist/components/TopMovers.svelte +8 -3
- package/dist/components/TopMovers.svelte.d.ts +2 -17
- package/dist/components.d.ts +2 -0
- package/dist/components.js +2 -0
- package/dist/controller.svelte.d.ts +54 -33
- package/dist/controller.svelte.js +927 -233
- package/dist/diagram-labels.d.ts +9 -0
- package/dist/diagram-labels.js +40 -0
- package/dist/display.d.ts +3 -7
- package/dist/display.js +31 -27
- package/dist/format.js +5 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +7 -2
- package/dist/map.d.ts +1 -1
- package/dist/map.js +1 -1
- package/dist/multiconductor.d.ts +11 -2
- package/dist/multiconductor.js +107 -14
- package/dist/notices.svelte.d.ts +29 -0
- package/dist/notices.svelte.js +82 -0
- package/dist/panels.svelte.d.ts +65 -0
- package/dist/panels.svelte.js +215 -0
- package/dist/state.svelte.d.ts +90 -23
- package/dist/state.svelte.js +94 -20
- package/dist/synthetic-layout.js +1 -0
- package/package.json +22 -9
- package/dist/study-package.d.ts +0 -8
- package/dist/study-package.js +0 -17
|
@@ -2,40 +2,49 @@
|
|
|
2
2
|
import { getAppState, getController } from '../context.svelte.js';
|
|
3
3
|
import { fmt } from '../format.js';
|
|
4
4
|
import type { LocalCase } from '../state.svelte.js';
|
|
5
|
+
import { formatPowerIoDiagnostic, type PowerIoDiagnostic } from '@tellegen/engine';
|
|
5
6
|
|
|
6
7
|
const app = getAppState();
|
|
7
8
|
const ctrl = getController();
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
// `model-json` is powerio's own model document, not a case format.
|
|
10
|
+
// Grid exchange formats the committed state can export.
|
|
11
11
|
const EXPORT_FORMATS = [
|
|
12
12
|
{ token: 'matpower', label: 'MATPOWER (.m)' },
|
|
13
13
|
{ token: 'psse', label: 'PSS/E (.raw)' },
|
|
14
14
|
{ token: 'powerworld', label: 'PowerWorld (.aux)' },
|
|
15
15
|
{ token: 'pslf', label: 'PSLF (.epc)' },
|
|
16
16
|
{ token: 'powermodels-json', label: 'PowerModels (.json)' },
|
|
17
|
-
{ token: 'pandapower-json', label: 'pandapower (.json)' }
|
|
18
|
-
{ token: 'model-json', label: 'PowerIO model (.json)' }
|
|
17
|
+
{ token: 'pandapower-json', label: 'pandapower (.json)' }
|
|
19
18
|
];
|
|
20
19
|
|
|
21
20
|
let busy = $state(false);
|
|
22
21
|
let exportOpen = $state(false);
|
|
23
|
-
let
|
|
22
|
+
let exportDiagnostics = $state<PowerIoDiagnostic[]>([]);
|
|
24
23
|
|
|
25
|
-
async function
|
|
24
|
+
async function saveCase(lc: LocalCase) {
|
|
26
25
|
busy = true;
|
|
27
|
-
|
|
26
|
+
exportDiagnostics = [];
|
|
28
27
|
try {
|
|
29
|
-
await ctrl.
|
|
28
|
+
await ctrl.saveCaseModule(lc);
|
|
30
29
|
} finally {
|
|
31
30
|
busy = false;
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
async function
|
|
34
|
+
async function saveSolution(lc: LocalCase) {
|
|
36
35
|
busy = true;
|
|
36
|
+
exportDiagnostics = [];
|
|
37
37
|
try {
|
|
38
|
-
|
|
38
|
+
await ctrl.saveSolutionModule(lc);
|
|
39
|
+
} finally {
|
|
40
|
+
busy = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function exportCase(lc: LocalCase, token: string) {
|
|
45
|
+
busy = true;
|
|
46
|
+
try {
|
|
47
|
+
exportDiagnostics = await ctrl.exportCaseAs(lc, token);
|
|
39
48
|
exportOpen = false;
|
|
40
49
|
} finally {
|
|
41
50
|
busy = false;
|
|
@@ -94,17 +103,17 @@
|
|
|
94
103
|
<dd>{fmt.format(lc.summary.base_mva)}</dd>
|
|
95
104
|
</div>
|
|
96
105
|
</dl>
|
|
97
|
-
{#if lc.
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{
|
|
106
|
+
{#if lc.displayMode === 'diagram' && lc.diagram}
|
|
107
|
+
<p class="footnote mono">Drawing: {lc.diagram.name}</p>
|
|
108
|
+
{#if lc.diagram.warnings.length}
|
|
109
|
+
<details>
|
|
110
|
+
<summary>Drawing details</summary>
|
|
111
|
+
<ul class="warnings mono">
|
|
112
|
+
{#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
|
|
113
|
+
</ul>
|
|
114
|
+
</details>
|
|
115
|
+
{/if}
|
|
116
|
+
{:else if !lc.view}
|
|
108
117
|
<p class="footnote mono">
|
|
109
118
|
no coordinates in this file: click the map or drop a geographic file
|
|
110
119
|
</p>
|
|
@@ -117,22 +126,17 @@
|
|
|
117
126
|
coordinates: geographic file data from {lc.geoSource}
|
|
118
127
|
</p>
|
|
119
128
|
{/if}
|
|
120
|
-
{#if lc.geoWarnings && lc.geoWarnings.length > 0}
|
|
121
|
-
<ul class="warnings mono">
|
|
122
|
-
{#each lc.geoWarnings.slice(0, 4) as w, i (i)}
|
|
123
|
-
<li>{w}</li>
|
|
124
|
-
{/each}
|
|
125
|
-
{#if lc.geoWarnings.length > 4}
|
|
126
|
-
<li>+{lc.geoWarnings.length - 4} more</li>
|
|
127
|
-
{/if}
|
|
128
|
-
</ul>
|
|
129
|
-
{/if}
|
|
130
129
|
<p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
|
|
131
|
-
{#if lc.
|
|
132
|
-
<div class="
|
|
133
|
-
<button class="reset mono" disabled={busy} onclick={() =>
|
|
134
|
-
save
|
|
130
|
+
{#if lc.studyInputJson}
|
|
131
|
+
<div class="case-actions">
|
|
132
|
+
<button class="reset mono" disabled={busy} onclick={() => saveCase(lc)}>
|
|
133
|
+
save PowerIO module (.json)
|
|
135
134
|
</button>
|
|
135
|
+
{#if lc.formulation === 'dcopf' && lc.solution}
|
|
136
|
+
<button class="reset mono" disabled={busy} onclick={() => saveSolution(lc)}>
|
|
137
|
+
save exact solution (.json)
|
|
138
|
+
</button>
|
|
139
|
+
{/if}
|
|
136
140
|
<div class="export">
|
|
137
141
|
<button
|
|
138
142
|
class="reset mono"
|
|
@@ -146,7 +150,11 @@
|
|
|
146
150
|
<ul class="export-menu mono">
|
|
147
151
|
{#each EXPORT_FORMATS as f (f.token)}
|
|
148
152
|
<li>
|
|
149
|
-
<button
|
|
153
|
+
<button
|
|
154
|
+
class="reset mono"
|
|
155
|
+
disabled={busy}
|
|
156
|
+
onclick={() => exportCase(lc, f.token)}
|
|
157
|
+
>
|
|
150
158
|
{f.label}
|
|
151
159
|
</button>
|
|
152
160
|
</li>
|
|
@@ -154,25 +162,27 @@
|
|
|
154
162
|
</ul>
|
|
155
163
|
{/if}
|
|
156
164
|
</div>
|
|
157
|
-
{#if lc.view}
|
|
165
|
+
{#if lc.view || lc.diagram}
|
|
158
166
|
<button class="reset mono" disabled={busy} onclick={() => downloadLayout(lc)}>
|
|
159
|
-
|
|
167
|
+
{lc.displayMode === 'diagram'
|
|
168
|
+
? 'Download drawing (.geo.json)'
|
|
169
|
+
: 'Download geography (.geo.json)'}
|
|
160
170
|
</button>
|
|
161
171
|
{/if}
|
|
162
172
|
</div>
|
|
163
|
-
{#if
|
|
173
|
+
{#if exportDiagnostics.length > 0}
|
|
164
174
|
<ul class="warnings mono">
|
|
165
|
-
{#each
|
|
166
|
-
<li>{
|
|
175
|
+
{#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
|
|
176
|
+
<li>{formatPowerIoDiagnostic(diagnostic)}</li>
|
|
167
177
|
{/each}
|
|
168
|
-
{#if
|
|
169
|
-
<li>+{
|
|
178
|
+
{#if exportDiagnostics.length > 4}
|
|
179
|
+
<li>+{exportDiagnostics.length - 4} more</li>
|
|
170
180
|
{/if}
|
|
171
181
|
</ul>
|
|
172
182
|
{/if}
|
|
173
183
|
{/if}
|
|
174
184
|
{/if}
|
|
175
|
-
{#if lc.topology && lc.coordsKind !== 'file'}
|
|
185
|
+
{#if lc.displayMode !== 'diagram' && lc.topology && lc.coordsKind !== 'file'}
|
|
176
186
|
<button class="reset mono" onclick={() => ctrl.moveLocalCase(lc)}>
|
|
177
187
|
{lc.coordsKind === 'synthetic_pending'
|
|
178
188
|
? 'place on map'
|
|
@@ -226,7 +236,7 @@
|
|
|
226
236
|
color: var(--text-accent);
|
|
227
237
|
}
|
|
228
238
|
|
|
229
|
-
.
|
|
239
|
+
.case-actions {
|
|
230
240
|
display: flex;
|
|
231
241
|
flex-wrap: wrap;
|
|
232
242
|
gap: 8px;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { McPfResult, McComplex, DistGraph } from '@tellegen/engine';
|
|
3
|
+
import { neutralTerminal } from '../multiconductor.js';
|
|
4
|
+
let {
|
|
5
|
+
result,
|
|
6
|
+
elapsedMs,
|
|
7
|
+
selectedBus,
|
|
8
|
+
selectedEdge,
|
|
9
|
+
graph
|
|
10
|
+
}: {
|
|
11
|
+
result: McPfResult;
|
|
12
|
+
elapsedMs: number | null;
|
|
13
|
+
selectedBus: string | null;
|
|
14
|
+
selectedEdge: string | null;
|
|
15
|
+
graph: DistGraph | null;
|
|
16
|
+
} = $props();
|
|
17
|
+
let busChoice = $state('');
|
|
18
|
+
let edgeChoice = $state('');
|
|
19
|
+
const busId = $derived(
|
|
20
|
+
selectedBus ??
|
|
21
|
+
(graph?.buses.some((b) => b.id === busChoice) ? busChoice : graph?.buses[0]?.id) ??
|
|
22
|
+
''
|
|
23
|
+
);
|
|
24
|
+
const bus = $derived(graph?.buses.find((b) => b.id === busId));
|
|
25
|
+
const neutralName = $derived(bus ? neutralTerminal(bus.terminals, bus.neutral_terminal) : null);
|
|
26
|
+
const neutral = $derived(
|
|
27
|
+
result.terminals.find((t) => t.bus === busId && t.terminal === neutralName)?.voltage
|
|
28
|
+
);
|
|
29
|
+
const edgeId = $derived(selectedEdge ?? edgeChoice);
|
|
30
|
+
const sourcePower = $derived(
|
|
31
|
+
result.source_reactions.reduce(
|
|
32
|
+
(sum, port) => ({
|
|
33
|
+
re: sum.re + port.power_into_network.re,
|
|
34
|
+
im: sum.im + port.power_into_network.im
|
|
35
|
+
}),
|
|
36
|
+
{ re: 0, im: 0 }
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
const passiveLoss = $derived(
|
|
40
|
+
result.element_ports
|
|
41
|
+
.filter((port) => !['load', 'generator', 'ibr'].includes(port.kind))
|
|
42
|
+
.reduce((sum, port) => sum + port.power_into_element.re, 0)
|
|
43
|
+
);
|
|
44
|
+
let search = $state('');
|
|
45
|
+
let page = $state(0);
|
|
46
|
+
const size = 20;
|
|
47
|
+
const terminals = $derived(
|
|
48
|
+
result.terminals.filter(
|
|
49
|
+
(row) =>
|
|
50
|
+
(!busId || row.bus === busId) &&
|
|
51
|
+
`${row.bus} ${row.terminal}`.toLowerCase().includes(search.toLowerCase())
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
const pageCount = $derived(Math.max(1, Math.ceil(terminals.length / size)));
|
|
55
|
+
const currentPage = $derived(Math.min(page, pageCount - 1));
|
|
56
|
+
const visible = $derived(terminals.slice(currentPage * size, (currentPage + 1) * size));
|
|
57
|
+
const ports = $derived(result.element_ports.filter((row) => !edgeId || row.element === edgeId));
|
|
58
|
+
let portPage = $state(0);
|
|
59
|
+
const portPages = $derived(Math.max(1, Math.ceil(ports.length / size)));
|
|
60
|
+
const currentPortPage = $derived(Math.min(portPage, portPages - 1));
|
|
61
|
+
const visiblePorts = $derived(ports.slice(currentPortPage * size, (currentPortPage + 1) * size));
|
|
62
|
+
const magnitude = (value: McComplex) => Math.hypot(value.re, value.im);
|
|
63
|
+
const fixed = (value: number) => (Number.isFinite(value) ? value.toFixed(3) : '-');
|
|
64
|
+
const angle = (value: McComplex) =>
|
|
65
|
+
magnitude(value) > 1e-12 ? ((Math.atan2(value.im, value.re) * 180) / Math.PI).toFixed(2) : '-';
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<section aria-label="AC power flow results" class="mc-results">
|
|
69
|
+
<div class="result-heading">
|
|
70
|
+
<strong>AC power flow</strong><span
|
|
71
|
+
>{result.iterations} iterations{elapsedMs === null
|
|
72
|
+
? ''
|
|
73
|
+
: `, ${Math.round(elapsedMs)} ms`}</span
|
|
74
|
+
>
|
|
75
|
+
</div>
|
|
76
|
+
<dl class="totals">
|
|
77
|
+
<div>
|
|
78
|
+
<dt>Status</dt>
|
|
79
|
+
<dd>{result.converged ? 'Converged' : 'Not converged'}</dd>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<dt>Voltage band</dt>
|
|
83
|
+
<dd>{result.voltage_valid ? 'Valid' : `${result.voltage_violations.length} violations`}</dd>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<dt>Load voltage range</dt>
|
|
87
|
+
<dd>{result.min_voltage_pu == null || result.max_voltage_pu == null ? 'n/a' : `${fixed(result.min_voltage_pu)}–${fixed(result.max_voltage_pu)} pu`}</dd>
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<dt>Source P / Q</dt>
|
|
91
|
+
<dd>{fixed(sourcePower.re / 1000)} / {fixed(sourcePower.im / 1000)} kW / kvar</dd>
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<dt>Passive loss</dt>
|
|
95
|
+
<dd>{fixed(passiveLoss / 1000)} kW</dd>
|
|
96
|
+
</div>
|
|
97
|
+
</dl>
|
|
98
|
+
<label class="search"
|
|
99
|
+
>Bus result<select
|
|
100
|
+
value={busId}
|
|
101
|
+
onchange={(event) => {
|
|
102
|
+
busChoice = event.currentTarget.value;
|
|
103
|
+
selectedBus = null;
|
|
104
|
+
page = 0;
|
|
105
|
+
}}
|
|
106
|
+
>{#each graph?.buses ?? [] as bus (bus.id)}<option value={bus.id}>{bus.id}</option
|
|
107
|
+
>{/each}</select
|
|
108
|
+
></label
|
|
109
|
+
>
|
|
110
|
+
<label class="search"
|
|
111
|
+
>Terminals<input
|
|
112
|
+
aria-label="Find terminal"
|
|
113
|
+
type="search"
|
|
114
|
+
placeholder="Bus or terminal"
|
|
115
|
+
bind:value={search}
|
|
116
|
+
oninput={() => (page = 0)}
|
|
117
|
+
/></label
|
|
118
|
+
>
|
|
119
|
+
<!-- Scrollable result tables accept keyboard navigation. -->
|
|
120
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
121
|
+
<div class="scroll" role="region" aria-label="Terminal result columns" tabindex="0">
|
|
122
|
+
<table aria-label="Terminal results">
|
|
123
|
+
<thead
|
|
124
|
+
><tr
|
|
125
|
+
><th>Terminal</th><th>To ground<br />V</th><th>Angle<br />deg</th><th
|
|
126
|
+
>Net current<br />A</th
|
|
127
|
+
></tr
|
|
128
|
+
></thead
|
|
129
|
+
>
|
|
130
|
+
<tbody
|
|
131
|
+
>{#each visible as row (JSON.stringify([row.bus, row.terminal]))}
|
|
132
|
+
<tr
|
|
133
|
+
><td>{row.terminal}</td><td>{fixed(magnitude(row.voltage))}</td><td
|
|
134
|
+
>{angle(row.voltage)}</td
|
|
135
|
+
><td>{fixed(magnitude(row.current_into_network))}</td></tr
|
|
136
|
+
>
|
|
137
|
+
{/each}</tbody
|
|
138
|
+
>
|
|
139
|
+
</table>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="pagination">
|
|
142
|
+
<span>{terminals.length} terminals{busId ? ` at bus ${busId}` : ''}</span
|
|
143
|
+
>{#if pageCount > 1}<button
|
|
144
|
+
disabled={currentPage === 0}
|
|
145
|
+
onclick={() => (page = currentPage - 1)}
|
|
146
|
+
aria-label="Previous terminals">Previous</button
|
|
147
|
+
><span>{currentPage + 1} / {pageCount}</span><button
|
|
148
|
+
disabled={currentPage + 1 >= pageCount}
|
|
149
|
+
onclick={() => (page = currentPage + 1)}
|
|
150
|
+
aria-label="Next terminals">Next</button
|
|
151
|
+
>{/if}
|
|
152
|
+
</div>
|
|
153
|
+
{#if neutral}
|
|
154
|
+
<details>
|
|
155
|
+
<summary>Voltages to neutral</summary>
|
|
156
|
+
<!-- Scrollable result tables accept keyboard navigation. -->
|
|
157
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
158
|
+
<div class="scroll" role="region" aria-label="Neutral voltage columns" tabindex="0">
|
|
159
|
+
<table aria-label="Neutral voltage results">
|
|
160
|
+
<thead><tr><th>Terminal</th><th>To neutral<br />V</th><th>Angle<br />deg</th></tr></thead>
|
|
161
|
+
<tbody
|
|
162
|
+
>{#each visible as row (JSON.stringify([row.bus, row.terminal]))}
|
|
163
|
+
{@const relative = {
|
|
164
|
+
re: row.voltage.re - neutral.re,
|
|
165
|
+
im: row.voltage.im - neutral.im
|
|
166
|
+
}}
|
|
167
|
+
<tr
|
|
168
|
+
><td>{row.terminal}</td><td>{fixed(magnitude(relative))}</td><td
|
|
169
|
+
>{angle(relative)}</td
|
|
170
|
+
></tr
|
|
171
|
+
>
|
|
172
|
+
{/each}</tbody
|
|
173
|
+
>
|
|
174
|
+
</table>
|
|
175
|
+
</div>
|
|
176
|
+
</details>
|
|
177
|
+
{/if}
|
|
178
|
+
<details>
|
|
179
|
+
<summary>Equipment currents and power</summary>
|
|
180
|
+
<label class="search"
|
|
181
|
+
>Branch result<select
|
|
182
|
+
value={edgeId}
|
|
183
|
+
onchange={(event) => {
|
|
184
|
+
edgeChoice = event.currentTarget.value;
|
|
185
|
+
selectedEdge = null;
|
|
186
|
+
portPage = 0;
|
|
187
|
+
}}
|
|
188
|
+
><option value="">All equipment</option>{#each graph?.edges ?? [] as edge (edge.id)}<option
|
|
189
|
+
value={edge.id}>{edge.from} - {edge.to}, {edge.id}</option
|
|
190
|
+
>{/each}</select
|
|
191
|
+
></label
|
|
192
|
+
>
|
|
193
|
+
<p>Current enters the named equipment at each bus terminal.</p>
|
|
194
|
+
<!-- Scrollable result tables accept keyboard navigation. -->
|
|
195
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
196
|
+
<div class="scroll" role="region" aria-label="Equipment current columns" tabindex="0">
|
|
197
|
+
<table aria-label="Equipment currents">
|
|
198
|
+
<thead
|
|
199
|
+
><tr
|
|
200
|
+
><th>Equipment</th><th>Bus</th><th>Terminal</th><th>Current<br />A</th><th>P<br />kW</th
|
|
201
|
+
><th>Q<br />kvar</th></tr
|
|
202
|
+
></thead
|
|
203
|
+
><tbody
|
|
204
|
+
>{#each visiblePorts as row (JSON.stringify( [row.kind, row.element, row.branch, row.bus, row.terminal] ))}<tr
|
|
205
|
+
><td>{row.kind} {row.element}</td><td>{row.bus}</td><td>{row.terminal}</td><td
|
|
206
|
+
>{fixed(magnitude(row.current_into_element))}</td
|
|
207
|
+
><td>{fixed(row.power_into_element.re / 1000)}</td><td
|
|
208
|
+
>{fixed(row.power_into_element.im / 1000)}</td
|
|
209
|
+
></tr
|
|
210
|
+
>{/each}</tbody
|
|
211
|
+
>
|
|
212
|
+
</table>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="pagination">
|
|
215
|
+
<span>{ports.length} connections</span>{#if portPages > 1}<button
|
|
216
|
+
disabled={currentPortPage === 0}
|
|
217
|
+
onclick={() => (portPage = currentPortPage - 1)}
|
|
218
|
+
aria-label="Previous currents">Previous</button
|
|
219
|
+
><span>{currentPortPage + 1} / {portPages}</span><button
|
|
220
|
+
disabled={currentPortPage + 1 >= portPages}
|
|
221
|
+
onclick={() => (portPage = currentPortPage + 1)}
|
|
222
|
+
aria-label="Next currents">Next</button
|
|
223
|
+
>{/if}
|
|
224
|
+
</div>
|
|
225
|
+
</details>
|
|
226
|
+
<details>
|
|
227
|
+
<summary>Calculation details</summary>
|
|
228
|
+
<dl>
|
|
229
|
+
<div>
|
|
230
|
+
<dt>Maximum KCL residual</dt>
|
|
231
|
+
<dd>{result.physical_kcl_residual.toExponential(3)} A</dd>
|
|
232
|
+
</div>
|
|
233
|
+
<div>
|
|
234
|
+
<dt>Scaled KCL residual</dt>
|
|
235
|
+
<dd>{result.scaled_kcl_residual.toExponential(3)}</dd>
|
|
236
|
+
</div>
|
|
237
|
+
<div>
|
|
238
|
+
<dt>Voltage change</dt>
|
|
239
|
+
<dd>{result.voltage_change.toExponential(3)} V</dd>
|
|
240
|
+
</div>
|
|
241
|
+
<div>
|
|
242
|
+
<dt>Matrix size</dt>
|
|
243
|
+
<dd>{result.matrix_dimension}</dd>
|
|
244
|
+
</div>
|
|
245
|
+
<div>
|
|
246
|
+
<dt>Nonzero entries</dt>
|
|
247
|
+
<dd>{result.matrix_nonzeros}</dd>
|
|
248
|
+
</div>
|
|
249
|
+
<div>
|
|
250
|
+
<dt>Factorizations</dt>
|
|
251
|
+
<dd>{result.factorization_count}</dd>
|
|
252
|
+
</div>
|
|
253
|
+
</dl>
|
|
254
|
+
<p>
|
|
255
|
+
Voltage is measured to ground. Net current is the injection into the network. The calculation
|
|
256
|
+
checks current balance at unconstrained terminals.
|
|
257
|
+
</p>
|
|
258
|
+
</details>
|
|
259
|
+
</section>
|
|
260
|
+
|
|
261
|
+
<style>
|
|
262
|
+
.mc-results {
|
|
263
|
+
margin: 14px 0;
|
|
264
|
+
padding-top: 12px;
|
|
265
|
+
border-top: 1px solid var(--line);
|
|
266
|
+
font-size: 11px;
|
|
267
|
+
}
|
|
268
|
+
.result-heading,
|
|
269
|
+
.pagination,
|
|
270
|
+
dl div {
|
|
271
|
+
display: flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
justify-content: space-between;
|
|
274
|
+
gap: 8px;
|
|
275
|
+
}
|
|
276
|
+
.result-heading span,
|
|
277
|
+
.pagination,
|
|
278
|
+
dt,
|
|
279
|
+
p {
|
|
280
|
+
color: var(--text-secondary);
|
|
281
|
+
}
|
|
282
|
+
.search {
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
gap: 5px;
|
|
286
|
+
margin: 12px 0 8px;
|
|
287
|
+
}
|
|
288
|
+
input,
|
|
289
|
+
select,
|
|
290
|
+
button {
|
|
291
|
+
font: inherit;
|
|
292
|
+
color: var(--ink);
|
|
293
|
+
background: var(--paper);
|
|
294
|
+
border: 1px solid var(--line);
|
|
295
|
+
border-radius: 3px;
|
|
296
|
+
padding: 5px 7px;
|
|
297
|
+
}
|
|
298
|
+
button {
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
}
|
|
301
|
+
button:disabled {
|
|
302
|
+
opacity: 0.4;
|
|
303
|
+
cursor: default;
|
|
304
|
+
}
|
|
305
|
+
input,
|
|
306
|
+
select {
|
|
307
|
+
width: 100%;
|
|
308
|
+
box-sizing: border-box;
|
|
309
|
+
}
|
|
310
|
+
.scroll {
|
|
311
|
+
overflow: auto;
|
|
312
|
+
max-width: 100%;
|
|
313
|
+
}
|
|
314
|
+
.scroll:focus-visible {
|
|
315
|
+
outline: 2px solid var(--accent);
|
|
316
|
+
outline-offset: 2px;
|
|
317
|
+
}
|
|
318
|
+
table {
|
|
319
|
+
width: 100%;
|
|
320
|
+
border-collapse: collapse;
|
|
321
|
+
font: 10px var(--font-mono);
|
|
322
|
+
}
|
|
323
|
+
th,
|
|
324
|
+
td {
|
|
325
|
+
padding: 6px 5px;
|
|
326
|
+
text-align: right;
|
|
327
|
+
white-space: nowrap;
|
|
328
|
+
border-bottom: 1px solid var(--line);
|
|
329
|
+
}
|
|
330
|
+
th:first-child,
|
|
331
|
+
td:first-child {
|
|
332
|
+
text-align: left;
|
|
333
|
+
}
|
|
334
|
+
th {
|
|
335
|
+
color: var(--text-secondary);
|
|
336
|
+
font-weight: 500;
|
|
337
|
+
}
|
|
338
|
+
.pagination {
|
|
339
|
+
margin: 8px 0;
|
|
340
|
+
justify-content: flex-end;
|
|
341
|
+
}
|
|
342
|
+
.pagination > span:first-child {
|
|
343
|
+
margin-right: auto;
|
|
344
|
+
}
|
|
345
|
+
details {
|
|
346
|
+
margin-top: 12px;
|
|
347
|
+
}
|
|
348
|
+
summary {
|
|
349
|
+
cursor: pointer;
|
|
350
|
+
font-weight: 500;
|
|
351
|
+
}
|
|
352
|
+
p {
|
|
353
|
+
line-height: 1.5;
|
|
354
|
+
margin: 8px 0;
|
|
355
|
+
}
|
|
356
|
+
dl {
|
|
357
|
+
font: 10px var(--font-mono);
|
|
358
|
+
}
|
|
359
|
+
dl div {
|
|
360
|
+
padding: 4px 0;
|
|
361
|
+
}
|
|
362
|
+
dd {
|
|
363
|
+
margin: 0;
|
|
364
|
+
}
|
|
365
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McPfResult, DistGraph } from '@tellegen/engine';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
result: McPfResult;
|
|
4
|
+
elapsedMs: number | null;
|
|
5
|
+
selectedBus: string | null;
|
|
6
|
+
selectedEdge: string | null;
|
|
7
|
+
graph: DistGraph | null;
|
|
8
|
+
};
|
|
9
|
+
declare const McResults: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type McResults = ReturnType<typeof McResults>;
|
|
11
|
+
export default McResults;
|