Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
39d8a73
added support of slicing for IDSStructArray
prasad-sawantdesai Feb 27, 2025
6704375
removed pull_request event
prasad-sawantdesai Mar 17, 2025
3246c71
merged with develop
prasad-sawantdesai Nov 3, 2025
a043911
Merge branch 'develop' into feature/allow-slices-for-IDSStructArray
prasad-sawantdesai Nov 3, 2025
7119a18
added ids_slice.py and updated IDSStructArray to return IDSSlice obje…
prasad-sawantdesai Nov 6, 2025
78727b4
added ids_slice.py and updated IDSStructArray to return IDSSlice obje…
prasad-sawantdesai Nov 6, 2025
67f24c6
removed flake8 issue
prasad-sawantdesai Nov 6, 2025
f389f74
Merge branch 'develop' into feature/allow-slices-for-IDSStructArray
prasad-sawantdesai Nov 6, 2025
88e6310
Merge branch 'iterorganization:develop' into feature/allow-slices-for…
prasad-sawantdesai Nov 6, 2025
c1a5fba
updated documentation and examples of using IDSSlice
prasad-sawantdesai Nov 7, 2025
b5f55de
Merge branch 'feature/allow-slices-for-IDSStructArray' of github.com:…
prasad-sawantdesai Nov 7, 2025
a1091ed
fixed black formatting
prasad-sawantdesai Nov 12, 2025
f30d305
fix flake8 issue
prasad-sawantdesai Nov 12, 2025
674c25f
add array_slicing in the documentation
prasad-sawantdesai Nov 12, 2025
4cf5937
Fix per review comment https://github.com/iterorganization/IMAS-Pytho…
prasad-sawantdesai Nov 21, 2025
c57e64e
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
c470bec
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
156aded
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
87158d2
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
94e20f2
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
8398537
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
18d7fd0
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
989513d
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
3939e55
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
2423a89
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
69cb566
formatting with black and flake8
prasad-sawantdesai Nov 21, 2025
e1502f1
refactored code
prasad-sawantdesai Nov 21, 2025
224fbea
Merge branch 'develop' into feature/allow-slices-for-IDSStructArray
prasad-sawantdesai Nov 21, 2025
8099741
updated documentation
prasad-sawantdesai Nov 21, 2025
21ded45
Fix as per review comment https://github.com/iterorganization/IMAS-Py…
prasad-sawantdesai Nov 21, 2025
e94d634
fix issue in sphinx docs
prasad-sawantdesai Nov 21, 2025
e448883
lazy loaded objects supports slicing
prasad-sawantdesai Nov 21, 2025
32265d1
fix representation issue and empty slice raises exception https://git…
prasad-sawantdesai Nov 24, 2025
00752d2
fix first and second point fix __repr__ and Empty slices do not check…
prasad-sawantdesai Nov 28, 2025
93bdb50
fix first and second point fix __repr__ and Empty slices do not check…
prasad-sawantdesai Nov 28, 2025
757af75
added tests
prasad-sawantdesai Nov 28, 2025
d862073
fixed issues with to_array function
prasad-sawantdesai Nov 28, 2025
b2143f8
fixed documentation issue
prasad-sawantdesai Nov 28, 2025
0aa5a2c
fixed issue in sphinx doc generation
prasad-sawantdesai Nov 29, 2025
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
26 changes: 20 additions & 6 deletions imas/ids_struct_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

import logging
import sys
from copy import deepcopy
from typing import Optional, Tuple

Expand Down Expand Up @@ -121,12 +122,25 @@ def _element_structure(self):
return struct

def __getitem__(self, item):
# value is a list, so the given item should be convertable to integer
# TODO: perhaps we should allow slices as well?
list_idx = int(item)
if self._lazy:
self._load(item)
return self.value[list_idx]
# allow slices
if isinstance(item, slice):
if self._lazy:
start, stop, step = item.start, item.stop, item.step
if stop is None:
stop = sys.maxsize

for i in range(start or 0, stop, step or 1):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hi Prasad, Python slices are weird and have lots of edge cases with negative indices... the current logic doesn't quite capture that.

For example:

