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

Offline bus #566

Open
jinningwang opened this issue Sep 26, 2024 · 12 comments
Open

Offline bus #566

jinningwang opened this issue Sep 26, 2024 · 12 comments
Assignees

Comments

@jinningwang
Copy link
Member

jinningwang commented Sep 26, 2024

Describe the bug

When some Buses are offline (u=0), they seem to be still in power flow calculation.

To Reproduce

In this case, Bus with idx=1 is connected with Lines with idx in [0, 3, 6]

Minimal code:

ss = andes.load(andes.get_case('5bus/pjm5bus.xlsx'))
ss.Bus.set(src='u', attr='v', idx=1, value=0)
ss.PFlow.run()
ss.Line.a1.e

Return:

array([ 1.49607857,  1.59622949, -2.48838663, -0.02059051,  0.21368975,
       -2.15862025,  1.49607857])

Expected:

array([0,  1.59622949, -2.48838663, 0,  0.21368975,
       -2.15862025,  0])

Note: the involved line should have no line flow, and other places numerical values are not necessary to be accurate

Expected behavior

For Lines, their effective online status should take that into account that the connected bus status.

Desktop (please complete the following information):

  • OS: [e.g. Windows]
  • ANDES version (please paste the output from andes misc --version)

**pip packages (please paste the output from pip list)

Additional context

Add any other context about the problem here.

@jinningwang
Copy link
Member Author

jinningwang commented Sep 26, 2024

Issue unexpected behaviors of find_idx()

For single input values, only return first match even there are multiple matches

Minimal code:

ss = andes.load(andes.get_case('ieee14/ieee14_pvd1.xlsx'))
ss.PVD1.find_idx(keys='bus', values=[4])

Return:

[1]

Expected return:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

For multiple inputs values, only return first match of first value

Minimal code:

ss = andes.load(andes.get_case('ieee14/ieee14_pvd1.xlsx'))
ss.DG.find_idx(keys='name', values=['PVD1_3', 'PVD1_4'])

Return:

[3]

Expected return:

[3, 4]

Proposed solution

Option1: clarify logic of find_idx()

If I misunderstood the intentional use, we could clarify it in docstring

Option2: improve code logic:

If I didn't misunderstand, I suggest following development:

Add param find_all=True to return all matches

Default as False to ensure consistent default behavior

Check all items in values

Improve group.find_idx() to follow it

@cuihantao
Copy link
Collaborator

idx is meant to be unique. Otherwise, it's an inconsistency in input data.

Not sure what went off, but with my fresh installation of ANDES, second case seems to work:

