Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ee92612
Fix find_idx to allow multiple matches
jinningwang Sep 27, 2024
9d3ea43
Add more test on find_idx
jinningwang Sep 27, 2024
6da66ea
Update release-notes
jinningwang Sep 27, 2024
1e71bf2
Minor fix
jinningwang Sep 27, 2024
4d69304
Add test on GroupBase.finx_idx
jinningwang Sep 27, 2024
1ae1f4e
Fix deprecateion of np.in1d
jinningwang Sep 27, 2024
0443b38
[WIP] Try to fix docker error in github actions
jinningwang Sep 29, 2024
eb7d23f
[WIP] Undo changes to pythonapp.yml
jinningwang Sep 29, 2024
e099c9b
[WIP] Fix github action error, add a step to install mamba
jinningwang Sep 29, 2024
71cc0e1
[WIP] Fix github action error
jinningwang Sep 29, 2024
45f86d0
[WIP] Try to fix github action error, reset .yml
jinningwang Sep 29, 2024
307bfae
[WIP] Try to fix github action error, use classic solver instead of l…
jinningwang Sep 29, 2024
f6ef1e2
[WIP] Try to fix github action error
jinningwang Sep 29, 2024
e5e19d4
Remove no_flatten in find_idx
jinningwang Oct 1, 2024
6abf2b4
Fix tests for find_idx
jinningwang Oct 1, 2024
1c489df
Typo
jinningwang Oct 1, 2024
bccc080
[WIP] Fix find_idx, revert changes
jinningwang Oct 1, 2024
919c8a4
[WIP] Fix find_idx, add parameter allow_all=False to ModelData.find_i…
jinningwang Oct 1, 2024
b01fe4d
[WIP] Fix find_idx, add inner lists length check
jinningwang Oct 3, 2024
e1422b2
[WIP] Fix find_idx, refactor input check
jinningwang Oct 3, 2024
fdad124
[WIP] Fix find_idx, move input check from modeldata to utils.func
jinningwang Oct 3, 2024
47e9352
[WIP] Fix find_idx
jinningwang Oct 3, 2024
ef34488
[WIP] Fix find_idx, minor fix
jinningwang Oct 3, 2024
33c7ee9
[WIP] Fix find_idx, fix invovled tests
jinningwang Oct 3, 2024
22eceeb
Restore pythonapp workflow
jinningwang Oct 3, 2024
9a675f5
Update release notes
jinningwang Oct 3, 2024
3adc110
Format
jinningwang Oct 4, 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
8 changes: 8 additions & 0 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ jobs:
channels: conda-forge,defaults
channel-priority: true
activate-environment: anaconda-client-env
- shell: bash -el {0}
name: Configure conda to use default solver
run: |
conda config --set solver classic
- shell: bash -el {0}
name: Install mamba
run: |
conda install -n base -c conda-forge mamba
- shell: bash -el {0}
name: Install dependencies
run: |
Expand Down
49 changes: 41 additions & 8 deletions andes/core/model/modeldata.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from andes.core.param import (BaseParam, DataParam, IdxParam, NumParam,
TimerParam)
from andes.shared import pd
from andes.utils.func import list_flatten

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -277,7 +278,8 @@ def find_param(self, prop):

return out

def find_idx(self, keys, values, allow_none=False, default=False):
def find_idx(self, keys, values, allow_none=False, default=False,
no_flatten=False):
"""
Find `idx` of devices whose values match the given pattern.

Expand All @@ -292,11 +294,30 @@ def find_idx(self, keys, values, allow_none=False, default=False):
Allow key, value to be not found. Used by groups.
default : bool
Default idx to return if not found (missing)
no_flatten : bool
If True, return the non-flattened list of idxes. Otherwise, flatten the list.

Returns
-------
list
indices of devices

Examples
--------
>>> # Use example case of IEEE 14-bus system with PVD1
>>> ss = andes.load(andes.get_case('ieee14/ieee14_pvd1.xlsx'))

>>> # To find the idx of `PVD1` with `name` of 'PVD1_1' and 'PVD1_2'
>>> ss.find_idx(keys='name', values=['PVD1_1', 'PVD1_2'])
[1, 2]

>>> # To find the idx of `PVD1` with `gammap` equals to 0.1
>>> ss.find_idx(keys='gammap', values=0.1)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> # To find the idx of `PVD1` with `gammap` equals to 0.1 and `name` of 'PVD1_1'
>>> ss.find_idx(keys=['gammap', 'name'], values=[[0.1], ['PVD1_1']])
[1]
"""
if isinstance(keys, str):
keys = (keys,)
Expand All @@ -316,21 +337,33 @@ def find_idx(self, keys, values, allow_none=False, default=False):
if len(keys) != len(values):
raise ValueError("keys and values must have the same length")

