Skip to content

Commit a7eec50

Browse files
committed
Plotting: handle GICLines in circuit plots
Currently we mirror the official implementation, which is limited. Could be extended in the future.
1 parent bae617d commit a7eec50

2 files changed

Lines changed: 148 additions & 96 deletions

File tree

dss/plot.py

Lines changed: 147 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from ._cffi_api_util import CffiApiUtil
1414
from .IDSS import IDSS
1515
from .IBus import IBus
16+
from .IObj import GICLine
1617
try:
1718
import numpy as np
1819
from matplotlib import pyplot as plt
@@ -762,6 +763,43 @@ def dss_profile_plot(DSS, params):
762763
ax2.autoscale_view()
763764

764765

766+
767+
def get_gic_line_data(DSS: IDSS, bus_coords, single_ph_line_style=1, three_ph_line_style=1):
768+
branch_objects = DSS.Obj.GICLine
769+
line_count = len(branch_objects)# if not idxs else len(idxs)
770+
lines = np.empty(shape=(line_count, 2, 2), dtype=np.float64)
771+
lines.fill(np.nan)
772+
values = np.empty(shape=(line_count, ), dtype=np.float64)
773+
values.fill(np.nan)
774+
lines_styles = np.zeros(shape=(line_count,), dtype=np.int8)
775+
offset = 0
776+
# skip = set()
777+
778+
# GIC lines are not exposed nicely in the classic API, so we'll use the new Obj API
779+
gic_line: GICLine
780+
for gic_line in DSS.Obj.GICLine:
781+
if not gic_line.enabled:
782+
continue
783+
784+
b1 = remove_nodes(gic_line.bus1)
785+
b2 = remove_nodes(gic_line.bus2)
786+
fr = bus_coords.get(b1)
787+
to = bus_coords.get(b2)
788+
789+
if fr is None or to is None:
790+
# skip.add(idx)
791+
continue
792+
793+
lines[offset, 0] = fr
794+
lines[offset, 1] = to
795+
796+
lines_styles[offset] = single_ph_line_style if gic_line.phases == 1 else three_ph_line_style
797+
max_current = DSS._lib.Obj_CktElement_MaxCurrent(gic_line._ptr, 1)
798+
values[offset] = max_current
799+
offset += 1
800+
801+
return lines[:offset], values[:offset], lines_styles[:offset]
802+
765803
def dss_circuit_plot(DSS: IDSS, params={}, fig=None, ax=None, is3d=False):
766804
quantity = str_to_pq.get(params.get('Quantity', None), pqNone)
767805
dots = params.get('Dots', False)
@@ -825,116 +863,131 @@ def dss_circuit_plot(DSS: IDSS, params={}, fig=None, ax=None, is3d=False):
825863

826864
quantity_suffix = ''
827865