Python 3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:37:07) [Clang 15.0.7 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import andes
>>> ss = andes.load(andes.get_case('ieee14/ieee14_pvd1.xlsx'))
>>> ss.DG.find_idx(keys='name', values=['PVD1_3', 'PVD1_4'])
[3, 4]

@cuihantao
Copy link
Collaborator

The following is my result. It needs a fix.

>>> ss.PVD1.find_idx(keys='bus', values=[4])
[1]

@jinningwang
Copy link
Member Author

jinningwang commented Sep 26, 2024

idx is meant to be unique. Otherwise, it's an inconsistency in input data.

Not sure what went off, but with my fresh installation of ANDES, second case seems to work:

Python 3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:37:07) [Clang 15.0.7 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import andes
>>> ss = andes.load(andes.get_case('ieee14/ieee14_pvd1.xlsx'))
>>> ss.DG.find_idx(keys='name', values=['PVD1_3', 'PVD1_4'])
[3, 4]

I doubled checked and you are right, this one work as expected. My end messed up due to my local development.

@jinningwang
Copy link
Member Author

The following is my result. It needs a fix.

>>> ss.PVD1.find_idx(keys='bus', values=[4])
[1]

I'm happy to do it, and I am actually doing it. Welcome your further suggestions if anything worth our attention.

@cuihantao
Copy link
Collaborator

for v_search in zip(*values):
v_idx = None
for pos, v_attr in enumerate(zip(*v_attrs)):
if all([i == j for i, j in zip(v_search, v_attr)]):
v_idx = self.idx.v[pos]
break
if v_idx is None:
if allow_none is False:
raise IndexError(f'{list(keys)}={v_search} not found in {self.class_name}')
else:
v_idx = default
idxes.append(v_idx)

It can be fixed from here. It might introduces one change. The output shall always be a list, which has the same length as values. Each element in the output list shall be a list, corresponding to each value input.

@jinningwang
Copy link
Member Author

jinningwang commented Sep 30, 2024

Develop a model ConnMan to handle connectivity check

To handle topology changes caused by turn off Bus and resulted other models' disconnection. This model can go to group Collection

In following conditions, it will trigger:

  1. Bus.u != 1; Or
  2. Bus.u is set/altered

Implementation:

  1. In System.setup() add a check
  2. Overwrite Bus.set() and Bus.alter() to trigger

Additional considerations:

  1. It seems this model should be a system level function. Developing it as a model is not the easiest way to cover the possible usage?

Pseudo code:

class ConnMan(ModelData, Model):
   """
   Connectivity Manager.
   """

    def __init__(self, system, config):
        ModelData.__init__(self)
        Model.__init__(self, system, config)

    def _act(self):
        """
        Update the connectivity.
        """
        if True:
            self._disconnect()
        else:
            self._connect()
        self.system.connectivity(info=True)
        return None

    def _disconnect(self):
        """
        Disconnect involved devices.
        """
        pass
        
    def _connect(self):
        """
        Connect involved devices.
        """
        pass

@jinningwang jinningwang mentioned this issue Oct 4, 2024
@jinningwang
Copy link
Member Author

@cuihantao Hantao, my ongoing work of connectivity manager is in https://github.com/jinningwang/andes/blob/conm/andes/core/connman.py

The outline is excerpted below for discussion, your suggestions are very welcome.

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

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

        system: system object
        """
        self.system = system
        self.busu0 = None           # placeholder for Bus.u.v
        self.is_act = False         # flag for act, True to act

    def init(self):
        """
        Initialize the connectivity.
        """
        self.busu0 = self.system.Bus.u.v.copy()
        return None

    def record(self):
        """
        Record the bus connectivity in-place.
        """
        self.busu0[...] = self.system.Bus.u
        return None

    def act(self):
        """
        Update the connectivity.
        """
        if not self.is_act:
            logger.debug('Connectivity is not need to be updated.')
            return None

        # --- action ---
        pass

        self.system.connectivity(info=True)
        return None

My considerations:

  1. status flag is_act to enable lazy evaluation
  2. In model Bus, overwriting set and alter to set is_act if values changed
  3. In action part, use Model.set to achieve target devices connectivity change
  4. In this module, define an OrderedDict to define the connectivity check logic

@cuihantao
Copy link
Collaborator

cuihantao commented Oct 5, 2024 via email

@jinningwang
Copy link
Member Author

@cuihantao I summarized my thoughts as below.

Usage Scenarios

  Description Results now Results expected
S1 After loading a case, there are offline buses. The offline buses and lines connected to them are still effective in PFlow and TDS. The offline buses and lines connected to them should be out of use.
S2 Before PFlow or TDS init, change bus online status. Same as above Same as above
S3 After the initialization/run of PFlow, change bus online status. Same as above PFlow should be reset.
S4 After the initialization/run of TDS, change bus online status is not allowed Nothing happened. Does not allow such operation as we don’t model the transient process of turning off a bus.

Proposed development efforts

Develop a module named ConnMan, and include it as a system level function. It means a System will have the module as an attribute System.conn

When described things happened, mark a flag.

Before running any analysis routine, do the connectivity action if flagged.

@cuihantao
Copy link
Collaborator

Can you turn these descriptions into code which describes how you expect to use your design? That will also help you think about the design.

The class API design is not the usage code.

@jinningwang
Copy link
Member Author

jinningwang commented Oct 9, 2024

Implementation plan

Connectivity Manager Class

class System has an attribute conn which is an instance of class ConnMan.

>>> ss = andes.load(andes.get_case('ieee14/ieee14_conn.xlsx'))
>>> ss.conn
<andes.core.connman.ConnMan at 0x11cb3f2b0>
>>> ss.conn.busu0
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Usage Scenarios

Action flag triggering condition

Let say, there is one bus offline in any following way:

  1. when loading case, there is any bus offline
  2. when running any routine, bus online status is not the same with last stored and connectivity passed online status

The connectivity action flag should be True.

>>> ss.Bus.u.v
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]

>>> ss.conn.to_act
True

Before a routine is initialized

When running any routine analysis, the system will check the connectivity action flag, and run the connectivity act if it is True.

This is applicable for all the three routiens, PFlow, TDS, and EIG.

# In the `Routine.init()`, add a flag check
class Routinebase:

    def init(self):
        if self.system.conn.to_act:
            self.system.conn.act()
        ...  # existing code

The logging message should be like:

>>> ss.<Routine>.init()
Connectivity action is required. Running connectivity action...
Bus <15> is offline, involved Line <15-16> is turned off.

After a routine is initialized

For PFlow and EIG, once the action flag is true, the routine results should be reset.

For TDS, once the action flag is true, the Bus online status should not be changed.

>>> ... # previous code that a Bus is offline
>>> ss.PFlow.init()
Connectivity action is required. Running connectivity action...
Bus <15> is offline, involved Line <15-16> is turned off.

>>> ... # previous code that a Bus is offline
>>> ss.EIG.init()
Connectivity action is required. Running connectivity action...
Bus <15> is offline, involved Line <15-16> is turned off.

>>> ... # previous code that a Bus is offline
>>> ss.TDS.init()
Bus connectivity status changed, but it is not allowed after TDS initialization.
The action is not taken, and TDS running is aborted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants