Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development of Connectivity Manager #576

Merged
merged 30 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5ab784b
Merge pull request #522 from CURENT/develop
cuihantao Mar 25, 2024
a9ac7bc
[WIP] ConnMan
jinningwang Oct 5, 2024
508fc8e
[WIP] ConnMan
jinningwang Oct 5, 2024
038e7be
Merge remote-tracking branch 'remotes/origin/develop' into conm
jinningwang Oct 5, 2024
d4b81a1
Add a case for ConnMan testing purpose
jinningwang Oct 5, 2024
8b4adde
[WIP] ConnMan
jinningwang Oct 5, 2024
28d24dc
Merge remote-tracking branch 'remotes/origin/master' into conm
jinningwang Oct 5, 2024
7e330c2
Add a wrapper to check_conn before a routine init
jinningwang Oct 11, 2024
fccff43
Add a ieee14 case for ConnMan debugging
jinningwang Oct 11, 2024
413ead0
Draft an implementation of ConnMan
jinningwang Oct 11, 2024
c3475d8
Rename wrapper check_conn as check_conn_before_init
jinningwang Oct 11, 2024
9c87939
Update connman logging messages
jinningwang Oct 28, 2024
64bbfc1
Update develoepr notes
jinningwang Oct 28, 2024
972a748
Act connectivity manager in system.setup
jinningwang Oct 28, 2024
6889afe
Update ConnMan logging messgaes
jinningwang Oct 28, 2024
7b5c46a
Rename ConnMan internal flag is_changed to is_off for clarification
jinningwang Oct 28, 2024
a9abaab
Rename ConnMan internal flag is_off to is_needed for clarification
jinningwang Oct 28, 2024
a671c62
In ConnMan, act in init
jinningwang Oct 28, 2024
321f6b3
Overwrite Bus methods set and alter for ConnMan logging
jinningwang Oct 28, 2024
9f2f005
Add a demo notebook for ConnMan
jinningwang Oct 28, 2024
4493082
Update release notes
jinningwang Oct 28, 2024
ebe331e
Format
jinningwang Oct 28, 2024
04d6899
Typo
jinningwang Oct 28, 2024
282d3a3
Refactor Bus.set to fix parameters passing error
jinningwang Oct 28, 2024
887fff3
Update develoepr notes in ConnMan
jinningwang Oct 28, 2024
712bbc8
Add tests for ConnMan
jinningwang Oct 28, 2024
ff49d15
Rename helper func _conn_status to _check_conn_status
jinningwang Nov 23, 2024
396cc35
Clarify scheme in docstring of class connman
jinningwang Nov 23, 2024
e522a8f
Rerun demo of ConnMan
jinningwang Nov 23, 2024
25318c6
Specify multiprocess version lower than 0.70.17 as it does not suppor…
jinningwang Nov 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added andes/cases/ieee14/ieee14_conn.xlsx
Binary file not shown.
1 change: 1 addition & 0 deletions andes/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
from andes.core.var import (Algeb, BaseVar, ExtAlgeb, ExtState, ExtVar, # NOQA
State,)
from andes.core.symprocessor import SymProcessor # NOQA
from andes.core.connman import ConnMan # NOQA
160 changes: 160 additions & 0 deletions andes/core/connman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""
Module for Connectivity Manager.
"""

import logging
from collections import OrderedDict

from andes.utils.func import list_flatten
from andes.shared import np

logger = logging.getLogger(__name__)


# connectivity dependencies of `Bus`
# NOTE: only include PFlow models and measurements models
# cause online status of dynamic models are expected to be handled by their
# corresponding static models
# TODO: DC Topologies are not included yet, `Node`, etc
bus_deps = OrderedDict([
('ACLine', ['bus1', 'bus2']),
('ACShort', ['bus1', 'bus2']),
('FreqMeasurement', ['bus']),
('Interface', ['bus']),
('Motor', ['bus']),
('PhasorMeasurement', ['bus']),
('StaticACDC', ['bus']),
('StaticGen', ['bus']),
('StaticLoad', ['bus']),
('StaticShunt', ['bus']),
])


class ConnMan:
"""
Define a Connectivity Manager class for System.

Connectivity Manager is used to automatically **turn off**
attached devices when a ``Bus`` is turned off **after** system
setup and **before** TDS initializtion.