# check if all elements in values have the same length
iterator = iter(values)
try:
first_length = len(next(iterator))
ok_len = all(len(element) == first_length for element in iterator)
except StopIteration: # empty iterable
ok_len = True
if not ok_len:
raise ValueError("All elements in values must have the same length")

v_attrs = [self.__dict__[key].v for key in keys]

idxes = []
for v_search in zip(*values):
v_idx = None
v_idx_list = []
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:
v_idx_list.append(self.idx.v[pos])
if not v_idx_list:
if allow_none is False:
raise IndexError(f'{list(keys)}={v_search} not found in {self.class_name}')
else:
v_idx = default
v_idx_list.append(default)

idxes.append(v_idx)
idxes.append(v_idx_list)

return idxes
if not no_flatten:
return list_flatten(idxes)
else:
return idxes
48 changes: 29 additions & 19 deletions andes/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,31 +243,41 @@ def set(self, src: str, idx, attr, value):

return True

def find_idx(self, keys, values, allow_none=False, default=None):
def find_idx(self, keys, values, allow_none=False, default=None,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default=None can be kept there to be consistent with ModelData.find_idx()

no_flatten=False):
Copy link
Member Author

@jinningwang jinningwang Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_flatten=False introduces more ambiguity. Remove it since we don't actually need it.
Enforce the return to be a list of lists, thus the behavior is more consistent across models and groups.

"""
Find indices of devices that satisfy the given `key=value` condition.

This method iterates over all models in this group.
"""
indices_found = []
# `indices_found` contains found indices returned from all models of this group
idx_mdls = []
for model in self.models.values():
indices_found.append(model.find_idx(keys, values, allow_none=True, default=default))

out = []
for idx, idx_found in enumerate(zip(*indices_found)):
if not allow_none:
if idx_found.count(None) == len(idx_found):
missing_values = [item[idx] for item in values]
raise IndexError(f'{list(keys)} = {missing_values} not found in {self.class_name}')

real_idx = default
for item in idx_found:
if item is not None:
real_idx = item
break
out.append(real_idx)
return out
idx_mdls.append(model.find_idx(keys, values, allow_none=True, default=default,
no_flatten=True))

# `indices_found` contains found indices returned from all models of this group
# NOTE: if the idx returned to [default] across all models, it means there is
# no such idx in this group. If so, return default or raise an key error.
indices_found = []
uid_missing = []
for uid, col in enumerate(zip(*idx_mdls)):
if all(item == [default] for item in col):
if allow_none:
indices_found.append([default])
else:
uid_missing.append(uid)
else:
col_filter = [item for item in col if item != [default]]
indices_found.append(list_flatten(col_filter))

if uid_missing:
miss_str = f'{keys}={[v[u] for v in values for u in uid_missing]}'
raise IndexError(f'Group <{self.class_name}> does not contain device with {miss_str}')

if not no_flatten:
return list_flatten(indices_found)
else:
return indices_found

def _check_src(self, src: str):
"""
Expand Down
4 changes: 2 additions & 2 deletions andes/models/misc/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def in1d(self, addr, v_code):
"""

if v_code == 'x':
return np.in1d(self.xidx, addr)
return np.isin(self.xidx, addr)
if v_code == 'y':
return np.in1d(self.yidx, addr)
return np.isin(self.yidx, addr)

raise NotImplementedError("v_code <%s> not recognized" % v_code)

