diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e932bf9d4..1765426fc 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -29,19 +29,12 @@
-
+
+
+
-
-
-
-
-
-
-
+
-
-
-
@@ -162,7 +155,7 @@
"Python tests.Nosetests in test_topology_processor.py.executor": "Debug",
"Python tests.Nosetests in tests.executor": "Run",
"Python.AnalysisDialogue.executor": "Run",
- "Python.ExecuteGridCal.executor": "Debug",
+ "Python.ExecuteGridCal.executor": "Run",
"Python.MainWindow.executor": "Run",
"Python.ac_opf_derivatives_bound_slacks.executor": "Debug",
"Python.acopf_run.executor": "Run",
@@ -176,17 +169,19 @@
"Python.new_circuit_objects.executor": "Run",
"Python.power_flow_research_sinj.executor": "Run",
"Python.pymoo_example.executor": "Debug",
+ "Python.sergio_dorado_example.executor": "Run",
+ "Python.sergio_dorado_example_node_breaker.executor": "Run",
"Python.short_circuit_run.executor": "Run",
"Python.test_continuation_power_flow.executor": "Debug",
"Python.update_gui_file (1).executor": "Run",
- "Python.update_gui_file.executor": "Debug",
+ "Python.update_gui_file.executor": "Run",
"Python.upload_to_pypi.executor": "Run",
"Python.voltage_control_ree.executor": "Run",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"WebServerToolWindowFactoryState": "false",
"git-widget-placeholder": "205__ACOPF",
- "last_opened_file_path": "/home/santi/Documentos/Git/GitHub/GridCal/src/GridCal/Gui/Diagrams/MapWidget/Schema",
+ "last_opened_file_path": "/home/santi/Documentos/Git/GitHub/GridCal/src/trunk/substation_reduction",
"node.js.detected.package.eslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
@@ -203,11 +198,11 @@
+
-
@@ -217,7 +212,7 @@
-
+
@@ -484,6 +479,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -570,40 +611,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -677,19 +684,19 @@
+
+
-
-
+
+
-
-
@@ -1417,7 +1424,7 @@
-
+
1656059954202
@@ -1762,7 +1769,7 @@
1698766404661
-
+
@@ -1808,8 +1815,6 @@
-
-
@@ -1833,7 +1838,9 @@
-
+
+
+
@@ -2332,7 +2339,7 @@
file://$PROJECT_DIR$/src/GridCalEngine/Devices/multi_circuit.py
- 6022
+ 6031
@@ -2345,6 +2352,11 @@
2576
+
+ file://$PROJECT_DIR$/src/GridCal/Gui/Diagrams/SchematicWidget/Branches/line_editor.py
+ 16
+
+
@@ -2430,6 +2442,7 @@
+
@@ -2499,7 +2512,7 @@
-
+
@@ -2603,7 +2616,7 @@
-
+
@@ -2662,6 +2675,7 @@
+
diff --git a/src/GridCalEngine/Devices/Substation/connectivity_node.py b/src/GridCalEngine/Devices/Substation/connectivity_node.py
index 733a246db..4eae0dc21 100644
--- a/src/GridCalEngine/Devices/Substation/connectivity_node.py
+++ b/src/GridCalEngine/Devices/Substation/connectivity_node.py
@@ -16,14 +16,16 @@
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from typing import Union
-from GridCalEngine.Devices.Parents.editable_device import EditableDevice, DeviceType
+
+from GridCalEngine.Devices import Substation
from GridCalEngine.Devices.Substation.bus import Bus
+from GridCalEngine.Devices.Parents.editable_device import EditableDevice, DeviceType
class ConnectivityNode(EditableDevice):
def __init__(self, name='CN', idtag=None, code='', dc: bool = False,
- default_bus: Union[None, Bus] = None):
+ default_bus: Union[None, Bus] = None, substation: Union[Substation, None] = None):
"""
Constructor
:param name: Name of the connectivity node
@@ -31,6 +33,7 @@ def __init__(self, name='CN', idtag=None, code='', dc: bool = False,
:param code: secondary identifyier
:param dc: is this a DC connectivity node?
:param default_bus: Default bus to use for topology processing (optional)
+ :param substation: Substation of this connectivity node (optional)
"""
EditableDevice.__init__(self,
name=name,
@@ -42,8 +45,12 @@ def __init__(self, name='CN', idtag=None, code='', dc: bool = False,
self.default_bus: Union[None, Bus] = default_bus
+ self.substation: Union[Substation, None] = substation
+
self.register("dc", "", bool, "is this a DC connectivity node?")
self.register("default_bus", "", DeviceType.BusDevice,
"Default bus to use for topology processing (optional)")
+ self.register("substation", "", DeviceType.SubstationDevice,
+ "Substation of this connectivity node (optional)")
diff --git a/src/GridCalEngine/Devices/multi_circuit.py b/src/GridCalEngine/Devices/multi_circuit.py
index 3ff93fb87..412f6f564 100644
--- a/src/GridCalEngine/Devices/multi_circuit.py
+++ b/src/GridCalEngine/Devices/multi_circuit.py
@@ -5941,10 +5941,19 @@ def convert_to_node_breaker(self) -> None:
if bus_bar.cn.default_bus:
bus_bar.cn.default_bus.code = bus.code # for soft checking later
- # branches
- for elm in self.get_branches():
- elm.cn_from = bus_to_busbar_cn.get(elm.bus_from.idtag, None)
- elm.cn_to = bus_to_busbar_cn.get(elm.bus_to.idtag, None)
+ # add the cn's at the branches
+ for lst in [self.get_branches(), self.get_switches()]:
+ for elm in lst:
+ if elm.bus_from:
+ elm.cn_from = bus_to_busbar_cn.get(elm.bus_from.idtag, None)
+ if elm.bus_to:
+ elm.cn_to = bus_to_busbar_cn.get(elm.bus_to.idtag, None)
+
+ # add the cn's at the branches
+ for lst in self.get_injection_devices_lists():
+ for elm in lst:
+ if elm.bus:
+ elm.cn = bus_to_busbar_cn.get(elm.bus.idtag, None)
def convert_to_node_breaker_adding_switches(self) -> None:
"""
@@ -6101,7 +6110,8 @@ def process_topology_at(self,
for dev_lst in self.get_injection_devices_lists():
for elm in dev_lst:
- elm.set_bus_at(t_idx=t_idx, val=process_info.get_final_bus(elm.cn))
+ if elm.cn is not None:
+ elm.set_bus_at(t_idx=t_idx, val=process_info.get_final_bus(elm.cn))
# return the TopologyProcessorInfo
return process_info
diff --git a/src/trunk/substation_reduction/sergio_dorado_example_bad.py b/src/trunk/substation_reduction/sergio_dorado_example_bad.py
new file mode 100644
index 000000000..12ab7be3a
--- /dev/null
+++ b/src/trunk/substation_reduction/sergio_dorado_example_bad.py
@@ -0,0 +1,88 @@
+import GridCalEngine.api as gce
+
+grid = gce.MultiCircuit(name="TNR_three_bus")
+
+# SADR: object to encapsulate the elements within the reconfigurable substation.
+s3 = gce.Substation(name="bus_3")
+grid.add_substation(s3)
+
+# SADR: substations modeled as bus-branch.
+bus_1 = gce.Bus("bus_1", vnom=240, vmax=1.10, vmin=0.90)
+bus_1.is_slack = True
+grid.add_bus(bus_1)
+
+bus_2 = gce.Bus("bus_2", vnom=240, vmax=1.10, vmin=0.90)
+grid.add_bus(bus_2)
+
+# SADR: substation modeled as a node-breaker model.
+# Busbars
+busbar_1 = gce.BusBar('busbar_1')
+grid.add_bus_bar(busbar_1)
+busbar_2 = gce.BusBar('busbar_2')
+grid.add_bus_bar(busbar_2)
+
+# SADR: connectivity nodes for the elements connected to the original substation.
+bus3_g3 = gce.Bus('bus3_g3', vnom=240, vmax=1.10, vmin=0.90)
+grid.add_bus(bus3_g3)
+
+bus3_l3 = gce.Bus('bus3_l3', vnom=240, vmax=1.10, vmin=0.90)
+grid.add_bus(bus3_l3)
+
+bus3_l13 = gce.Bus('bus3_l13', vnom=240, vmax=1.10, vmin=0.90)
+grid.add_bus(bus3_l13)
+
+bus3_l32 = gce.Bus('bus3_l32', vnom=240, vmax=1.10, vmin=0.90)
+grid.add_bus(bus3_l32)
+
+# SADR: generator at bus 1.
+gen_1 = gce.Generator(name='gen_1', vset=1.00,
+ Pmin=0, Pmax=307, Qmin=-1000, Qmax=1000,
+ Cost2=0.11, Cost=5.00, Cost0=0.00,
+ P=153.5
+ )
+grid.add_generator(bus_1, gen_1)
+
+# SADR: generator at bus 2.
+gen_2 = gce.Generator(name='gen_2',
+ Pmin=0, Pmax=214, Qmin=-1000, Qmax=1000,
+ Cost2=0.085, Cost=1.200, Cost0=0.00,
+ P=107.0)
+grid.add_generator(bus_2, gen_2)
+
+# SADR: generator at bus 3 (synchronous condenser).
+gen_3 = gce.Generator(name='gen_3',
+ Pmin=0.0, Pmax=0.00, Qmin=-1000, Qmax=1000,
+ Cost2=0.000, Cost=0.000, Cost0=0.000,
+ P=0.0)
+grid.add_generator(bus3_g3, gen_3)
+
+# SADR: lines
+grid.add_line(gce.Line(bus_1, bus3_l13, name='line 1-3', r=0.065, x=0.62, b=0.45, rate=9000))
+grid.add_line(gce.Line(bus3_l32, bus_2, name='line 3-2', r=0.025, x=0.75, b=0.70, rate=50))
+grid.add_line(gce.Line(bus_1, bus_2, name='line 1-2', r=0.042, x=0.90, b=0.30, rate=9000))
+
+grid.add_load(bus_1, gce.Load(name='load1', P=147.08, Q=40.00))
+grid.add_load(bus_2, gce.Load(name='load2', P=147.08, Q=40.00))
+grid.add_load(bus3_l3, gce.Load(name='load3', P=127.03, Q=50.00))
+
+grid.add_switch(gce.Switch(name="CB1", bus_from=bus3_g3, bus_to=busbar_1, is_open=False))
+grid.add_switch(gce.Switch(name="CB2", bus_from=bus3_l13, bus_to=busbar_1, is_open=False))
+grid.add_switch(gce.Switch(name="CB3", bus_from=bus3_l32, bus_to=busbar_1, is_open=False))
+grid.add_switch(gce.Switch(name="CB4", bus_from=bus3_l3, bus_to=busbar_1, is_open=False))
+grid.add_switch(gce.Switch(name="CB5", bus_from=bus3_g3, bus_to=busbar_2, is_open=True))
+grid.add_switch(gce.Switch(name="CB6", bus_from=bus3_g3, bus_to=busbar_2, is_open=True))
+grid.add_switch(gce.Switch(name="CB7", bus_from=bus3_g3, bus_to=busbar_2, is_open=True))
+grid.add_switch(gce.Switch(name="CB8", bus_from=bus3_g3, bus_to=busbar_2, is_open=True))
+
+# grid.convert_to_node_breaker()
+# grid.process_topology_at(t_idx=None)
+
+options = gce.PowerFlowOptions(gce.SolverType.NR, verbose=True, control_q=False)
+power_flow = gce.PowerFlowDriver(grid, options)
+
+power_flow.run()
+print(f"Converged: {power_flow.results.converged}")
+if power_flow.results.converged:
+ print(f"Error: {power_flow.results.error}")
+ print(power_flow.results.get_bus_df())
+ print(power_flow.results.get_branch_df())
diff --git a/src/trunk/substation_reduction/sergio_dorado_example_good.py b/src/trunk/substation_reduction/sergio_dorado_example_good.py
new file mode 100644
index 000000000..323990d5a
--- /dev/null
+++ b/src/trunk/substation_reduction/sergio_dorado_example_good.py
@@ -0,0 +1,159 @@
+import GridCalEngine.api as gce
+
+
+def get_grid_bus_branch() -> gce.MultiCircuit:
+ """
+ Example from Sergio Dorado: 3-bus grid with no switches
+ see: https://github.com/SanPen/GridCal/issues/279
+ :return: MultiCircuit
+ """
+ grid1 = gce.MultiCircuit(name="Bus-branch grid")
+
+ b1 = gce.Bus(name="B1")
+ b2 = gce.Bus(name="B2")
+ b3 = gce.Bus(name="B3")
+
+ grid1.add_bus(b1)
+ grid1.add_bus(b2)
+ grid1.add_bus(b3)
+
+ # SADR: generator at bus 1.
+ gen_1 = gce.Generator(name='gen_1', vset=1.00,
+ Pmin=0, Pmax=307, Qmin=-1000, Qmax=1000,
+ Cost2=0.11, Cost=5.00, Cost0=0.00,
+ P=153.5)
+ grid1.add_generator(b1, gen_1)
+
+ # SADR: generator at bus 2.
+ gen_2 = gce.Generator(name='gen_2',
+ Pmin=0, Pmax=214, Qmin=-1000, Qmax=1000,
+ Cost2=0.085, Cost=1.200, Cost0=0.00,
+ P=107.0)
+ grid1.add_generator(b2, gen_2)
+
+ # SADR: generator at bus 3 (synchronous condenser).
+ gen_3 = gce.Generator(name='gen_3',
+ Pmin=0.0, Pmax=0.00, Qmin=-1000, Qmax=1000,
+ Cost2=0.000, Cost=0.000, Cost0=0.000,
+ P=0.0)
+ grid1.add_generator(b3, gen_3)
+
+ # SADR: lines
+ grid1.add_line(gce.Line(b1, b2, name='line 1-2', r=0.042, x=0.90, b=0.30, rate=9000))
+ grid1.add_line(gce.Line(b1, b3, name='line 1-3', r=0.065, x=0.62, b=0.45, rate=9000))
+ grid1.add_line(gce.Line(b3, b2, name='line 3-2', r=0.025, x=0.75, b=0.70, rate=50))
+
+ # add the loads
+ grid1.add_load(b1, gce.Load(name='load1', P=147.08, Q=40.00))
+ grid1.add_load(b2, gce.Load(name='load2', P=147.08, Q=40.00))
+ grid1.add_load(b3, gce.Load(name='load3', P=127.03, Q=50.00))
+
+ return grid1
+
+
+def get_grid_node_breaker() -> gce.MultiCircuit:
+ """
+ Example from Sergio Dorado: 3-bus grid with switches
+ See: https://github.com/SanPen/GridCal/issues/279
+ :return: MultiCircuit
+ """
+ grid1 = gce.MultiCircuit(name="Node-breaker grid")
+
+ # add buses: Buses can be thought as calculation nodes.
+ # they are not necessarily substation busbars
+ b1 = gce.Bus(name="B1")
+ b2 = gce.Bus(name="B2")
+
+ grid1.add_bus(b1)
+ grid1.add_bus(b2)
+
+ # add a proper substation
+ se3 = gce.Substation(name="Substation3")
+ grid1.add_substation(se3)
+
+ # add the substation voltage levels
+ vl3_1 = gce.VoltageLevel(name="VL3-1", substation=se3)
+ vl3_2 = gce.VoltageLevel(name="VL3-2", substation=se3)
+ grid1.add_voltage_level(vl3_1)
+ grid1.add_voltage_level(vl3_2)
+
+ # add the substation busbars
+ # we create 2 busbars, each busbar will have a connectivity node inside automatically created
+ b3_1 = gce.BusBar(name="BusBar 3-1", voltage_level=vl3_1)
+ b3_2 = gce.BusBar(name="BusBar 3-2", voltage_level=vl3_2)
+
+ grid1.add_bus_bar(b3_1)
+ grid1.add_bus_bar(b3_2)
+
+ # we create the 4 middle connectivity nodes
+ cn3_1 = gce.ConnectivityNode(name="CN3_1")
+ cn3_2 = gce.ConnectivityNode(name="CN3_2")
+ cn3_3 = gce.ConnectivityNode(name="CN3_3")
+ cn3_4 = gce.ConnectivityNode(name="CN3_4")
+
+ grid1.add_connectivity_node(cn3_1)
+ grid1.add_connectivity_node(cn3_2)
+ grid1.add_connectivity_node(cn3_3)
+ grid1.add_connectivity_node(cn3_4)
+
+ # Add the generators
+ g1 = gce.Generator(name='gen_1', vset=1.00, Pmin=0, Pmax=307, Qmin=-1000, Qmax=1000,
+ Cost2=0.11, Cost=5.00, Cost0=0.00, P=153.5)
+
+ g2 = gce.Generator(name='gen_2', Pmin=0, Pmax=214, Qmin=-1000, Qmax=1000,
+ Cost2=0.085, Cost=1.200, Cost0=0.00, P=107.0)
+
+ g3 = gce.Generator(name='gen_3', Pmin=0.0, Pmax=0.00, Qmin=-1000, Qmax=1000,
+ Cost2=0.000, Cost=0.000, Cost0=0.000, P=0.0)
+
+ grid1.add_generator(bus=b1, api_obj=g1)
+ grid1.add_generator(bus=b2, api_obj=g2)
+ grid1.add_generator(bus=None, api_obj=g3, cn=cn3_2)
+
+ # SADR: lines
+ grid1.add_line(gce.Line(b1, b2, name='line 1-2', r=0.042, x=0.90, b=0.30, rate=9000))
+ grid1.add_line(gce.Line(bus_from=b1, cn_to=cn3_1, name='line 1-3', r=0.065, x=0.62, b=0.45, rate=9000))
+ grid1.add_line(gce.Line(cn_from=cn3_4, bus_to=b2, name='line 3-2', r=0.025, x=0.75, b=0.70, rate=50))
+
+ # add loads
+ grid1.add_load(bus=b1, api_obj=gce.Load(name='load1', P=147.08, Q=40.00))
+ grid1.add_load(bus=b2, api_obj=gce.Load(name='load2', P=147.08, Q=40.00))
+ grid1.add_load(bus=None, api_obj=gce.Load(name='load3', P=127.03, Q=50.00), cn=cn3_3)
+
+ # add switches
+ grid1.add_switch(gce.Switch(name="CB1", cn_from=cn3_1, cn_to=b3_1.cn, is_open=False))
+ grid1.add_switch(gce.Switch(name="CB2", cn_from=cn3_2, cn_to=b3_1.cn, is_open=False))
+ grid1.add_switch(gce.Switch(name="CB3", cn_from=cn3_3, cn_to=b3_1.cn, is_open=False))
+ grid1.add_switch(gce.Switch(name="CB4", cn_from=cn3_4, cn_to=b3_1.cn, is_open=False))
+
+ grid1.add_switch(gce.Switch(name="CB5", cn_from=cn3_1, cn_to=b3_2.cn, is_open=False))
+ grid1.add_switch(gce.Switch(name="CB6", cn_from=cn3_2, cn_to=b3_2.cn, is_open=False))
+ grid1.add_switch(gce.Switch(name="CB7", cn_from=cn3_3, cn_to=b3_2.cn, is_open=False))
+ grid1.add_switch(gce.Switch(name="CB8", cn_from=cn3_4, cn_to=b3_2.cn, is_open=False))
+
+ # Note: As you may observe, objects can be independently connected to buses or connectivity nodes
+ # busbars are just a convenient proxy for connectivity nodes, which they store internally.
+ # any model with connectivity nodes must be previously processed (only once, or upon switch state changes)
+ # Switches are not used as regular branches and this constitutes the proper way of handling substation tpologies.
+
+ # process the topology
+ grid1.process_topology_at(t_idx=None)
+
+ return grid1
+
+
+if __name__ == "__main__":
+
+ for grid_ in [
+ get_grid_bus_branch(),
+ get_grid_node_breaker()
+ ]:
+ options = gce.PowerFlowOptions(gce.SolverType.NR, verbose=True, control_q=False)
+ power_flow = gce.PowerFlowDriver(grid_, options)
+
+ power_flow.run()
+ print(f"Converged: {power_flow.results.converged}")
+ if power_flow.results.converged:
+ print(f"Error: {power_flow.results.error}")
+ print(power_flow.results.get_bus_df())
+ print(power_flow.results.get_branch_df())