Attributes
----------
system: system object
System object to manage the connectivity.
busu0: ndarray
Last recorded bus connection status.
is_needed: bool
Flag to indicate if connectivity update is needed.
changes: dict
Dictionary to record bus connectivity changes ('on' and 'off').
'on' means the bus is previous offline and now online.
'off' means the bus is previous online and now offline.
"""

def __init__(self, system=None):
"""
Initialize the connectivity manager.

Parameters
----------
system: system object
System object to manage the connectivity.
"""
self.system = system
self.busu0 = None # placeholder for Bus.u.v
self.is_needed = False # flag to indicate if check is needed
self.changes = {'on': None, 'off': None} # dict of bus connectivity changes

def init(self):
"""
Initialize the connectivity.

`ConnMan` is initialized in `System.setup()`, where all buses are considered online
by default. This method records the initial bus connectivity.
"""
# NOTE: here, we expect all buses are online before the system setup
self.busu0 = np.ones(self.system.Bus.n, dtype=int)
self.changes['on'] = np.zeros(self.system.Bus.n, dtype=int)
self.changes['off'] = np.logical_and(self.busu0 == 1, self.system.Bus.u.v == 0).astype(int)

if np.any(self.changes['off']):
self.is_needed = True

self.act()

return True

def _update(self):
"""
Helper function for in-place update of bus connectivity.
"""
self.changes['on'][...] = np.logical_and(self.busu0 == 0, self.system.Bus.u.v == 1)
self.changes['off'][...] = np.logical_and(self.busu0 == 1, self.system.Bus.u.v == 0)
self.busu0[...] = self.system.Bus.u.v

def record(self):
"""
Record the bus connectivity in-place.

This method should be called if `Bus.set()` or `Bus.alter()` is called.
"""
self._update()

if np.any(self.changes['on']):
onbus_idx = [self.system.Bus.idx.v[i] for i in np.nonzero(self.changes["on"])[0]]
logger.warning(f'Bus turned on: {onbus_idx}')
self.is_needed = True
if len(onbus_idx) > 0:
raise NotImplementedError('Turning on bus after system setup is not supported yet!')

if np.any(self.changes['off']):
offbus_idx = [self.system.Bus.idx.v[i] for i in np.nonzero(self.changes["off"])[0]]
logger.warning(f'Bus turned off: {offbus_idx}')
self.is_needed = True

return self.changes

def act(self):
"""
Update the connectivity.
"""
if not self.is_needed:
logger.debug('No need to update connectivity.')
return True

if self.system.TDS.initialized:
raise NotImplementedError('Bus connectivity update during TDS is not supported yet!')

# --- action ---
offbus_idx = [self.system.Bus.idx.v[i] for i in np.nonzero(self.changes["off"])[0]]

# skip if no bus is turned off
if len(offbus_idx) == 0:
return True

logger.warning('Entering connectivity update.')
logger.warning(f'Following bus(es) are turned off: {offbus_idx}')

logger.warning('-> System connectivity update results:')
for grp_name, src_list in bus_deps.items():
devices = []
for src in src_list:
grp_devs = self.system.__dict__[grp_name].find_idx(keys=src, values=offbus_idx,
allow_none=True, allow_all=True,
default=None)
grp_devs_flat = list_flatten(grp_devs)
if grp_devs_flat != [None]:
devices.append(grp_devs_flat)

devices_flat = list_flatten(devices)

if len(devices_flat) > 0:
self.system.__dict__[grp_name].set(src='u', attr='v',
idx=devices_flat, value=0)
logger.warning(f'In <{grp_name}>, turn off {devices_flat}')

self.is_needed = False # reset the action flag
self._update() # update but not record
self.system.connectivity(info=True)
return True
29 changes: 29 additions & 0 deletions andes/models/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,32 @@ def __init__(self, system=None, config=None):
'(1-flat_start)*a0'
self.v.v_str = 'flat_start*1 + ' \
'(1-flat_start)*v0'

def set(self, src, idx, attr, value):
super().set(src=src, idx=idx, attr=attr, value=value)
_check_conn_status(system=self.system, src=src, attr=attr)


def _check_conn_status(system, src, attr):
"""
Helper function to determine if connectivity update is needed.

Parameters
----------
system : System
The system object.
src : str
Name of the model property
attr : str
The internal attribute of the property to get.
"""
# Check if connectivity update is required
if src == 'u' and attr == 'v':
if system.is_setup:
system.conn.record() # Record connectivity once setup is confirmed

if not system.TDS.initialized:
# Log a warning if Power Flow needs resolution before EIG or TDS
if system.PFlow.converged:
logger.warning('Bus connectivity is touched, resolve PFlow before running EIG or TDS!')
system.PFlow.converged = False # Flag Power Flow as not converged
25 changes: 25 additions & 0 deletions andes/routines/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@
from collections import OrderedDict


def check_conn_before_init(func):
"""
A decorator that ensures the connection is active before calling the `init` or `run`
method of a `BaseRoutine` derived class.

