Skip to content

Commit b93f339

Browse files
hamelphiTorax team
authored andcommitted
Calculate ExB drift
PiperOrigin-RevId: 832323537
1 parent 7f32eef commit b93f339

File tree

65 files changed

+365
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+365
-2
lines changed

docs/configuration.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,14 @@ time-dependence of temperature, density, and current.
339339
This setting is ignored for the ad-hoc circular geometry, which has no
340340
numerical geometry.
341341

342+
``toroidal_velocity`` (**time-varying-array** | None [default = None])
343+
Toroidal velocity profile. If not provided, the velocity will be set to zero.
344+
345+
``toroidal_velocity_right_bc`` (**time-varying-scalar** | None [default = None])
346+
Toroidal velocity boundary condition for r=a_minor. If this is ``None`` the
347+
boundary condition will instead be taken from ``toroidal_velocity`` at
348+
:math:`\hat{\rho}=1`.
349+
342350
.. _numerics_dataclass:
343351

344352
numerics

docs/output.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ These are called out in the list of profiles below, and generate relate to:
301301
``Phi`` (time, rho_norm)
302302
Toroidal magnetic flux at each radial grid point [:math:`Wb`].
303303

304+
``poloidal_velocity`` (time, rho_norm)
305+
Poloidal velocity [:math:`m/s`].
306+
304307
``pprime`` (time, rho_face_norm)
305308
Derivative of total pressure with respect to poloidal flux [:math:`Pa/Wb`].
306309

@@ -331,6 +334,9 @@ These are called out in the list of profiles below, and generate relate to:
331334
``q`` (time, rho_face_norm)
332335
Safety factor profile on the face grid [dimensionless].
333336

337+
``radial_electric_field`` (time, rho_norm)
338+
Radial electric field [:math:`V/m`].
339+
334340
``radiation_impurity_species`` (impurity_symbol, time, rho_cell_norm)
335341
Impurity radiation power density per species [:math:`W/m^3`]. Only output
336342
if the ``mavrin_fit`` model is active for ``impurity_radiation``. In this
@@ -371,6 +377,9 @@ These are called out in the list of profiles below, and generate relate to:
371377
``T_i`` (time, rho_norm)
372378
Ion temperature [:math:`keV`].
373379

380+
``toroidal_velocity`` (time, rho_norm)
381+
Toroidal velocity [:math:`m/s`].
382+
374383
``v_loop`` (time, rho_norm)
375384
Loop voltage profile :math:`V_{loop}=\frac{\partial\psi}{\partial t}`
376385
[:math:`V`].

