Coverage for tcvx21/grillix_post/components/normalisation_m.py: 82%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Normalisation parameters, used for converting to SI units.
4Based on the pint unit-handling library.
6The typical usage is as follows;
71. Use read_normalisation_file to read a formatted fortran namelist, typically called 'physical_parameters.nml'
8 and return a dictionary called 'normalisation'
92. Pass the 'normalisation' dictionary to dependent paramters
10 i.e. rho_s0(**normalisation)
11 which converts the dictionary into keyword arguments
13Alternatively, one can manually generate the normalisation dictionary
14"""
15from pathlib import Path
16import numpy as np
17import xarray as xr
18from tcvx21.units_m import Quantity as Q_, unit_registry as ureg
19from .namelist_reader_m import read_fortran_namelist
22class Normalisation:
23 """
24 dict-like class for converting from dimensionless arrays to SI units
25 """
27 @classmethod
28 def initialise_from_normalisation_file(cls, normalisation_filepath: Path):
29 """
30 Reads from the normalisation namelist, and returns a dictionary of quantities
31 """
33 experiment_parameters = dict(read_fortran_namelist(normalisation_filepath)[
34 'physical_parameters'])
36 physical_parameters = {}
38 for key in ['b0', 'te0', 'ti0', 'n0', 'r0', 'mi', 'z', 'z_eff']:
39 assert key in experiment_parameters
41 # Magnetic field normalisation, usually taken on axis, in Tesla
42 physical_parameters['B0'] = Q_(experiment_parameters['b0'], 'tesla')
43 # Electron temperature normalisation, in electron-volts
44 physical_parameters['Te0'] = Q_(
45 experiment_parameters['te0'],
46 'electron_volt')
47 # Ion temperature normalisation, in electron-volts
48 physical_parameters['Ti0'] = Q_(
49 experiment_parameters['ti0'],
50 'electron_volt')
51 # Density normalisation, in particles-per-cubic-metres
52 physical_parameters['n0'] = Q_(experiment_parameters['n0'], 'metre**-3')
53 # Major radius, in metres (n.b. this is also the scale length for parallel
54 # quantities)
55 physical_parameters['R0'] = Q_(experiment_parameters['r0'], 'metre')
56 # Ion mass, in amu
57 physical_parameters['Mi'] = Q_(
58 experiment_parameters['mi'],
59 'proton_mass')
60 # Ion charge
61 physical_parameters['Z'] = experiment_parameters['z']
62 # Ion effective charge
63 physical_parameters['Z_eff'] = experiment_parameters['z_eff']
65 return cls(physical_parameters)
67 def __init__(self, normalisation_dictionary: dict):
68 """
69 Each element of the input normalisation dictionary is copied as an attribute of the object.
71 i.e. If you define
72 norm = Normalisation({'a':1, 'b':2})
73 then
74 norm.a == 1 and norm.b == 2
75 """
76 self.B0 = np.nan
77 self.Te0 = np.nan
78 self.Ti0 = np.nan
79 self.n0 = np.nan
80 self.R0 = np.nan
81 self.Mi = np.nan
82 self.Z = np.nan
83 self.Z_eff = np.nan
84 self.__dict__ = normalisation_dictionary
86 def __repr__(self):
87 """
88 Returns a string representation for each element of the namelist
89 """
90 string = f"\t{'Value':<30}\t{'Magnitude':<10}\t{'Units':<20}\n"
92 for parameter_name in sorted(dir(self), key=str.casefold):
93 parameter = getattr(self, parameter_name, None)
94 if isinstance(parameter, Q_):
95 magnitude = parameter.magnitude
96 units = str(parameter.units)
98 string += f"\t{parameter_name:<30}\t{magnitude:<10.4e}\t{units:<30}\n"
100 return string
102 def __getitem__(self, key):
103 """
104 Allows normalisation to be indexed like a dictionary
105 """
106 return getattr(self, key)
108 def convert_norm(self, input_array: xr.DataArray):
109 """
110 Converts the 'norm' attribute of a xarray from a string to a Quantity
111 """
112 assert isinstance(input_array, xr.DataArray), "convert_norm requires an xr.DataArray input"
113 assert 'norm' in input_array.attrs, "convert_norm requires that the DataArray has an attribute 'norm'"
115 if isinstance(input_array.norm, str):
116 input_array.attrs['norm'] = getattr(self, input_array.norm)
118 elif not isinstance(input_array.norm, Q_):
119 raise NotImplementedError(f"Error: norm must be string or Quantity, but type was {type(input_array.norm)}")
121 return input_array
123 def as_dict(self):
124 return_dict = {}
125 for parameter_name in sorted(dir(self), key=str.casefold):
126 parameter = getattr(self, parameter_name, None)
127 if isinstance(parameter, Q_):
128 return_dict[parameter_name] = parameter
130 return return_dict
132 elementary_charge = Q_(1, 'elementary_charge')
133 electron_mass = Q_(1, 'electron_mass')
134 proton_mass = Q_(1, 'proton_mass')
135 atomic_mass_units = Q_(1, 'amu')
136 speed_of_light = Q_(1, 'speed_of_light')
137 vacuum_permeability = Q_(1, 'mu0')
138 vacuum_permittivity = Q_(1, 'vacuum_permittivity')
140 @property
141 def rho_s0(self):
142 """Ion Larmor Radius [m] (n.b. this is also the scale length for perpendicular quantities)"""
143 return (np.sqrt(self.Ti0 * self.Mi) / (self.elementary_charge * self.B0)).to_base_units()
145 @property
146 def c_s0(self):
147 """Sound speed [m/s]"""
148 return (np.sqrt(self.Te0 / self.Mi)).to_base_units()
150 @property
151 def v_A0(self):
152 """Alfven speed [m/s]"""
153 return (self.B0 / np.sqrt(self.vacuum_permeability * self.n0 * self.Mi)).to_base_units()
155 @property
156 def tau_0(self):
157 """Time normalisation [s]"""
158 return (self.R0 / self.c_s0).to('s')
160 @staticmethod
161 @ureg.wraps(ret="", args=['cm**-3', 'eV'], strict=True)
162 def Coulomb_logarithm_ee(n, Te):
163 """
164 Coulomb logarithm (unitless) for thermal electron-electron collisions
166 You can pass n and Te in any units, and these will be converted to
167 cgs+eV such that the following formula applies
168 """
169 if Te > 10:
170 return 24. - np.log(np.sqrt(n) / Te)
171 else:
172 return 23. - np.log(np.sqrt(n) / Te**(1.5))
175 @property
176 def lnLambda0(self):
177 """
178 Thermal electron-electron Coulomb logarithm for reference parameters.
180 Note that we usually the Coulomb logarithm to be constant to ensure that the
181 collision operator is bilinear
182 """
183 return self.Coulomb_logarithm_ee(self.n0, self.Te0)
186 def tau_ee(self, n, Te, Z):
187 """
188 Electron-electron collision time
190 This is the mean time required for the direction of motion of an individual electron
191 to be deflected by approximately 90 degrees due to collisions with electrons
193 Using 3.182 from Fitzpatrick (in SI units), but taking the parametric dependency
194 from equation 2.5e from Braginskii (n = n_e = Z n_i, so n_i = n/Z.
195 Therefore Z**2 * n_i = Z n) -- since Braginskii is c.g.s. and conversion to s.i. is a pain
196 """
197 lnLambda = self.Coulomb_logarithm_ee(n, Te)
199 return ((6. * np.sqrt(2.) * np.pi**1.5 * self.vacuum_permittivity**2 * np.sqrt(self.electron_mass) * Te**1.5)/
200 (lnLambda * self.elementary_charge**4 * Z * n)
201 ).to('s')
204 def tau_ii(self, n, Te, Ti, Mi, Z):
205 """
206 Ion-ion collision time
208 This is the mean time required for the direction of motion of an individual ion
209 to be deflected by approximately 90 degrees due to collisions with ions
211 Using 3.184 from Fitzpatrick (in SI units), but taking the parametric dependency from
212 equation 2.5i from Braginskii (n = n_e = Z n_i, so n_i = n/Z.
213 Therefore Z**4 * n_i = Z**3 n)
214 """
215 lnLambda = self.Coulomb_logarithm_ee(n, Te)
217 return ((12. * np.pi**1.5 * self.vacuum_permittivity**2 * np.sqrt(Mi) * Ti**1.5)/
218 (lnLambda * self.elementary_charge**4 * Z**3 * n)
219 ).to('s')
222 @property
223 def tau_e(self):
224 """
225 Reference electron-electron collision time, calculated with the effective charge Z_eff
226 """
227 return self.tau_ee(self.n0, self.Te0, self.Z_eff)
230 @property
231 def tau_i(self):
232 """
233 Reference ion-ion collision time
234 """
235 return self.tau_ii(self.n0, self.Te0, self.Ti0, self.Mi, self.Z)
238 @property
239 def delta(self):
240 """"
241 Ratio of major radius to ion larmor radius.
242 Conversion from perpendicular length scale to parallel length scale (>>1)
243 """
244 return self.R0 / self.rho_s0
246 @property
247 def rhostar(self):
248 """
249 Ratio of ion larmor radius to major radius (inverse delta)
250 Conversion from parallel length scale to perpendicular length scale (<<1)
251 """
252 return self.rho_s0 / self.R0
254 @property
255 def beta_0(self):
256 """Electron dynamical beta"""
257 return self.c_s0 ** 2 / (self.v_A0 ** 2)
259 @property
260 def mu(self):
261 """Electron to ion mass ratio"""
262 return (self.electron_mass / self.Mi).to('')
264 @property
265 def zeta(self):
266 """Ti0/Te0: Ion to electron temperature ratio"""
267 return self.Ti0 / self.Te0
269 @property
270 def tau_e_norm(self):
271 """Normalised electron collision time"""
272 return self.tau_e * self.c_s0 / self.R0
274 @property
275 def tau_i_norm(self):
276 """Normalised ion collision time"""
277 return self.tau_i * self.c_s0 / self.R0
279 @property
280 def nu_e0(self):
281 """Normalised electron collision frequency"""
282 return self.tau_e_norm ** -1
284 @property
285 def nu_i0(self):
286 """Normalised ion collision frequency"""
287 return self.tau_i_norm ** -1
289 @property
290 def chipar_e(self):
291 """Parallel electron heat conductivity"""
292 return 3.16 * self.tau_e_norm * self.mu**-1
294 @property
295 def chipar_i(self):
296 """Parallel ion heat conductivity"""
297 return 3.90 * self.zeta * self.tau_i_norm
299 @property
300 def etapar(self):
301 """Parallel resistivity"""
302 return 0.51 * self.nu_e0 * self.mu
304 @property
305 def ion_viscosity(self):
306 """Ion viscosity coefficient"""
307 return 0.96 * self.tau_i_norm