Source code for omfit_classes.omfit_omas_kstar

"""
Functions for adding KSTAR data to the IMAS schema by writing to ODS instances
"""

try:
    # framework is running
    from .startup_choice import *
except ImportError as _excp:
    # class is imported by itself
    if (
        'attempted relative import with no known parent package' in str(_excp)
        or 'No module named \'omfit_classes\'' in str(_excp)
        or "No module named '__main__.startup_choice'" in str(_excp)
    ):
        from startup_choice import *
    else:
        raise

import numpy as np
from omfit_classes.omfit_mds import OMFITmds, OMFITmdsValue
from omfit_classes.utils_base import compare_version
from omfit_classes.omfit_omas_utils import ensure_consistent_experiment_info

# noinspection PyBroadException
try:
    import omas

    imas_version = omas.ODS().imas_version
except Exception:
    imas_version = '0.0'

__all__ = []


# Decorators
def _id(obj):
    """Trivial decorator as an alternative to make_available()"""
    return obj


def make_available(f):
    """Decorator for listing a function in __all__ so it will be readily available in other scripts"""
    __all__.append(f.__name__)
    return f


def make_available_if(condition):
    """
    Make available only if a condition is met

    :param condition: bool
        True: make_available decorator is used and function is added to __all__ to be readily available
        False: _id decorator is used, function is not added to __all__, and it cannot be accessed quite so easily
            Some functions inspect __all__ to determine which hardware systems are available
    """
    if condition:
        return make_available
    return _id


