|
7 | 7 | import logging |
8 | 8 | from functools import lru_cache, partial |
9 | 9 | from pathlib import Path |
10 | | -from typing import Callable, Dict, Iterator, Optional, Set, Tuple |
| 10 | +from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple |
11 | 11 | from xml.etree.ElementTree import Element, ElementTree |
12 | 12 |
|
13 | 13 | import numpy |
@@ -87,6 +87,15 @@ def __init__(self) -> None: |
87 | 87 | converted. |
88 | 88 | """ |
89 | 89 |
|
| 90 | + self.post_process_ids: List[ |
| 91 | + Callable[[IDSToplevel, IDSToplevel, bool], None] |
| 92 | + ] = [] |
| 93 | + """Postprocess functions to be applied to the whole IDS. |
| 94 | +
|
| 95 | + These postprocess functions should be applied to the whole IDS after all data is |
| 96 | + converted. The arguments supplied are: source IDS, target IDS, deepcopy boolean. |
| 97 | + """ |
| 98 | + |
90 | 99 | self.ignore_missing_paths: Set[str] = set() |
91 | 100 | """Set of paths that should not be logged when data is present.""" |
92 | 101 |
|
@@ -362,6 +371,13 @@ def _apply_3to4_conversion(self, old: Element, new: Element) -> None: |
362 | 371 | self.new_to_old.post_process[new_path] = _cocos_change |
363 | 372 | self.old_to_new.post_process[old_path] = _cocos_change |
364 | 373 |
|
| 374 | + # Convert equilibrium boundary_separatrix and populate contour_tree |
| 375 | + if self.ids_name == "equilibrium": |
| 376 | + self.old_to_new.post_process_ids.append(_equilibrium_boundary_3to4) |
| 377 | + self.old_to_new.ignore_missing_paths |= { |
| 378 | + "time_slice/boundary_separatrix", |
| 379 | + "time_slice/boundary_secondary_separatrix", |
| 380 | + } |
365 | 381 | # Definition change for pf_active circuit/connections |
366 | 382 | if self.ids_name == "pf_active": |
367 | 383 | path = "circuit/connections" |
@@ -603,6 +619,10 @@ def convert_ids( |
603 | 619 | else: |
604 | 620 | _copy_structure(toplevel, target, deepcopy, rename_map) |
605 | 621 |
|
| 622 | + # Global post-processing functions |
| 623 | + for callback in rename_map.post_process_ids: |
| 624 | + callback(toplevel, target, deepcopy) |
| 625 | + |
606 | 626 | logger.info("Conversion of IDS %s finished.", ids_name) |
607 | 627 | if provenance_origin_uri: |
608 | 628 | _add_provenance_entry(target, toplevel._version, provenance_origin_uri) |
@@ -1229,3 +1249,70 @@ def _pulse_schedule_resample_callback(timebase, item: IDSBase, target_item: IDSB |
1229 | 1249 | assume_sorted=True, |
1230 | 1250 | )(timebase) |
1231 | 1251 | target_item.value = value.astype(numpy.int32) if is_integer else value |
| 1252 | + |
| 1253 | + |
| 1254 | +def _equilibrium_boundary_3to4(eq3: IDSToplevel, eq4: IDSToplevel, deepcopy: bool): |
| 1255 | + """Convert DD3 boundary[[_secondary]_separatrix] to DD4 contour_tree""" |
| 1256 | + # Implement https://github.com/iterorganization/IMAS-Python/issues/60 |
| 1257 | + copy = numpy.copy if deepcopy else lambda x: x |
| 1258 | + for ts3, ts4 in zip(eq3.time_slice, eq4.time_slice): |
| 1259 | + if not ts3.global_quantities.psi_axis.has_value: |
| 1260 | + # No magnetic axis, assume no boundary either: |
| 1261 | + continue |
| 1262 | + n_nodes = 1 # magnetic axis |
| 1263 | + if ts3.boundary_separatrix.psi.has_value: |
| 1264 | + n_nodes = 2 |
| 1265 | + if ( # boundary_secondary_separatrix is introduced in DD 3.32.0 |
| 1266 | + hasattr(ts3, "boundary_secondary_separatrix") |
| 1267 | + and ts3.boundary_secondary_separatrix.psi.has_value |
| 1268 | + ): |
| 1269 | + n_nodes = 3 |
| 1270 | + node = ts4.contour_tree.node |
| 1271 | + node.resize(n_nodes) |
| 1272 | + # Magnetic axis (primary O-point) |
| 1273 | + gq = ts3.global_quantities |
| 1274 | + # Note the sign flip for psi due to the COCOS change between DD3 and DD4! |
| 1275 | + axis_is_psi_minimum = -gq.psi_axis < -gq.psi_boundary |
| 1276 | + |
| 1277 | + node[0].critical_type = 0 if axis_is_psi_minimum else 2 |
| 1278 | + node[0].r = gq.magnetic_axis.r |
| 1279 | + node[0].z = gq.magnetic_axis.z |
| 1280 | + node[0].psi = -gq.psi_axis # COCOS change |
| 1281 | + |
| 1282 | + # X-points |
| 1283 | + if n_nodes >= 2: |
| 1284 | + if ts3.boundary_separatrix.type == 0: # limiter plasma |
| 1285 | + node[1].critical_type = 2 if axis_is_psi_minimum else 0 |
| 1286 | + node[1].r = ts3.boundary_separatrix.active_limiter_point.r |
| 1287 | + node[1].z = ts3.boundary_separatrix.active_limiter_point.z |
| 1288 | + else: |
| 1289 | + node[1].critical_type = 1 # saddle-point (x-point) |
| 1290 | + if len(ts3.boundary_separatrix.x_point): |
| 1291 | + node[1].r = ts3.boundary_separatrix.x_point[0].r |
| 1292 | + node[1].z = ts3.boundary_separatrix.x_point[0].z |
| 1293 | + # Additional x-points. N.B. levelset is only stored on the first node |
| 1294 | + for i in range(1, len(ts3.boundary_separatrix.x_point)): |
| 1295 | + node.resize(len(node) + 1, keep=True) |
| 1296 | + node[-1].critical_type = 1 |
| 1297 | + node[-1].r = ts3.boundary_separatrix.x_point[i].r |
| 1298 | + node[-1].z = ts3.boundary_separatrix.x_point[i].z |
| 1299 | + node[-1].psi = -ts3.boundary_separatrix.psi |
| 1300 | + node[1].psi = -ts3.boundary_separatrix.psi # COCOS change |
| 1301 | + node[1].levelset.r = copy(ts3.boundary_separatrix.outline.r) |
| 1302 | + node[1].levelset.z = copy(ts3.boundary_separatrix.outline.z) |
| 1303 | + |
| 1304 | + if n_nodes >= 3: |
| 1305 | + node[2].critical_type = 1 # saddle-point (x-point) |
| 1306 | + if len(ts3.boundary_secondary_separatrix.x_point): |
| 1307 | + node[2].r = ts3.boundary_secondary_separatrix.x_point[0].r |
| 1308 | + node[2].z = ts3.boundary_secondary_separatrix.x_point[0].z |
| 1309 | + # Additional x-points. N.B. levelset is only stored on the first node |
| 1310 | + for i in range(1, len(ts3.boundary_secondary_separatrix.x_point)): |
| 1311 | + node.resize(len(node) + 1, keep=True) |
| 1312 | + node[-1].critical_type = 1 |
| 1313 | + node[-1].r = ts3.boundary_secondary_separatrix.x_point[i].r |
| 1314 | + node[-1].z = ts3.boundary_secondary_separatrix.x_point[i].z |
| 1315 | + node[-1].psi = -ts3.boundary_secondary_separatrix.psi |
| 1316 | + node[2].psi = -ts3.boundary_secondary_separatrix.psi # COCOS change |
| 1317 | + node[2].levelset.r = copy(ts3.boundary_secondary_separatrix.outline.r) |
| 1318 | + node[2].levelset.z = copy(ts3.boundary_secondary_separatrix.outline.z) |
0 commit comments