|
26 | 26 |
|
27 | 27 | @chex.dataclass
|
28 | 28 | class RuntimeParams(quasilinear_transport_model.RuntimeParams):
|
29 |
| - """Shared parameters for TGLF-based models.""" |
| 29 | + """Shared parameters for TGLF-based models.""" |
30 | 30 |
|
31 |
| - def make_provider( |
32 |
| - self, torax_mesh: geometry.Grid1D | None = None |
33 |
| - ) -> "RuntimeParamsProvider": |
34 |
| - return RuntimeParamsProvider(**self.get_provider_kwargs(torax_mesh)) |
| 31 | + def make_provider( |
| 32 | + self, torax_mesh: geometry.Grid1D | None = None |
| 33 | + ) -> "RuntimeParamsProvider": |
| 34 | + return RuntimeParamsProvider(**self.get_provider_kwargs(torax_mesh)) |
35 | 35 |
|
36 | 36 |
|
37 | 37 | @chex.dataclass(frozen=True)
|
38 | 38 | class DynamicRuntimeParams(quasilinear_transport_model.DynamicRuntimeParams):
|
39 |
| - """Shared parameters for TGLF-based models.""" |
| 39 | + """Shared parameters for TGLF-based models.""" |
40 | 40 |
|
41 |
| - pass |
| 41 | + pass |
42 | 42 |
|
43 | 43 |
|
44 | 44 | @chex.dataclass
|
45 | 45 | class RuntimeParamsProvider(runtime_params_lib.RuntimeParamsProvider):
|
46 |
| - """Provides a RuntimeParams to use during time t of the sim.""" |
| 46 | + """Provides a RuntimeParams to use during time t of the sim.""" |
47 | 47 |
|
48 |
| - runtime_params_config: RuntimeParams |
| 48 | + runtime_params_config: RuntimeParams |
49 | 49 |
|
50 |
| - def build_dynamic_params(self, t: chex.Numeric) -> DynamicRuntimeParams: |
51 |
| - return DynamicRuntimeParams(**self.get_dynamic_params_kwargs(t)) |
| 50 | + def build_dynamic_params(self, t: chex.Numeric) -> DynamicRuntimeParams: |
| 51 | + return DynamicRuntimeParams(**self.get_dynamic_params_kwargs(t)) |
52 | 52 |
|
53 | 53 |
|
54 | 54 | @chex.dataclass(frozen=True)
|
55 | 55 | class TGLFInputs(quasilinear_transport_model.QuasilinearInputs):
|
56 |
| - r"""Dimensionless inputs to TGLF-based models. |
57 |
| -
|
58 |
| - See https://gafusion.github.io/doc/tglf/tglf_table.html for definitions. |
59 |
| - """ |
60 |
| - |
61 |
| - # Ti/Te |
62 |
| - Ti_over_Te: chex.Array |
63 |
| - # dRmaj/dr |
64 |
| - dRmaj: chex.Array |
65 |
| - # q |
66 |
| - q: chex.Array |
67 |
| - # r/q dq/dr |
68 |
| - s_hat: chex.Array |
69 |
| - # nu_ee (see note in prepare_tglf_inputs) |
70 |
| - nu_ee: chex.Array |
71 |
| - # Elongation, kappa |
72 |
| - kappa: chex.Array |
73 |
| - # Shear in elongation, r/kappa dkappa/dr |
74 |
| - kappa_shear: chex.Array |
75 |
| - # Triangularity, delta |
76 |
| - delta: chex.Array |
77 |
| - # Shear in triangularity, r ddelta/dr |
78 |
| - delta_shear: chex.Array |
79 |
| - # Electron pressure defined w.r.t B_unit |
80 |
| - beta_e: chex.Array |
81 |
| - # Effective charge |
82 |
| - Zeff: chex.Array |
83 |
| - |
84 |
| - |
85 |
| -class TGLFBasedTransportModel(quasilinear_transport_model.QuasilinearTransportModel): |
86 |
| - """Base class for TGLF-based transport models.""" |
87 |
| - |
88 |
| - def _prepare_tglf_inputs( |
89 |
| - Zeff_face: chex.Array, |
90 |
| - q_correction_factor: chex.Numeric, |
91 |
| - geo: geometry.Geometry, |
92 |
| - core_profiles: state.CoreProfiles, |
93 |
| - ) -> TGLFInputs: |
94 |
| - ## Shorthand 'standard' variables |
95 |
| - Te = core_profiles.temp_el |
96 |
| - Ti = core_profiles.temp_ion |
97 |
| - ne = core_profiles.ne * core_profiles.nref |
98 |
| - # q must be recalculated since in the nonlinear solver psi has intermediate |
99 |
| - # states in the iterative solve |
100 |
| - q, _ = physics.calc_q_from_psi( |
101 |
| - geo=geo, |
102 |
| - psi=core_profiles.psi, |
103 |
| - q_correction_factor=q_correction_factor, |
104 |
| - ) |
105 |
| - |
106 |
| - ## Reference values used for TGLF-specific normalisation |
107 |
| - # https://gafusion.github.io/doc/cgyro/outputs.html#output-normalization |
108 |
| - # Note: B_unit = q/r dpsi/dr [SOURCE?] |
109 |
| - # Note: TGLF uses geo.rmid = (Rmax - Rmin)/2 as the radial coordinate |
110 |
| - # This means all gradients are calculated w.r.t. rmid |
111 |
| - vref = (Te.face_value() / (core_profiles.Ai * CONSTANTS.mp)) ** 0.5 |
112 |
| - lref = geo.Rmin[-1] # Minor radius at LCFS |
113 |
| - B_unit = ( |
114 |
| - q / geo.rmid * jnp.gradient(core_profiles.psi, geo.rmid) |
115 |
| - ) |
116 |
| - |
117 |
| - ## Dimensionless gradients, eg lref_over_lti where lref=amin, lti = -ti / (dti/dr) |
118 |
| - normalized_log_gradients = quasilinear_transport_model.NormalizedLogarithmicGradients.from_profiles( |
119 |
| - core_profiles=core_profiles, |
120 |
| - radial_coordinate=geo.rmid, |
121 |
| - reference_length=lref, |
122 |
| - ) |
123 |
| - |
124 |
| - ## Dimensionless temperature ratio |
125 |
| - Ti_over_Te = Ti.face_value() / Te.face_value() |
126 |
| - |
127 |
| - ## Dimensionless electron-electron collision frequency = nu_ee / (vref/lref) |
128 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#xnue |
129 |
| - # https://gafusion.github.io/doc/cgyro/cgyro_list.html#cgyro-nu-ee |
130 |
| - # Note: In the TGLF docs, XNUE is mislabelled as electron-ion collision frequency. |
131 |
| - # It is actually the electron-electron collision frequency, and is defined as in CGYRO |
132 |
| - # See https://pyrokinetics.readthedocs.io/en/latest/user_guide/collisions.html#tglf |
133 |
| - Lambda_ee = physics._calculate_lambda_ee(Te, ne) |
134 |
| - normalised_nu_ee = (4 * jnp.pi * ne * CONSTANTS.qe**4 * Lambda_ee) / ( |
135 |
| - CONSTANTS.me**0.5 * (2 * Te) ** 1.5 |
136 |
| - ) |
137 |
| - nu_ee = normalised_nu_ee / (vref / lref) |
138 |
| - |
139 |
| - ## Safety factor, q |
140 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#q-sa |
141 |
| - # defined before |
142 |
| - |
143 |
| - ## Safety factor shear, s_hat = r/q dq/dr |
144 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-shat-sa |
145 |
| - # Note: calc_s_from_psi_rmid gives rq dq/dr, so we divide by q**2 |
146 |
| - s_hat = physics.calc_s_from_psi_rmid(geo, core_profiles.psi) / q**2 |
147 |
| - |
148 |
| - ## Electron beta |
149 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-betae |
150 |
| - p_e = ne * (Te * 1e3) # ne in m^-3, Te in eV |
151 |
| - beta_e = 8 * jnp.pi * p_e / B_unit**2 |
152 |
| - |
153 |
| - ## Major radius shear = dRmaj/dr |
154 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-drmajdx-loc |
155 |
| - dRmaj = jnp.gradient(geo.Rmaj, geo.rmid) |
156 |
| - |
157 |
| - ## Elongation shear = r/kappa dkappa/dr |
158 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-s-kappa-loc |
159 |
| - kappa = geo.elongation_face |
160 |
| - kappa_shear = geo.rmid_face / kappa * jnp.gradient(kappa, geo.rmid_face) |
161 |
| - |
162 |
| - ## Triangularity shear = r ddelta/dr |
163 |
| - # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-s-delta-loc |
164 |
| - delta = geo.delta_face |
165 |
| - delta_shear = geo.rmid_face * jnp.gradient(delta, geo.rmid_face) |
166 |
| - |
167 |
| - ## Gyrobohm diffusivity |
168 |
| - # https://gafusion.github.io/doc/tglf/tglf_table.html#id7 |
169 |
| - # https://gafusion.github.io/doc/cgyro/outputs.html#output-normalization |
170 |
| - # Note: TGLF uses the same normalisation as CGYRO, ie |
171 |
| - # chiGB = ne * vref * T_e * (rho_s_unit / lref)**2 |
172 |
| - # where rho_s_unit = vref / (e*B_unit/ m_D / c) |
173 |
| - # TODO: Check if this code implementation is correct/equivalent to the above |
174 |
| - chiGB = quasilinear_transport_model.calculate_chiGB( |
175 |
| - reference_temperature=core_profiles.temp_ion.face_value(), |
176 |
| - reference_magnetic_field=geo.B0, |
177 |
| - reference_mass=core_profiles.Ai, |
178 |
| - reference_length=geo.Rmin, |
179 |
| - ) |
180 |
| - # gammaGB = |
181 |
| - |
182 |
| - return TGLFInputs( |
183 |
| - # From QuasilinearInputs |
184 |
| - chiGB=chiGB, |
185 |
| - Rmin=geo.Rmin, |
186 |
| - Rmaj=geo.Rmaj, |
187 |
| - lref_over_lti=normalized_log_gradients.lref_over_lti, |
188 |
| - lref_over_lte=normalized_log_gradients.lref_over_lte, |
189 |
| - lref_over_lne=normalized_log_gradients.lref_over_lne, |
190 |
| - lref_over_lni0=normalized_log_gradients.lref_over_lni0, |
191 |
| - lref_over_lni1=normalized_log_gradients.lref_over_lni1, |
192 |
| - # From TGLFInputs |
193 |
| - Ti_over_Te=Ti_over_Te, |
194 |
| - dRmaj=dRmaj, |
195 |
| - q=q, |
196 |
| - s_hat=s_hat, |
197 |
| - nu_ee=nu_ee, |
198 |
| - kappa=kappa, |
199 |
| - kappa_shear=kappa_shear, |
200 |
| - delta=delta, |
201 |
| - delta_shear=delta_shear, |
202 |
| - beta_e=beta_e, |
203 |
| - Zeff=Zeff_face, |
| 56 | + r"""Dimensionless inputs to TGLF-based models. |
| 57 | +
|
| 58 | + See https://gafusion.github.io/doc/tglf/tglf_table.html for definitions. |
| 59 | + """ |
| 60 | + |
| 61 | + # Ti/Te |
| 62 | + Ti_over_Te: chex.Array |
| 63 | + # dRmaj/dr |
| 64 | + dRmaj: chex.Array |
| 65 | + # q |
| 66 | + q: chex.Array |
| 67 | + # r/q dq/dr |
| 68 | + s_hat: chex.Array |
| 69 | + # nu_ee (see note in prepare_tglf_inputs) |
| 70 | + nu_ee: chex.Array |
| 71 | + # Elongation, kappa |
| 72 | + kappa: chex.Array |
| 73 | + # Shear in elongation, r/kappa dkappa/dr |
| 74 | + kappa_shear: chex.Array |
| 75 | + # Triangularity, delta |
| 76 | + delta: chex.Array |
| 77 | + # Shear in triangularity, r ddelta/dr |
| 78 | + delta_shear: chex.Array |
| 79 | + # Electron pressure defined w.r.t B_unit |
| 80 | + beta_e: chex.Array |
| 81 | + # Effective charge |
| 82 | + Zeff: chex.Array |
| 83 | + |
| 84 | + |
| 85 | +class TGLFBasedTransportModel( |
| 86 | + quasilinear_transport_model.QuasilinearTransportModel |
| 87 | +): |
| 88 | + """Base class for TGLF-based transport models.""" |
| 89 | + |
| 90 | + def _prepare_tglf_inputs( |
| 91 | + Zeff_face: chex.Array, |
| 92 | + q_correction_factor: chex.Numeric, |
| 93 | + geo: geometry.Geometry, |
| 94 | + core_profiles: state.CoreProfiles, |
| 95 | + ) -> TGLFInputs: |
| 96 | + ## Shorthand 'standard' variables |
| 97 | + Te_keV = core_profiles.temp_el.face_value() |
| 98 | + Te_eV = Te_keV * 1e3 |
| 99 | + Te_J = Te_keV * CONSTANTS.keV2J |
| 100 | + Ti_keV = core_profiles.temp_ion.face_value() |
| 101 | + ne = core_profiles.ne * core_profiles.nref |
| 102 | + # q must be recalculated since in the nonlinear solver psi has intermediate |
| 103 | + # states in the iterative solve |
| 104 | + q, _ = physics.calc_q_from_psi( |
| 105 | + geo=geo, |
| 106 | + psi=core_profiles.psi, |
| 107 | + q_correction_factor=q_correction_factor, |
| 108 | + ) |
| 109 | + |
| 110 | + ## Reference values used for TGLF-specific normalisation |
| 111 | + # https://gafusion.github.io/doc/cgyro/outputs.html#output-normalization |
| 112 | + # https://gafusion.github.io/doc/geometry.html#effective-field |
| 113 | + # B_unit = 1/r d(psi_tor)/dr = q/r dpsi/dr |
| 114 | + # Note: TGLF uses geo.rmid = (Rmax - Rmin)/2 as the radial coordinate |
| 115 | + # This means all gradients are calculated w.r.t. rmid |
| 116 | + m_D_amu = 2.014 # Mass of deuterium |
| 117 | + m_D = m_D_amu * CONSTANTS.mp # Mass of deuterium |
| 118 | + c_s = (Te_J / m_D) ** 0.5 |
| 119 | + a = geo.Rmin[-1] # Minor radius at LCFS |
| 120 | + B_unit = q / (geo.rmid) * jnp.gradient(core_profiles.psi, geo.rmid) |
| 121 | + |
| 122 | + ## Dimensionless gradients, eg lref_over_lti where lref=amin, lti = -ti / (dti/dr) |
| 123 | + normalized_log_gradients = quasilinear_transport_model.NormalizedLogarithmicGradients.from_profiles( |
| 124 | + core_profiles=core_profiles, |
| 125 | + radial_coordinate=geo.rmid, |
| 126 | + reference_length=a, |
| 127 | + ) |
| 128 | + |
| 129 | + ## Dimensionless temperature ratio |
| 130 | + Ti_over_Te = Ti_keV / Te_keV |
| 131 | + |
| 132 | + ## Dimensionless electron-electron collision frequency = nu_ee / (c_s/a) |
| 133 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#xnue |
| 134 | + # https://gafusion.github.io/doc/cgyro/cgyro_list.html#cgyro-nu-ee |
| 135 | + # Note: In the TGLF docs, XNUE is mislabelled as electron-ion collision frequency. |
| 136 | + # It is actually the electron-electron collision frequency, and is defined as in CGYRO |
| 137 | + # See https://pyrokinetics.readthedocs.io/en/latest/user_guide/collisions.html#tglf |
| 138 | + # Lambda_ee is computed with keV and m^-3 units |
| 139 | + # normalised_nu_ee is computed with SI units (ie J rather than keV) |
| 140 | + Lambda_ee = physics._calculate_lambda_ee(Te_keV, ne) |
| 141 | + normalised_nu_ee = (4 * jnp.pi * ne * CONSTANTS.qe**4 * Lambda_ee) / ( |
| 142 | + CONSTANTS.me**0.5 * (2 * Te_J) ** 1.5 |
| 143 | + ) |
| 144 | + nu_ee = normalised_nu_ee / (c_s / a) |
| 145 | + |
| 146 | + ## Safety factor, q |
| 147 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#q-sa |
| 148 | + # defined before |
| 149 | + |
| 150 | + ## Safety factor shear, s_hat = r/q dq/dr |
| 151 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-shat-sa |
| 152 | + # Note: calc_s_from_psi_rmid gives rq dq/dr, so we divide by q**2 |
| 153 | + s_hat = physics.calc_s_from_psi_rmid(geo, core_profiles.psi) / q**2 |
| 154 | + |
| 155 | + ## Electron beta |
| 156 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-betae |
| 157 | + # Note: Te in eV |
| 158 | + beta_e = 8 * jnp.pi * ne * Te_eV / B_unit**2 |
| 159 | + |
| 160 | + ## Major radius shear = dRmaj/dr |
| 161 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-drmajdx-loc |
| 162 | + dRmaj = jnp.gradient(geo.Rmaj, geo.rmid) |
| 163 | + |
| 164 | + ## Elongation shear = r/kappa dkappa/dr |
| 165 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-s-kappa-loc |
| 166 | + kappa = geo.elongation_face |
| 167 | + kappa_shear = geo.rmid_face / kappa * jnp.gradient(kappa, geo.rmid_face) |
| 168 | + |
| 169 | + ## Triangularity shear = r ddelta/dr |
| 170 | + # https://gafusion.github.io/doc/tglf/tglf_list.html#tglf-s-delta-loc |
| 171 | + delta = geo.delta_face |
| 172 | + delta_shear = geo.rmid_face * jnp.gradient(delta, geo.rmid_face) |
| 173 | + |
| 174 | + ## Gyrobohm diffusivity |
| 175 | + # https://gafusion.github.io/doc/tglf/tglf_table.html#id7 |
| 176 | + # https://gafusion.github.io/doc/cgyro/outputs.html#output-normalization |
| 177 | + # Note: TGLF uses the same normalisation as CGYRO |
| 178 | + # This has an extra c^2 factor compared to TORAX's calculate_chiGB |
| 179 | + chiGB = ( |
| 180 | + quasilinear_transport_model.calculate_chiGB( |
| 181 | + reference_temperature=Te_keV, # conversion to J done internally |
| 182 | + reference_magnetic_field=B_unit, |
| 183 | + reference_mass=m_D_amu, |
| 184 | + reference_length=a, |
204 | 185 | )
|
| 186 | + * CONSTANTS.c**2 |
| 187 | + ) |
| 188 | + |
| 189 | + return TGLFInputs( |
| 190 | + # From QuasilinearInputs |
| 191 | + chiGB=chiGB, |
| 192 | + Rmin=geo.Rmin, |
| 193 | + Rmaj=geo.Rmaj, |
| 194 | + lref_over_lti=normalized_log_gradients.lref_over_lti, |
| 195 | + lref_over_lte=normalized_log_gradients.lref_over_lte, |
| 196 | + lref_over_lne=normalized_log_gradients.lref_over_lne, |
| 197 | + lref_over_lni0=normalized_log_gradients.lref_over_lni0, |
| 198 | + lref_over_lni1=normalized_log_gradients.lref_over_lni1, |
| 199 | + # From TGLFInputs |
| 200 | + Ti_over_Te=Ti_over_Te, |
| 201 | + dRmaj=dRmaj, |
| 202 | + q=q, |
| 203 | + s_hat=s_hat, |
| 204 | + nu_ee=nu_ee, |
| 205 | + kappa=kappa, |
| 206 | + kappa_shear=kappa_shear, |
| 207 | + delta=delta, |
| 208 | + delta_shear=delta_shear, |
| 209 | + beta_e=beta_e, |
| 210 | + Zeff=Zeff_face, |
| 211 | + ) |
0 commit comments