828-
if quantity in (pqVoltage,):
829-
colors = []
830-
for v in lines_values:
831-
if v > norm_min_volts or np.isnan(v):
832-
colors.append(color1)
833-
elif v > emerg_min_volts:
834-
colors.append(color2)
835-
else:
836-
colors.append(color3)
837-
838-
839-
for ls in set(lines_styles):
840-
line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs]
841-
if not is3d:
842-
edgecolors = [colors[i] for i in line_idx]
843-
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=edgecolors, capstyle='round'))
844-
if dots:
845-
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1)
846-
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1)
847-
848-
# if is3d:
849-
# ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=[colors[i] for i in line_idx], capstyle='round'))
850-
# ax.set_xlim(np.min(lines_lines_3d[:, :, 0]), np.max(lines_lines_3d[:, :, 0]))
851-
# ax.set_ylim(np.min(lines_lines_3d[:, :, 1]), np.max(lines_lines_3d[:, :, 1]))
852-
853-
quantity_max_value = 0
854-
elif quantity in (pqLosses,):
855-
856-
if quantity_max_value == 0:
857-
# quantity_max_value = max(lines_values) * 1e-3
858-
# For compatibility with the official version, loop through all lines instead
859-
# of the actual plotted lines
860-
element = DSS.ActiveCircuit.ActiveCktElement
861-
quantity_max_value = max(
862-
abs(element.Losses[0] / line.Length)
863-
for line in DSS.ActiveCircuit.Lines
864-
if element.Enabled
865-
) * 0.001
866-
867-
lines_values = np.clip(3 * 1e-3 * lines_values / quantity_max_value, 0.5, max_lw)
868-
if not is3d:
866+
if lines_lines is not None and len(lines_lines) > 0:
867+
if quantity in (pqVoltage,):
868+
colors = []
869+
for v in lines_values:
870+
if v > norm_min_volts or np.isnan(v):
871+
colors.append(color1)
872+
elif v > emerg_min_volts:
873+
colors.append(color2)
874+
else:
875+
colors.append(color3)
876+
877+
869878
for ls in set(lines_styles):
870879
line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs]
871-
# edgecolors = [colors[i] for i in line_idx]
872-
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round'))
873-
if dots:
874-
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
875-
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
876-
877-
elif quantity in (pqCurrent, pqCapacity):
878-
line_idx = [i for i in range(lines_lines.shape[0]) if i not in isolated_idxs and i not in switch_idxs]
879-
colors = [color3 if v > 100 and not np.isnan(v) else color1 for v in lines_values[line_idx]]
880-
881-
if quantity_max_value == 0:
882-
quantity_max_value = max(lines_values)
883-
884-
lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw)
885-
if not is3d:
886-
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle='-', color=colors, capstyle='round'))
887-
if dots:
888-
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1)
889-
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1)
890-
891-
elif quantity != pqNone:
892-
if quantity == pqPower:
893-
quantity_suffix = ' kW'
880+
if not is3d:
881+
edgecolors = [colors[i] for i in line_idx]
882+
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=edgecolors, capstyle='round'))
883+
if dots:
884+
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1)
885+
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1)
886+
887+
# if is3d:
888+
# ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=[colors[i] for i in line_idx], capstyle='round'))
889+
# ax.set_xlim(np.min(lines_lines_3d[:, :, 0]), np.max(lines_lines_3d[:, :, 0]))
890+
# ax.set_ylim(np.min(lines_lines_3d[:, :, 1]), np.max(lines_lines_3d[:, :, 1]))
891+
892+
quantity_max_value = 0
893+
elif quantity in (pqLosses,):
894+
894895
if quantity_max_value == 0:
895-
#lines_values *= 1e-3
896-
896+
# quantity_max_value = max(lines_values) * 1e-3
897897
# For compatibility with the official version, loop through all lines instead
898898
# of the actual plotted lines
899899
element = DSS.ActiveCircuit.ActiveCktElement
900-
901900
quantity_max_value = max(
902-
element.TotalPowers[0]
903-
for _ in DSS.ActiveCircuit.Lines
901+
abs(element.Losses[0] / line.Length)
902+
for line in DSS.ActiveCircuit.Lines
904903
if element.Enabled
905-
) #* 0.001
906-
else:
907-
#TODO:may need workaround about GeneralPlotQuantity
908-
quantity_max_value = max(lines_values)
904+
) * 0.001
909905