This decorator calls the `act` method on `self.system.conn` to ensure the connection
is active before proceeding with the initialization.

Parameters
----------
func : function
The `init` method of a `BaseRoutine` derived class.

Returns
-------
function
The wrapped function with connection check.
"""

def wrapper(self, *args, **kwargs):
self.system.conn.act()
return func(self, *args, **kwargs)
return wrapper


class BaseRoutine:
"""
Base routine class.
Expand Down
3 changes: 2 additions & 1 deletion andes/routines/eig.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

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

return status

@check_conn_before_init
def run(self, **kwargs):
"""
Run small-signal stability analysis.
Expand Down
3 changes: 2 additions & 1 deletion andes/routines/pflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from collections import OrderedDict

from andes.utils.misc import elapsed
from andes.routines.base import BaseRoutine
from andes.routines.base import BaseRoutine, check_conn_before_init
from andes.variables.report import Report
from andes.shared import np, matrix, sparse, newton_krylov

Expand Down Expand Up @@ -63,6 +63,7 @@ def __init__(self, system=None, config=None):
self.x_sol = None
self.y_sol = None

@check_conn_before_init
def init(self):
"""
Initialize variables for power flow.
Expand Down
3 changes: 2 additions & 1 deletion andes/routines/tds.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import time
from collections import OrderedDict

from andes.routines.base import BaseRoutine
from andes.routines.base import BaseRoutine, check_conn_before_init
from andes.routines.daeint import Trapezoid, method_map
from andes.routines.criteria import deltadelta
from andes.shared import matrix, np, pd, spdiag, tqdm, tqdm_nb
Expand Down Expand Up @@ -174,6 +174,7 @@ def __init__(self, system=None, config=None):
self.method = Trapezoid()
self.set_method(self.config.method)

@check_conn_before_init
def init(self):
"""
Initialize the status, storage and values for TDS.
Expand Down
6 changes: 5 additions & 1 deletion andes/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from typing import Dict, Optional, Tuple, Union

import andes.io
from andes.core import AntiWindup, Config, Model
from andes.core import AntiWindup, Config, Model, ConnMan
from andes.io.streaming import Streaming
from andes.models import file_classes
from andes.models.group import GroupBase
Expand Down Expand Up @@ -193,6 +193,7 @@ def __init__(self,
self.files = FileMan(case=case, **self.options) # file path manager
self.dae = DAE(system=self) # numerical DAE storage
self.streaming = Streaming(self) # Dime2 streaming
self.conn = ConnMan(system=self) # connectivity manager

# dynamic imports of groups, models and routines
self.import_groups()
Expand Down Expand Up @@ -488,6 +489,9 @@ def setup(self):
self.store_sparse_pattern(self.exist.pflow)
self.store_adder_setter(self.exist.pflow)

# init connectivity manager
self.conn.init()

if ret is True:
self.is_setup = True # set `is_setup` if no error occurred
else:
Expand Down
10 changes: 10 additions & 0 deletions docs/source/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ v1.9 Notes

v1.9.3 (2024-04-XX)
-------------------
Development of connectivity manager `ConnMan`:

- Add case `ieee14_conn.xlsx` for demonstration.
- Add `ConnMan` class to manage connectivity.
- Add `ConnMan` to `System` to as an attribute `conn`.
- Add a demo notebook for `ConnMan`.

Other changes:

- In the ``dae`` module, change `self.t.itemset` to array assignment to ensure compatibility with NumPy 2.0.
- Follow RTD's deprecation of Sphinx context injection at build time
- In symbolic processor, most variables are assumed to be real, except some
Expand All @@ -22,6 +31,7 @@ v1.9.3 (2024-04-XX)
- Add parameter `allow_all=False` to `ModelData.find_idx()` `GroupBase.find_idx()` to allow searching all matches.
- Add method `GroupBase.get_all_idxes()` to get all indices of a group.
- 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.
- Specify `multiprocess <=0.70.16` in requirements as 0.70.17 does not support Linux.

v1.9.2 (2024-03-25)
-------------------
Expand Down
Loading
Loading