Skip to content

Commit 97eb0b7

Browse files
theo-brownTorax team
authored andcommitted
Add a dynamic pedestal model, adjusting transport in the pedestal region based on distance to the LH transition and the ballooning stability limit.
PiperOrigin-RevId: 868157007
1 parent 51ab828 commit 97eb0b7

16 files changed

Lines changed: 689 additions & 113 deletions

torax/_src/fvm/calc_coeffs.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from torax._src.geometry import geometry
2929
from torax._src.internal_boundary_conditions import internal_boundary_conditions as internal_boundary_conditions_lib
3030
from torax._src.pedestal_model import pedestal_model as pedestal_model_lib
31+
from torax._src.pedestal_model import runtime_params as pedestal_runtime_params_lib
3132
from torax._src.sources import source_profile_builders
3233
from torax._src.sources import source_profiles as source_profiles_lib
3334
import typing_extensions
@@ -344,12 +345,28 @@ def _calc_coeffs_full(
344345
tic_dens_el = geo.vpr
345346

346347
# Diffusion term coefficients
347-
turbulent_transport = physics_models.transport_model(
348-
runtime_params, geo, core_profiles, pedestal_model_output
349-
)
350348
neoclassical_transport = physics_models.neoclassical_models.transport(
351349
runtime_params, geo, core_profiles
352350
)
351+
turbulent_transport = physics_models.transport_model(
352+
runtime_params, geo, core_profiles, pedestal_model_output
353+
)
354+
# Modify the turbulent transport coefficients if the pedestal model is in
355+
# ADAPTIVE_TRANSPORT mode.
356+
if (
357+
runtime_params.pedestal.mode
358+
== pedestal_runtime_params_lib.Mode.ADAPTIVE_TRANSPORT
359+
):
360+
assert isinstance(
361+
pedestal_model_output,
362+
pedestal_model_lib.AdaptiveTransportPedestalModelOutput,
363+
)
364+
turbulent_transport = (
365+
pedestal_model_output.combine_with_turbulent_transport(
366+
turbulent_transport=turbulent_transport,
367+
geo=geo,
368+
)
369+
)
353370

354371
chi_face_ion_total = (
355372
turbulent_transport.chi_face_ion + neoclassical_transport.chi_neo_i
@@ -522,27 +539,33 @@ def _calc_coeffs_full(
522539
)
523540

524541
# Add internal boundary condition source terms
525-
(
526-
source_i,
527-
source_e,
528-
source_n_e,
529-
source_mat_ii,
530-
source_mat_ee,
531-
source_mat_nn,
532-
) = internal_boundary_conditions_lib.apply_adaptive_source(
533-
source_T_i=source_i,
534-
source_T_e=source_e,
535-
source_n_e=source_n_e,
536-
source_mat_ii=source_mat_ii,
537-
source_mat_ee=source_mat_ee,
538-
source_mat_nn=source_mat_nn,
539-
runtime_params=runtime_params,
540-
# Pedestal contributes an internal boundary condition to the source
541-
# terms at the pedestal top.
542-
internal_boundary_conditions=pedestal_model_output.to_internal_boundary_conditions(
543-
geo
544-
),
545-
)
542+
if (
543+
runtime_params.pedestal.mode
544+
== pedestal_runtime_params_lib.Mode.ADAPTIVE_SOURCE
545+
):
546+
assert isinstance(
547+
pedestal_model_output,
548+
pedestal_model_lib.AdaptiveSourcePedestalModelOutput,
549+
)
550+
(
551+
source_i,
552+
source_e,
553+
source_n_e,
554+
source_mat_ii,
555+
source_mat_ee,
556+
source_mat_nn,
557+
) = internal_boundary_conditions_lib.apply_adaptive_source(
558+
source_T_i=source_i,
559+
source_T_e=source_e,
560+
source_n_e=source_n_e,
561+
source_mat_ii=source_mat_ii,
562+
source_mat_ee=source_mat_ee,
563+
source_mat_nn=source_mat_nn,
564+
runtime_params=runtime_params,
565+
internal_boundary_conditions=(
566+
pedestal_model_output.to_internal_boundary_conditions(geo)
567+
),
568+
)
546569

547570
# Build arguments to solver based on which variables are evolving
548571
var_to_toc = {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Copyright 2026 DeepMind Technologies Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""A pedestal that forms dynamically based on the LH threshold and critical ballooning parameter."""
15+
16+
import dataclasses
17+
import jax
18+
from jax import numpy as jnp
19+
from torax._src import array_typing
20+
from torax._src import constants
21+
from torax._src import state
22+
from torax._src.config import runtime_params as runtime_params_lib
23+
from torax._src.geometry import geometry
24+
from torax._src.pedestal_model import pedestal_model
25+
from torax._src.pedestal_model import runtime_params as pedestal_runtime_params_lib
26+
from torax._src.physics import formulas
27+
from torax._src.physics import scaling_laws
28+
29+
# pylint: disable=invalid-name
30+
31+
32+
@jax.tree_util.register_dataclass
33+
@dataclasses.dataclass(frozen=True)
34+
class RuntimeParams(pedestal_runtime_params_lib.RuntimeParams):
35+
"""Runtime params for the DynamicPedestalModel."""
36+
37+
suppression_factor: array_typing.FloatScalar
38+
suppression_rate: array_typing.FloatScalar
39+
augmentation_factor: array_typing.FloatScalar
40+
augmentation_rate: array_typing.FloatScalar
41+
alpha_crit: array_typing.FloatScalar
42+
rho_norm_ped_top: array_typing.FloatScalar
43+
44+
45+
@dataclasses.dataclass(frozen=True, eq=False)
46+
class DynamicPedestal(pedestal_model.PedestalModel):
47+
"""A pedestal that forms dynamically based on the LH threshold and critical ballooning parameter."""
48+
49+
def _call_implementation(
50+
self,
51+
runtime_params: runtime_params_lib.RuntimeParams,
52+
geo: geometry.Geometry,
53+
core_profiles: state.CoreProfiles,
54+
) -> pedestal_model.PedestalModelOutput:
55+
if (
56+
runtime_params.pedestal.mode
57+
!= pedestal_runtime_params_lib.Mode.ADAPTIVE_TRANSPORT
58+
):
59+
raise ValueError('DynamicPedestal only supports ADAPTIVE_TRANSPORT mode.')
60+
61+
pedestal_runtime_params = runtime_params.pedestal
62+
assert isinstance(pedestal_runtime_params, RuntimeParams)
63+
64+
# Get the pedestal top location.
65+
rho_norm_ped_top_idx = jnp.abs(
66+
geo.rho_norm - pedestal_runtime_params.rho_norm_ped_top
67+
).argmin()
68+
rho_norm_ped_top = jax.lax.dynamic_index_in_dim(
69+
geo.rho_norm,
70+
rho_norm_ped_top_idx,
71+
keepdims=False,
72+
)
73+
pedestal_active_mask_face = jnp.where(
74+
geo.rho_face_norm >= rho_norm_ped_top, 1.0, 0.0
75+
)
76+
77+
# Are we above P_LH? If so, decrease chi
78+
_, _, P_LH, _ = scaling_laws.calculate_plh_scaling_factor(
79+
geo, core_profiles
80+
)
81+
# TODO(b/323504363): use the correct source profiles to calculate P_SOL_total.
82+
# dP_e_drho_norm = merged_source_profiles.total_sources('T_e', geo)
83+
# dP_i_drho_norm = merged_source_profiles.total_sources('T_i', geo)
84+
# Integrate over rho_norm to get total power out of the separatrix [W].
85+
# P_SOL_total = math_utils.volume_integration(
86+
# dP_e_drho_norm + dP_i_drho_norm, geo
87+
# )
88+
P_SOL_total = P_LH # TODO(b/323504363): replace with calculated value
89+
# We use a sigmoid function to smooth the transition.
90+
# If P < P_LH, h_mode_weight -> 0, transport_decrease_multiplier -> 1.0.
91+
# If P > P_LH, h_mode_weight -> 1, transport_decrease_multiplier ->
92+
# suppression_factor.
93+
h_mode_weight = jax.nn.sigmoid(
94+
(P_SOL_total - P_LH) / (pedestal_runtime_params.suppression_rate * P_LH)
95+
)
96+
transport_decrease_multiplier = (
97+
1.0 - h_mode_weight
98+
) * 1.0 + h_mode_weight * pedestal_runtime_params.suppression_factor
99+
100+
# Are we above the critical ballooning parameter anywhere in the
101+
# pedestal region? If so, increase chi.
102+
dp_dr_face = formulas.calc_pprime(core_profiles)
103+
alpha_face = jnp.abs(
104+
2
105+
* constants.CONSTANTS.mu_0
106+
* geo.R_major_profile_face
107+
* core_profiles.q_face**2
108+
/ geo.B_0**2
109+
* dp_dr_face
110+
)
111+
max_alpha = jnp.max(pedestal_active_mask_face * alpha_face)
112+
# We use a softplus function to smooth the transition.
113+
# If max_alpha < alpha_crit, continuous_elm_weight -> 0,
114+
# transport_increase_multiplier -> 1.0
115+
# If max_alpha > alpha_crit, continuous_elm_weight -> inf,
116+
# transport_increase_multiplier -> inf.
117+
continuous_elm_weight = jax.nn.softplus(
118+
(max_alpha - pedestal_runtime_params.alpha_crit)
119+
/ (
120+
pedestal_runtime_params.augmentation_rate
121+
* pedestal_runtime_params.alpha_crit
122+
)
123+
)
124+
transport_increase_multiplier = 1.0 + (
125+
continuous_elm_weight * pedestal_runtime_params.augmentation_factor
126+
)
127+
128+
# Combine the multipliers.
129+
transport_multiplier = jnp.exp(
130+
jnp.log(transport_decrease_multiplier)
131+
+ jnp.log(transport_increase_multiplier)
132+
)
133+
134+
# For simplicity, we currently scale all coefficients by the same factor.
135+
return pedestal_model.AdaptiveTransportPedestalModelOutput(
136+
rho_norm_ped_top=rho_norm_ped_top,
137+
rho_norm_ped_top_idx=rho_norm_ped_top_idx,
138+
chi_e_multiplier=transport_multiplier,
139+
chi_i_multiplier=transport_multiplier,
140+
D_e_multiplier=transport_multiplier,
141+
v_e_multiplier=transport_multiplier,
142+
)

torax/_src/pedestal_model/no_pedestal.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from torax._src.config import runtime_params as runtime_params_lib
1919
from torax._src.geometry import geometry
2020
from torax._src.pedestal_model import pedestal_model
21+
from torax._src.pedestal_model import runtime_params as pedestal_runtime_params_lib
2122

2223

2324
@dataclasses.dataclass(frozen=True, eq=False)
@@ -37,10 +38,30 @@ def _call_implementation(
3738
geo: geometry.Geometry,
3839
core_profiles: state.CoreProfiles,
3940
) -> pedestal_model.PedestalModelOutput:
40-
return pedestal_model.PedestalModelOutput(
41-
rho_norm_ped_top=jnp.inf,
42-
T_i_ped=0.0,
43-
T_e_ped=0.0,
44-
n_e_ped=0.0,
45-
rho_norm_ped_top_idx=geo.torax_mesh.nx,
46-
)
41+
if (
42+
runtime_params.pedestal.mode
43+
== pedestal_runtime_params_lib.Mode.ADAPTIVE_SOURCE
44+
):
45+
return pedestal_model.AdaptiveSourcePedestalModelOutput(
46+
rho_norm_ped_top=jnp.inf,
47+
T_i_ped=0.0,
48+
T_e_ped=0.0,
49+
n_e_ped=0.0,
50+
rho_norm_ped_top_idx=geo.torax_mesh.nx,
51+
)
52+
elif (
53+
runtime_params.pedestal.mode
54+
== pedestal_runtime_params_lib.Mode.ADAPTIVE_TRANSPORT
55+
):
56+
return pedestal_model.AdaptiveTransportPedestalModelOutput(
57+
rho_norm_ped_top=jnp.inf,
58+
rho_norm_ped_top_idx=geo.torax_mesh.nx,
59+
chi_e_multiplier=1.0,
60+
chi_i_multiplier=1.0,
61+
D_e_multiplier=1.0,
62+
v_e_multiplier=1.0,
63+
)
64+
else:
65+
raise ValueError(
66+
f'Unsupported pedestal model mode: {runtime_params.pedestal.mode}'
67+
)

0 commit comments

Comments
 (0)