Skip to content

Commit 12135ed

Browse files
authored
Merge pull request #576 from jinningwang/conm
Development of Connectivity Manager
2 parents 2b17faa + 25318c6 commit 12135ed

File tree

13 files changed

+1019
-4
lines changed

13 files changed

+1019
-4
lines changed

andes/cases/ieee14/ieee14_conn.xlsx

24.9 KB
Binary file not shown.

andes/core/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
from andes.core.var import (Algeb, BaseVar, ExtAlgeb, ExtState, ExtVar, # NOQA
1818
State,)
1919
from andes.core.symprocessor import SymProcessor # NOQA
20+
from andes.core.connman import ConnMan # NOQA

andes/core/connman.py

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
Module for Connectivity Manager.
3+
"""
4+
5+
import logging
6+
from collections import OrderedDict
7+
8+
from andes.utils.func import list_flatten
9+
from andes.shared import np
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
# connectivity dependencies of `Bus`
15+
# NOTE: only include PFlow models and measurements models
16+
# cause online status of dynamic models are expected to be handled by their
17+
# corresponding static models
18+
# TODO: DC Topologies are not included yet, `Node`, etc
19+
bus_deps = OrderedDict([
20+
('ACLine', ['bus1', 'bus2']),
21+
('ACShort', ['bus1', 'bus2']),
22+
('FreqMeasurement', ['bus']),
23+
('Interface', ['bus']),
24+
('Motor', ['bus']),
25+
('PhasorMeasurement', ['bus']),
26+
('StaticACDC', ['bus']),
27+
('StaticGen', ['bus']),
28+
('StaticLoad', ['bus']),
29+
('StaticShunt', ['bus']),
30+
])
31+
32+
33+
class ConnMan:
34+
"""
35+
Define a Connectivity Manager class for System.
36+
37+
Connectivity Manager is used to automatically **turn off**
38+
attached devices when a ``Bus`` is turned off **after** system
39+
setup and **before** TDS initializtion.
40+
41+
Attributes
42+
----------
43+
system: system object
44+
System object to manage the connectivity.
45+
busu0: ndarray
46+
Last recorded bus connection status.
47+
is_needed: bool
48+
Flag to indicate if connectivity update is needed.
49+
changes: dict
50+
Dictionary to record bus connectivity changes ('on' and 'off').
51+
'on' means the bus is previous offline and now online.
52+
'off' means the bus is previous online and now offline.
53+
"""
54+
55+
def __init__(self, system=None):
56+
"""
57+
Initialize the connectivity manager.
58+
59+
Parameters
60+
----------
61+
system: system object
62+
System object to manage the connectivity.
63+
"""
64+
self.system = system
65+
self.busu0 = None # placeholder for Bus.u.v
66+
self.is_needed = False # flag to indicate if check is needed
67+
self.changes = {'on': None, 'off': None} # dict of bus connectivity changes
68+
69+
def init(self):
70+
"""
71+
Initialize the connectivity.
72+
73+
`ConnMan` is initialized in `System.setup()`, where all buses are considered online
74+
by default. This method records the initial bus connectivity.
75+
"""
76+
# NOTE: here, we expect all buses are online before the system setup
77+
self.busu0 = np.ones(self.system.Bus.n, dtype=int)
78+
self.changes['on'] = np.zeros(self.system.Bus.n, dtype=int)
79+
self.changes['off'] = np.logical_and(self.busu0 == 1, self.system.Bus.u.v == 0).astype(int)
80+
81+
if np.any(self.changes['off']):
82+
self.is_needed = True
83+
84+
self.act()
85+
86+
return True
87+
88+
def _update(self):
89+
"""
90+
Helper function for in-place update of bus connectivity.
91+
"""
92+
self.changes['on'][...] = np.logical_and(self.busu0 == 0, self.system.Bus.u.v == 1)
93+
self.changes['off'][...] = np.logical_and(self.busu0 == 1, self.system.Bus.u.v == 0)
94+
self.busu0[...] = self.system.Bus.u.v
95+
96+
def record(self):
97+
"""
98+
Record the bus connectivity in-place.
99+
100+
This method should be called if `Bus.set()` or `Bus.alter()` is called.
101+
"""
102+
self._update()
103+
104+
if np.any(self.changes['on']):
105+
onbus_idx = [self.system.Bus.idx.v[i] for i in np.nonzero(self.changes["on"])[0]]
106+
logger.warning(f'Bus turned on: {onbus_idx}')
107+
self.is_needed = True
108+
if len(onbus_idx) > 0:
109+
raise NotImplementedError('Turning on bus after system setup is not supported yet!')
110+
111+
if np.any(self.changes['off']):
112+
offbus_idx = [self.system.Bus.idx.v[i] for i in np.nonzero(self.changes["off"])[0]]
113+
logger.warning(f'Bus turned off: {offbus_idx}')
114+
self.is_needed = True
115+
116+
return self.changes
117+
118+
def act(self):
119+
"""
120+
Update the connectivity.
121+
"""
122+
if not self.is_needed:
123+
logger.debug('No need to update connectivity.')
124+
return True
125+
126+
if self.system.TDS.initialized:
127+
raise NotImplementedError('Bus connectivity update during TDS is not supported yet!')
128+
129+
# --- action ---
130+
offbus_idx = [self.system.Bus.idx.v[i] for i in np.nonzero(self.changes["off"])[0]]
131+
132+
# skip if no bus is turned off
133+
if len(offbus_idx) == 0:
134+
return True
135+
136+
logger.warning('Entering connectivity update.')
137+
logger.warning(f'Following bus(es) are turned off: {offbus_idx}')
138+
139+
logger.warning('-> System connectivity update results:')
140+
for grp_name, src_list in bus_deps.items():
141+
devices = []
142+
for src in src_list:
143+
grp_devs = self.system.__dict__[grp_name].find_idx(keys=src, values=offbus_idx,
144+
allow_none=True, allow_all=True,
145+
default=None)
146+
grp_devs_flat = list_flatten(grp_devs)
147+
if grp_devs_flat != [None]:
148+
devices.append(grp_devs_flat)
149+
150+
devices_flat = list_flatten(devices)
151+
152+
if len(devices_flat) > 0:
153+
self.system.__dict__[grp_name].set(src='u', attr='v',
154+
idx=devices_flat, value=0)
155+
logger.warning(f'In <{grp_name}>, turn off {devices_flat}')
156+
157+
self.is_needed = False # reset the action flag
158+
self._update() # update but not record
159+
self.system.connectivity(info=True)
160+
return True

andes/models/bus.py

+29
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,32 @@ def __init__(self, system=None, config=None):
124124
'(1-flat_start)*a0'
125125
self.v.v_str = 'flat_start*1 + ' \
126126
'(1-flat_start)*v0'
127+
128+
def set(self, src, idx, attr, value):
129+
super().set(src=src, idx=idx, attr=attr, value=value)
130+
_check_conn_status(system=self.system, src=src, attr=attr)
131+
132+
133+
def _check_conn_status(system, src, attr):
134+
"""
135+
Helper function to determine if connectivity update is needed.
136+
137+
Parameters
138+
----------
139+
system : System
140+
The system object.
141+
src : str
142+
Name of the model property
143+
attr : str
144+
The internal attribute of the property to get.
145+
"""
146+
# Check if connectivity update is required
147+
if src == 'u' and attr == 'v':
148+
if system.is_setup:
149+
system.conn.record() # Record connectivity once setup is confirmed
150+
151+
if not system.TDS.initialized:
152+
# Log a warning if Power Flow needs resolution before EIG or TDS
153+
if system.PFlow.converged:
154+
logger.warning('Bus connectivity is touched, resolve PFlow before running EIG or TDS!')
155+
system.PFlow.converged = False # Flag Power Flow as not converged

andes/routines/base.py

+25
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@
77
from collections import OrderedDict
88

99

10+
def check_conn_before_init(func):
11+
"""
12+
A decorator that ensures the connection is active before calling the `init` or `run`
13+
method of a `BaseRoutine` derived class.
14+
15+
This decorator calls the `act` method on `self.system.conn` to ensure the connection
16+
is active before proceeding with the initialization.
17+
18+
Parameters
19+
----------
20+
func : function
21+
The `init` method of a `BaseRoutine` derived class.
22+
23+
Returns
24+
-------
25+
function
26+
The wrapped function with connection check.
27+
"""
28+
29+
def wrapper(self, *args, **kwargs):
30+
self.system.conn.act()
31+
return func(self, *args, **kwargs)
32+
return wrapper
33+
34+
1035
class BaseRoutine:
1136
"""
1237
Base routine class.