# Data inspection utilities
[docs]@make_available def find_active_kstar_probes(shot, allowed_probes=None): """ Serves LP functions by identifying active probes (those that have actual data saved) for a given shot Sorry, I couldn't figure out how to do this with a server-side loop over all the probes, so we have to loop MDS calls on the client side. At least I resampled to speed that part up. This could be a lot faster if I could figure out how to make the GETNCI commands work on records of array signals. :param shot: int :param allowed_probes: int array Restrict the search to a certain range of probe numbers to speed things up :return: list of ints """ print(f'Searching for active KSTAR probes in shot {shot}...') omv_kw = dict(server='KSTAR', shot=shot, treename='KSTAR') lp_paths_unfiltered = OMFITmdsValue(TDI=r'GETNCI("\\TOP.ELECTRON.ELPA:EP*", "MINPATH")', **omv_kw).data() lp_paths = [lpp.strip() for lpp in lp_paths_unfiltered if re.match('[0-9]', lpp.strip().split(':')[-1][2:])] print(f'{len(lp_paths)} possible probes found') lp_nums = np.array([int(lpp.strip().split(':')[-1][2:]) for lpp in lp_paths]) idx = lp_nums.argsort() lp_nums = lp_nums[idx] lp_paths = np.array(lp_paths)[idx] if allowed_probes is None: acceptable_probe_numbers = lp_nums acceptable_paths = lp_paths else: acceptable_probe_numbers = np.array([lpn for lpn in lp_nums if lpn in allowed_probes]) acceptable_paths = np.array([lp_paths[i] for i in range(len(lp_nums)) if lp_nums[i] in allowed_probes]) print(f'{len(acceptable_paths)} candidates remain after limiting search to only some specific probes.') valid = [None] * len(acceptable_paths) i = wd = tc = 0 for i in range(len(acceptable_paths)): ascii_progress_bar( i, 0, len(acceptable_paths), mess=f'Checking KSTAR#{shot} LP probes for valid data {lp_paths[i]} valid={valid[i]}; ' f'probes with valid data so far = {wd}/{tc}', ) valid[i] = OMFITmdsValue(TDI=f'resample({acceptable_paths[i]}.FOO, 0, 1, 1)', **omv_kw).check() wd = np.sum(np.array(valid).astype(bool)) tc = len([a for a in valid if a is not None]) ascii_progress_bar( i + 1, 0, len(acceptable_paths), mess=f'Checked KSTAR#{shot} LP probes for valid data and found {wd} out of {tc} probes have valid data.', ) print() return acceptable_probe_numbers[np.array(valid).astype(bool)]
# Data loading tools # noinspection PyBroadException
[docs]@make_available_if(compare_version(imas_version, '3.25.0') >= 0) # Might actually be 3.24.X. Close enough for now. def load_data_langmuir_probes_kstar( ods, shot, probes=None, allowed_probes=None, tstart=0, tend=10, dt=0.0002, overwrite=False, quantities=None ): """ Downloads LP probe data from MDSplus and loads them to the ODS :param ods: ODS instance :param shot: int :param probes: int array-like [optional] Integer array of KSTAR probe numbers. If not provided, find_active_kstar_probes() will be used. :param allowed_probes: int array-like [optional] Passed to find_active_kstar_probes(), if applicable. Improves speed by limiting search to a specific range of probe numbers. :param tstart: float Time to start resample (s) :param tend: float Time to end resample (s) Set to <= tstart to disable resample :param dt: float Resample interval (s) Set to 0 to disable resample :param overwrite: bool Download and write data even if they already are present in the ODS. :param quantities: list of strings [optional] List of quantities to gather. None to gather all available. Options are: ion_saturation_current Since KSTAR has only one option at the moment, this keyword is ignored, but is accepted to provide a consistent call signature compared to similar functions for other devices. :return: ODS instance The data are added in-place, so catching the return is probably unnecessary. """ # Make sure the ODS is for the right device/shot device = 'KSTAR' ensure_consistent_experiment_info(ods, device, shot) # Make sure the ODS already has probe positions in it hw_ready = True try: if not is_numeric(ods['langmuir_probes.embedded[0].position.r']): hw_ready = False if 'name' not in ods['langmuir_probes.embedded.0']: hw_ready = False except Exception: hw_ready = False if not hw_ready: setup_langmuir_probes_hardware_description_kstar(ods, shot) # Select probes to gather probes = probes or find_active_kstar_probes(shot, allowed_probes=allowed_probes) do_resample = (dt > 0) and (tend > tstart) i = 0 for i, probe in enumerate(probes): p_idx = probe - 1 tdi = rf'\TOP.ELECTRON.ELPA:EP{probe}.FOO' # Sorry, there doesn't seem to be a pointname for uncertainty. ascii_progress_bar(i, 0, len(probes), mess=f'Loading {device}#{shot} LP probe data ({probe}, {tdi})') if not overwrite: try: _ = ods['langmuir_probes.embedded'][p_idx]['time'] _ = ods['langmuir_probes.embedded'][p_idx]['ion_saturation_current.data'] except Exception: pass else: printd(f'Skipping probe {probe} with index {p_idx} because it already has data', topic='omfit_omas_kstar') continue if do_resample: tdi = f'resample({tdi}, {tstart}, {tend}, {dt})' m = OMFITmdsValue(server=device, shot=shot, treename='KSTAR', TDI=tdi) if m.check(): # TODO: deal with calibrations and units; this is definitely a real signal that looks like it's # proportional to A, but that doesn't mean a factor isn't missing. ods['langmuir_probes.embedded'][p_idx]['time'] = m.dim_of(0) ods['langmuir_probes.embedded'][p_idx]['ion_saturation_current.data'] = m.data() # Just nominal values else: printd(f'Probe {p_idx} does not appear to have good data. Skipping...', topic='omfit_omas_kstar') ascii_progress_bar(i + 1, 0, len(probes), mess=f'Loaded {device}#{shot} LP probe data') return ods
# Hardware descriptor functions
[docs]@make_available_if(compare_version(imas_version, '3.25.0') >= 0) # Might actually be 3.24.X. Close enough for now. def setup_langmuir_probes_hardware_description_kstar(ods, shot): """ Load KSTAR Langmuir probe locations into an ODS :param ods: ODS instance :param shot: int :return: dict Information or instructions for follow up in central hardware description setup """ import MDSplus # Is it okay to try this? if compare_version(ods.imas_version, '3.25.0') < 0: printe('langmuir_probes.embedded requires a newer version of IMAS. It was added by 3.25.0.') printe('ABORTED setup_langmuir_probes_hardware_description_kstar due to old IMAS version.') return {} # Reused items; set once ucf = 1e-3 # Unit Conversion Factor: mm to m omv_kw = dict(server='KSTAR', shot=shot, treename='KSTAR') base = r'\\TOP.ELECTRON.ELPA:EP*' # Get probe numbers from paths in MDSplus lp_paths_unfiltered = OMFITmdsValue(TDI=rf'GETNCI("{base}", "MINPATH")', **omv_kw).data() lp_paths = [lpp for lpp in lp_paths_unfiltered if re.match('[0-9]', lpp.strip().split(':')[-1][2:])] lp_numbers = np.array([int(lpp.strip().split(':')[-1][2:]) for lpp in lp_paths]) nprobe = len(lp_numbers) # Get probe coordinates r = OMFITmdsValue(TDI=rf'GETNCI("{base}.R", "RECORD")', **omv_kw).data() z = OMFITmdsValue(TDI=rf'GETNCI("{base}.Z", "RECORD")', **omv_kw).data() phi = OMFITmdsValue(TDI=rf'GETNCI("{base}.TOR_ANGLE", "RECORD")', **omv_kw).data() side_area = OMFITmdsValue(TDI=rf'GETNCI("{base}.NA", "RECORD")', **omv_kw).data() # Verify data integrity assert len(r) == nprobe, f'Arrays should be length {nprobe} (based on probe#), but R has length {len(r)}' assert len(z) == nprobe, f'Arrays should be length {nprobe} (based on probe#), but Z has length {len(z)}' assert len(phi) == nprobe, f'Arrays should be length {nprobe} (based on probe#), but phi has length {len(phi)}' assert len(side_area) == nprobe, f'Expected len(side_area)={nprobe} (based on probe#), not {len(side_area)}' # Sort idx = lp_numbers.argsort() lp_numbers = lp_numbers[idx] r = r[idx] z = z[idx] phi = phi[idx] side_area = side_area[idx] # Only the side area is recorded in MDSplus. If it matches data I got from Jun-Gyo BAK, then assume # that the top area also matches. Otherwise, we don't know the top area. top_area = np.empty(nprobe) top_area[:] = np.NaN top_area[np.isclose(side_area, 4.088e-6, atol=1e-12)] = 2.827e-5 # m^2 # Load for i in range(nprobe): ods['langmuir_probes.embedded'][i]['position.r'] = r[i] * ucf ods['langmuir_probes.embedded'][i]['position.z'] = z[i] * ucf ods['langmuir_probes.embedded'][i]['position.phi'] = phi[i] * np.pi / 180.0 # Is having the surface area without the B-field confusing? One cannot just divide by this area # to get J_sat; additional transformations are needed. ods['langmuir_probes.embedded'][i]['surface_area'] = top_area[i] # TODO: Add side surface area, too, if that gets added (see https://jira.iter.org/browse/IMAS-3311) ods['langmuir_probes.embedded'][i]['name'] = lp_numbers[i] # It should be possible to add surface area as well, but information about angles is needed. # From Jun-Gyo BAK at KSTAR: # 1. The projected area (of the probe tip) for the normal incidence ( 90 degree ) # S_1= 2.827 E-5 m^2 # 2. The projected area for incidence angle of 0 degree # S_2= 4.088 E-6 m^2 # S ~ (S_1*sin(theta) + S_2*cos(theta)) # Here, theta (incidence angle ) was assumed as 5 degree # Thus, J_sat = I_sat/S # So, we need the angle between the probe tip and the field. # S_2 is recorded in MDSplus as NA. Any probe that has NA = 4.088e-06 m^2 # should be safe to assume has S_1 = 2.827e-5 m^2, S_2 = 4.088e-6 m^2. # Add to langmuir_probes.embedded[:].surface_area return {}