910-
for ls in set(lines_styles):
911-
line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs]
906+
lines_values = np.clip(3 * 1e-3 * lines_values / quantity_max_value, 0.5, max_lw)
907+
if not is3d:
908+
for ls in set(lines_styles):
909+
line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs]
910+
# edgecolors = [colors[i] for i in line_idx]
911+
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round'))
912+
if dots:
913+
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
914+
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
915+
916+
elif quantity in (pqCurrent, pqCapacity):
917+
line_idx = [i for i in range(lines_lines.shape[0]) if i not in isolated_idxs and i not in switch_idxs]
918+
colors = [color3 if v > 100 and not np.isnan(v) else color1 for v in lines_values[line_idx]]
919+
920+
if quantity_max_value == 0:
921+
quantity_max_value = max(lines_values)
922+
923+
lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw)
912924
if not is3d:
913-
ax.add_collection(LineCollection(
914-
lines_lines[line_idx, :],
915-
linewidths=np.clip(0.5 + 3 * lines_values[line_idx] / quantity_max_value, 0.5, max_lw),
916-
linestyle=LINES_STYLE_CODE.get(ls, 'solid'),
917-
color=color1,
918-
capstyle='round'
919-
))
925+
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle='-', color=colors, capstyle='round'))
920926
if dots:
921-
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
922-
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
923-
else:
924-
#TODO: handle 1 and 3 phase, etc.?
925-
if not is3d:
926-
ax.add_collection(LineCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round'))
927-
# else:
928-
# ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round'))
929-
# ax.set_xlim(np.min(lines_lines[:, :, 0]), np.max(lines_lines[:, :, 0]))
930-
# ax.set_ylim(np.min(lines_lines[:, :, 1]), np.max(lines_lines[:, :, 1]))
927+
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1)
928+
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1)
929+
930+
elif quantity != pqNone:
931+
if quantity == pqPower:
932+
quantity_suffix = ' kW'
933+
if quantity_max_value == 0:
934+
#lines_values *= 1e-3
935+
936+
# For compatibility with the official version, loop through all lines instead
937+
# of the actual plotted lines
938+
element = DSS.ActiveCircuit.ActiveCktElement
939+
940+
quantity_max_value = max(
941+
element.TotalPowers[0]
942+
for _ in DSS.ActiveCircuit.Lines
943+
if element.Enabled
944+
) #* 0.001
945+
else:
946+
#TODO:may need workaround about GeneralPlotQuantity
947+
quantity_max_value = max(lines_values)
948+
949+
for ls in set(lines_styles):
950+
line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs]
951+
if not is3d:
952+
ax.add_collection(LineCollection(
953+
lines_lines[line_idx, :],
954+
linewidths=np.clip(0.5 + 3 * lines_values[line_idx] / quantity_max_value, 0.5, max_lw),
955+
linestyle=LINES_STYLE_CODE.get(ls, 'solid'),
956+
color=color1,
957+
capstyle='round'
958+
))
959+
if dots:
960+
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
961+
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
962+
else:
963+
#TODO: handle 1 and 3 phase, etc.?
964+
if not is3d:
965+
ax.add_collection(LineCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round'))
966+
# else:
967+
# ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round'))
968+
# ax.set_xlim(np.min(lines_lines[:, :, 0]), np.max(lines_lines[:, :, 0]))
969+
# ax.set_ylim(np.min(lines_lines[:, :, 1]), np.max(lines_lines[:, :, 1]))
931970

932971
transformers_lines, *_ = get_branch_data(DSS, DSS.ActiveCircuit.Transformers, bus_coords)
933972

934973
if not is3d:
935974
lc_transformers = LineCollection(transformers_lines, linewidth=3, linestyle='solid', color='gray')
936975
ax.add_collection(lc_transformers)
937976

977+
lines_lines, lines_values, lines_styles, *_ = get_gic_line_data(DSS, bus_coords, single_ph_line_style=single_ph_line_style, three_ph_line_style=three_ph_line_style)
978+
if len(lines_lines) != 0:
979+
if quantity_max_value == 0:
980+
quantity_max_value = max(lines_values)
981+
982+
lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw)
983+
for ls in set(lines_styles):
984+
line_idx = [i for i, c in enumerate(lines_styles) if c == ls]
985+
ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round'))
986+
if dots:
987+
ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
988+
ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1)
989+
990+
938991

939992
# 'Daisysize'
940993
# 'Markercode', 'Nodewidth' # NodeMarkerCode
@@ -1802,12 +1855,10 @@ def dss_plot(DSS, params):
18021855
dss_plot_funcs.get(ptype)(DSS, params)
18031856

18041857
except Exception as ex:
1805-
from traceback import print_exc
1858+
from traceback import format_exc
18061859
# print('DSS: Error while plotting. Parameters:', params, file=sys.stderr)
1807-
# print_exc()
1808-
18091860
DSS._errorPtr[0] = 777
1810-
DSS._lib.Error_Set_Description(f"Error in the plot backend: {ex}".encode())
1861+
DSS._lib.Error_Set_Description(f"Error in the plot backend: {ex}\n{format_exc()}".encode())
18111862
return 777
18121863

18131864
return 0

tests/_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@
212212
Version8/Distrib/Examples/InverterModels/PVSystem/InvControl/volt-var/SnapShot_voltvar_Standard_varaval_kvarlimitation-2.dss
213213
L!Version8/Distrib/Examples/Scripts/Storage-Quasi-Static-Example/Run_Demo1.dss
214214
L!Version8/Distrib/IEEETestCases/123Bus/Run_YearlySim.dss
215+
Version8/Distrib/Examples/GICExample/GIC_Example.dss
215216
'''.strip().split('\n')
216217

217218
cimxml_test_filenames = '''

0 commit comments

Comments
 (0)