andes/routines/eig.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from andes.io.txt import dump_data
1414
from andes.plot import set_latex, set_style
15-
from andes.routines.base import BaseRoutine
15+
from andes.routines.base import BaseRoutine, check_conn_before_init
1616
from andes.shared import div, matrix, plt, sparse, spdiag, spmatrix
1717
from andes.utils.misc import elapsed
1818
from andes.variables.report import report_info
@@ -492,6 +492,7 @@ def _pre_check(self):
492492

493493
return status
494494

495+
@check_conn_before_init
495496
def run(self, **kwargs):
496497
"""
497498
Run small-signal stability analysis.

andes/routines/pflow.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections import OrderedDict
77

88
from andes.utils.misc import elapsed
9-
from andes.routines.base import BaseRoutine
9+
from andes.routines.base import BaseRoutine, check_conn_before_init
1010
from andes.variables.report import Report
1111
from andes.shared import np, matrix, sparse, newton_krylov
1212

@@ -63,6 +63,7 @@ def __init__(self, system=None, config=None):
6363
self.x_sol = None
6464
self.y_sol = None
6565

66+
@check_conn_before_init
6667
def init(self):
6768
"""
6869
Initialize variables for power flow.

andes/routines/tds.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import time
1010
from collections import OrderedDict
1111