>>> import imas
>>> entry = imas.DBEntry("imas:hdf5?path=./test", "w")
09:44:08 INFO     Parsing data dictionary version 4.0.0 @dd_zip.py:166
>>> cp = entry.factory.core_profiles()
>>> cp.profiles_1d.resize(10)
>>> cp.ids_properties.homogeneous_time = 1
>>> cp.time = [*range(10)]
09:45:05 INFO     Assigning incorrect type 'int64' to <IDSNumericArray (IDS:core_profiles, time, empty FLT_1D)>, attempting automatic conversion. @ids_primitive.py:483
>>> entry.put(cp)
>>> cp.profiles_1d[:-5]
[<IDSStructure (IDS:core_profiles, profiles_1d[0])>, <IDSStructure (IDS:core_profiles, profiles_1d[1])>, <IDSStructure (IDS:core_profiles, profiles_1d[2])>, <IDSStructure (IDS:core_profiles, profiles_1d[3])>, <IDSStructure (IDS:core_profiles, profiles_1d[4])>]
>>> entry.get("core_profiles", lazy=True).profiles_1d[:-5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/maarten/projects/iter-python/imas-python/imas/ids_struct_array.py", line 137, in __getitem__
    return self.value[item]
           ~~~~~~~~~~^^^^^^
TypeError: 'NoneType' object is not subscriptable

Edge cases with negative indices:

  • Negative start, e.g. [-5:5], all items from the fifth from the end to the fifth from the beginning
  • Negative stop, e.g. [2:-2], all items from the third to the second-to-last
  • Negative increment, e.g. [::-1] all items in reverse order

What is the the use case for this feature? With a list comprehension users could capture (most) scenarios as well:

p1ds = [cp.profiles_1d[i] for i in range(2, 5)]
# in reverse
for p1d in reversed(cp.profiles_1d):
    ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for reviewing @maarten-ic

Actually, this issue arises when using slicing for wall IDs, as shown below. There is a workaround—you can convert it into a list—but then you lose the IDSStructArray metadata. It would be convenient for users to work it as a list, what do you think @olivhoenen but of course we need to consider edge cases.
units = list(walids.description_2d[0].vessel.unit)

import imaspy as imas

entry = imas.DBEntry("imas:mdsplus?user=public;pulse=116000;run=4;database=ITER_MD;version=3", "r")  
walids = entry.get("wall", autoconvert=False)
units = walids.description_2d[0].vessel.unit
# units = list(walids.description_2d[0].vessel.unit) workaround
print(units[8:])

# 09:01:55 INFO     Parsing data dictionary version 4.0.0 @dd_zip.py:166
# 09:01:59 INFO     Parsing data dictionary version 3.40.0 @dd_zip.py:166
# Traceback (most recent call last):
#  File "/home/ITER/sawantp1/git/idstoolsimaspy/idstools/testbug.py", line 6, in <module>
#    print(units[8:])
#         ~~~~~^^^^
#  File "/work/imas/opt/EasyBuild/software/IMASPy/1.2.0-intel-2023b/lib/python3.11/site-
# packages/imaspy/ids_struct_array.py", line 126, in __getitem__
#    list_idx = int(item)
               ^^^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'slice'

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hi Prasad,

you can convert it into a list—but then you lose the IDSStructArray metadata

I don't understand the drawback: with the slicing you have implemented, unit[8:] is also a list, right?

when using slicing for wall IDs, as shown below

Yes, sure, but what's the actual use case for doing this? 🙂 I don't understand why a user would want to print all units except the first 8 (as you do in your code listing).

Note that I found out that slice objects have a method that give you the start/stop/step values for a sequence of given length: https://docs.python.org/3/reference/datamodel.html#slice.indices

Copy link
Collaborator

Choose a reason for hiding this comment

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

This came up in the context of @prasad-sawantdesai work on IDStools, where different scripts are receiving IMAS URIs that may contain fragments (#ids:occ/ids_path) where the ids_path has slicing (as described in https://imas-data-dictionary.readthedocs.io/en/latest/_downloads/9e7ffd162c0237b61062528affd5fe2a/IDS-path-syntax.md).

What would be the easiest/recommended way to go from an ids_path string that contains slicing indices to an actual list of objects?

ps: in the case of the list of unit from the wall IDS, the machine description contains many different types of limiter and vessel units, not all of them make sense to overlay when plotting for example an equilibrium psi map

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hi Olivier,

where the ids_path has slicing

This PR doesn't really solve that case either. I could still not do wall.description_2d[0].vessel.unit[8:].element[:].name: the slice at unit[8:] will return a list that has no attribute element.

What would be the easiest/recommended way to go from an ids_path string that contains slicing indices to an actual list of objects?

This is actually a surprisingly tricky problem! And it mostly has to do with the possibility that IDSs don't have homogeneous array sizes deep down in the tree.

ibex has an implementation of this and, since it converts everything to JSON (which supports ragged arrays), it can avoid some of the difficulty. Looks like it is mostly implemented in _expand_single_path_element in this file. Though quickly scanning it, it looks like it doesn't handle the negative indices either (which is probably fine for the ibex use case).

I see two options for implementing your use case, with option 1 the easiest to implement and option 2 more generally useful 🙂

  1. Add a method to IDSPath, for example IDSPath.goto_slices that returns a list of elements that match the slice syntax. It would be an extended form of IDSPath.goto (which returns a single element and raises a NotImplementedError when a slice is used in the path),
  2. Implement slices on arrays of structures, which return a new object (e.g. IDSSlice). The IDSSlice can keep track of all objects that matched the slice expression, and allow further slicing of child elements, getting the matching elements, etc. That could look as follows:
>>> ids_slice = wall.description_2d[0].vessel.unit[8:]
>>> ids_slice
<IDSSlice (IDS:wall, description_2d[0].vessel.unit[8:], 4 matches)>
>>> ids_slice.element
<IDSSlice (IDS:wall, description_2d[0].vessel.unit[8:].element, 4 matches)>
>>> ids_slice.element[:]
<IDSSlice (IDS:wall, description_2d[0].vessel.unit[8:].element[:], 6 matches)>
>>> ids_slice.element[:].name
<IDSSlice (IDS:wall, description_2d[0].vessel.unit[8:].element[:].name, 6 matches)>
>>> list(ids_slice.element[:].name)
[<IDSString0D (IDS:wall, description_2d[0].vessel.unit[8].element[0].name)>,
 <IDSString0D (IDS:wall, description_2d[0].vessel.unit[8].element[1].name)>,
 <IDSString0D (IDS:wall, description_2d[0].vessel.unit[9].element[0].name)>,
 ...
 ]

In either case you'll need to decide how to handle edge cases. For example, what does it mean when you do unit[:2].element[2] when unit[0] has 4 elements and unit[1] has only 2 elements. Is this an IndexError? Or does this return a match of [unit[0].element[2]] and just ignore that there is no unit[1].element[2]?

There's probably more dragons hiding when implementing something like this -- there's a reason that it's not currently implemented in IDSPath 😉

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure why inhomogeneity is a problem there: if a user ask for a slice that does not exist we have an out-of-bound error, whether it is homogeneous or not (admittedly the risk increase in inhomogeneous case, but that should not be a concern for the implementation). IMO index-errors shall not be swallowed if they happen in nested elements, the error shall then just be propagated.

What do you think @prasad-sawantdesai, would something like 1 or 2 be useful in this case? 2 looks more appealing to me (but maybe that's because of the code snippet 🙄 ).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will try to implement second option :)

try:
self._load(i)
except IndexError:
break
return self.value[item]
else:
# value is a list, so the given item should be convertable to integer
list_idx = int(item)
if self._lazy:
self._load(item)
return self.value[list_idx]

def __setitem__(self, item, value):
# value is a list, so the given item should be convertable to integer
Expand Down
12 changes: 12 additions & 0 deletions imas/test/test_ids_struct_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,15 @@ def test_struct_array_eq():
assert cp1.profiles_1d != cp2.profiles_1d
cp2.profiles_1d[0].time = 1
assert cp1.profiles_1d == cp2.profiles_1d


def test_struct_array_slice():
cp1 = IDSFactory("3.39.0").core_profiles()
cp1.profiles_1d.resize(20)

assert len(cp1.profiles_1d) == 20
assert len(cp1.profiles_1d[:]) == 20
assert len(cp1.profiles_1d[5:10]) == 5
assert len(cp1.profiles_1d[10:]) == 10
assert len(cp1.profiles_1d[:5]) == 5
assert len(cp1.profiles_1d[::2]) == 10