Skip to content

Commit af912e2

Browse files
committed
Introduce NicknameSettingList for explicit efit tree cascade
Signed-off-by: Sameer Chaturvedi <sameerc@mit.edu>
1 parent 2f7bb12 commit af912e2

4 files changed

Lines changed: 381 additions & 10 deletions

File tree

disruption_py/core/retrieval_manager.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
from disruption_py.inout.mds import MDSConnection, mdsExceptions
1616
from disruption_py.inout.sql import ShotDatabase
1717
from disruption_py.machine.tokamak import Tokamak
18-
from disruption_py.settings.nickname_setting import NicknameSettingParams
18+
from disruption_py.settings.nickname_setting import (
19+
NicknameSettingList,
20+
NicknameSettingParams,
21+
)
1922
from disruption_py.settings.retrieval_settings import RetrievalSettings
2023
from disruption_py.settings.time_setting import TimeSettingParams
2124

@@ -143,9 +146,15 @@ def shot_setup(
143146
data_conn = self.process_data_conn.get_shot_connection(shot_id=shot_id)
144147

145148
if isinstance(data_conn, MDSConnection):
149+
efit_setting = retrieval_settings.efit_nickname_setting
150+
cascades = (
151+
{"_efit_tree": efit_setting}
152+
if isinstance(efit_setting, NicknameSettingList)
153+
else None
154+
)
146155
data_conn.add_tree_nickname_funcs(
147156
tree_nickname_funcs={
148-
"_efit_tree": lambda: retrieval_settings.efit_nickname_setting.get_tree_name(
157+
"_efit_tree": lambda: efit_setting.get_tree_name(
149158
NicknameSettingParams(
150159
shot_id=shot_id,
151160
data_conn=data_conn,
@@ -154,7 +163,8 @@ def shot_setup(
154163
tokamak=self.tokamak,
155164
)
156165
)
157-
}
166+
},
167+
tree_nickname_cascades=cascades,
158168
)
159169

160170
physics_method_params = self.setup_physics_method_params(

disruption_py/inout/mds.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ def __init__(
147147
self._shot_id = shot_id
148148
self.tree_nickname_funcs = {}
149149
self.tree_nicknames = {}
150+
# Phase 2 hook: cascade objects keyed by nickname, for retry on data-path failures.
151+
self.tree_nickname_cascades = {}
150152
self.open_trees = []
151153

152154
@property
@@ -374,13 +376,28 @@ def get_dims(
374376

375377
# nicknames
376378

377-
def add_tree_nickname_funcs(self, tree_nickname_funcs: Dict[str, Callable]):
379+
def add_tree_nickname_funcs(
380+
self,
381+
tree_nickname_funcs: Dict[str, Callable],
382+
tree_nickname_cascades: Dict[str, Any] = None,
383+
):
378384
"""
379385
Add tree nickname functions to the connection.
380386
381387
Required because some tree nickname functions require the connection to exist.
388+
389+
Parameters
390+
----------
391+
tree_nickname_funcs : dict
392+
Mapping of nickname -> callable that resolves to a tree name.
393+
tree_nickname_cascades : dict, optional
394+
Mapping of nickname -> originating NicknameSetting (e.g. a
395+
NicknameSettingList). Stored on the connection so the cascade is
396+
available to future retry logic.
382397
"""
383398
self.tree_nickname_funcs.update(tree_nickname_funcs)
399+
if tree_nickname_cascades:
400+
self.tree_nickname_cascades.update(tree_nickname_cascades)
384401

385402
def get_tree_name_of_nickname(self, nickname: str):
386403
"""

disruption_py/settings/nickname_setting.py

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,25 @@
1010
import time
1111
from abc import ABC, abstractmethod
1212
from dataclasses import dataclass
13-
from typing import Dict, Union
13+
from typing import Dict, List, Union
1414

1515
import pandas as pd
1616
from loguru import logger
1717

1818
from disruption_py.config import config
19+
from disruption_py.core.physics_method.errors import FetchDataError
1920
from disruption_py.core.utils.enums import map_string_to_enum
2021
from disruption_py.core.utils.misc import get_temporary_folder
2122
from disruption_py.inout.base import DataConnection
23+
from disruption_py.inout.mds import mdsExceptions
2224
from disruption_py.inout.sql import ShotDatabase
2325
from disruption_py.machine.tokamak import Tokamak
2426

2527
NicknameSettingType = Union[
26-
"NicknameSettingType", str, Dict[Tokamak, "NicknameSettingType"]
28+
"NicknameSetting",
29+
str,
30+
List[Union[str, "NicknameSetting"]],
31+
Dict[Tokamak, "NicknameSettingType"],
2732
]
2833

2934

@@ -232,6 +237,91 @@ def _get_tree_name(self, params: NicknameSettingParams) -> str:
232237
return self.tree_name
233238

234239

240+
class NicknameSettingList(NicknameSetting):
241+
"""
242+
Cascade nickname setting. Tries each item in order via ``open_tree``,
243+
returning the first that opens successfully. Falls back to the next on
244+
``mdsExceptions.TreeFOPENR``.
245+
246+
Parameters
247+
----------
248+
items : list
249+
Cascade of tree-name strings or NicknameSetting instances. Tried in
250+
order; the first item that opens wins.
251+
"""
252+
253+
def __init__(self, items: list):
254+
"""
255+
Initialize with a cascade of items to try in order.
256+
257+
Parameters
258+
----------
259+
items : list
260+
Non-empty cascade. Each item is a tree-name string or a
261+
NicknameSetting instance.
262+
"""
263+
if not items:
264+
raise ValueError("NicknameSettingList requires at least one item.")
265+
self.items = items
266+
self.resolved_items = [self._resolve_item(item) for item in items]
267+
268+
@staticmethod
269+
def _resolve_item(item):
270+
"""Validate a cascade item: must be a string or NicknameSetting."""
271+
if isinstance(item, NicknameSetting):
272+
return item
273+
if isinstance(item, str):
274+
return item
275+
raise ValueError(
276+
"NicknameSettingList items must be str or NicknameSetting, "
277+
f"got {type(item).__name__}: {item!r}"
278+
)
279+
280+
def _get_tree_name(self, params: NicknameSettingParams) -> str:
281+
"""
282+
Try each cascade item via ``open_tree``; return the first that opens.
283+
284+
Parameters
285+
----------
286+
params : NicknameSettingParams
287+
Parameters needed to determine the nickname.
288+
289+
Returns
290+
-------
291+
str
292+
The tree name of the first cascade item that opened successfully.
293+
294+
Raises
295+
------
296+
FetchDataError
297+
If no item in the cascade could be opened.
298+
"""
299+
attempts: List[str] = []
300+
for item in self.resolved_items:
301+
candidate = item if isinstance(item, str) else type(item).__name__
302+
try:
303+
if isinstance(item, NicknameSetting):
304+
candidate = item.get_tree_name(params)
305+
params.data_conn.open_tree(candidate)
306+
except (mdsExceptions.TreeFOPENR, FetchDataError):
307+
# TreeFOPENR = tree missing; FetchDataError = nested cascade exhausted.
308+
attempts.append(candidate)
309+
continue
310+
if attempts:
311+
logger.verbose(
312+
"Nickname cascade for shot {shot}: selected '{name}' "
313+
"after failed: {prior}",
314+
shot=params.shot_id,
315+
name=candidate,
316+
prior=", ".join(attempts),
317+
)
318+
return candidate
319+
raise FetchDataError(
320+
f"No tree in nickname cascade could be opened for shot "
321+
f"{params.shot_id}. Tried: {attempts}"
322+
)
323+
324+
235325
class DefaultNicknameSetting(NicknameSetting):
236326
"""
237327
Nickname setting to resolve the '_efit_tree' nickname to the default EFIT tree.
@@ -417,9 +507,7 @@ def _cmod_nickname(self, params: NicknameSettingParams) -> str:
417507
tree = config(params.tokamak).efit.tree
418508
if "pytest" in sys.modules:
419509
tree = "efit18"
420-
if tree == "efit18" and params.disruption_time is None:
421-
return DefaultNicknameSetting().get_tree_name(params)
422-
return tree
510+
return NicknameSettingList([tree, "analysis"]).get_tree_name(params)
423511

424512
def _get_tree_name(self, params: NicknameSettingParams) -> str:
425513
"""
@@ -453,7 +541,9 @@ def resolve_nickname_setting(nickname_setting: NicknameSettingType) -> NicknameS
453541
Parameters
454542
----------
455543
nickname_setting : NicknameSettingType
456-
The nickname setting, which can be a string, a dictionary, or a NicknameSetting instance.
544+
The nickname setting: a NicknameSetting instance, a string (single tree
545+
name, registered key like "disruption", or comma-separated cascade), a
546+
list (cascade), or a dictionary (per-tokamak dispatch).
457547
458548
Returns
459549
-------
@@ -462,11 +552,21 @@ def resolve_nickname_setting(nickname_setting: NicknameSettingType) -> NicknameS
462552
"""
463553
if isinstance(nickname_setting, NicknameSetting):
464554
return nickname_setting
555+
if isinstance(nickname_setting, list):
556+
return NicknameSettingList(nickname_setting)
465557
if isinstance(nickname_setting, dict):
466558
return NicknameSettingDict(nickname_setting)
467559
if isinstance(nickname_setting, str):
468560
if nickname_setting in _nickname_setting_mappings:
469561
return _nickname_setting_mappings[nickname_setting]
562+
if "," in nickname_setting:
563+
tokens = [s.strip() for s in nickname_setting.split(",") if s.strip()]
564+
# Registered keys map to their class; other tokens are literal tree names.
565+
items = [
566+
_nickname_setting_mappings[s] if s in _nickname_setting_mappings else s
567+
for s in tokens
568+
]
569+
return NicknameSettingList(items)
470570
return StaticNicknameSetting(nickname_setting)
471571

472572
raise ValueError(f"Invalid nickname setting type {type(nickname_setting)}.")

0 commit comments

Comments
 (0)