12-
from andes.routines.base import BaseRoutine
12+
from andes.routines.base import BaseRoutine, check_conn_before_init
1313
from andes.routines.daeint import Trapezoid, method_map
1414
from andes.routines.criteria import deltadelta
1515
from andes.shared import matrix, np, pd, spdiag, tqdm, tqdm_nb
@@ -174,6 +174,7 @@ def __init__(self, system=None, config=None):
174174
self.method = Trapezoid()
175175
self.set_method(self.config.method)
176176

177+
@check_conn_before_init
177178
def init(self):
178179
"""
179180
Initialize the status, storage and values for TDS.

andes/system.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from typing import Dict, Optional, Tuple, Union
2424

2525
import andes.io
26-
from andes.core import AntiWindup, Config, Model
26+
from andes.core import AntiWindup, Config, Model, ConnMan
2727
from andes.io.streaming import Streaming
2828
from andes.models import file_classes
2929
from andes.models.group import GroupBase
@@ -193,6 +193,7 @@ def __init__(self,
193193
self.files = FileMan(case=case, **self.options) # file path manager
194194
self.dae = DAE(system=self) # numerical DAE storage
195195
self.streaming = Streaming(self) # Dime2 streaming
196+
self.conn = ConnMan(system=self) # connectivity manager
196197

197198
# dynamic imports of groups, models and routines
198199
self.import_groups()
@@ -488,6 +489,9 @@ def setup(self):
488489
self.store_sparse_pattern(self.exist.pflow)
489490
self.store_adder_setter(self.exist.pflow)
490491

492+
# init connectivity manager
493+
self.conn.init()
494+
491495
if ret is True:
492496
self.is_setup = True # set `is_setup` if no error occurred
493497
else:

docs/source/release-notes.rst

+10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ v1.9 Notes
1111

1212
v1.9.3 (2024-04-XX)
1313
-------------------
14+
Development of connectivity manager `ConnMan`:
15+
16+
- Add case `ieee14_conn.xlsx` for demonstration.
17+
- Add `ConnMan` class to manage connectivity.
18+
- Add `ConnMan` to `System` to as an attribute `conn`.
19+
- Add a demo notebook for `ConnMan`.
20+
21+
Other changes:
22+
1423
- In the ``dae`` module, change `self.t.itemset` to array assignment to ensure compatibility with NumPy 2.0.
1524
- Follow RTD's deprecation of Sphinx context injection at build time
1625
- In symbolic processor, most variables are assumed to be real, except some
@@ -22,6 +31,7 @@ v1.9.3 (2024-04-XX)
2231
- Add parameter `allow_all=False` to `ModelData.find_idx()` `GroupBase.find_idx()` to allow searching all matches.
2332
- Add method `GroupBase.get_all_idxes()` to get all indices of a group.
2433
- Enhanced three-winding transformer parsing in PSS/E raw files by assigning the equivalent star bus `area`, `owner`, and `zone` using the high-voltage bus values.
34+
- Specify `multiprocess <=0.70.16` in requirements as 0.70.17 does not support Linux.
2535

2636
v1.9.2 (2024-03-25)
2737
-------------------

0 commit comments

Comments
 (0)