torax/_src/core_profiles/getters.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ def get_updated_electron_density(
161161
return n_e
162162

163163

164+
def get_updated_toroidal_velocity(
165+
profile_conditions_params: profile_conditions.RuntimeParams,
166+
geo: geometry.Geometry,
167+
) -> cell_variable.CellVariable:
168+
"""Gets initial and/or prescribed toroidal velocity profiles."""
169+
if profile_conditions_params.toroidal_velocity is None:
170+
value = jnp.zeros_like(geo.rho)
171+
else:
172+
value = profile_conditions_params.toroidal_velocity
173+
toroidal_velocity = cell_variable.CellVariable(
174+
value=value,
175+
right_face_grad_constraint=None,
176+
right_face_constraint=profile_conditions_params.toroidal_velocity_right_bc,
177+
dr=geo.drho_norm,
178+
)
179+
return toroidal_velocity
180+
181+
164182
@dataclasses.dataclass(frozen=True)
165183
class _IonProperties:
166184
"""Helper container for holding ion calculation outputs."""

torax/_src/core_profiles/initialization.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from torax._src.geometry import standard_geometry
3333
from torax._src.neoclassical import neoclassical_models as neoclassical_models_lib
3434
from torax._src.neoclassical.bootstrap_current import base as bootstrap_current_base
35+
from torax._src.physics import formulas
3536
from torax._src.physics import psi_calculations
3637
from torax._src.sources import source_models as source_models_lib
3738
from torax._src.sources import source_profile_builders
@@ -69,7 +70,9 @@ def initial_core_profiles(
6970
runtime_params.profile_conditions, geo
7071
)
7172
ions = getters.get_updated_ions(runtime_params, geo, n_e, T_e)
72-
73+
toroidal_velocity = getters.get_updated_toroidal_velocity(
74+
runtime_params.profile_conditions, geo
75+
)
7376
# Set v_loop_lcfs. Two branches:
7477
# 1. Set the v_loop_lcfs from profile_conditions if using the v_loop BC option
7578
# 2. Initialize v_loop_lcfs to 0 if using the Ip boundary condition for psi.
@@ -95,6 +98,20 @@ def initial_core_profiles(
9598
dr=geo.drho_norm,
9699
)
97100

101+
poloidal_velocity = cell_variable.CellVariable(
102+
value=jnp.zeros_like(geo.rho),
103+
dr=geo.drho_norm,
104+
right_face_constraint=0.0,
105+
right_face_grad_constraint=None,
106+
)
107+
108+
radial_electric_field = cell_variable.CellVariable(
109+
value=jnp.zeros_like(geo.rho),
110+
dr=geo.drho_norm,
111+
right_face_constraint=0.0,
112+
right_face_grad_constraint=None,
113+
)
114+
98115
core_profiles = state.CoreProfiles(
99116
T_i=T_i,
100117
T_e=T_e,
@@ -121,6 +138,9 @@ def initial_core_profiles(
121138
j_total=jnp.zeros_like(geo.rho, dtype=jax_utils.get_dtype()),
122139
j_total_face=jnp.zeros_like(geo.rho_face, dtype=jax_utils.get_dtype()),
123140
Ip_profile_face=jnp.zeros_like(geo.rho_face, dtype=jax_utils.get_dtype()),
141+
toroidal_velocity=toroidal_velocity,
142+
poloidal_velocity=poloidal_velocity,
143+
radial_electric_field=radial_electric_field,
124144
)
125145

126146
return _init_psi_and_psi_derived(
@@ -434,6 +454,12 @@ def _calculate_all_psi_dependent_profiles(
434454
core_profiles,
435455
)
436456

457+
# TODO(b/456456279): Make sure poloidal_velocity has been updated.
458+
# Calculate radial electric field
459+
radial_electric_field = formulas.calculate_radial_electric_field(
460+
core_profiles, geo
461+
)
462+
437463
# Calculate sources if they have not already been calculated.
438464
if not sources_are_calculated:
439465
source_profiles = _get_bootstrap_and_standard_source_profiles(
@@ -485,6 +511,7 @@ def _calculate_all_psi_dependent_profiles(
485511
psidot=psidot,
486512
sigma=conductivity.sigma,
487513
sigma_face=conductivity.sigma_face,
514+
radial_electric_field=radial_electric_field,
488515
)
489516
return core_profiles
490517

torax/_src/core_profiles/profile_conditions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class RuntimeParams:
5858
# If provided as array, Psi profile defined on the cell grid.
5959
psi: array_typing.FloatVector | None
6060
psidot: array_typing.FloatVector | None
61+
toroidal_velocity: array_typing.FloatVector | None
62+
toroidal_velocity_right_bc: array_typing.FloatScalar | None
6163
# Electron density profile on the cell grid.
6264
n_e: array_typing.FloatVector
6365
nbar: array_typing.FloatScalar
@@ -112,6 +114,11 @@ class ProfileConditions(torax_pydantic.BaseModelFrozen):
112114
`psidot` will be used instead of the internally calculated one. This is
113115
useful for cases where an unphysical transient `psidot` from the initial
114116
`psi` condition needs to be overridden.
117+
toroidal_velocity: Prescribed or evolving values for toroidal velocity. If
118+
None, toroidal_velocity will be initialized to zero.
119+
toroidal_velocity_right_bc: Toroidal velocity boundary condition for
120+
r=a_minor. If this is `None` the boundary condition will instead be taken
121+
from `toroidal_velocity` at rhon=1.
115122
n_e: Prescribed or evolving values for electron density at different times.
116123
normalize_n_e_to_nbar: Whether to renormalize the density profile to have
117124
the desired line averaged density `nbar`.
@@ -163,6 +170,8 @@ class ProfileConditions(torax_pydantic.BaseModelFrozen):
163170
)
164171
psi: torax_pydantic.TimeVaryingArray | None = None
165172
psidot: torax_pydantic.TimeVaryingArray | None = None
173+
toroidal_velocity: torax_pydantic.TimeVaryingArray | None = None
174+
toroidal_velocity_right_bc: torax_pydantic.TimeVaryingScalar | None = None
166175
n_e: torax_pydantic.PositiveTimeVaryingArray = (
167176
torax_pydantic.ValidatedDefault({0: {0: 1.2e20, 1: 0.8e20}})
168177
)
@@ -398,6 +407,14 @@ def build_runtime_params(self, t: chex.Numeric) -> RuntimeParams:
398407
t, grid_type='face_right'
399408
)
400409

410+
if self.toroidal_velocity_right_bc is None:
411+
if self.toroidal_velocity is None:
412+
runtime_params['toroidal_velocity_right_bc'] = 0.0
413+
else:
414+
runtime_params['toroidal_velocity_right_bc'] = (
415+
self.toroidal_velocity.get_value(t, grid_type='face_right')
416+
)
417+
401418
if self.n_e_right_bc is None:
402419
runtime_params['n_e_right_bc'] = self.n_e.get_value(
403420
t, grid_type='face_right'

torax/_src/core_profiles/tests/convertors_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def setUp(self):
8383
j_total=mock.ANY,
8484
j_total_face=mock.ANY,
8585
Ip_profile_face=mock.ANY,
86+
toroidal_velocity=mock.ANY,
87+
poloidal_velocity=mock.ANY,
88+
radial_electric_field=mock.ANY,
8689
)
8790

8891
def test_core_profiles_to_solver_x_tuple(self):

torax/_src/core_profiles/tests/initialization_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,40 @@ def test_update_psi_from_j(self):
8383
).value
8484
np.testing.assert_allclose(psi, references.psi.value)
8585

86+
def test_initial_core_profiles_velocities(self):
87+
config = default_configs.get_default_config_dict()
88+
# Test default initialization (zeros)
89+
torax_config = model_config.ToraxConfig.from_dict(config)
90+
core_profiles, geo, _ = _get_initial_state(torax_config)
91+
np.testing.assert_allclose(
92+
core_profiles.toroidal_velocity.value, np.zeros_like(geo.rho)
93+
)
94+
np.testing.assert_allclose(
95+
core_profiles.poloidal_velocity.value, np.zeros_like(geo.rho)
96+
)
97+
98+
def test_initial_toroidal_velocity_from_profile_conditions(self):
99+
config = default_configs.get_default_config_dict()
100+
# Test initialization with toroidal_velocity from profile_conditions
101+
toroidal_velocity_test = np.array([10.0, 20.0, 30.0, 40.0])
102+
_, geo, _ = _get_initial_state(
103+
model_config.ToraxConfig.from_dict(config)
104+
)
105+
config['profile_conditions']['toroidal_velocity'] = {
106+
0.0: {
107+
rho: value
108+
for rho, value in zip(geo.rho_norm, toroidal_velocity_test)
109+
}
110+
}
111+
torax_config = model_config.ToraxConfig.from_dict(config)
112+
core_profiles, _, _ = _get_initial_state(torax_config)
113+
np.testing.assert_allclose(
114+
core_profiles.toroidal_velocity.value, toroidal_velocity_test
115+
)
116+
np.testing.assert_allclose(
117+
core_profiles.poloidal_velocity.value, np.zeros_like(geo.rho)
118+
)
119+
86120
@parameterized.parameters(
87121
(
88122
{0.0: {0.0: 0.0, 1.0: 1.0}},

torax/_src/core_profiles/updaters.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ def get_prescribed_core_profile_values(
115115
n_i = ions.n_i.value
116116
n_impurity = ions.n_impurity.value
117117
impurity_fractions = ions.impurity_fractions
118+
toroidal_velocity = getters.get_updated_toroidal_velocity(
119+
runtime_params.profile_conditions, geo
120+
).value
118121

119122
return {
120123
'T_i': T_i,
@@ -132,6 +135,7 @@ def get_prescribed_core_profile_values(
132135
'A_impurity_face': ions.A_impurity_face,
133136
'Z_eff': ions.Z_eff,
134137
'Z_eff_face': ions.Z_eff_face,
138+
'toroidal_velocity': toroidal_velocity,
135139
}
136140

137141

@@ -169,7 +173,7 @@ def update_core_profiles_during_step(
169173
updated_core_profiles.T_e,
170174
)
171175

172-
return dataclasses.replace(
176+
updated_core_profiles = dataclasses.replace(
173177
updated_core_profiles,
174178
n_i=ions.n_i,
175179
n_impurity=ions.n_impurity,
@@ -187,6 +191,18 @@ def update_core_profiles_during_step(
187191
s_face=psi_calculations.calc_s_face(geo, updated_core_profiles.psi),
188192
)
189193

194+
# TODO(b/456456279): Make sure poloidal_velocity has been updated.
195+
# Computing the radial electric field requires the updated ions densities.
196+
radial_electric_field = formulas.calculate_radial_electric_field(
197+
updated_core_profiles, geo
198+
)
199+
200+
updated_core_profiles = dataclasses.replace(
201+
updated_core_profiles,
202+
radial_electric_field=radial_electric_field,
203+
)
204+
return updated_core_profiles
205+
190206

191207
def update_core_and_source_profiles_after_step(
192208
dt: array_typing.FloatScalar,
@@ -280,16 +296,28 @@ def update_core_and_source_profiles_after_step(
280296
j_total=j_total,
281297
j_total_face=j_total_face,
282298
Ip_profile_face=Ip_profile_face,
299+
toroidal_velocity=updated_core_profiles_t_plus_dt.toroidal_velocity,
300+
poloidal_velocity=updated_core_profiles_t_plus_dt.poloidal_velocity,
301+
radial_electric_field=(
302+
updated_core_profiles_t_plus_dt.radial_electric_field
303+
), # Not yet updated
283304
)
284305

285306
conductivity = neoclassical_models.conductivity.calculate_conductivity(
286307
geo, intermediate_core_profiles
287308
)
288309

310+
# TODO(b/456456279): Make sure poloidal_velocity has been updated.
311+
# Computing the radial electric field requires the updated ions densities.
312+
radial_electric_field = formulas.calculate_radial_electric_field(
313+
intermediate_core_profiles, geo
314+
)
315+
289316
intermediate_core_profiles = dataclasses.replace(
290317
intermediate_core_profiles,
291318
sigma=conductivity.sigma,
292319
sigma_face=conductivity.sigma_face,
320+
radial_electric_field=radial_electric_field,
293321
)
294322

295323
# build_source_profiles calculates the union with explicit + implicit

torax/_src/neoclassical/pydantic_model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Neoclassical(torax_pydantic.BaseModelFrozen):
4040
transport: (
4141
transport_zeros.ZerosModelConfig | angioni_sauter.AngioniSauterModelConfig
4242
) = pydantic.Field(discriminator="model_name")
43+
compute_poloidal_velocity: bool = False
4344

4445
@pydantic.model_validator(mode="before")
4546
@classmethod
@@ -66,6 +67,11 @@ def build_runtime_params(self) -> runtime_params.RuntimeParams:
6667
)
6768

6869
def build_models(self) -> neoclassical_models.NeoclassicalModels:
70+
if self.compute_poloidal_velocity:
71+
# TODO(b/376326615): Implement poloidal velocity computation.
72+
raise NotImplementedError(
73+
"Computation of poloidal velocity is not yet implemented."
74+
)
6975
return neoclassical_models.NeoclassicalModels(
7076
conductivity=self.conductivity.build_model(),
7177
bootstrap_current=self.bootstrap_current.build_model(),

torax/_src/neoclassical/tests/pydantic_model_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ def f(x: pydantic_model.Neoclassical):
8181
output, pydantic_model.runtime_params.RuntimeParams
8282
)
8383

84+
def test_compute_poloidal_velocity_raises_error(self):
85+
model = pydantic_model.Neoclassical.from_dict(
86+
{"compute_poloidal_velocity": True}
87+
)
88+
with self.assertRaisesRegex(
89+
NotImplementedError,
90+
"Computation of poloidal velocity is not yet implemented",
91+
):
92+
model.build_models()
93+
8494

8595
if __name__ == "__main__":
8696
absltest.main()

0 commit comments

Comments
 (0)