@tellegen/svelte 0.4.6 → 0.5.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/dist/TellegenMap.svelte +21 -26
- 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/globe-horizon.d.ts +6 -0
- package/dist/globe-horizon.js +41 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/multiconductor.js +38 -4
- package/dist/state.svelte.d.ts +28 -8
- package/dist/state.svelte.js +29 -5
- package/package.json +2 -2
package/dist/TellegenMap.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
3
3
|
import { tick, untrack } from 'svelte';
|
|
4
|
-
import type { Layer, PickingInfo } from '@deck.gl/core';
|
|
4
|
+
import type { Layer, LayerExtension, PickingInfo } from '@deck.gl/core';
|
|
5
5
|
import type { IconLayer, IconLayerProps, PathLayer, ScatterplotLayer } from '@deck.gl/layers';
|
|
6
6
|
import type { MapLibreOverlay } from '@deck.gl/maplibre';
|
|
7
7
|
import type { LngLatBoundsLike, Map as MapLibreMap } from 'maplibre-gl';
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
transformerIconAtlas,
|
|
55
55
|
transformerIconMapping
|
|
56
56
|
} from './transformer-icon.js';
|
|
57
|
+
import { createGlobeHorizon, withGlobeHorizon } from './globe-horizon.js';
|
|
57
58
|
import { foldMapBounds } from './map-bounds.js';
|
|
58
59
|
import { getAppState, getController } from './context.svelte.js';
|
|
59
60
|
import { displayFmt, fmt, rgbaCss } from './format.js';
|
|
@@ -239,6 +240,8 @@
|
|
|
239
240
|
isHovering: boolean;
|
|
240
241
|
};
|
|
241
242
|
let layerCtors = $state.raw<LayerCtors | null>(null);
|
|
243
|
+
// Hides the far side of the globe; built with the deck modules.
|
|
244
|
+
let globeHorizon: LayerExtension | null = null;
|
|
242
245
|
|
|
243
246
|
interface CaseDisplay {
|
|
244
247
|
prices: Map<number, number>;
|
|
@@ -818,9 +821,10 @@
|
|
|
818
821
|
}
|
|
819
822
|
|
|
820
823
|
async function loadMapModules() {
|
|
821
|
-
const [maplibre, deckMap, layers, workerUrl] = await Promise.all([
|
|
824
|
+
const [maplibre, deckMap, deckCore, layers, workerUrl] = await Promise.all([
|
|
822
825
|
import('maplibre-gl'),
|
|
823
826
|
import('@deck.gl/maplibre'),
|
|
827
|
+
import('@deck.gl/core'),
|
|
824
828
|
import('@deck.gl/layers'),
|
|
825
829
|
// A package export, not a relative import: its `development`
|
|
826
830
|
// condition keeps the worker's `?worker&url` import out of the dev
|
|
@@ -832,6 +836,7 @@
|
|
|
832
836
|
return {
|
|
833
837
|
maplibregl: maplibre,
|
|
834
838
|
MapLibreOverlay: deckMap.MapLibreOverlay,
|
|
839
|
+
horizon: createGlobeHorizon(deckCore.LayerExtension),
|
|
835
840
|
PathLayer: layers.PathLayer,
|
|
836
841
|
ScatterplotLayer: layers.ScatterplotLayer,
|
|
837
842
|
IconLayer: layers.IconLayer
|
|
@@ -844,26 +849,19 @@
|
|
|
844
849
|
// maplibre's own WebGL2 context instead of a second canvas, so the page
|
|
845
850
|
// holds one GL context rather than two. Safari evicts WebGL contexts under
|
|
846
851
|
// memory and tab pressure, and one context survives that far better; the
|
|
847
|
-
// deck layers also inherit maplibre's 4096 canvas clamp. The
|
|
848
|
-
// writes no depth
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
function
|
|
852
|
-
return {
|
|
853
|
-
depthWriteEnabled: false,
|
|
854
|
-
depthCompare: type === 'globe' ? ('less-equal' as const) : ('always' as const)
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function buildOverlay(
|
|
859
|
-
MapLibreOverlay: MapModules['MapLibreOverlay'],
|
|
860
|
-
type: MapProjection
|
|
861
|
-
): MapLibreOverlay {
|
|
852
|
+
// deck layers also inherit maplibre's 4096 canvas clamp. The basemap
|
|
853
|
+
// writes no depth we can trust (on the globe, deck's sphere and maplibre's
|
|
854
|
+
// disagree near the surface), so the overlay always draws on top, and on
|
|
855
|
+
// the globe the horizon extension hides what lies on the far side.
|
|
856
|
+
function buildOverlay(MapLibreOverlay: MapModules['MapLibreOverlay']): MapLibreOverlay {
|
|
862
857
|
const touch = coarsePointer();
|
|
863
858
|
return new MapLibreOverlay({
|
|
864
859
|
interleaved: true,
|
|
865
860
|
layers: [],
|
|
866
|
-
parameters:
|
|
861
|
+
parameters: {
|
|
862
|
+
depthWriteEnabled: false,
|
|
863
|
+
depthCompare: 'always'
|
|
864
|
+
},
|
|
867
865
|
// a small bus draws ~9px across, well under a fingertip
|
|
868
866
|
pickingRadius: touch ? 12 : 0,
|
|
869
867
|
onClick: (info: PickingInfo) => {
|
|
@@ -894,9 +892,10 @@
|
|
|
894
892
|
let cleanup = () => {};
|
|
895
893
|
let cancelled = false;
|
|
896
894
|
void loadMapModules()
|
|
897
|
-
.then(({ maplibregl, MapLibreOverlay, PathLayer, ScatterplotLayer, IconLayer }) => {
|
|
895
|
+
.then(({ maplibregl, MapLibreOverlay, horizon, PathLayer, ScatterplotLayer, IconLayer }) => {
|
|
898
896
|
if (cancelled) return;
|
|
899
897
|
layerCtors = { PathLayer, ScatterplotLayer, IconLayer };
|
|
898
|
+
globeHorizon = horizon;
|
|
900
899
|
const m = new maplibregl.Map({
|
|
901
900
|
container,
|
|
902
901
|
style: spec.primary,
|
|
@@ -924,10 +923,7 @@
|
|
|
924
923
|
onFallback: (reason) => onBasemapFallback?.(reason),
|
|
925
924
|
onFailure: (reason) => onBasemapError?.(reason)
|
|
926
925
|
});
|
|
927
|
-
const o = buildOverlay(
|
|
928
|
-
MapLibreOverlay,
|
|
929
|
-
untrack(() => projection)
|
|
930
|
-
);
|
|
926
|
+
const o = buildOverlay(MapLibreOverlay);
|
|
931
927
|
m.addControl(o);
|
|
932
928
|
m.on('click', (e) => {
|
|
933
929
|
if (app.placingId) onplacecase(e.lngLat.lng, e.lngLat.lat);
|
|
@@ -1069,7 +1065,6 @@
|
|
|
1069
1065
|
// MapLibre throws, and the `style.load` handler above applies it instead.
|
|
1070
1066
|
$effect(() => {
|
|
1071
1067
|
const type = projection;
|
|
1072
|
-
overlay?.setProps({ parameters: overlayParameters(type) });
|
|
1073
1068
|
const m = map;
|
|
1074
1069
|
if (!m) return;
|
|
1075
1070
|
try {
|
|
@@ -1158,7 +1153,7 @@
|
|
|
1158
1153
|
updateTriggers: { getFillColor: [study, mode, theme], getRadius: [app.busSizing] }
|
|
1159
1154
|
})
|
|
1160
1155
|
);
|
|
1161
|
-
overlay.setProps({ layers: [...layers, ...extraLayers] });
|
|
1156
|
+
overlay.setProps({ layers: withGlobeHorizon([...layers, ...extraLayers], globeHorizon) });
|
|
1162
1157
|
return;
|
|
1163
1158
|
}
|
|
1164
1159
|
for (const c of app.cases) {
|
|
@@ -1431,7 +1426,7 @@
|
|
|
1431
1426
|
})
|
|
1432
1427
|
);
|
|
1433
1428
|
}
|
|
1434
|
-
overlay.setProps({ layers: [...layers, ...extraLayers] });
|
|
1429
|
+
overlay.setProps({ layers: withGlobeHorizon([...layers, ...extraLayers], globeHorizon) });
|
|
1435
1430
|
});
|
|
1436
1431
|
|
|
1437
1432
|
$effect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { mcNetworkScopeLabel, mcLoadModelLabel } from '@tellegen/engine';
|
|
2
3
|
import { untrack } from 'svelte';
|
|
3
4
|
import { on } from 'svelte/events';
|
|
4
5
|
import type { Network, NetworkBus } from '../api.js';
|
|
@@ -395,7 +396,10 @@
|
|
|
395
396
|
>
|
|
396
397
|
</div>
|
|
397
398
|
</div>
|
|
398
|
-
<p class="coordinate-label">
|
|
399
|
+
<p class="coordinate-label">
|
|
400
|
+
Diagram coordinates{#if multiconductor?.result}
|
|
401
|
+
· {mcLoadModelLabel(multiconductor.result.load_model)} · {mcNetworkScopeLabel(multiconductor.result.network_scope)}{/if}
|
|
402
|
+
</p>
|
|
399
403
|
</div>
|
|
400
404
|
|
|
401
405
|
<style>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { mcLoadModelLabel, type McLoadModelAssessment } from '@tellegen/engine';
|
|
3
|
+
let { model, compact = false }: { model?: McLoadModelAssessment | null; compact?: boolean } =
|
|
4
|
+
$props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<aside class:warning={model?.status !== 'original'} aria-label="Load-model qualification">
|
|
8
|
+
<strong>{mcLoadModelLabel(model)}</strong>
|
|
9
|
+
{#if model?.status === 'modified'}
|
|
10
|
+
<p>
|
|
11
|
+
The calculation converged using modified load equations on {model.modified_branch_count}
|
|
12
|
+
load {model.modified_branch_count === 1 ? 'branch' : 'branches'}. These values are not a
|
|
13
|
+
verified solution of the original load model. The qualification is retained when saving or
|
|
14
|
+
exporting.
|
|
15
|
+
</p>
|
|
16
|
+
{:else if model?.status !== 'original'}
|
|
17
|
+
<p>
|
|
18
|
+
These results have no verified solution of the original load equations. Rerun to assess the
|
|
19
|
+
load model.
|
|
20
|
+
</p>
|
|
21
|
+
{/if}
|
|
22
|
+
{#if model && !compact}
|
|
23
|
+
<details>
|
|
24
|
+
<summary>Model assessment and settings used</summary>
|
|
25
|
+
<p>
|
|
26
|
+
Original-model KCL residual: {model.original_kcl_residual === null
|
|
27
|
+
? 'undefined at this voltage'
|
|
28
|
+
: `${model.original_kcl_residual.toExponential(3)} A`}. Scaled residual: {model.original_scaled_kcl_residual ===
|
|
29
|
+
null
|
|
30
|
+
? 'unavailable'
|
|
31
|
+
: model.original_scaled_kcl_residual.toPrecision(4)} (at most 1 passes).
|
|
32
|
+
</p>
|
|
33
|
+
<p>
|
|
34
|
+
Load-model envelope {model.options.voltage_envelope ? 'enabled' : 'disabled'}; Vlow {model
|
|
35
|
+
.options.v_low_pu}, Vmin {model.options.v_min_pu}, Vmax {model.options.v_max_pu} pu. These thresholds
|
|
36
|
+
change load equations; they are not operating voltage limits.
|
|
37
|
+
</p>
|
|
38
|
+
<p>
|
|
39
|
+
Maximum iterations {model.options.max_iterations}; damping {model.options.damping};
|
|
40
|
+
voltage-change tolerance {model.options.tolerance} V; zero-voltage tolerance {model.options
|
|
41
|
+
.zero_voltage_tolerance} V; KCL tolerances {model.options.absolute_kcl_tolerance} A absolute
|
|
42
|
+
and {model.options.relative_kcl_tolerance} relative.
|
|
43
|
+
</p>
|
|
44
|
+
</details>
|
|
45
|
+
{/if}
|
|
46
|
+
</aside>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
aside {
|
|
50
|
+
margin: 0.75rem 0;
|
|
51
|
+
padding: 0.65rem;
|
|
52
|
+
border: 1px solid var(--line);
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
}
|
|
56
|
+
.warning {
|
|
57
|
+
border-left: 4px solid #b7791f;
|
|
58
|
+
}
|
|
59
|
+
p {
|
|
60
|
+
margin: 0.4rem 0;
|
|
61
|
+
line-height: 1.5;
|
|
62
|
+
}
|
|
63
|
+
summary {
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
margin-top: 0.4rem;
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type McLoadModelAssessment } from '@tellegen/engine';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
model?: McLoadModelAssessment | null;
|
|
4
|
+
compact?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const McModelQualification: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type McModelQualification = ReturnType<typeof McModelQualification>;
|
|
8
|
+
export default McModelQualification;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
mcNetworkScopeLabel,
|
|
4
|
+
type McNetworkScope as NetworkScope,
|
|
5
|
+
type McExcludedIsland
|
|
6
|
+
} from '@tellegen/engine';
|
|
7
|
+
let {
|
|
8
|
+
scope,
|
|
9
|
+
islands = [],
|
|
10
|
+
preview = false
|
|
11
|
+
}: {
|
|
12
|
+
scope?: NetworkScope | null;
|
|
13
|
+
islands?: McExcludedIsland[];
|
|
14
|
+
preview?: boolean;
|
|
15
|
+
} = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
{#if scope}
|
|
19
|
+
<aside aria-label="Network calculation scope">
|
|
20
|
+
<strong>{preview ? 'Unpowered islands detected' : mcNetworkScopeLabel(scope)}</strong>
|
|
21
|
+
{#if preview}<p>
|
|
22
|
+
{scope.excluded_island_count}
|
|
23
|
+
{scope.excluded_island_count === 1 ? 'island has' : 'islands have'} no voltage source.
|
|
24
|
+
</p>{/if}
|
|
25
|
+
<p>
|
|
26
|
+
{scope.excluded_bus_count}
|
|
27
|
+
{scope.excluded_bus_count === 1 ? 'bus' : 'buses'} and {scope.excluded_equipment_count}
|
|
28
|
+
equipment
|
|
29
|
+
{scope.excluded_equipment_count === 1 ? 'record' : 'records'}
|
|
30
|
+
{preview
|
|
31
|
+
? 'will be excluded when you solve the energized network'
|
|
32
|
+
: 'are excluded from the calculation'}
|
|
33
|
+
because their {scope.excluded_island_count === 1 ? 'island has' : 'islands have'} no voltage source.
|
|
34
|
+
The full network remains available for inspection. Excluded equipment has no calculated result;
|
|
35
|
+
its voltage and power are not reported as zero.
|
|
36
|
+
</p>
|
|
37
|
+
{#if scope.excluded_load_count}<p>
|
|
38
|
+
{scope.excluded_load_count}
|
|
39
|
+
{scope.excluded_load_count === 1 ? 'load has' : 'loads have'} no voltage source and {preview
|
|
40
|
+
? 'will be omitted'
|
|
41
|
+
: scope.excluded_load_count === 1
|
|
42
|
+
? 'is absent'
|
|
43
|
+
: 'are absent'} from solved totals.
|
|
44
|
+
</p>{/if}
|
|
45
|
+
{#if islands.length}
|
|
46
|
+
<details>
|
|
47
|
+
<summary
|
|
48
|
+
>{preview
|
|
49
|
+
? 'Islands and equipment to exclude'
|
|
50
|
+
: 'Excluded islands and equipment'}</summary
|
|
51
|
+
>
|
|
52
|
+
{#each islands as island, i}
|
|
53
|
+
<h4>Island {i + 1}: no voltage source</h4>
|
|
54
|
+
<p>Buses: {island.buses.join(', ')}</p>
|
|
55
|
+
<ul>
|
|
56
|
+
{#each island.equipment as item}<li>
|
|
57
|
+
{item.kind}: {item.name}
|
|
58
|
+
</li>{/each}
|
|
59
|
+
</ul>
|
|
60
|
+
{/each}
|
|
61
|
+
</details>
|
|
62
|
+
{/if}
|
|
63
|
+
</aside>
|
|
64
|
+
{/if}
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
aside {
|
|
68
|
+
margin: 0.75rem 0;
|
|
69
|
+
padding: 0.65rem;
|
|
70
|
+
border: 1px solid var(--line);
|
|
71
|
+
border-left: 4px solid #b7791f;
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
overflow-wrap: anywhere;
|
|
75
|
+
}
|
|
76
|
+
p {
|
|
77
|
+
margin: 0.4rem 0;
|
|
78
|
+
line-height: 1.5;
|
|
79
|
+
}
|
|
80
|
+
summary {
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
details {
|
|
84
|
+
max-height: 20rem;
|
|
85
|
+
overflow: auto;
|
|
86
|
+
}
|
|
87
|
+
h4 {
|
|
88
|
+
margin: 0.75rem 0 0.25rem;
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type McNetworkScope as NetworkScope, type McExcludedIsland } from '@tellegen/engine';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
scope?: NetworkScope | null;
|
|
4
|
+
islands?: McExcludedIsland[];
|
|
5
|
+
preview?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const McNetworkScope: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type McNetworkScope = ReturnType<typeof McNetworkScope>;
|
|
9
|
+
export default McNetworkScope;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { DEFAULT_MC_PF_OPTIONS, validateMcPfOptions, type McPfOptions } from '@tellegen/engine';
|
|
3
|
+
let {
|
|
4
|
+
options = $bindable<McPfOptions>({ ...DEFAULT_MC_PF_OPTIONS }),
|
|
5
|
+
disabled = false,
|
|
6
|
+
onapply
|
|
7
|
+
}: {
|
|
8
|
+
options: McPfOptions;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
onapply?: (options: McPfOptions) => Promise<unknown>;
|
|
11
|
+
} = $props();
|
|
12
|
+
let failure = $state('');
|
|
13
|
+
const validation = $derived(validateMcPfOptions(options));
|
|
14
|
+
// Keep one shared set of next-run settings for every solve action. Fill
|
|
15
|
+
// omitted options when opening an older snapshot with partial settings.
|
|
16
|
+
$effect(() => {
|
|
17
|
+
if (Object.keys(DEFAULT_MC_PF_OPTIONS).some((key) => !(key in options))) {
|
|
18
|
+
options = { ...DEFAULT_MC_PF_OPTIONS, ...options };
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
async function apply() {
|
|
22
|
+
failure = '';
|
|
23
|
+
if (validation) return;
|
|
24
|
+
try {
|
|
25
|
+
await onapply?.({ ...options });
|
|
26
|
+
} catch (error) {
|
|
27
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<details class="settings">
|
|
33
|
+
<summary>Power flow settings</summary>
|
|
34
|
+
<p>
|
|
35
|
+
These settings apply to both Run power flow and Solve energized network. Editing them does not
|
|
36
|
+
start a calculation. A failed run retains the previous result and the settings used for that
|
|
37
|
+
result.
|
|
38
|
+
</p>
|
|
39
|
+
<fieldset {disabled}>
|
|
40
|
+
<legend>Load-model envelope</legend>
|
|
41
|
+
<label class="toggle"
|
|
42
|
+
><input type="checkbox" bind:checked={options.voltage_envelope} />Use load-model envelope</label
|
|
43
|
+
>
|
|
44
|
+
<p>
|
|
45
|
+
Outside the normal band, the envelope changes the load equations to help convergence. Modified
|
|
46
|
+
results are labelled explicitly. These thresholds are not operating voltage limits.
|
|
47
|
+
</p>
|
|
48
|
+
<label>Vlow (pu)<input type="number" step="any" bind:value={options.v_low_pu} /></label>
|
|
49
|
+
<label>Vmin (pu)<input type="number" step="any" bind:value={options.v_min_pu} /></label>
|
|
50
|
+
<label>Vmax (pu)<input type="number" step="any" bind:value={options.v_max_pu} /></label>
|
|
51
|
+
<p>
|
|
52
|
+
With the envelope disabled, the original load equations are used at every voltage; convergence
|
|
53
|
+
is not guaranteed.
|
|
54
|
+
</p>
|
|
55
|
+
</fieldset>
|
|
56
|
+
<fieldset {disabled}>
|
|
57
|
+
<legend>Numerical solver</legend>
|
|
58
|
+
<label
|
|
59
|
+
>Maximum iterations<input
|
|
60
|
+
type="number"
|
|
61
|
+
min="1"
|
|
62
|
+
step="1"
|
|
63
|
+
bind:value={options.max_iterations}
|
|
64
|
+
/></label
|
|
65
|
+
>
|
|
66
|
+
<label
|
|
67
|
+
>Damping<input type="number" min="0" max="1" step="any" bind:value={options.damping} /></label
|
|
68
|
+
>
|
|
69
|
+
<details>
|
|
70
|
+
<summary>Advanced tolerances</summary>
|
|
71
|
+
<label
|
|
72
|
+
>Voltage-change tolerance (V)<input
|
|
73
|
+
type="number"
|
|
74
|
+
step="any"
|
|
75
|
+
bind:value={options.tolerance}
|
|
76
|
+
/></label
|
|
77
|
+
>
|
|
78
|
+
<label
|
|
79
|
+
>Zero-voltage tolerance (V)<input
|
|
80
|
+
type="number"
|
|
81
|
+
step="any"
|
|
82
|
+
bind:value={options.zero_voltage_tolerance}
|
|
83
|
+
/></label
|
|
84
|
+
>
|
|
85
|
+
<label
|
|
86
|
+
>Absolute KCL tolerance (A)<input
|
|
87
|
+
type="number"
|
|
88
|
+
step="any"
|
|
89
|
+
bind:value={options.absolute_kcl_tolerance}
|
|
90
|
+
/></label
|
|
91
|
+
>
|
|
92
|
+
<label
|
|
93
|
+
>Relative KCL tolerance<input
|
|
94
|
+
type="number"
|
|
95
|
+
step="any"
|
|
96
|
+
bind:value={options.relative_kcl_tolerance}
|
|
97
|
+
/></label
|
|
98
|
+
>
|
|
99
|
+
</details>
|
|
100
|
+
</fieldset>
|
|
101
|
+
{#if validation}<p role="alert">{validation}</p>{/if}
|
|
102
|
+
{#if failure}<p role="alert">{failure}</p>{/if}
|
|
103
|
+
<div class="actions">
|
|
104
|
+
{#if onapply}<button disabled={disabled || !!validation} onclick={() => void apply()}
|
|
105
|
+
>Apply settings and run</button
|
|
106
|
+
>{/if}
|
|
107
|
+
<button
|
|
108
|
+
{disabled}
|
|
109
|
+
onclick={() => {
|
|
110
|
+
options = { ...DEFAULT_MC_PF_OPTIONS };
|
|
111
|
+
failure = '';
|
|
112
|
+
}}>Restore defaults</button
|
|
113
|
+
>
|
|
114
|
+
</div>
|
|
115
|
+
</details>
|
|
116
|
+
|
|
117
|
+
<style>
|
|
118
|
+
.settings {
|
|
119
|
+
margin: 0.75rem 0;
|
|
120
|
+
font-size: 12px;
|
|
121
|
+
}
|
|
122
|
+
summary {
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
}
|
|
126
|
+
fieldset {
|
|
127
|
+
margin: 0.75rem 0;
|
|
128
|
+
border: 1px solid var(--line);
|
|
129
|
+
border-radius: 4px;
|
|
130
|
+
}
|
|
131
|
+
label {
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
justify-content: space-between;
|
|
135
|
+
gap: 0.5rem;
|
|
136
|
+
margin: 0.5rem 0;
|
|
137
|
+
}
|
|
138
|
+
input[type='number'] {
|
|
139
|
+
width: 7rem;
|
|
140
|
+
min-width: 0;
|
|
141
|
+
}
|
|
142
|
+
.toggle {
|
|
143
|
+
justify-content: flex-start;
|
|
144
|
+
}
|
|
145
|
+
p {
|
|
146
|
+
line-height: 1.5;
|
|
147
|
+
}
|
|
148
|
+
.actions {
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-wrap: wrap;
|
|
151
|
+
gap: 0.5rem;
|
|
152
|
+
}
|
|
153
|
+
[role='alert'] {
|
|
154
|
+
color: var(--text-primary);
|
|
155
|
+
border-left: 3px solid #b7791f;
|
|
156
|
+
padding-left: 0.5rem;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type McPfOptions } from '@tellegen/engine';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
options: McPfOptions;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
onapply?: (options: McPfOptions) => Promise<unknown>;
|
|
6
|
+
};
|
|
7
|
+
declare const McPfSettings: import("svelte").Component<$$ComponentProps, {}, "options">;
|
|
8
|
+
type McPfSettings = ReturnType<typeof McPfSettings>;
|
|
9
|
+
export default McPfSettings;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import McNetworkScope from './McNetworkScope.svelte';
|
|
3
|
+
import McModelQualification from './McModelQualification.svelte';
|
|
2
4
|
import {
|
|
3
5
|
mcPfResultDetail,
|
|
4
6
|
summarizeMcPfResult,
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
import type { McEditTiming } from '../state.svelte.js';
|
|
14
16
|
let {
|
|
15
17
|
result,
|
|
18
|
+
excludedIslands = [],
|
|
16
19
|
detail,
|
|
17
20
|
timing = null,
|
|
18
21
|
elapsedMs,
|
|
@@ -22,6 +25,7 @@
|
|
|
22
25
|
}: {
|
|
23
26
|
/** A live session's summary, or a stored full result. */
|
|
24
27
|
result: McPfSummary | McPfResult;
|
|
28
|
+
excludedIslands?: import('@tellegen/engine').McExcludedIsland[];
|
|
25
29
|
/** Fetches one bus's terminals and a page of equipment ports. A full
|
|
26
30
|
* `result` serves its own pages when this is omitted. */
|
|
27
31
|
detail?: (query: McPfDetailQuery) => Promise<McPfDetail | null>;
|
|
@@ -120,6 +124,11 @@
|
|
|
120
124
|
</script>
|
|
121
125
|
|
|
122
126
|
<section aria-label="AC power flow results" class="mc-results">
|
|
127
|
+
<McNetworkScope
|
|
128
|
+
scope={summary.network_scope}
|
|
129
|
+
islands={'excluded_islands' in result ? result.excluded_islands : excludedIslands}
|
|
130
|
+
/>
|
|
131
|
+
<McModelQualification model={summary.load_model} />
|
|
123
132
|
<div class="result-heading">
|
|
124
133
|
<strong>AC power flow</strong><span
|
|
125
134
|
>{summary.iterations} iterations{elapsedMs === null
|
|
@@ -133,7 +142,7 @@
|
|
|
133
142
|
<dd>{summary.converged ? 'Converged' : 'Not converged'}</dd>
|
|
134
143
|
</div>
|
|
135
144
|
<div>
|
|
136
|
-
<dt>
|
|
145
|
+
<dt>Load-model voltage band</dt>
|
|
137
146
|
<dd>{summary.voltage_valid ? 'Valid' : `${summary.voltage_violation_count} violations`}</dd>
|
|
138
147
|
</div>
|
|
139
148
|
<div>
|
|
@@ -165,6 +174,12 @@
|
|
|
165
174
|
>{/each}</select
|
|
166
175
|
></label
|
|
167
176
|
>
|
|
177
|
+
{#if excludedIslands.some((island) => island.buses.includes(busId ?? ''))}
|
|
178
|
+
<p role="status">
|
|
179
|
+
This bus is excluded from the calculation because its island has no voltage source. No result
|
|
180
|
+
is available.
|
|
181
|
+
</p>
|
|
182
|
+
{/if}
|
|
168
183
|
<label class="search"
|
|
169
184
|
>Terminals<input
|
|
170
185
|
aria-label="Find terminal"
|
|
@@ -3,6 +3,7 @@ import type { McEditTiming } from '../state.svelte.js';
|
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
/** A live session's summary, or a stored full result. */
|
|
5
5
|
result: McPfSummary | McPfResult;
|
|
6
|
+
excludedIslands?: import('@tellegen/engine').McExcludedIsland[];
|
|
6
7
|
/** Fetches one bus's terminals and a page of equipment ports. A full
|
|
7
8
|
* `result` serves its own pages when this is omitted. */
|
|
8
9
|
detail?: (query: McPfDetailQuery) => Promise<McPfDetail | null>;
|
|
@@ -206,6 +206,7 @@
|
|
|
206
206
|
`Tellegen export ${new Date().toISOString()}`,
|
|
207
207
|
`Case: ${label}`,
|
|
208
208
|
`${snapshot.label}; ${snapshot.iterations ?? '?'} iterations`,
|
|
209
|
+
...(snapshot.provenance ?? []),
|
|
209
210
|
'P/Q are positive into the equipment at each end; voltages are terminal-pair magnitudes'
|
|
210
211
|
]);
|
|
211
212
|
const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }));
|
|
@@ -227,6 +228,7 @@
|
|
|
227
228
|
onclick={() => onschematic(!schematic)}>{schematic ? 'Show map' : 'Show diagram'}</button
|
|
228
229
|
>{/if}
|
|
229
230
|
</div>
|
|
231
|
+
{#if snapshot}<p class="result-provenance">{snapshot.label}</p>{/if}
|
|
230
232
|
<OmniSearch
|
|
231
233
|
index={search}
|
|
232
234
|
hint={searchHint}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import McNetworkScope from './McNetworkScope.svelte';
|
|
3
|
+
import McModelQualification from './McModelQualification.svelte';
|
|
2
4
|
import { getAppState, getController } from '../context.svelte.js';
|
|
3
5
|
import { fmt, rgbaCss } from '../format.js';
|
|
4
6
|
import {
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
} from '../multiconductor.js';
|
|
11
13
|
import { getPanelLayout } from '../panels.svelte.js';
|
|
12
14
|
import { getNoticeCenter } from '../notices.svelte.js';
|
|
13
|
-
import { type DistAttachmentKind } from '@tellegen/engine';
|
|
15
|
+
import { validateMcPfOptions, type DistAttachmentKind } from '@tellegen/engine';
|
|
14
16
|
|
|
15
17
|
const app = getAppState();
|
|
16
18
|
const ctrl = getController();
|
|
@@ -50,7 +52,10 @@
|
|
|
50
52
|
{#if s}
|
|
51
53
|
<p class="tag">Multiconductor</p>
|
|
52
54
|
<details class="case-details">
|
|
53
|
-
<summary
|
|
55
|
+
<summary
|
|
56
|
+
>{s.n_bus} buses, {s.n_edge}
|
|
57
|
+
{s.n_edge === 1 ? 'branch' : 'branches'}</summary
|
|
58
|
+
>
|
|
54
59
|
<dl>
|
|
55
60
|
<div>
|
|
56
61
|
<dt>Buses</dt>
|
|
@@ -97,6 +102,15 @@
|
|
|
97
102
|
</dd>
|
|
98
103
|
</div>
|
|
99
104
|
</dl>
|
|
105
|
+
<McNetworkScope
|
|
106
|
+
scope={mc.mcPreparedScope}
|
|
107
|
+
islands={mc.mcExcludedIslands}
|
|
108
|
+
preview={!mc.result}
|
|
109
|
+
/>
|
|
110
|
+
{#if !mc.mcPfSupported}<p class="footnote">{mc.mcPfReason}</p>{/if}
|
|
111
|
+
{#if validateMcPfOptions(mc.mcOptions)}<p class="footnote">
|
|
112
|
+
{validateMcPfOptions(mc.mcOptions)} Update Power flow settings in Studies before solving.
|
|
113
|
+
</p>{/if}
|
|
100
114
|
<div class="calculation-actions">
|
|
101
115
|
{#if mc.solving}
|
|
102
116
|
<span role="status">Calculating…</span>{#if mc.solveAbort}<button
|
|
@@ -104,13 +118,13 @@
|
|
|
104
118
|
>{/if}
|
|
105
119
|
{:else}
|
|
106
120
|
<button
|
|
107
|
-
disabled={!mc.mcPfSupported}
|
|
121
|
+
disabled={!mc.mcPfSupported || !!validateMcPfOptions(mc.mcOptions)}
|
|
108
122
|
onclick={() => {
|
|
109
123
|
void ctrl
|
|
110
|
-
.solveMultiCase(mc)
|
|
124
|
+
.solveMultiCase(mc, mc.mcOptions, undefined, mc.mcRequiresEnergizedSolve)
|
|
111
125
|
.then(openResults)
|
|
112
126
|
.catch(() => {});
|
|
113
|
-
}}>
|
|
127
|
+
}}>{mc.mcSolveLabel}</button
|
|
114
128
|
>
|
|
115
129
|
{#if !mc.mcPfSupported}
|
|
116
130
|
<button
|
|
@@ -125,12 +139,17 @@
|
|
|
125
139
|
{/if}
|
|
126
140
|
{/if}
|
|
127
141
|
</div>
|
|
128
|
-
{#if mc.result}<
|
|
142
|
+
{#if mc.result}<McModelQualification model={mc.result.load_model} compact />
|
|
143
|
+
<p class="footnote">Converged, {mc.result.iterations} iterations</p>
|
|
129
144
|
<button class="reset" onclick={openResults}>View results in Studies</button>{/if}
|
|
130
145
|
{/if}
|
|
131
146
|
|
|
132
147
|
{#if mc.selectedBus}
|
|
133
148
|
{@const b = mc.selectedBus}
|
|
149
|
+
{#if mc.mcExcludedIslands.some((island) => island.buses.includes(b.id))}<p role="status">
|
|
150
|
+
This bus belongs to an island with no voltage source and will be excluded from
|
|
151
|
+
energized-network calculations. No power-flow result is available for it.
|
|
152
|
+
</p>{/if}
|
|
134
153
|
<hr />
|
|
135
154
|
<h3>Bus <span class="mono">{b.id}</span></h3>
|
|
136
155
|
<div class="terminals">
|
package/dist/components.d.ts
CHANGED
|
@@ -25,3 +25,6 @@ export { default as TopMovers } from './components/TopMovers.svelte';
|
|
|
25
25
|
export { default as Sparkline } from './Sparkline.svelte';
|
|
26
26
|
export { default as McResults } from './components/McResults.svelte';
|
|
27
27
|
export { default as McWorkbench } from './components/McWorkbench.svelte';
|
|
28
|
+
export { default as McPfSettings } from './components/McPfSettings.svelte';
|
|
29
|
+
export { default as McModelQualification } from './components/McModelQualification.svelte';
|
|
30
|
+
export { default as McNetworkScope } from './components/McNetworkScope.svelte';
|
package/dist/components.js
CHANGED
|
@@ -25,3 +25,6 @@ export { default as TopMovers } from './components/TopMovers.svelte';
|
|
|
25
25
|
export { default as Sparkline } from './Sparkline.svelte';
|
|
26
26
|
export { default as McResults } from './components/McResults.svelte';
|
|
27
27
|
export { default as McWorkbench } from './components/McWorkbench.svelte';
|
|
28
|
+
export { default as McPfSettings } from './components/McPfSettings.svelte';
|
|
29
|
+
export { default as McModelQualification } from './components/McModelQualification.svelte';
|
|
30
|
+
export { default as McNetworkScope } from './components/McNetworkScope.svelte';
|