@tellegen/svelte 0.1.2 → 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 +46 -0
- package/dist/Sparkline.svelte.d.ts +0 -1
- package/dist/TellegenMap.svelte +328 -200
- package/dist/TellegenMap.svelte.d.ts +0 -1
- package/dist/TellegenProvider.svelte +4 -0
- package/dist/TellegenProvider.svelte.d.ts +0 -1
- package/dist/TellegenShell.svelte +19 -13
- package/dist/TellegenShell.svelte.d.ts +0 -1
- package/dist/TellegenViewer.svelte.d.ts +0 -1
- package/dist/api.d.ts +8 -2
- package/dist/api.js +6 -2
- package/dist/colors.d.ts +7 -8
- package/dist/colors.js +13 -13
- package/dist/components/AppFooter.svelte.d.ts +0 -1
- package/dist/components/AppHeader.svelte +27 -13
- package/dist/components/AppHeader.svelte.d.ts +0 -1
- package/dist/components/BindingLines.svelte +3 -6
- package/dist/components/BindingLines.svelte.d.ts +0 -1
- package/dist/components/BusPicker.svelte +8 -2
- package/dist/components/BusPicker.svelte.d.ts +0 -1
- package/dist/components/ControlPanel.svelte +106 -343
- package/dist/components/ControlPanel.svelte.d.ts +0 -1
- package/dist/components/DemandSlider.svelte +26 -8
- package/dist/components/DemandSlider.svelte.d.ts +0 -1
- 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/DisplayControls.svelte.d.ts +0 -1
- package/dist/components/DropZone.svelte.d.ts +0 -1
- package/dist/components/FormulationSelector.svelte +8 -3
- package/dist/components/FormulationSelector.svelte.d.ts +0 -1
- package/dist/components/LocalCaseDetails.svelte +57 -44
- package/dist/components/LocalCaseDetails.svelte.d.ts +0 -1
- 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/MulticonductorDetails.svelte.d.ts +0 -1
- package/dist/components/NetworkStats.svelte +20 -18
- package/dist/components/NetworkStats.svelte.d.ts +0 -1
- 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/PlacementCue.svelte.d.ts +0 -1
- 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/RatingSlider.svelte.d.ts +0 -1
- package/dist/components/RestoreDefaultsButton.svelte.d.ts +0 -1
- 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/SensitivityReadout.svelte.d.ts +0 -1
- package/dist/components/SizeLegend.svelte.d.ts +0 -1
- package/dist/components/SolveCard.svelte +46 -49
- package/dist/components/SolveCard.svelte.d.ts +0 -1
- package/dist/components/TopMovers.svelte +8 -3
- package/dist/components/TopMovers.svelte.d.ts +2 -18
- package/dist/components.d.ts +2 -1
- package/dist/components.js +2 -0
- package/dist/context.svelte.d.ts +0 -1
- package/dist/controller.svelte.d.ts +74 -41
- package/dist/controller.svelte.js +1095 -284
- package/dist/diagram-labels.d.ts +9 -0
- package/dist/diagram-labels.js +40 -0
- package/dist/display.d.ts +3 -8
- package/dist/display.js +31 -27
- package/dist/drop-classify.d.ts +5 -42
- package/dist/drop-classify.js +5 -78
- package/dist/drop-limits.d.ts +27 -0
- package/dist/drop-limits.js +83 -0
- package/dist/format.d.ts +0 -1
- package/dist/format.js +5 -4
- package/dist/index.d.ts +11 -7
- package/dist/index.js +8 -3
- package/dist/map-bounds.d.ts +5 -0
- package/dist/map-bounds.js +41 -0
- package/dist/map.d.ts +1 -2
- package/dist/map.js +1 -1
- package/dist/multiconductor.d.ts +11 -3
- 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 -24
- package/dist/state.svelte.js +94 -20
- package/dist/synthetic-layout.d.ts +0 -1
- package/dist/synthetic-layout.js +17 -2
- package/dist/transformer-icon.d.ts +0 -1
- package/dist/types.d.ts +0 -1
- package/package.json +28 -12
- package/dist/Sparkline.svelte.d.ts.map +0 -1
- package/dist/TellegenMap.svelte.d.ts.map +0 -1
- package/dist/TellegenProvider.svelte.d.ts.map +0 -1
- package/dist/TellegenShell.svelte.d.ts.map +0 -1
- package/dist/TellegenViewer.svelte.d.ts.map +0 -1
- package/dist/api.d.ts.map +0 -1
- package/dist/colors.d.ts.map +0 -1
- package/dist/components/AppFooter.svelte.d.ts.map +0 -1
- package/dist/components/AppHeader.svelte.d.ts.map +0 -1
- package/dist/components/BindingLines.svelte.d.ts.map +0 -1
- package/dist/components/BusPicker.svelte.d.ts.map +0 -1
- package/dist/components/ControlPanel.svelte.d.ts.map +0 -1
- package/dist/components/DemandSlider.svelte.d.ts.map +0 -1
- package/dist/components/DisplayControls.svelte.d.ts.map +0 -1
- package/dist/components/DropZone.svelte.d.ts.map +0 -1
- package/dist/components/FormulationSelector.svelte.d.ts.map +0 -1
- package/dist/components/LocalCaseDetails.svelte.d.ts.map +0 -1
- package/dist/components/MulticonductorDetails.svelte.d.ts.map +0 -1
- package/dist/components/NetworkStats.svelte.d.ts.map +0 -1
- package/dist/components/PlacementCue.svelte.d.ts.map +0 -1
- package/dist/components/RatingSlider.svelte.d.ts.map +0 -1
- package/dist/components/RestoreDefaultsButton.svelte.d.ts.map +0 -1
- package/dist/components/SensitivityReadout.svelte.d.ts.map +0 -1
- package/dist/components/SizeLegend.svelte.d.ts.map +0 -1
- package/dist/components/SolveCard.svelte.d.ts.map +0 -1
- package/dist/components/TopMovers.svelte.d.ts.map +0 -1
- package/dist/components.d.ts.map +0 -1
- package/dist/context.svelte.d.ts.map +0 -1
- package/dist/controller.svelte.d.ts.map +0 -1
- package/dist/display.d.ts.map +0 -1
- package/dist/drop-classify.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/map.d.ts.map +0 -1
- package/dist/multiconductor.d.ts.map +0 -1
- package/dist/state.svelte.d.ts.map +0 -1
- package/dist/study-package.d.ts +0 -9
- package/dist/study-package.d.ts.map +0 -1
- package/dist/study-package.js +0 -17
- package/dist/synthetic-layout.d.ts.map +0 -1
- package/dist/transformer-icon.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
|
@@ -2,37 +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
|
+
// Grid exchange formats the committed state can export.
|
|
10
11
|
const EXPORT_FORMATS = [
|
|
11
12
|
{ token: 'matpower', label: 'MATPOWER (.m)' },
|
|
12
13
|
{ token: 'psse', label: 'PSS/E (.raw)' },
|
|
14
|
+
{ token: 'powerworld', label: 'PowerWorld (.aux)' },
|
|
15
|
+
{ token: 'pslf', label: 'PSLF (.epc)' },
|
|
13
16
|
{ token: 'powermodels-json', label: 'PowerModels (.json)' },
|
|
14
|
-
{ token: 'pandapower-json', label: 'pandapower (.json)' }
|
|
15
|
-
{ token: 'powerio-json', label: 'PowerIO (.json)' }
|
|
17
|
+
{ token: 'pandapower-json', label: 'pandapower (.json)' }
|
|
16
18
|
];
|
|
17
19
|
|
|
18
20
|
let busy = $state(false);
|
|
19
21
|
let exportOpen = $state(false);
|
|
20
|
-
let
|
|
22
|
+
let exportDiagnostics = $state<PowerIoDiagnostic[]>([]);
|
|
21
23
|
|
|
22
|
-
async function
|
|
24
|
+
async function saveCase(lc: LocalCase) {
|
|
23
25
|
busy = true;
|
|
24
|
-
|
|
26
|
+
exportDiagnostics = [];
|
|
25
27
|
try {
|
|
26
|
-
await ctrl.
|
|
28
|
+
await ctrl.saveCaseModule(lc);
|
|
27
29
|
} finally {
|
|
28
30
|
busy = false;
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
async function
|
|
34
|
+
async function saveSolution(lc: LocalCase) {
|
|
33
35
|
busy = true;
|
|
36
|
+
exportDiagnostics = [];
|
|
34
37
|
try {
|
|
35
|
-
|
|
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);
|
|
36
48
|
exportOpen = false;
|
|
37
49
|
} finally {
|
|
38
50
|
busy = false;
|
|
@@ -91,17 +103,17 @@
|
|
|
91
103
|
<dd>{fmt.format(lc.summary.base_mva)}</dd>
|
|
92
104
|
</div>
|
|
93
105
|
</dl>
|
|
94
|
-
{#if lc.
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{
|
|
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}
|
|
105
117
|
<p class="footnote mono">
|
|
106
118
|
no coordinates in this file: click the map or drop a geographic file
|
|
107
119
|
</p>
|
|
@@ -114,22 +126,17 @@
|
|
|
114
126
|
coordinates: geographic file data from {lc.geoSource}
|
|
115
127
|
</p>
|
|
116
128
|
{/if}
|
|
117
|
-
{#if lc.geoWarnings && lc.geoWarnings.length > 0}
|
|
118
|
-
<ul class="warnings mono">
|
|
119
|
-
{#each lc.geoWarnings.slice(0, 4) as w, i (i)}
|
|
120
|
-
<li>{w}</li>
|
|
121
|
-
{/each}
|
|
122
|
-
{#if lc.geoWarnings.length > 4}
|
|
123
|
-
<li>+{lc.geoWarnings.length - 4} more</li>
|
|
124
|
-
{/if}
|
|
125
|
-
</ul>
|
|
126
|
-
{/if}
|
|
127
129
|
<p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
|
|
128
|
-
{#if lc.
|
|
129
|
-
<div class="
|
|
130
|
-
<button class="reset mono" disabled={busy} onclick={() =>
|
|
131
|
-
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)
|
|
132
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}
|
|
133
140
|
<div class="export">
|
|
134
141
|
<button
|
|
135
142
|
class="reset mono"
|
|
@@ -143,7 +150,11 @@
|
|
|
143
150
|
<ul class="export-menu mono">
|
|
144
151
|
{#each EXPORT_FORMATS as f (f.token)}
|
|
145
152
|
<li>
|
|
146
|
-
<button
|
|
153
|
+
<button
|
|
154
|
+
class="reset mono"
|
|
155
|
+
disabled={busy}
|
|
156
|
+
onclick={() => exportCase(lc, f.token)}
|
|
157
|
+
>
|
|
147
158
|
{f.label}
|
|
148
159
|
</button>
|
|
149
160
|
</li>
|
|
@@ -151,25 +162,27 @@
|
|
|
151
162
|
</ul>
|
|
152
163
|
{/if}
|
|
153
164
|
</div>
|
|
154
|
-
{#if lc.view}
|
|
165
|
+
{#if lc.view || lc.diagram}
|
|
155
166
|
<button class="reset mono" disabled={busy} onclick={() => downloadLayout(lc)}>
|
|
156
|
-
|
|
167
|
+
{lc.displayMode === 'diagram'
|
|
168
|
+
? 'Download drawing (.geo.json)'
|
|
169
|
+
: 'Download geography (.geo.json)'}
|
|
157
170
|
</button>
|
|
158
171
|
{/if}
|
|
159
172
|
</div>
|
|
160
|
-
{#if
|
|
173
|
+
{#if exportDiagnostics.length > 0}
|
|
161
174
|
<ul class="warnings mono">
|
|
162
|
-
{#each
|
|
163
|
-
<li>{
|
|
175
|
+
{#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
|
|
176
|
+
<li>{formatPowerIoDiagnostic(diagnostic)}</li>
|
|
164
177
|
{/each}
|
|
165
|
-
{#if
|
|
166
|
-
<li>+{
|
|
178
|
+
{#if exportDiagnostics.length > 4}
|
|
179
|
+
<li>+{exportDiagnostics.length - 4} more</li>
|
|
167
180
|
{/if}
|
|
168
181
|
</ul>
|
|
169
182
|
{/if}
|
|
170
183
|
{/if}
|
|
171
184
|
{/if}
|
|
172
|
-
{#if lc.topology && lc.coordsKind !== 'file'}
|
|
185
|
+
{#if lc.displayMode !== 'diagram' && lc.topology && lc.coordsKind !== 'file'}
|
|
173
186
|
<button class="reset mono" onclick={() => ctrl.moveLocalCase(lc)}>
|
|
174
187
|
{lc.coordsKind === 'synthetic_pending'
|
|
175
188
|
? 'place on map'
|
|
@@ -223,7 +236,7 @@
|
|
|
223
236
|
color: var(--text-accent);
|
|
224
237
|
}
|
|
225
238
|
|
|
226
|
-
.
|
|
239
|
+
.case-actions {
|
|
227
240
|
display: flex;
|
|
228
241
|
flex-wrap: wrap;
|
|
229
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;
|