Skip to content

Commit 2a47db7

Browse files
committed
Complete output mapping + cleanup
Write for each indivual ion species in the output from the mixtures
1 parent bbf6643 commit 2a47db7

1 file changed

Lines changed: 66 additions & 41 deletions

File tree

torax/imas_tools/core_profiles.py

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
except ImportError:
2525
IDSToplevel = Any
2626
from torax._src.orchestration.sim_state import ToraxSimState
27+
from torax._src import constants
2728
from torax._src.output_tools import post_processing
29+
from torax._src.config import runtime_params_slice
30+
from torax._src.torax_pydantic import model_config
2831
from torax._src.geometry.geometry import face_to_cell
2932

3033
def update_dict(old_dict:dict, updates:dict) -> dict:
@@ -54,7 +57,7 @@ def core_profiles_from_IMAS(
5457
) -> dict:
5558
"""Converts core_profiles IDS to a dict with the input profiles for the config.
5659
Args:
57-
ids: IDS object. Can be either core_profiles or plasma_profiles.
60+
ids: IDS object. Can be either core_profiles or plasma_profiles. The IDS can contain several time slices.
5861
read_psi_from_geo: Decides either to read psi from the geometry or from the input core/plasma_profiles IDS. Default value is True meaning that psi is taken from the geometry.
5962
6063
Returns:
@@ -65,16 +68,13 @@ def core_profiles_from_IMAS(
6568
# numerics
6669
t_initial = float(profiles_1d[0].time)
6770

68-
#plasma_composition (should be set in the config as user defined free parameter)
69-
#Zeff taken from here or set into config ?
71+
#plasma_composition
72+
#Zeff taken from here or set into config before ?
7073
if len(ids.global_quantities.z_eff_resistive>0):
7174
Z_eff = {time_array[ti]: ids.global_quantities.z_eff_resistive[ti] for ti in range(len(time_array))}
7275
else:
7376
Z_eff = {time_array[ti]: {rhon_array[ti][rj]: profiles_1d[ti].zeff[rj] for rj in range(len(rhon_array[ti]))} for ti in range(len(time_array))}
74-
# Zi_override = 1.0
75-
# Ai_override = 2.5
76-
# Zimp_override = 1.0
77-
# Aimp_override = 1.0
77+
7878

7979
#profile_conditions
8080
# Should we shift it to get psi=0 at the center ?
@@ -125,13 +125,17 @@ def core_profiles_from_IMAS(
125125

126126
#TODO: Add option to save entire state history in one core_profiles output.
127127
def core_profiles_to_IMAS(
128+
config: model_config.ToraxConfig,
129+
dynamic_runtime_params_slice: runtime_params_slice.DynamicRuntimeParamsSlice,
128130
post_processed_outputs: post_processing.PostProcessedOutputs,
129131
state: ToraxSimState,
130132
ids: IDSToplevel = imas.IDSFactory().core_profiles,
131133
) -> IDSToplevel:
132134
"""Converts torax core_profiles to IMAS IDS.
133135
Takes the cell grid as a basis and converts values on face grid to cell.
134136
Args:
137+
config: TORAX config used for the simulation to get the names of the ions.
138+
dynamic_runtime_params_slice: Used to get the ions fractions for the time slice used.
135139
post_processed_outputs: TORAX post_processed_outputs with many useful data to output to the IDS.
136140
state: A ToraxSimState object.
137141
ids: Optional IDS object to be filled. Can be either core_profiles or plasma_profiles. Default is an empty core_profiles IDS. Note that both exists currently from Data Dictionary version 4, with plasma_profiles being the union of core_profiles and edge_profiles.
@@ -142,7 +146,7 @@ def core_profiles_to_IMAS(
142146
cp_state = state.core_profiles
143147
cs_state = state.core_sources
144148
geometry = state.geometry
145-
ids.ids_properties.comment = "IDS built from TORAX sim output. Grid based on torax cell grid, used cell grid values and interpolated face grid values"
149+
ids.ids_properties.comment = "IDS built from TORAX sim output. Grid based on torax cell grid + boundaries."
146150
ids.ids_properties.homogeneous_time = 1
147151
ids.ids_properties.creation_date = datetime.date.today().isoformat()
148152
ids.time = [t]
@@ -159,15 +163,9 @@ def core_profiles_to_IMAS(
159163
ids.global_quantities.beta_tor_norm.resize(1)
160164
ids.global_quantities.t_e_volume_average.resize(1)
161165
ids.global_quantities.n_e_volume_average.resize(1)
162-
ids.global_quantities.ion.resize(1) #Volume average Ti and ni only available for main ion (could be modified to define it for each of the main ions at least, and t_i_average for all ions, impurities included).
163-
ids.global_quantities.ion[0].t_i_volume_average.resize(1)
164-
ids.global_quantities.ion[0].n_i_volume_average.resize(1)
165166

166167
ids.profiles_1d.resize(1)
167168
ids.profiles_1d[0].time = t
168-
ids.profiles_1d[0].ion.resize(2)
169-
ids.profiles_1d[0].ion[0].element.resize(1)
170-
ids.profiles_1d[0].ion[1].element.resize(1)
171169

172170
ids.vacuum_toroidal_field.r0 = geometry.R_major
173171
ids.vacuum_toroidal_field.b0[0] = geometry.B_0 # +1 or -1 ?
@@ -181,8 +179,7 @@ def core_profiles_to_IMAS(
181179
ids.global_quantities.beta_tor_norm[0] = post_processed_outputs.beta_N
182180
ids.global_quantities.t_e_volume_average[0] = post_processed_outputs.T_e_volume_avg * 1e3
183181
ids.global_quantities.n_e_volume_average[0] = post_processed_outputs.n_e_volume_avg
184-
ids.global_quantities.ion[0].t_i_volume_average[0] = post_processed_outputs.T_i_volume_avg * 1e3
185-
ids.global_quantities.ion[0].n_i_volume_average[0] = post_processed_outputs.n_i_volume_avg
182+
ids.global_quantities.ion_time_slice = t
186183

187184
ids.profiles_1d[0].grid.rho_tor_norm = np.concatenate([[0.0], geometry.torax_mesh.cell_centers, [1.0]])
188185
Phi = np.concatenate([[geometry.Phi_face[0]],geometry.Phi, [geometry.Phi_face[-1]]])
@@ -204,30 +201,58 @@ def core_profiles_to_IMAS(
204201
ids.profiles_1d[0].pressure_thermal = post_processed_outputs.pressure_thermal_total.cell_plus_boundaries()
205202
ids.profiles_1d[0].t_i_average = cp_state.T_i.cell_plus_boundaries() * 1e3
206203
ids.profiles_1d[0].n_i_total_over_n_e = (cp_state.n_i.cell_plus_boundaries() + cp_state.n_impurity.cell_plus_boundaries()) / cp_state.n_e.cell_plus_boundaries()
207-
Z_i = np.concatenate([[cp_state.Z_i_face[0]], cp_state.Z_i, [cp_state.Z_i_face[-1]]])
208-
Z_impurity = np.concatenate([[cp_state.Z_impurity_face[0]], cp_state.Z_impurity, [cp_state.Z_impurity_face[-1]]])
209-
ids.profiles_1d[0].zeff = (Z_i**2 * cp_state.n_i.cell_plus_boundaries() + Z_impurity**2 * cp_state.n_impurity.cell_plus_boundaries()) / cp_state.n_e.cell_plus_boundaries() #Formula correct ?
210-
211-
#TODO:add ion mixture details. Currently, only fill values for main ion and impurity averaged, do not take into account the mixture from config
212-
ids.profiles_1d[0].ion[0].z_ion = np.mean(cp_state.Z_i) # Change to make it correspond to volume average over plasma radius
213-
ids.profiles_1d[0].ion[0].z_ion_1d = Z_i
214-
ids.profiles_1d[0].ion[0].temperature = cp_state.T_i.cell_plus_boundaries() * 1e3
215-
ids.profiles_1d[0].ion[0].density = cp_state.n_i.cell_plus_boundaries()
216-
ids.profiles_1d[0].ion[0].density_thermal = cp_state.n_i.cell_plus_boundaries()
217-
ids.profiles_1d[0].ion[0].density_fast = np.zeros(len(ids.profiles_1d[0].grid.rho_tor_norm))
218-
# assume no molecules, revisit later
219-
ids.profiles_1d[0].ion[0].element[0].a = cp_state.A_i
220-
ids.profiles_1d[0].ion[0].element[0].z_n = np.round(np.mean(cp_state.Z_i)) # This or read the data from IonMixture ?
221-
222-
ids.profiles_1d[0].ion[1].z_ion = np.mean(cp_state.Z_impurity_face) # Change to make it correspond to volume average over plasma radius
223-
ids.profiles_1d[0].ion[1].z_ion_1d = Z_impurity
224-
ids.profiles_1d[0].ion[1].temperature = cp_state.T_i.cell_plus_boundaries()
225-
ids.profiles_1d[0].ion[1].density = cp_state.n_impurity.cell_plus_boundaries()
226-
ids.profiles_1d[0].ion[1].density_thermal = cp_state.n_impurity.cell_plus_boundaries()
227-
ids.profiles_1d[0].ion[1].density_fast = np.zeros(len(ids.profiles_1d[0].grid.rho_tor_norm))
228-
# assume no molecules, revisit later
229-
ids.profiles_1d[0].ion[1].element[0].a = cp_state.A_impurity
230-
ids.profiles_1d[0].ion[1].element[0].z_n = np.round(np.mean(cp_state.Z_impurity_face)) # This or read the data from IonMixture ?
204+
Z_eff = np.concatenate([[cp_state.Z_eff_face[0]], cp_state.Z_eff, [cp_state.Z_eff_face[-1]]])
205+
ids.profiles_1d[0].zeff = Z_eff #Keep the formula below instead or keep zeff from cp as source of truth ?
206+
# Z_i = np.concatenate([[cp_state.Z_i_face[0]], cp_state.Z_i, [cp_state.Z_i_face[-1]]])
207+
# Z_impurity = np.concatenate([[cp_state.Z_impurity_face[0]], cp_state.Z_impurity, [cp_state.Z_impurity_face[-1]]])
208+
# ids.profiles_1d[0].zeff = (Z_i**2 * cp_state.n_i.cell_plus_boundaries() + Z_impurity**2 * cp_state.n_impurity.cell_plus_boundaries()) / cp_state.n_e.cell_plus_boundaries() #Formula correct ?
209+
210+
main_ion = list(zip(config.plasma_composition.get_main_ion_names(), dynamic_runtime_params_slice.plasma_composition.main_ion.fractions))
211+
impurities = list(zip(config.plasma_composition.get_impurity_names(), dynamic_runtime_params_slice.plasma_composition.impurity.fractions))
212+
num_of_main_ions = len(main_ion)
213+
num_ions = num_of_main_ions + len(impurities)
214+
ids.profiles_1d[0].ion.resize(num_ions)
215+
ids.global_quantities.ion.resize(num_ions)
216+
#Fill main ions quantities
217+
for iion in range(len(main_ion)):
218+
symbol, frac = main_ion[iion]
219+
ion_properties = constants.ION_PROPERTIES_DICT[symbol]
220+
#Should we read z_ion_1d from charge_states.get_average_charge_state().Z_per_species ?
221+
# ids.profiles_1d[0].ion[iion].z_ion = np.mean(cp_state.Z_i) # Change to make it correspond to volume average over plasma radius
222+
# ids.profiles_1d[0].ion[iion].z_ion_1d = Z_i
223+
ids.profiles_1d[0].ion[iion].temperature = cp_state.T_i.cell_plus_boundaries() * 1e3
224+
ids.profiles_1d[0].ion[iion].density = cp_state.n_i.cell_plus_boundaries() * frac
225+
ids.profiles_1d[0].ion[iion].density_thermal = cp_state.n_i.cell_plus_boundaries() * frac #Is it true that all ions are thermal ?
226+
ids.profiles_1d[0].ion[iion].density_fast = np.zeros(len(ids.profiles_1d[0].grid.rho_tor_norm))
227+
ids.profiles_1d[0].ion[iion].element.resize(1)
228+
ids.profiles_1d[0].ion[iion].element[0].a = ion_properties.A
229+
ids.profiles_1d[0].ion[iion].element[0].z_n = ion_properties.Z
230+
231+
ids.global_quantities.ion[iion].t_i_volume_average.resize(1)
232+
ids.global_quantities.ion[iion].n_i_volume_average.resize(1)
233+
ids.global_quantities.ion[iion].t_i_volume_average[0] = post_processed_outputs.T_i_volume_avg * 1e3
234+
ids.global_quantities.ion[iion].n_i_volume_average[0] = post_processed_outputs.n_i_volume_avg * frac #Valid to do like this ? Volume average ni only available for main ion.
235+
236+
#Fill impurity quantities
237+
#TODO: Include the impurity_mode from PR #1408 for the fractions calculations etc and impurity fractions stacked into core_profiles.
238+
for iion in range(len(impurities)):
239+
symbol, frac = impurities[iion]
240+
ion_properties = constants.ION_PROPERTIES_DICT[symbol]
241+
#Should we read z_ion_1d from charge_states.get_average_charge_state().Z_per_species ?
242+
# ids.profiles_1d[0].ion[num_of_main_ions+iion].z_ion = np.mean(cp_state.Z_impurity_face) # Change to make it correspond to volume average over plasma radius
243+
# ids.profiles_1d[0].ion[num_of_main_ions+iion].z_ion_1d = Z_impurity
244+
ids.profiles_1d[0].ion[num_of_main_ions+iion].temperature = cp_state.T_i.cell_plus_boundaries()
245+
ids.profiles_1d[0].ion[num_of_main_ions+iion].density = cp_state.n_impurity.cell_plus_boundaries() * frac
246+
ids.profiles_1d[0].ion[num_of_main_ions+iion].density_thermal = cp_state.n_impurity.cell_plus_boundaries() * frac
247+
ids.profiles_1d[0].ion[num_of_main_ions+iion].density_fast = np.zeros(len(ids.profiles_1d[0].grid.rho_tor_norm))
248+
ids.profiles_1d[0].ion[num_of_main_ions+iion].element.resize(1)
249+
ids.profiles_1d[0].ion[num_of_main_ions+iion].element[0].a = ion_properties.A
250+
ids.profiles_1d[0].ion[num_of_main_ions+iion].element[0].z_n = ion_properties.Z
251+
252+
ids.global_quantities.ion[num_of_main_ions+iion].t_i_volume_average.resize(1)
253+
ids.global_quantities.ion[num_of_main_ions+iion].t_i_volume_average[0] = post_processed_outputs.T_i_volume_avg * 1e3 #Volume average Ti and ni only available for main ion.
254+
255+
231256
q_cell = face_to_cell(cp_state.q_face)
232257
s_cell = face_to_cell(cp_state.s_face)
233258
ids.profiles_1d[0].q = np.concatenate([[cp_state.q_face[0]], q_cell, [cp_state.q_face[-1]]])
@@ -237,7 +262,7 @@ def core_profiles_to_IMAS(
237262
ids.profiles_1d[0].j_total = -1 * j_total
238263
ids.profiles_1d[0].j_ohmic = -1 * post_processed_outputs.j_ohmic #TODO: Extend grid with boundaries : Need to find a way for these 2 as there is only values on cell grid for external current sources
239264
ids.profiles_1d[0].j_non_inductive = -1 *(sum(cs_state.psi.values()) + cs_state.bootstrap_current.j_bootstrap) # Extend grid with boundaries
240-
ids.profiles_1d[0].j_bootstrap = j_bootstrap
265+
ids.profiles_1d[0].j_bootstrap = -1 * j_bootstrap
241266
sigma = np.concatenate([[cp_state.sigma_face[0]], cp_state.sigma, [cp_state.sigma_face[-1]]])
242267
ids.profiles_1d[0].conductivity_parallel = sigma
243268
return ids

0 commit comments

Comments
 (0)