Skip to content

Commit 4abf3f7

Browse files
committed
ensure AoS in structure is long enough to fill IDS quantity
1 parent 6f8507f commit 4abf3f7

File tree

3 files changed

+148
-10
lines changed

3 files changed

+148
-10
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies = [
1717
"click",
1818
"matplotlib",
1919
"scipy",
20-
# "imas-python",
20+
"imas-python[netcdf] @ git+https://github.com/iterorganization/IMAS-Python.git",
2121
"panel",
2222
"pygments",
2323
"plotly",

tests/test_waveform_exporter.py

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import csv
22
from pathlib import Path
33

4+
# TODO: use netcdf and imas instead of imaspy
5+
import imaspy as imas
46
import numpy as np
57
import pytest
68

@@ -109,6 +111,95 @@ def test_to_png(exporter, tmp_path):
109111
assert Path(file_path).exists()
110112

111113

112-
# TODO: Write tests for exporting to IDS, this requires IMASPy as a dependency
113-
# def test_to_ids(exporter):
114-
# pass
114+
def test_to_ids_flt1d(exporter, tmp_path):
115+
ids = imas.IDSFactory().ec_launchers()
116+
ids.time = [0]
117+
ids.ids_properties.homogeneous_time = imas.ids_defs.IDS_TIME_MODE_HOMOGENEOUS
118+
file_path = f"imas:hdf5?path={tmp_path}/test_ec_launchers"
119+
with imas.DBEntry(file_path, "w") as dbentry:
120+
dbentry.put(ids)
121+
122+
uri = f"{file_path}#ec_launchers/beam(1)/power_launched"
123+
exporter.to_ids(uri)
124+
125+
with imas.DBEntry(file_path, "r") as dbentry:
126+
ids = dbentry.get("ec_launchers", autoconvert=False)
127+
assert np.all(ids.time == exporter.times)
128+
assert np.all(ids.beam[0].power_launched.data == exporter.values)
129+
130+
131+
def test_to_ids_flt1d_heterogeneous(exporter, tmp_path):
132+
ids = imas.IDSFactory().ec_launchers()
133+
ids.time = [0]
134+
ids.ids_properties.homogeneous_time = imas.ids_defs.IDS_TIME_MODE_HETEROGENEOUS
135+
file_path = f"imas:hdf5?path={tmp_path}/test_ec_launchers"
136+
with imas.DBEntry(file_path, "w") as dbentry:
137+
dbentry.put(ids)
138+
139+
uri = f"{file_path}#ec_launchers/beam(1)/power_launched"
140+
exporter.to_ids(uri)
141+
142+
with imas.DBEntry(file_path, "r") as dbentry:
143+
ids = dbentry.get("ec_launchers", autoconvert=False)
144+
assert np.all(ids.beam[0].power_launched.time == exporter.times)
145+
assert np.all(ids.beam[0].power_launched.data == exporter.values)
146+
147+
148+
def test_to_ids_flt1d_occurrence(exporter, tmp_path):
149+
ids = imas.IDSFactory().ec_launchers()
150+
ids.time = [0]
151+
ids.ids_properties.homogeneous_time = imas.ids_defs.IDS_TIME_MODE_HOMOGENEOUS
152+
file_path = f"imas:hdf5?path={tmp_path}/test_ec_launchers"
153+
with imas.DBEntry(file_path, "w") as dbentry:
154+
dbentry.put(ids)
155+
dbentry.put(ids, occurrence=1)
156+
157+
uri = f"{file_path}#ec_launchers:1/beam(1)/power_launched"
158+
exporter.to_ids(uri)
159+
160+
with imas.DBEntry(file_path, "r") as dbentry:
161+
# Check if only occurrence 1 is filled
162+
ids = dbentry.get("ec_launchers", occurrence=0, autoconvert=False)
163+
assert np.all(ids.time == [0])
164+
assert not ids.beam
165+
166+
ids = dbentry.get("ec_launchers", occurrence=1, autoconvert=False)
167+
assert np.all(ids.time == exporter.times)
168+
assert np.all(ids.beam[0].power_launched.data == exporter.values)
169+
170+
171+
def test_to_ids_flt0d(exporter, tmp_path):
172+
ids = imas.IDSFactory().equilibrium()
173+
ids.time = [0]
174+
ids.ids_properties.homogeneous_time = imas.ids_defs.IDS_TIME_MODE_HOMOGENEOUS
175+
file_path = f"imas:hdf5?path={tmp_path}/test_equilibrium"
176+
with imas.DBEntry(file_path, "w") as dbentry:
177+
dbentry.put(ids)
178+
179+
uri = f"{file_path}#equilibrium/time_slice()/boundary/elongation"
180+
exporter.to_ids(uri)
181+
182+
with imas.DBEntry(file_path, "r") as dbentry:
183+
ids = dbentry.get("equilibrium", autoconvert=False)
184+
assert np.all(ids.time == exporter.times)
185+
for i, time_slice in enumerate(ids.time_slice):
186+
assert time_slice.boundary.elongation == exporter.values[i]
187+
188+
189+
def test_to_ids_flt0d_heterogeneous(exporter, tmp_path):
190+
ids = imas.IDSFactory().equilibrium()
191+
ids.time = [0]
192+
ids.ids_properties.homogeneous_time = imas.ids_defs.IDS_TIME_MODE_HETEROGENEOUS
193+
file_path = f"imas:hdf5?path={tmp_path}/test_equilibrium"
194+
with imas.DBEntry(file_path, "w") as dbentry:
195+
dbentry.put(ids)
196+
197+
uri = f"{file_path}#equilibrium/time_slice()/boundary/elongation"
198+
exporter.to_ids(uri)
199+
200+
with imas.DBEntry(file_path, "r") as dbentry:
201+
ids = dbentry.get("equilibrium", autoconvert=False)
202+
imas.util.print_tree(ids)
203+
for i, time_slice in enumerate(ids.time_slice):
204+
assert time_slice.boundary.elongation == exporter.values[i]
205+
assert time_slice.time == exporter.times[i]

waveform_editor/waveform_exporter.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import csv
22
import logging
3+
import re
34

4-
import imaspy
5+
import imas
56
import plotly.graph_objects as go
67

78
logger = logging.getLogger(__name__)
@@ -53,17 +54,17 @@ def to_ids(self, uri, dd_version=None):
5354
default version will be used.
5455
"""
5556
uri_entry, uri_ids, occurrence, path = self.parse_uri(uri)
56-
entry = imaspy.DBEntry(uri_entry, "r", dd_version=dd_version)
57+
entry = imas.DBEntry(uri_entry, "r", dd_version=dd_version)
5758
ids = entry.get(uri_ids, occurrence, autoconvert=False)
5859

5960
if (
6061
ids.ids_properties.homogeneous_time
61-
== imaspy.ids_defs.IDS_TIME_MODE_HETEROGENEOUS
62+
== imas.ids_defs.IDS_TIME_MODE_HETEROGENEOUS
6263
):
6364
is_homogeneous = False
6465
elif (
6566
ids.ids_properties.homogeneous_time
66-
== imaspy.ids_defs.IDS_TIME_MODE_HOMOGENEOUS
67+
== imas.ids_defs.IDS_TIME_MODE_HOMOGENEOUS
6768
):
6869
is_homogeneous = True
6970
ids.time = self.times
@@ -72,12 +73,14 @@ def to_ids(self, uri, dd_version=None):
7273
"The time mode must be homogeneous or heterogeneous."
7374
)
7475

76+
self._ensure_path_exists(ids, path)
77+
7578
if "()" in path:
7679
self._fill_flt_0d(ids, path, is_homogeneous)
7780
else:
7881
self._fill_flt_1d(ids, path, is_homogeneous)
7982

80-
entry.put(ids)
83+
entry.put(ids, occurrence)
8184
entry.close()
8285

8386
def to_csv(self, file_path):
@@ -132,7 +135,6 @@ def _fill_flt_0d(self, ids, path, is_homogeneous):
132135
aos_path = aos_path.strip("/")
133136
remaining_path = remaining_path.strip("/")
134137
aos = ids[aos_path]
135-
aos.resize(len(self.times))
136138

137139
for i, time in enumerate(self.times):
138140
if aos[i][remaining_path].data_type == "FLT_0D":
@@ -158,3 +160,48 @@ def _fill_flt_1d(self, ids, path, is_homogeneous):
158160
quantity.time = self.times
159161
else:
160162
raise NotImplementedError("Invalid data")
163+
164+
def _ensure_path_exists(self, ids, path):
165+
"""
166+
Traverses a given path and modifies the AoS in the IDS to ensure the IDS
167+
quantity to be filled exists.
168+
169+
Examples:
170+
171+
- imas:hdf5?path=./testdb#ec_launchers/beam(123)/power_launched
172+
This will ensure ec_launchers.beam has a length of at least 123. Note, 1-based
173+
indexing is used in the URI.
174+
175+
- imas:hdf5?path=./testdb#equilibrium/time_slice()/boundary/elongation
176+
When '()' is encountered, it is assumed that this AoS should be the length of
177+
the exported time array, i.e. len(equilibrium.time_slice) == len(self.times)
178+
179+
Args:
180+
ids: The IDS to export to.
181+
path: The path of the IDS quantity to export to.
182+
183+
Returns:
184+
None. Modifies the `ids` structure in place.
185+
"""
186+
path = path.strip("/")
187+
path_parts = path.split("/")
188+
current = ids
189+
for part in path_parts:
190+
if "()" in part:
191+
current = current[part.split("(")[0]]
192+
current.resize(len(self.times))
193+
current = current[0]
194+
195+
elif "(" in part:
196+
match = re.search(r"\((\d+)\)", part)
197+
index = int(match.group(1))
198+
current = current[part.split("(")[0]]
199+
200+
# We use 1-based indexing in the URI
201+
if len(current) < index:
202+
current.resize(index)
203+
204+
# Revert to 0-based indexing
205+
current = current[index - 1]
206+
else:
207+
current = current[part]

0 commit comments

Comments
 (0)