'''sample ODS methods and utilities
-------
'''
from .omas_utils import *
from .omas_core import ODS
from .omas_physics import constants
from .omas_plot import geo_type_lookup
__all__ = []
__ods__ = []
def add_to_ODS(f):
"""
anything wrapped here will be available as a ODS method with name 'sample_'+f.__name__
"""
__ods__.append(f.__name__)
return f
[docs]def ods_sample(ntimes=1):
"""
Returns an ODS populated with all of the samples
:param ntimes: number of time slices to generate
:return: sample ods
"""
return ODS().sample(ntimes=ntimes)
@add_to_ODS
def dataset_description(ods):
ods['dataset_description.data_entry.machine'] = 'test'
ods['dataset_description.data_entry.pulse'] = 100
return ods
@add_to_ODS
def equilibrium(
ods, time_index=0, include_profiles=True, include_phi=True, include_psi=True, include_wall=True, include_q=True, include_xpoint=False
):
"""
Add sample equilibrium data
This method operates in in-place.
:param ods: ODS instance
:param time_index: int
Under which time index should fake equilibrium data be loaded?
:param include_profiles: bool
Include 1D profiles of pressure, q, p', FF'
They are in the sample set, so not including them means deleting them.
:param include_phi: bool
Include 1D and 2D profiles of phi (toroidal flux, for calculating rho)
This is in the sample set, so not including it means deleting it.
:param include_psi: bool
Include 1D and 2D profiles of psi (poloidal flux)
This is in the sample set, so not including it means deleting it.
:param include_wall: bool
Include the first wall
This is in the sample set, so not including it means deleting it.
:param include_q: bool
Include safety factor
This is in the sample set, so not including it means deleting it.
:param include_xpoint: bool
Include X-point R-Z coordinates
This is not in the sample set, so including it means making it up
:return: ODS instance with equilibrium data added
"""
from omas import load_omas_json
eq = load_omas_json(omas_dir + 'samples/sample_equilibrium_ods.json', consistency_check=False)
phi = eq['equilibrium.time_slice.0.profiles_1d.phi']
psi = eq['equilibrium.time_slice.0.profiles_1d.psi']
q = eq['equilibrium.time_slice.0.profiles_1d.q']
if not include_profiles:
del eq['equilibrium.time_slice.0.profiles_1d']
if not include_phi:
if 'profiles_1d' in eq['equilibrium.time_slice.0']:
del eq['equilibrium.time_slice.0.profiles_1d.phi']
del eq['equilibrium.time_slice.0.profiles_2d.0.phi']
else:
eq['equilibrium.time_slice.0.profiles_1d.phi'] = phi
if not include_psi:
if 'profiles_1d' in eq['equilibrium.time_slice.0'] and 'psi' in eq['equilibrium.time_slice.0.profiles_1d']:
del eq['equilibrium.time_slice.0.profiles_1d.psi']
del eq['equilibrium.time_slice.0.profiles_2d.0.psi']
else:
eq['equilibrium.time_slice.0.profiles_1d.psi'] = psi
if not include_q:
if 'profiles_1d' in eq['equilibrium.time_slice.0'] and 'q' in eq['equilibrium.time_slice.0.profiles_1d']:
del eq['equilibrium.time_slice.0.profiles_1d.q']
else:
eq['equilibrium.time_slice.0.profiles_1d.q'] = q
if not include_wall:
del eq['wall']
if include_xpoint:
eq['equilibrium.time_slice'][0]['boundary.x_point.0.r'] = 1.304
eq['equilibrium.time_slice'][0]['boundary.x_point.0.z'] = -1.222
ods['equilibrium.time_slice'][time_index].update(eq['equilibrium.time_slice.0'])
ods['equilibrium.time_slice'][time_index]['time'] = float(time_index)
ods['equilibrium.vacuum_toroidal_field.r0'] = eq['equilibrium.vacuum_toroidal_field.r0']
ods.set_time_array('equilibrium.vacuum_toroidal_field.b0', time_index, eq['equilibrium.vacuum_toroidal_field.b0'][0])
ods.set_time_array('equilibrium.time', time_index, float(time_index))
return ods
@add_to_ODS
def core_profiles(ods, time_index=0, add_junk_ion=False, include_pressure=True):
"""
Add sample core_profiles data
This method operates in in-place.
:param ods: ODS instance
:param time_index: int
:param add_junk_ion: bool
Flag for adding a junk ion for testing how well functions tolerate problems. This will be missing labels, etc.
:param include_pressure: bool
Include pressure profiles when temperature and density are added
:return: ODS instance with profiles added
"""
from omas import load_omas_json
pr = load_omas_json(omas_dir + 'samples/sample_core_profiles_ods.json', consistency_check=False)
ods['core_profiles.profiles_1d'][time_index].update(pr['core_profiles.profiles_1d.0'])
ods['core_profiles.vacuum_toroidal_field.r0'] = pr['core_profiles.vacuum_toroidal_field.r0']
ods.set_time_array('core_profiles.vacuum_toroidal_field.b0', time_index, pr['core_profiles.vacuum_toroidal_field.b0'][0])
if add_junk_ion:
ions = ods['core_profiles.profiles_1d'][time_index]['ion']
ions[len(ions)] = copy.deepcopy(ions[len(ions) - 1])
for item in ions[len(ions) - 1].flat():
ions[len(ions) - 1][item] *= 0
if not include_pressure:
for item in ods.physics_core_profiles_pressures(update=False).flat().keys():
if p2l(item)[0] == 'core_profiles' and p2l(item)[-1].startswith('pres') and item in ods:
del ods[item]
ods['core_profiles.profiles_1d'][time_index]['time'] = float(time_index)
ods.set_time_array('core_profiles.time', time_index, float(time_index))
return ods
@add_to_ODS
def ic_antennas(ods):
"""
Add sample ic_antennas data
This method operates in in-place.
:param ods: ODS instance
:return: ODS instance with profiles added
"""
from omas import load_omas_json
ods.update(load_omas_json(omas_dir + 'samples/sample_ic_antennas_ods.json', consistency_check=False))
return ods
@add_to_ODS
def core_sources(ods, time_index=0):
"""
Add sample core_profiles data
This method operates in in-place.
:param ods: ODS instance
:param time_index: int
:return: ODS instance with sources added
"""
from omas import load_omas_json
pr = load_omas_json(omas_dir + 'samples/sample_core_sources_ods.json', consistency_check=False)['core_sources']
if 'core_sources' not in ods:
ods['core_sources'].update(pr)
else:
for item in pr:
if item not in ods['core_sources']:
ods['core_sources'][item] = pr[item]
sources = pr['source']
for source in sources:
ods['core_sources.source'][source]['identifier'].update(sources[source]['identifier'])
ods['core_sources.source'][source]['profiles_1d'][time_index].update(sources[source]['profiles_1d.0'])
ods.set_time_array('core_sources.time', time_index, float(time_index))
return ods
@add_to_ODS
def core_transport(ods, time_index=0):
"""
Add sample core_profiles data
This method operates in in-place
:param ods: ODS instance
:param time_index: int
:return: ODS instance with sources added
"""
from omas import load_omas_json
pr = load_omas_json(omas_dir + 'samples/sample_core_transport_ods.json', consistency_check=False)['core_transport']
if 'core_transport' not in ods:
ods['core_transport'].update(pr)
else:
for item in pr:
if item not in ods['core_transport']:
ods['core_transport'][item] = pr[item]
models = pr['model']
for model in models:
ods['core_transport.model'][model]['identifier'].update(models[model]['identifier'])
ods['core_transport.model'][model]['profiles_1d'][time_index].update(models[model]['profiles_1d.0'])
ods.set_time_array('core_transport.time', time_index, float(time_index))
return ods
@add_to_ODS
def summary(ods):
"""
Add sample core_profiles data
This method operates in in-place
:param ods: ODS instance
:return: ODS instance with sources added
"""
from omas import load_omas_json
pr = load_omas_json(omas_dir + 'samples/sample_summary_ods.json', consistency_check=False)['summary']
ods['summary'].update(pr)
return ods
@add_to_ODS
def pf_active(ods, nc_weird=0, nc_undefined=0):
"""
Adds fake active PF coil locations
This method operates in in-place
:param ods: ODS instance
:param nc_weird: int
Number of coils with badly defined geometry to include for testing plot overlay robustness
:param nc_undefined: int
Number of coils with undefined geometry_type (But valid r, z outlines)
to include for testing plot overlay robustness.
:return: ODS instance with PF active hardware information added
"""
nc_reg = 4
nc = nc_reg + nc_weird + nc_undefined
fc_dat = [
# R Z dR dZ tilt1 tilt2
[0.8608, 0.16830, 0.0508, 0.32106, 0.0, 0.0],
[1.0041, 1.5169, 0.13920, 0.11940, 45.0, 0.0],
[2.6124, 0.4376, 0.17320, 0.1946, 0.0, 92.40],
[2.3834, -1.1171, 0.1880, 0.16920, 0.0, -108.06],
]
rect_code = geo_type_lookup('rectangle', 'pf_active', ods.imas_version, reverse=True)
outline_code = geo_type_lookup('outline', 'pf_active', ods.imas_version, reverse=True)
for i in range(nc_reg):
if (fc_dat[i][4] == 0) and (fc_dat[i][5] == 0):
rect = ods['pf_active.coil'][i]['element.0.geometry.rectangle']
rect['r'] = fc_dat[i][0]
rect['z'] = fc_dat[i][1]
rect['width'] = fc_dat[i][2] # Or width in R
rect['height'] = fc_dat[i][3] # Or height in Z
ods['pf_active.coil'][i]['element.0.geometry.geometry_type'] = rect_code
else:
outline = ods['pf_active.coil'][i]['element.0.geometry.outline']
fdat = fc_dat[i]
fdat[4] = -fc_dat[i][4] * numpy.pi / 180.0
fdat[5] = -(fc_dat[i][5] * numpy.pi / 180.0 if fc_dat[i][5] != 0 else numpy.pi / 2.0)
outline['r'] = [
fdat[0] - fdat[2] / 2.0 - fdat[3] / 2.0 * numpy.tan((numpy.pi / 2.0 + fdat[5])),
fdat[0] - fdat[2] / 2.0 + fdat[3] / 2.0 * numpy.tan((numpy.pi / 2.0 + fdat[5])),
fdat[0] + fdat[2] / 2.0 + fdat[3] / 2.0 * numpy.tan((numpy.pi / 2.0 + fdat[5])),
fdat[0] + fdat[2] / 2.0 - fdat[3] / 2.0 * numpy.tan((numpy.pi / 2.0 + fdat[5])),
]
outline['z'] = [
fdat[1] - fdat[3] / 2.0 - fdat[2] / 2.0 * numpy.tan(-fdat[4]),
fdat[1] + fdat[3] / 2.0 - fdat[2] / 2.0 * numpy.tan(-fdat[4]),
fdat[1] + fdat[3] / 2.0 + fdat[2] / 2.0 * numpy.tan(-fdat[4]),
fdat[1] - fdat[3] / 2.0 + fdat[2] / 2.0 * numpy.tan(-fdat[4]),
]
ods['pf_active.coil'][i]['element.0.geometry.geometry_type'] = outline_code
for i in range(nc_reg, nc_reg + nc_weird):
# This isn't a real geometry_type, so it should trigger the contingency
ods['pf_active.coil'][i]['element.0.geometry.geometry_type'] = 99
for i in range(nc_reg + nc_weird, nc):
# This one doesn't have geometry_type defined, so the plot overlay will have trouble looking up which type it is
outline = ods['pf_active.coil'][i]['element.0.geometry.outline']
outline['r'] = [1.5, 1.6, 1.7, 1.5]
outline['z'] = [0.1, 0.3, -0.1, 0]
for i in range(nc):
# Give the dummy coils identifiers so the plot overlay can try to label them; otherwise the labels aren't tested
ods['pf_active.coil'][i]['element.0.identifier'] = f'samp{i}'
# Generate some data that are not time homogeneous
for i in range(nc_reg):
n = (1 + i) * 10
ods['pf_active.coil'][i]['current.data'] = numpy.linspace(0, 1, n)
ods['pf_active.coil'][i]['current.time'] = numpy.linspace(0, 1, n)
return ods
@add_to_ODS
def magnetics(ods):
"""
Adds fake magnetic probe locations
This method operates in place
:param ods: ODS instance
:return: ODS instance with fake magnetics hardware information added
"""
from omas import load_omas_json
pr = load_omas_json(omas_dir + 'samples/sample_magnetics_ods.json', consistency_check=False)['magnetics']
ods['magnetics'].update(pr)
return ods
@add_to_ODS
def thomson_scattering(ods, nc=10):
"""
Adds fake Thomson scattering channel locations
This method operates in place
:param ods: ODS instance
:param nc: Number of channels to add.
:return: ODS instance with fake Thomson hardware information added
"""
r = numpy.linspace(1.935, 1.945, nc)
z = numpy.linspace(-0.7, 0.2, nc)
for i in range(nc):
ch = ods['thomson_scattering.channel'][i]
ch['identifier'] = 'F_TS_{:02d}'.format(i) # F for fake
ch['name'] = 'Fake Thomson channel for testing {}'.format(i)
ch['position.phi'] = 6.5 # This angle in rad should look bad to someone who doesn't notice the Fake labels
ch['position.r'] = r[i]
ch['position.z'] = z[i]
ods['thomson_scattering.time'] = [0]
return ods
@add_to_ODS
def charge_exchange(ods, nc=10):
"""
Adds fake CER channel locations
This method operates in-place
:param ods: ODS instance
:param nc: Number of channels to add
:return: ODS instance with fake CER hardware information added
"""
r = numpy.linspace(1.4, 2.2, nc)
z = numpy.linspace(0.05, -0.07, nc)
for i in range(nc):
ch = ods['charge_exchange.channel'][i]
ch['identifier'] = 'FAKE_CER_{:02d}'.format(i) # F for fake
ch['name'] = 'Fake CER channel for testing {}'.format(i)
for x in ['r', 'z', 'phi']:
ch['position'][x]['time'] = numpy.array([0])
ch['position.phi.data'] = numpy.array([6.5])
ch['position.r.data'] = numpy.array([r[i]])
ch['position.z.data'] = numpy.array([z[i]])
ods['charge_exchange.time'] = [0.0]
return ods
@add_to_ODS
def interferometer(ods):
"""
Adds fake interferometer locations
This method operates in place
:param ods: ODS instance
:return: ODS instance with fake interferometer hardware information addedd
"""
ods['interferometer.channel.0.identifier'] = 'FAKE horz. interf.'
r0 = ods['interferometer.channel.0.line_of_sight']
r0['first_point.phi'] = r0['second_point.phi'] = 225 * (-numpy.pi / 180)
r0['first_point.r'], r0['second_point.r'] = 3.0, 0.8
r0['first_point.z'] = r0['second_point.z'] = 0.0
i = 0
ods['interferometer.channel'][i + 1]['identifier'] = 'FAKE vert. interf.'
los = ods['interferometer.channel'][i + 1]['line_of_sight']
los['first_point.phi'] = los['second_point.phi'] = 240 * (-numpy.pi / 180)
los['first_point.r'] = los['second_point.r'] = 1.48
los['first_point.z'], los['second_point.z'] = -1.8, 1.8
for j in range(len(ods['interferometer.channel'])):
ch = ods['interferometer.channel'][j]
ch['line_of_sight.third_point'] = copy.deepcopy(ch['line_of_sight.first_point'])
ods['interferometer.time'] = [0]
return ods
@add_to_ODS
def bolometer(ods, nc=10):
"""
Adds fake bolometer chord locations
This method operates in place
:param ods: ODS instance
:param nc: 10 # Number of fake channels to make up for testing
:return: ODS instance with fake bolometer hardware information added
"""
angles = numpy.pi + numpy.linspace(-numpy.pi / 4.0, numpy.pi / 4.0, nc)
# FAKE origin for the FAKE bolometer fan
r0 = 2.5
z0 = 0.05
for i in range(nc):
ch = ods['bolometer.channel'][i]['line_of_sight']
ch['first_point.r'] = r0
ch['first_point.z'] = z0 + 0.001 * i
ch['second_point.r'] = ch['first_point.r'] + numpy.cos(angles[i])
ch['second_point.z'] = ch['first_point.z'] + numpy.sin(angles[i])
ods['bolometer.channel'][i]['identifier'] = 'fake bolo {}'.format(i)
ods['bolometer.channel'][nc - 1]['identifier'] = 'bolo fan 2 fake' # This tests separate colors per fan in overlay
ods['bolometer.time'] = [0]
return ods
@add_to_ODS
def gas_injection(ods):
"""
Adds fake gas injection locations
This method operates in place
:param ods: ODS instance
:return: ODS instance with fake gas injection hardware information added
"""
imas_version = getattr(ods, 'imas_version', None)
separate_valves = imas_version is not None and compare_version(imas_version, "3.34.0") >= 0
ods['gas_injection.time'] = [0]
ip = 0
iv = 0
ods[f'gas_injection.pipe.{ip}.name'] = 'FAKE_GAS_A'
ods[f'gas_injection.pipe.{ip}.exit_position.r'] = 2.25
ods[f'gas_injection.pipe.{ip}.exit_position.z'] = 0.0
ods[f'gas_injection.pipe.{ip}.exit_position.phi'] = 6.5
if separate_valves:
ods[f'gas_injection.pipe.{ip}.valve_indices'] = [iv]
ods[f'gas_injection.valve.{iv}.name'] = 'FAKE_GAS_VALVE_A'
ods[f'gas_injection.valve.{iv}.identifier'] = 'FAKE_GAS_VALVE_A'
ods[f'gas_injection.valve.{iv}.pipe_indices'] = [ip]
else:
ods[f'gas_injection.pipe.{ip}.valve.0.identifier'] = 'FAKE_GAS_VALVE_A'
ods['gas_injection.pipe.1.name'] = 'FAKE_GAS_B'
ods['gas_injection.pipe.1.exit_position.r'] = 1.65
ods['gas_injection.pipe.1.exit_position.z'] = 1.1
ods['gas_injection.pipe.1.exit_position.phi'] = 6.5
ods['gas_injection.pipe.1.second_point.r'] = 1.63
ods['gas_injection.pipe.1.second_point.z'] = 1.08
ods['gas_injection.pipe.1.second_point.phi'] = 6.5
if separate_valves:
ods['gas_injection.pipe.1.valve_indices'] = [1]
ods['gas_injection.valve.1.name'] = 'FAKE_GAS_VALVE_B'
ods['gas_injection.valve.1.identifier'] = 'FAKE_GAS_VALVE_B'
ods['gas_injection.valve.1.pipe_indices'] = [1]
else:
ods['gas_injection.pipe.1.valve.0.identifier'] = 'FAKE_GAS_VALVE_B'
ods['gas_injection.pipe.2.name'] = 'FAKE_GAS_C'
ods['gas_injection.pipe.2.exit_position.r'] = 1.65
ods['gas_injection.pipe.2.exit_position.z'] = 1.1
ods['gas_injection.pipe.2.exit_position.phi'] = 6.5
ods['gas_injection.pipe.2.second_point.phi'] = 6.5
if separate_valves:
ods['gas_injection.pipe.2.valve_indices'] = [2]
ods['gas_injection.valve.2.name'] = 'FAKE_GAS_VALVE_C'
ods['gas_injection.valve.2.identifier'] = 'FAKE_GAS_VALVE_C'
ods['gas_injection.valve.2.pipe_indices'] = [2]
else:
ods['gas_injection.pipe.2.valve.0.identifier'] = 'FAKE_GAS_VALVE_C'
# This one is at the same R,Z as FAKE_GAS_B, but it doesn't have a second point defined; this supports testing.
ods['gas_injection.pipe.3.name'] = 'FAKE_GAS_D'
ods['gas_injection.pipe.3.exit_position.r'] = 2.1
ods['gas_injection.pipe.3.exit_position.z'] = -0.6
if separate_valves:
ods['gas_injection.pipe.3.valve_indices'] = [3, 4]
ods['gas_injection.valve.3.name'] = 'FAKE_GAS_VALVE_B'
ods['gas_injection.valve.3.identifier'] = 'FAKE_GAS_VALVE_B'
ods['gas_injection.valve.3.pipe_indices'] = [3]
else:
ods['gas_injection.pipe.3.valve.0.identifier'] = 'FAKE_GAS_VALVE_D'
# This one deliberately doesn't have a phi angle defined, for testing purposes.
# Add a second inlet to GASD; let's pretend that there is a branch after the valve
ods['gas_injection.pipe.4.name'] = 'FAKE_GAS_D_SECOND_INLET'
ods['gas_injection.pipe.4.exit_position.r'] = 2.15
ods['gas_injection.pipe.4.exit_position.z'] = -0.65
if separate_valves:
ods['gas_injection.pipe.4.valve_indices'] = [3]
else:
ods['gas_injection.pipe.3.valve.0.identifier'] = 'FAKE_GAS_VALVE_D'
return ods
@add_to_ODS
def langmuir_probes(ods):
"""
Adds fake Langmuir probe locations
This method operates in place
:param ods: ODS instance
:return: ODS instance with fake Langmuir probe hardware information added
"""
ods['langmuir_probes.time'] = numpy.array([0])
ods['langmuir_probes.embedded.0.identifier'] = 0
ods['langmuir_probes.embedded.0.name'] = 'p1'
ods['langmuir_probes.embedded.0.position.r'] = 0.9
ods['langmuir_probes.embedded.0.position.z'] = 0.1
ods['langmuir_probes.embedded.0.position.phi'] = 0
ods['langmuir_probes.embedded.1.identifier'] = -1
ods['langmuir_probes.embedded.1.name'] = 'p23'
ods['langmuir_probes.embedded.1.position.r'] = 0.9
ods['langmuir_probes.embedded.1.position.z'] = -0.9
ods['langmuir_probes.embedded.1.position.phi'] = numpy.nan
ods['langmuir_probes.embedded.2.identifier'] = -2
ods['langmuir_probes.embedded.2.name'] = 'blah'
ods['langmuir_probes.embedded.2.position.r'] = 1.5
ods['langmuir_probes.embedded.2.position.z'] = -1.25
ods['langmuir_probes.embedded.2.position.phi'] = numpy.nan
ods['langmuir_probes.embedded.3.identifier'] = -3
ods['langmuir_probes.embedded.3.name'] = 'donkey!'
ods['langmuir_probes.embedded.3.position.r'] = 1.525
ods['langmuir_probes.embedded.3.position.z'] = -1.25
ods['langmuir_probes.embedded.3.position.phi'] = numpy.nan
ods['langmuir_probes.embedded.4.identifier'] = -4
ods['langmuir_probes.embedded.4.name'] = 'zzz'
ods['langmuir_probes.embedded.4.position.r'] = 1.4
ods['langmuir_probes.embedded.4.position.z'] = 1.4
ods['langmuir_probes.embedded.4.position.phi'] = numpy.pi
ods['langmuir_probes.embedded.5.identifier'] = -5
ods['langmuir_probes.embedded.5.name'] = "it's just a test"
ods['langmuir_probes.embedded.5.position.r'] = 2.45
ods['langmuir_probes.embedded.5.position.z'] = 0.25
ods['langmuir_probes.embedded.5.position.phi'] = numpy.nan
return ods
@add_to_ODS
def wall(ods):
"""
Adds fake wall data
This method operates in in-place
:param ods: ODS instance
:return: ODS instance with added wall description
"""
ods['wall.time'] = [0.0]
ods['wall.description_2d[0].limiter.type.description'] = 'first wall'
ods['wall.description_2d[0].limiter.type.index'] = 0
ods['wall.description_2d[0].limiter.type.name'] = 'first_wall'
ods['wall.description_2d[0].limiter.unit[0].outline.r'] = [
# fmt: off
1.0, 1.0, 1.3, 1.4, 1.6, 2.15, 2.35, 2.35, 2.15, 1.800, 1.350, 1.35, 1.10, 1.00, 1.0
# fmt: on
]
ods['wall.description_2d[0].limiter.unit[0].outline.z'] = [
# fmt: off
0.0, 1.4, 1.4, 1.3, 1.1, 1.00, 0.50, -0.5, -1.0, -1.25, -1.25, -1.4, -1.4, -1.3, 0.0
# fmt: on
]
return ods
@add_to_ODS
def pulse_schedule(ods):
"""
Adds fake control target data to support testing
This method operates in in-place
:param ods: ODS instance
:return: ODS instance with added pulse schedule
"""
def add_position_control(ods_):
"""Adds sample data for the position control subset"""
# These data are sampled from DIII-D#161558 at the following times:
t = numpy.array([0.1, 0.52, 0.99, 1.29, 1.46, 2.01, 3.91, 5.97, 6.6, 6.9]) # s
bdry = ods_['pulse_schedule.position_control.boundary_outline']
bdry[0]['r.reference'] = numpy.array([2.31, 2.27, 2.27, 2.27, 2.27, 2.27, 2.27, 2.27, 2.25, 2.25]) # m
bdry[0]['z.reference'] = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
bdry[1]['r.reference'] = numpy.array([2.21, 2.17, 2.17, 2.16, 2.16, 2.15, 2.16, 2.15, 2.12, 2.12])
bdry[1]['z.reference'] = numpy.array([0.43, 0.43, 0.43, 0.43, 0.43, 0.43, 0.43, 0.43, 0.43, 0.43])
bdry[2]['r.reference'] = numpy.array([1.9, 1.9, 1.9, 1.88, 1.87, 1.87, 1.87, 1.87, 1.83, 1.83])
bdry[2]['z.reference'] = numpy.array([0.81, 0.81, 0.81, 0.78, 0.76, 0.76, 0.76, 0.76, 0.69, 0.69])
bdry[3]['r.reference'] = numpy.array([1.52, 1.52, 1.52, 1.52, 1.52, 1.52, 1.52, 1.52, 1.52, 1.52])
bdry[3]['z.reference'] = numpy.array([0.84, 0.91, 0.91, 0.87, 0.84, 0.83, 0.83, 0.83, 1.35, 1.35])
bdry[4]['r.reference'] = numpy.array([1.44, 1.39, 1.39, 1.41, 1.43, 1.43, 1.43, 1.43, 1.1, 1.1])
bdry[4]['z.reference'] = numpy.array([0.8, 0.86, 0.86, 0.83, 0.81, 0.81, 0.81, 0.81, 1.21, 1.21])
bdry[5]['r.reference'] = numpy.array([1.32, 1.28, 1.28, 1.3, 1.31, 1.32, 1.32, 1.32, 1.07, 1.07])
bdry[5]['z.reference'] = numpy.array([0.73, 0.77, 0.77, 0.75, 0.74, 0.73, 0.73, 0.73, 0.99, 0.99])
bdry[6]['r.reference'] = numpy.array([0.92, 1.16, 1.16, 1.16, 1.17, 1.17, 1.17, 1.17, 0.92, 0.92])
bdry[6]['z.reference'] = numpy.array([0.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, 0.0])
bdry[7]['r.reference'] = numpy.array([1.15, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.2, 1.2])
bdry[7]['z.reference'] = numpy.array([0.5, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.5, 0.5])
bdry[8]['r.reference'] = numpy.array([1.08, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.08, 1.08])
bdry[8]['z.reference'] = numpy.array([-0.5, -0.16, -0.16, -0.16, -0.16, -0.16, -0.16, -0.16, -0.5, -0.5])
bdry[9]['r.reference'] = numpy.array([1.23, 1.12, 1.12, 1.14, 1.14, 1.14, 1.14, 1.14, 1.23, 1.23])
bdry[9]['z.reference'] = numpy.array([-0.78, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.79, -0.79])
bdry[10]['r.reference'] = numpy.array([1.94, 1.18, 1.18, 1.21, 1.22, 1.22, 1.22, 1.22, 1.92, 1.92])
bdry[10]['z.reference'] = numpy.array([-0.88, -0.81, -0.81, -0.79, -0.79, -0.79, -0.79, -0.79, -0.85, -0.85])
bdry[11]['r.reference'] = numpy.array([2.23, 1.89, 1.89, 1.89, 1.89, 1.89, 1.89, 1.89, 2.18, 2.18])
bdry[11]['z.reference'] = numpy.array([-0.43, -0.8, -0.8, -0.8, -0.8, -0.8, -0.8, -0.8, -0.43, -0.43])
for i in range(12):
bdry[i]['r.reference_type'] = bdry[i]['z.reference_type'] = 1
bdry[i]['r.reference_name'] = bdry[i]['z.reference_name'] = 'seg{}'.format(i)
strk = ods_['pulse_schedule.position_control.strike_point']
strk[0]['r.reference'] = numpy.array([numpy.nan, 1.35, 1.35, 1.35, 1.35, 1.35, 1.35, 1.35, numpy.nan, numpy.nan])
strk[0]['z.reference'] = numpy.array([numpy.nan, -1.35, -1.35, -1.35, -1.35, -1.35, -1.35, -1.35, numpy.nan, numpy.nan])
strk[1]['r.reference'] = numpy.array([numpy.nan, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, numpy.nan, numpy.nan])
strk[1]['z.reference'] = numpy.array([numpy.nan, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, numpy.nan, numpy.nan])
for i in range(2):
strk[i]['r.reference_type'] = strk[i]['z.reference_type'] = 1
strk[i]['r.reference_name'] = strk[i]['z.reference_name'] = 'strk{}'.format(i)
xpt = ods_['pulse_schedule.position_control.x_point']
xpt[0]['r.reference'] = numpy.array([1.1, 1.34, 1.34, 1.43, 1.43, 1.43, 1.43, 1.43, 1.13, 1.13])
xpt[0]['z.reference'] = numpy.array([-1.6, -1.21, -1.21, -1.15, -1.15, -1.15, -1.15, -1.15, -1.41, -1.41])
xpt[1]['r.reference'] = numpy.array([numpy.nan] * len(t))
xpt[1]['z.reference'] = numpy.array([numpy.nan] * len(t))
for i in range(2):
xpt[i]['r.reference_type'] = xpt[i]['z.reference_type'] = 1
xpt[i]['r.reference_name'] = xpt[i]['z.reference_name'] = 'strk{}'.format(i)
ods_['pulse_schedule.position_control.time'] = t
ods_['pulse_schedule.time'] = t
add_position_control(ods)
return ods
@add_to_ODS
def ec_launchers(ods, ngyros=2, ntimes=6):
"""
Adds fake ec launchers data to support testing
This method operates in in-place
:param ods: ODS instance
:param ngyros: number of gyrotrons
:param ntimes: number of times
:return: ODS instance with added ec_launchers
"""
times = numpy.linspace(0.0, 1.0, ntimes)
ones = numpy.ones(ntimes)
ods['ec_launchers.ids_properties.homogeneous_time'] = 1
for gyro in range(ngyros):
ods['ec_launchers']['time'] = times
ods['ec_launchers']['beam'][gyro]['time'] = times
ods['ec_launchers']['beam'][gyro]['identifier'] = 'GYRO_' + str(gyro)
ods['ec_launchers']['beam'][gyro]['frequency']['data'] = ones * 110e9
ods['ec_launchers']['beam'][gyro]['frequency']['time'] = times
ods['ec_launchers']['beam'][gyro]['launching_position']['phi'] = 0.0 * ones
ods['ec_launchers']['beam'][gyro]['launching_position']['r'] = 2.4 * ones
ods['ec_launchers']['beam'][gyro]['launching_position']['z'] = 0.68 * ones
ods['ec_launchers']['beam'][gyro]['mode'] = -1
ods['ec_launchers']['beam'][gyro]['power_launched']['data'] = 0.5e6 * (ones - 0.5 * numpy.cos(2 * numpy.pi * times + gyro / ngyros))
ods['ec_launchers']['beam'][gyro]['power_launched']['time'] = times
ods['ec_launchers']['beam'][gyro]['steering_angle_pol'] = 0.61 * ones
ods['ec_launchers']['beam'][gyro]['steering_angle_tor'] = 0.0 * ones
return ods
@add_to_ODS
def nbi(ods, nunits=2, ntimes=6):
"""
Adds fake nbi data to support testing
This method operates in in-place
:param ods: ODS instance
:param nunits: number of times
:param ntimes: number of times
:return: ODS instance with added nbi
"""
times = numpy.linspace(0.0, 1.0, ntimes)
ones = numpy.ones(ntimes)
ods['nbi.time'] = times
ods['nbi.ids_properties.homogeneous_time'] = 1
for unit in range(nunits):
ods['nbi']['unit'][unit]['beam_current_fraction']['data'] = [0.27032773 * ones, 0.14438479 * ones, 0.07613747 * ones]
ods['nbi']['unit'][unit]['beam_current_fraction']['time'] = times
ods['nbi']['unit'][unit]['beam_power_fraction']['data'] = [0.36067036 * ones, 0.09631886 * ones, 0.03386079 * ones]
ods['nbi']['unit'][unit]['beam_power_fraction']['time'] = times
ods['nbi']['unit'][unit]['beamlets_group'][0]['angle'] = -0
ods['nbi']['unit'][unit]['beamlets_group'][0]['direction'] = 1
ods['nbi']['unit'][unit]['beamlets_group'][0]['divergence_component'][0]['horizontal'] = 0.00873
ods['nbi']['unit'][unit]['beamlets_group'][0]['divergence_component'][0]['particles_fraction'] = 1.0
ods['nbi']['unit'][unit]['beamlets_group'][0]['divergence_component'][0]['vertical'] = 0.227
ods['nbi']['unit'][unit]['beamlets_group'][0]['focus']['focal_length_horizontal'] = 1e31
ods['nbi']['unit'][unit]['beamlets_group'][0]['focus']['focal_length_vertical'] = 10.0
ods['nbi']['unit'][unit]['beamlets_group'][0]['position']['phi'] = -0.773
ods['nbi']['unit'][unit]['beamlets_group'][0]['position']['r'] = 8.11
ods['nbi']['unit'][unit]['beamlets_group'][0]['position']['z'] = 0.0
ods['nbi']['unit'][unit]['beamlets_group'][0]['tangency_radius'] = 1.146
ods['nbi']['unit'][unit]['beamlets_group'][0]['width_vertical'] = 0.48
ods['nbi']['unit'][unit]['beamlets_group'][0]['width_horizontal'] = 0.1
ods['nbi']['unit'][unit]['identifier'] = 'beam_' + str(unit)
ods['nbi']['unit'][unit]['energy']['data'] = 80e3 * ones
ods['nbi']['unit'][unit]['energy']['time'] = times
ods['nbi']['unit'][unit]['power_launched']['data'] = 2.0e6 * (ones - 0.5 * numpy.cos(2 * numpy.pi * times + unit / nunits))
ods['nbi']['unit'][unit]['power_launched']['time'] = times
ods['nbi']['unit'][unit]['species']['a'] = 2.0
ods['nbi']['unit'][unit]['species']['z_n'] = 1.0
return ods