Expand Down
1 change: 1 addition & 0 deletions docs/source/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ v1.9.3 (2024-04-XX)
- Adjust `BusFreq.Tw.default` to 0.1.
- Add parameter from_csv=None in TDS.run() to allow loading data from CSV files at TDS begining.
- Fix `TDS.init()` and `TDS._csv_step()` to fit loading from CSV when `Output` exists.
- Fix `ModelData.find_idx()` to return all matches

v1.9.2 (2024-03-25)
-------------------
Expand Down
17 changes: 17 additions & 0 deletions tests/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_group_access(self):
[6, 7, 8, 1])

# --- find_idx ---
# same Model
self.assertListEqual(ss.DG.find_idx('name', ['PVD1_1', 'PVD1_2']),
ss.PVD1.find_idx('name', ['PVD1_1', 'PVD1_2']),
)
Expand All @@ -82,6 +83,22 @@ def test_group_access(self):
[('PVD1_1', 'PVD1_2'),
(1.0, 1.0)]))

# cross Model, given results
self.assertListEqual(ss.StaticGen.find_idx(keys='bus',
values=[1, 2, 3, 4]),
[1, 2, 3, 6])

self.assertListEqual(ss.StaticGen.find_idx(keys='bus',
values=[1, 2, 3, 4],
no_flatten=True),
[[1], [2], [3], [6]])

self.assertListEqual(ss.StaticGen.find_idx(keys='bus',
values=[1, 2, 3, 4, 2024],
allow_none=True,
default=2011),
[1, 2, 3, 6, 2011])

# --- get_field ---
ff = ss.DG.get_field('f', list(ss.DG._idx2model.keys()), 'v_code')
self.assertTrue(any([item == 'y' for item in ff]))
49 changes: 49 additions & 0 deletions tests/test_model_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,52 @@ def test_model_set(self):
ss.GENROU.set("M", np.array(["GENROU_4"]), "v", 6.0)
np.testing.assert_equal(ss.GENROU.M.v[3], 6.0)
self.assertEqual(ss.TDS.Teye[omega_addr[3], omega_addr[3]], 6.0)

def test_find_idx(self):
ss = andes.load(andes.get_case('ieee14/ieee14_pvd1.xlsx'))
mdl = ss.PVD1

# multiple values
self.assertListEqual(mdl.find_idx(keys='name', values=['PVD1_1', 'PVD1_2'],
allow_none=False, default=False, no_flatten=False),
[1, 2])
# multiple values, no flatten
self.assertListEqual(mdl.find_idx(keys='name', values=['PVD1_1', 'PVD1_2'],
allow_none=False, default=False, no_flatten=True),
[[1], [2]])
# non-existing value
self.assertListEqual(mdl.find_idx(keys='name', values=['PVD1_999'],
allow_none=True, default=False, no_flatten=False),
[False])

# non-existing value is not allowed
with self.assertRaises(IndexError):
mdl.find_idx(keys='name', values=['PVD1_999'],
allow_none=False, default=False, no_flatten=False)

# multiple keys
self.assertListEqual(mdl.find_idx(keys=['gammap', 'name'],
values=[[0.1, 0.1], ['PVD1_1', 'PVD1_2']]),
[1, 2])

# multiple keys, with non-existing values
self.assertListEqual(mdl.find_idx(keys=['gammap', 'name'],
values=[[0.1, 0.1], ['PVD1_1', 'PVD1_999']],
allow_none=True, default='CURENT'),
[1, 'CURENT'])

# multiple keys, with non-existing values not allowed
with self.assertRaises(IndexError):
mdl.find_idx(keys=['gammap', 'name'],
values=[[0.1, 0.1], ['PVD1_1', 'PVD1_999']],
allow_none=False, default=999)

# multiple keys, values are not iterable
with self.assertRaises(ValueError):
mdl.find_idx(keys=['gammap', 'name'],
values=[0.1, 0.1])

# multiple keys, items length are inconsistent in values
with self.assertRaises(ValueError):
mdl.find_idx(keys=['gammap', 'name'],
values=[[0.1, 0.1], ['PVD1_1']])
Loading