Skip to content

Commit df82fe5

Browse files
jcitrinTorax team
authored andcommitted
Consolidate neoclassical evaluations into single-call NeoclassicalModels.__call__
- Introduce `NeoclassicalOutputs` and `NeoclassicalModels.__call__` to evaluate all four neoclassical sub-models (`conductivity`, `bootstrap_current`, `transport`, `poloidal_velocity`) in a single call. - Pass the resulting precomputed outputs to source and transport coefficient builders instead of calling neoclassical sub-models separately. PiperOrigin-RevId: 986345805
1 parent 7fb2d81 commit df82fe5

44 files changed

Lines changed: 639 additions & 302 deletions

Some content is hidden

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

‎torax/_src/core_profiles/initialization.py‎

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ def initial_core_profiles(
141141
j_total_face=jnp.zeros_like(geo.rho_face, dtype=jax_utils.get_dtype()),
142142
Ip_profile_face=jnp.zeros_like(geo.rho_face, dtype=jax_utils.get_dtype()),
143143
toroidal_angular_velocity=toroidal_angular_velocity,
144+
poloidal_velocity=cell_variable.CellVariable(
145+
value=jnp.zeros_like(geo.rho, dtype=jax_utils.get_dtype()),
146+
face_centers=geo.rho_face_norm,
147+
right_face_constraint=jnp.zeros((), dtype=jax_utils.get_dtype()),
148+
right_face_grad_constraint=None,
149+
),
144150
charge_state_info=ions.charge_state_info,
145151
charge_state_info_face=ions.charge_state_info_face,
146152
)
@@ -481,10 +487,8 @@ def _calculate_all_psi_dependent_profiles(
481487
j_total_face=j_total_face,
482488
Ip_profile_face=Ip_profile_face,
483489
)
484-
# Calculate conductivity once we have a consistent set of core profiles
485-
conductivity = neoclassical_models.conductivity.calculate_conductivity(
486-
geo,
487-
core_profiles,
490+
neoclassical_outputs = neoclassical_models(
491+
runtime_params, geo, core_profiles
488492
)
489493

490494
# Calculate sources if they have not already been calculated.
@@ -493,7 +497,7 @@ def _calculate_all_psi_dependent_profiles(
493497
runtime_params,
494498
geo,
495499
core_profiles,
496-
neoclassical_models,
500+
neoclassical_outputs.bootstrap_current,
497501
source_models,
498502
source_profiles,
499503
)
@@ -513,7 +517,7 @@ def _calculate_all_psi_dependent_profiles(
513517
psi_sources = source_profiles.total_psi_sources(geo)
514518
psidot_value = psi_calculations.calculate_psidot_from_psi_sources(
515519
psi_sources=psi_sources,
516-
sigma=conductivity.sigma,
520+
sigma=neoclassical_outputs.conductivity.sigma,
517521
resistivity_multiplier=runtime_params.numerics.resistivity_multiplier, # pyrefly: ignore[bad-argument-type]
518522
psi=psi,
519523
geo=geo,
@@ -536,8 +540,9 @@ def _calculate_all_psi_dependent_profiles(
536540
core_profiles = dataclasses.replace(
537541
core_profiles,
538542
psidot=psidot,
539-
sigma=conductivity.sigma,
540-
sigma_face=conductivity.sigma_face,
543+
sigma=neoclassical_outputs.conductivity.sigma,
544+
sigma_face=neoclassical_outputs.conductivity.sigma_face,
545+
poloidal_velocity=neoclassical_outputs.poloidal_velocity.v_pol,
541546
)
542547
return core_profiles
543548

@@ -546,11 +551,11 @@ def _get_bootstrap_and_standard_source_profiles(
546551
runtime_params: runtime_params_lib.RuntimeParams,
547552
geo: geometry.Geometry,
548553
core_profiles: state.CoreProfiles,
549-
neoclassical_models: neoclassical_models_lib.NeoclassicalModels,
554+
bootstrap_current: bootstrap_current_base.BootstrapCurrent,
550555
source_models: source_models_lib.SourceModels,
551556
source_profiles: source_profiles_lib.SourceProfiles,
552557
) -> source_profiles_lib.SourceProfiles:
553-
"""Calculates bootstrap current and updates source profiles."""
558+
"""Updates source profiles with standard psi sources and bootstrap current."""
554559
source_profile_builders.build_standard_source_profiles(
555560
runtime_params=runtime_params,
556561
geo=geo,
@@ -560,13 +565,9 @@ def _get_bootstrap_and_standard_source_profiles(
560565
calculate_anyway=True,
561566
calculated_source_profiles=source_profiles,
562567
)
563-
bootstrap_current = (
564-
neoclassical_models.bootstrap_current.calculate_bootstrap_current(
565-
runtime_params, geo, core_profiles
566-
)
567-
)
568568
source_profiles = dataclasses.replace(
569-
source_profiles, bootstrap_current=bootstrap_current
569+
source_profiles,
570+
bootstrap_current=bootstrap_current,
570571
)
571572
return source_profiles
572573

@@ -587,7 +588,9 @@ def _iterate_psi_and_sources(
587588
runtime_params,
588589
geo,
589590
core_profiles,
590-
neoclassical_models,
591+
neoclassical_models(
592+
runtime_params, geo, core_profiles
593+
).bootstrap_current,
591594
source_models,
592595
source_profiles,
593596
)

‎torax/_src/core_profiles/tests/convertors_test.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def setUp(self):
8585
j_total_face=mock.ANY,
8686
Ip_profile_face=mock.ANY,
8787
toroidal_angular_velocity=mock.ANY,
88+
poloidal_velocity=mock.ANY,
8889
charge_state_info=mock.ANY,
8990
charge_state_info_face=mock.ANY,
9091
fast_ions=mock.ANY,

‎torax/_src/core_profiles/tests/initialization_test.py‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -521,16 +521,14 @@ def _get_initial_state(
521521
source_models=source_models,
522522
neoclassical_models=neoclassical_models,
523523
)
524-
conductivity = neoclassical_models.conductivity.calculate_conductivity(
525-
geo, core_profiles
526-
)
524+
neoclassical_outputs = neoclassical_models(runtime_params, geo, core_profiles)
527525
core_sources = source_profile_builders.get_all_source_profiles(
528526
runtime_params=runtime_params,
529527
geo=geo,
530528
core_profiles=core_profiles,
531529
source_models=source_models,
532-
neoclassical_models=neoclassical_models,
533-
conductivity=conductivity,
530+
conductivity=neoclassical_outputs.conductivity,
531+
bootstrap_current=neoclassical_outputs.bootstrap_current,
534532
)
535533
j_toroidal_total = core_profiles.j_total
536534
j_toroidal_total_face = core_profiles.j_total_face

‎torax/_src/core_profiles/updaters.py‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ def update_core_and_source_profiles_after_step(
224224
j_total_face=j_total_face,
225225
Ip_profile_face=Ip_profile_face,
226226
toroidal_angular_velocity=updated_core_profiles_t_plus_dt.toroidal_angular_velocity,
227+
poloidal_velocity=(
228+
core_profiles_t_plus_dt.poloidal_velocity
229+
), # Not yet updated
227230
charge_state_info=ions.charge_state_info,
228231
charge_state_info_face=ions.charge_state_info_face,
229232
fast_ions=core_profiles_t_plus_dt.fast_ions,
@@ -236,14 +239,15 @@ def update_core_and_source_profiles_after_step(
236239
dt,
237240
)
238241

239-
conductivity = neoclassical_models.conductivity.calculate_conductivity(
240-
geo, intermediate_core_profiles
242+
neoclassical_outputs = neoclassical_models(
243+
runtime_params_t_plus_dt, geo, intermediate_core_profiles
241244
)
242245

243246
intermediate_core_profiles = dataclasses.replace(
244247
intermediate_core_profiles,
245-
sigma=conductivity.sigma,
246-
sigma_face=conductivity.sigma_face,
248+
sigma=neoclassical_outputs.conductivity.sigma,
249+
sigma_face=neoclassical_outputs.conductivity.sigma_face,
250+
poloidal_velocity=neoclassical_outputs.poloidal_velocity.v_pol,
247251
internal_plasma_energy=energy_state,
248252
)
249253

@@ -252,11 +256,11 @@ def update_core_and_source_profiles_after_step(
252256
runtime_params=runtime_params_t_plus_dt,
253257
geo=geo,
254258
source_models=source_models,
255-
neoclassical_models=neoclassical_models,
256259
core_profiles=intermediate_core_profiles,
257260
explicit=False,
258261
explicit_source_profiles=explicit_source_profiles,
259-
conductivity=conductivity,
262+
conductivity=neoclassical_outputs.conductivity,
263+
bootstrap_current=neoclassical_outputs.bootstrap_current,
260264
)
261265

262266
intermediate_core_profiles = dataclasses.replace(

‎torax/_src/fvm/calc_coeffs.py‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -231,22 +231,26 @@ def _calc_coeffs_full(
231231

232232
consts = constants.CONSTANTS
233233

234-
conductivity = models.neoclassical_models.conductivity.calculate_conductivity(
235-
geo, core_profiles
234+
neoclassical_outputs = models.neoclassical_models(
235+
runtime_params, geo, core_profiles
236+
)
237+
core_profiles = dataclasses.replace(
238+
core_profiles,
239+
poloidal_velocity=neoclassical_outputs.poloidal_velocity.v_pol,
236240
)
237241

238242
# Calculate the implicit source profiles and combine them with the explicit
239243
# source profiles. These are needed for the pedestal model, so are computed
240244
# here rather than in the source terms section.
241245
merged_source_profiles = source_profile_builders.build_source_profiles(
242246
source_models=models.source_models,
243-
neoclassical_models=models.neoclassical_models,
244247
runtime_params=runtime_params,
245248
geo=geo,
246249
core_profiles=core_profiles,
247250
explicit=False,
248251
explicit_source_profiles=explicit_source_profiles,
249-
conductivity=conductivity,
252+
conductivity=neoclassical_outputs.conductivity,
253+
bootstrap_current=neoclassical_outputs.bootstrap_current,
250254
)
251255

252256
# --- Transient term coefficients --- #
@@ -259,7 +263,7 @@ def _calc_coeffs_full(
259263
1.0
260264
/ runtime_params.numerics.resistivity_multiplier
261265
* geo.rho_norm
262-
* conductivity.sigma
266+
* neoclassical_outputs.conductivity.sigma
263267
* consts.mu_0
264268
* 16
265269
* jnp.pi**2
@@ -320,12 +324,12 @@ def _calc_coeffs_full(
320324
transport_coefficients = (
321325
transport_coefficients_builder.calculate_all_transport_coeffs(
322326
transport_model=models.transport_model,
323-
neoclassical_models=models.neoclassical_models,
324327
internal_boundary_condition_model=models.internal_boundary_condition_model,
325328
runtime_params=runtime_params,
326329
geo=geo,
327330
core_profiles=core_profiles,
328331
pedestal_transition_state=pedestal_transition_state,
332+
neoclassical_transport=neoclassical_outputs.transport,
329333
use_pereverzev=use_pereverzev,
330334
)
331335
)
@@ -469,7 +473,7 @@ def _calc_coeffs_full(
469473
* geo.Phi_b_dot
470474
* geo.Phi_b
471475
* geo.rho_norm**2
472-
* conductivity.sigma
476+
* neoclassical_outputs.conductivity.sigma
473477
/ geo.F**2
474478
* core_profiles.psi.grad()
475479
)

‎torax/_src/fvm/tests/calc_coeffs_test.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ def test_calc_coeffs_smoke_test(
8282
runtime_params=runtime_params,
8383
geo=geo,
8484
core_profiles=core_profiles,
85-
neoclassical_models=models.neoclassical_models,
8685
explicit=True,
8786
)
8887
calc_coeffs.calc_coeffs(

‎torax/_src/fvm/tests/fvm_test.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ def test_nonlinear_solve_block_loss_minimum(
250250
evolving_names = tuple(['T_i'])
251251
explicit_source_profiles = source_profile_builders.build_source_profiles(
252252
source_models=models.source_models,
253-
neoclassical_models=models.neoclassical_models,
254253
runtime_params=runtime_params,
255254
geo=geo,
256255
core_profiles=core_profiles,
@@ -365,7 +364,6 @@ def test_implicit_solve_block_uses_updated_boundary_conditions(self):
365364
geo=geo,
366365
core_profiles=initial_core_profiles,
367366
source_models=models.source_models,
368-
neoclassical_models=models.neoclassical_models,
369367
explicit=True,
370368
)
371369

@@ -488,7 +486,6 @@ def test_theta_residual_uses_updated_boundary_conditions(self):
488486
geo=geo,
489487
core_profiles=initial_core_profiles,
490488
source_models=models.source_models,
491-
neoclassical_models=models.neoclassical_models,
492489
explicit=True,
493490
)
494491

‎torax/_src/neoclassical/formulas/formulas.py‎

Lines changed: 1 addition & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
# limitations under the License.
1414
"""Common formulas used in neoclassical models."""
1515

16-
import jax
1716
import jax.numpy as jnp
1817
from torax._src import array_typing
1918
from torax._src import constants
2019
from torax._src import math_utils
2120
from torax._src.fvm import cell_variable
2221
from torax._src.geometry import geometry as geometry_lib
2322
from torax._src.neoclassical.bootstrap_current import base as bootstrap_current_base
24-
from torax._src.physics import collisions
2523

2624

2725
# pylint: disable=invalid-name
@@ -133,8 +131,7 @@ def calculate_nu_i_star(
133131
)
134132

135133

136-
# Functions to calculate the neoclassical poloidal velocity.
137-
def _calculate_neoclassical_k_neo(
134+
def calculate_neoclassical_k_neo(
138135
nu_star: array_typing.FloatScalar, epsilon: array_typing.FloatScalar
139136
):
140137
"""Calculates the neoclassical coefficient k_neo.
@@ -177,94 +174,6 @@ def _calculate_neoclassical_k_neo(
177174
# See Sauter (1999) Eq. 17a-17b
178175

179176

180-
@jax.jit
181-
def calculate_poloidal_velocity(
182-
T_i: cell_variable.CellVariable,
183-
n_i: array_typing.FloatVectorFace,
184-
q: array_typing.FloatVectorFace,
185-
Z_eff: array_typing.FloatVectorFace,
186-
Z_i: array_typing.FloatVectorFace,
187-
B_tor: array_typing.FloatVectorFace,
188-
B_total_squared: array_typing.FloatVectorFace,
189-
geo: geometry_lib.Geometry,
190-
poloidal_velocity_multiplier: array_typing.FloatScalar = 1.0,
191-
) -> cell_variable.CellVariable:
192-
"""Computes the neoclassical ion poloidal velocity profile.
193-
194-
Implementing eq.33 from
195-
Y. B. Kim , P. H. Diamond , R. J. Groebner.
196-
"Neoclassical poloidal and toroidal rotation in tokamaks"
197-
Phys. Fluids B 3, 2050–2060 (1991)
198-
https://doi.org/10.1063/1.859671
199-
200-
Eq. 33 can be simplified to the following form in SI units:
201-
v_pol = k_neo * (dT/dr) * (B_tor / <B^2>) / (Z * e)
202-
203-
Args:
204-
T_i: Ion temperature as a cell variable [keV].
205-
n_i: Ion density on the face grid [m^-3].
206-
q: Safety factor on the face grid.
207-
Z_eff: Effective charge on the face grid.
208-
Z_i: Main ion charge on the face grid.
209-
B_tor: Toroidal magnetic field on the face grid [T].
210-
B_total_squared: Total magnetic field (toroidal + poloidal) on the face grid
211-
[T].
212-
geo: Geometry
213-
poloidal_velocity_multiplier: A multiplier to apply to the poloidal
214-
velocity.
215-
216-
Returns:
217-
v_pol : Poloidal velocity profile [m/s].
218-
"""
219-
# Note: all computations are performed on the face grid.
220-
221-
T_i_face = T_i.face_value()
222-
epsilon = geo.epsilon_face
223-
224-
# Calculate Neoclassical Coefficient k_i
225-
log_lambda_ii = collisions.calculate_log_lambda_ii(
226-
T_i_face, # pyrefly: ignore[bad-argument-type]
227-
n_i, # pyrefly: ignore[bad-argument-type]
228-
Z_eff, # pyrefly: ignore[bad-argument-type]
229-
)
230-
nu_i_star = calculate_nu_i_star(
231-
q=q,
232-
geo=geo,
233-
n_i=n_i,
234-
T_i=T_i_face, # pyrefly: ignore[bad-argument-type]
235-
Z_eff=Z_eff,
236-
log_lambda_ii=log_lambda_ii,
237-
)
238-
k_neo = _calculate_neoclassical_k_neo(nu_i_star, epsilon)
239-
240-
# Calculate Radial Temperature Gradient (dT/dr)
241-
grad_Ti = (
242-
T_i.face_grad(
243-
x=geo.r_mid, x_left=geo.r_mid_face[0], x_right=geo.r_mid_face[-1]
244-
)
245-
* constants.CONSTANTS.keV_to_J
246-
) # [J/m]
247-
248-
# Calculate Poloidal Velocity
249-
# v_pol = k_i * (dT/dr) * (B_tor / <B^2>) / (Z * e)
250-
B_total_squared_safe = jnp.maximum(B_total_squared, constants.CONSTANTS.eps)
251-
v_pol = (
252-
k_neo
253-
* grad_Ti
254-
* (B_tor / B_total_squared_safe)
255-
/ (constants.CONSTANTS.q_e * Z_i)
256-
)
257-
258-
v_pol = poloidal_velocity_multiplier * v_pol
259-
260-
return cell_variable.CellVariable(
261-
value=geometry_lib.face_to_cell(v_pol),
262-
face_centers=geo.rho_face_norm,
263-
right_face_constraint=v_pol[-1],
264-
right_face_grad_constraint=None,
265-
)
266-
267-
268177
def calculate_analytic_bootstrap_current(
269178
*,
270179
bootstrap_multiplier: float,

0 commit comments

Comments
 (0)