Vlasiator ebf0dd394 on dev (v5.4.0 + 1054 commits)
Loading...
Searching...
No Matches
sigmaProfiles.cpp
Go to the documentation of this file.
1#include <iostream>
2#include <fstream>
3#include <array>
4#include <string>
5#include <cstring>
6#include <vector>
7#include <cmath>
8#include <errno.h>
9
10using namespace std;
11
12typedef double Real;
13constexpr static int productionNumAccEnergies = 60;
14constexpr static int productionNumTemperatures = 60;
15constexpr static int productionNumParticleEnergies = 100;
16constexpr static Real productionMinAccEnergy = 0.1; // keV
17constexpr static Real productionMaxAccEnergy = 100.; // keV
18constexpr static Real productionMinTemperature = 0.1; // keV
19constexpr static Real productionMaxTemperature = 100.; // keV
20constexpr static int numAtmosphereLevels = 20;
21
22static const Real kB = 1.380649e-23;
23static const Real CHARGE = 1.602176634e-19;
24static const Real MASS_ELECTRON = 9.1093837015e-31;
25static const Real MASS_PROTON = 1.67262158e-27;
26static const Real recombAlpha = 2.4e-13; // m³/s
27
28std::array< Real, productionNumParticleEnergies+1 > particle_energy;
29std::array< Real, productionNumParticleEnergies > differentialFlux;
35 Real depth; // integrated density from the top of the atmosphere
39};
40std::array<AtmosphericLayer, numAtmosphereLevels> atmosphere;
41
42std::array< Real, numAtmosphereLevels > productionTable;
43
44
45// Fractional energy dissipation rate for a isotropic beam, based on Rees (1963), figure 1
47 static const Real P[7] = { -11.639, 32.1133, -30.8543, 14.6063, -6.3375, 0.6138, 1.4946};
48 Real lambda = (((((P[0] * x + P[1])*x +P[2])*x+ P[3])*x + P[4])*x +P[5])* x+P[6];
49 if(x > 1. || lambda < 0) {
50 return 0;
51 }
52 return lambda;
53}
54
62
63
64// Energy dissipasion function based on Sergienko & Ivanov (1993), eq. A2
66
67 const static SergienkoIvanovParameters SIparameters[] = {
68 {50, 0.0409, 1.072, -0.0641, -1.054},
69 {100, 0.0711, 0.899, -0.171, -0.720},
70 {500, 0.130, 0.674, -0.271, -0.319},
71 {1000,0.142, 0.657, -0.277, -0.268}
72 };
73
74 Real C1=0;
75 Real C2=0;
76 Real C3=0;
77 Real C4=0;
78 if(E0 <= SIparameters[0].E) {
79 C1 = SIparameters[0].C1;
80 C2 = SIparameters[0].C2;
81 C3 = SIparameters[0].C3;
82 C4 = SIparameters[0].C4;
83 } else if (E0 >= SIparameters[3].E) {
84 C1 = SIparameters[3].C1;
85 C2 = SIparameters[3].C2;
86 C3 = SIparameters[3].C3;
87 C4 = SIparameters[3].C4;
88 } else {
89 for(int i=0; i<3; i++) {
90 if(SIparameters[i].E < E0 && SIparameters[i+1].E > E0) {
91 Real interp = (E0 - SIparameters[i].E) / (SIparameters[i+1].E - SIparameters[i].E);
92 C1 = (1.-interp) * SIparameters[i].C1 + interp * SIparameters[i+1].C1;
93 C2 = (1.-interp) * SIparameters[i].C2 + interp * SIparameters[i+1].C2;
94 C3 = (1.-interp) * SIparameters[i].C3 + interp * SIparameters[i+1].C3;
95 C4 = (1.-interp) * SIparameters[i].C4 + interp * SIparameters[i+1].C4;
96 }
97 }
98 }
99 return (C2 + C1*Chi)*exp(C4*Chi + C3*Chi*Chi);
100}
101
102
103int main(int argc, char** argv) {
104
105 if(argc < 3) {
106 std::cerr << "Syntax: sigmaProfiles <Density (1/m³)> <Temperature (K)>" << std::endl;
107 return 1;
108 }
109 Real rhon = atof(argv[1]);
110 Real T = atof(argv[2]);
111 std::string filename = "NRLMSIS.dat";
112
113 // -------------- Build atmosphere model --------------
114 // These are the only height values (in km) we are actually interested in
115 static const float alt[numAtmosphereLevels] = {
116 66, 68, 71, 74, 78, 82, 87, 92, 98, 104, 111,
117 118, 126, 134, 143, 152, 162, 172, 183, 194
118 };
119
120 // Open file, read in
121 ifstream in(filename);
122 if(!in) {
123 cerr << "(ionosphere) WARNING: Atmospheric Model file " << filename << " could not be opened: " <<
124 strerror(errno) << endl
125 << "(ionosphere) All atmospheric values will be zero, and there will be no ionization!" << endl;
126 }
127 int altindex = 0;
128 Real integratedDensity = 0;
129 Real prevDensity = 0;
130 Real prevAltitude = 0;
131 std::vector<std::array<Real, 5>> MSISvalues;
132 while(in) {
133 Real altitude, massdensity, Odensity, N2density, O2density, neutralTemperature;
134 in >> altitude >> Odensity >> N2density >> O2density >> massdensity >> neutralTemperature;
135
136 integratedDensity += (altitude - prevAltitude) *1000 * 0.5 * (massdensity + prevDensity);
137 // Ion-neutral scattering frequencies (from Schunk and Nagy, 2009, Table 4.5)
138 Real nui = 1e-17*(3.67*Odensity + 5.14*N2density + 2.59*O2density);
139 // Elctron-neutral scattering frequencies (Same source, Table 4.6)
140 Real nue = 1e-17*(8.9*Odensity + 2.33*N2density + 18.2*O2density);
141 prevAltitude = altitude;
142 prevDensity = massdensity;
143 MSISvalues.push_back({altitude, massdensity, nui, nue, integratedDensity});
144 }
145
146 // Iterate through the read data and linearly interpolate
147 for(unsigned int i=1; i<MSISvalues.size(); i++) {
148 Real altitude = MSISvalues[i][0];
149
150 // When we encounter one of our reference layers, record its values
151 while(altitude >= alt[altindex] && altindex < numAtmosphereLevels) {
152 Real interpolationFactor = (alt[altindex] - MSISvalues[i-1][0]) / (MSISvalues[i][0] - MSISvalues[i-1][0]);
153
154 AtmosphericLayer newLayer;
155 newLayer.altitude = alt[altindex]; // in km
156 newLayer.density = fmax((1.-interpolationFactor) * MSISvalues[i-1][1] + interpolationFactor * MSISvalues[i][1], 0.); // kg/m^3
157 newLayer.depth = fmax((1.-interpolationFactor) * MSISvalues[i-1][4] + interpolationFactor * MSISvalues[i][4], 0.); // kg/m^2
158
159 newLayer.nui = fmax((1.-interpolationFactor) * MSISvalues[i-1][2] + interpolationFactor * MSISvalues[i][2], 0.); // m^-3 s^-1
160 newLayer.nue = fmax((1.-interpolationFactor) * MSISvalues[i-1][3] + interpolationFactor * MSISvalues[i][3], 0.); // m^-3 s^-1
161 atmosphere[altindex++] = newLayer;
162 }
163 }
164
165 // Now we have integrated density from the bottom of the atmosphere in the depth field.
166 // Flip it around.
167 for(int h=0; h<numAtmosphereLevels; h++) {
168 atmosphere[h].depth = integratedDensity - atmosphere[h].depth;
169 }
170
171 // Calculate Hall and Pedersen conductivity coefficient based on charge carrier density
172 const Real Bval = 5e-5; // TODO: Hardcoded B strength here?
173 const Real NO_gyroFreq = CHARGE * Bval / (31*MASS_PROTON); // Ion (NO+) gyration frequency
174 const Real e_gyroFreq = CHARGE * Bval / (MASS_ELECTRON); // Elctron gyration frequency
175 for(int h=0; h<numAtmosphereLevels; h++) {
176 // Vlasiator version
177 Real sigma_i = CHARGE*CHARGE / ((31. * MASS_PROTON) * atmosphere[h].nui);
178 Real sigma_e = CHARGE*CHARGE / (MASS_ELECTRON * atmosphere[h].nue);
179
180 atmosphere[h].pedersencoeff = sigma_i * (atmosphere[h].nui * atmosphere[h].nui)/(atmosphere[h].nui*atmosphere[h].nui + NO_gyroFreq*NO_gyroFreq)
181 + sigma_e *(atmosphere[h].nue * atmosphere[h].nue)/(atmosphere[h].nue*atmosphere[h].nue + e_gyroFreq*e_gyroFreq);
182 atmosphere[h].hallcoeff = -sigma_i * (atmosphere[h].nui * NO_gyroFreq)/(atmosphere[h].nui*atmosphere[h].nui + NO_gyroFreq*NO_gyroFreq)
183 + sigma_e *(atmosphere[h].nue * e_gyroFreq)/(atmosphere[h].nue*atmosphere[h].nue + e_gyroFreq*e_gyroFreq);
184
185 atmosphere[h].parallelcoeff = sigma_e;
186
187
188 // GUMICS version
189 //const double gyro = (1.6e-19*Bval/(31*1.66e-27));
190 //const double rho = atmosphere[h].nui/gyro;
191 //atmosphere[h].pedersencoeff = (1.6e-19/Bval)*(rho/(1+rho*rho));
192 //atmosphere[h].hallcoeff = rho * atmosphere[h].pedersencoeff;
193 }
194
195
196 // Energies of particles that sample the production array
197 // are logspace-distributed from 10^-1 to 10^2.3 keV
198 for(int e=0; e<productionNumParticleEnergies; e++) {
199 particle_energy[e] = pow(10.0, -1.+e*(2.3+1.)/(productionNumParticleEnergies-1));
200 }
202
203
204 // Precalculate scattering rates
205 const Real eps_ion_keV = 0.035; // Energy required to create one ion
206 std::array< std::array< Real, numAtmosphereLevels >, productionNumParticleEnergies > scatteringRate;
207 for(int e=0;e<productionNumParticleEnergies; e++) {
208
209 // From Rees, M. H. (1989), q 3.4.4
210 const Real electronRange = 4.3e-6 + 5.36e-5 * pow(particle_energy[e], 1.67); // kg m^-2
211 // From Rees 1963, eq 2
212 //const Real electronRange = 4.57e-5 * pow(particle_energy[e], 1.75); // kg m^-2
213 // From Sergienko & Ivanov, 1993, eq A3
214 //const Real electronRange = 1.64e-5 * pow(particle_energy[e], 1.67) * (1. + 9.48e-2 * pow(particle_energy[e], -1.57));
215 Real rho_R=0.;
216 // Integrate downwards through the atmosphre to find density at depth=1
217 for(int h=numAtmosphereLevels-1; h>=0; h--) {
218 if(atmosphere[h].depth / electronRange > 1) {
219 rho_R = atmosphere[h].density;
220 break;
221 }
222 }
223 if(rho_R == 0.) {
224 rho_R = atmosphere[0].density;
225 }
226
227 for(int h=0; h<numAtmosphereLevels; h++) {
228 // Rees et al 1963, eq. 1
229 //const Real lambda = ReesIsotropicLambda(atmosphere[h].depth/electronRange);
230 //const Real rate = particle_energy[e] / (electronRange / rho_R) / eps_ion_keV * lambda * atmosphere[h].density / integratedDensity;
231 // Rees 1989, eq. 3.3.7 / 3.3.8
232 const Real lambda = ReesIsotropicLambda(atmosphere[h].depth/electronRange);
233 const Real rate = particle_energy[e] * lambda * atmosphere[h].density / electronRange / eps_ion_keV;
234 // Sergienko & Ivanov 1993, eq A4
235 //const Real lambda = SergienkoIvanovLambda(particle_energy[e]*1000., atmosphere[h].depth/electronRange);
236 //const Real rate = atmosphere[h].density / eps_ion_keV * particle_energy[e] * lambda / electronRange; // TODO: Albedo flux?
237 scatteringRate[e][h] = max(0., rate); // m^-1
238 }
239 }
240
241
242
243 // -------------- Build differential flux --------------
244 Real tempenergy = kB * T / CHARGE / 1000;
245 Real accenergy = productionMinAccEnergy;
246 std::cerr << "# Temperature of " << T << " K == Thermal energy of " << tempenergy << " keV" << std::endl;
247 Real integralFlux = 0;
248
249 for(int p=0; p<productionNumParticleEnergies; p++) {
250 // TODO: Kappa distribution here? Now only going for maxwellian
251 Real energyparam = (particle_energy[p]-accenergy)/tempenergy;
252
253 if(particle_energy[p] > accenergy) {
254 Real deltaE = (particle_energy[p+1] - particle_energy[p])* 1e3*CHARGE; // dE in J
255
256 differentialFlux[p] = sqrt(1. / (2. * M_PI * MASS_ELECTRON))
257 * particle_energy[p] / tempenergy / sqrt(tempenergy * 1e3 *CHARGE)
258 * deltaE * exp(-energyparam);
259 integralFlux += rhon * differentialFlux[p];
260 } else {
261 differentialFlux[p] = 0;
262 }
263 }
264
265 // -------------- Fill production table --------------
266 for(int h=0; h < numAtmosphereLevels; h++) {
267 productionTable[h] = 0;
268 for(int p=0; p<productionNumParticleEnergies; p++) {
269 productionTable[h] += scatteringRate[p][h]*differentialFlux[p];
270 }
271 }
272
273 // Calculate and output electron density and conductivities
274 std::cout << "# Altitude (m)\tn_e (1/m³)\tsigmaP (mho)\tsigmaH (mho)\tsigmaParallel (mho)\tProduction rate (m^-3 s^-1)" << std::endl;
275 std::array<Real, numAtmosphereLevels> electronDensity;
276 Real SigmaH=0;
277 Real SigmaP=0;
278 Real SigmaParallel=0;
279
280 for(int h=1; h < numAtmosphereLevels; h++) {
281 Real qref = rhon*productionTable[h];
282
283 // Get equilibrium electron density
284 electronDensity[h] = sqrt(qref/recombAlpha);
285
286 // Calculate conductivities
287 Real halfdx = 1000 * 0.5 * (atmosphere[h].altitude - atmosphere[h-1].altitude);
288
289 // Gumics-like integration
290 Real halfCH = halfdx * 0.5 * (atmosphere[h-1].hallcoeff + atmosphere[h].hallcoeff);
291 Real halfCP = halfdx * 0.5 * (atmosphere[h-1].pedersencoeff + atmosphere[h].pedersencoeff);
292 Real halfCpara = halfdx * 0.5 * (atmosphere[h-1].parallelcoeff + atmosphere[h].parallelcoeff);
293
294 Real sigmap = (electronDensity[h]+electronDensity[h-1]) * halfCP;
295 Real sigmah = (electronDensity[h]+electronDensity[h-1]) * halfCH;
296 Real sigmaParallel = (electronDensity[h]+electronDensity[h-1]) * halfCpara;
297
298 // Jonas-like integration
299 //Real sigmap = halfdx * 0.5 * (electronDensity.at(h)*atmosphere.at(h).pedersencoeff + electronDensity.at(h-1)*atmosphere.at(h-1).pedersencoeff);
300 //Real sigmah = halfdx * 0.5 * (electronDensity.at(h)*atmosphere.at(h).hallcoeff + electronDensity.at(h-1)*atmosphere.at(h-1).hallcoeff);
301 //Real sigmaParallel = halfdx * 0.5 * (electronDensity.at(h)*atmosphere.at(h).parallelcoeff + electronDensity.at(h-1)*atmosphere.at(h-1).parallelcoeff);
302
303 SigmaP += sigmap;
304 SigmaH += sigmah;
305 SigmaParallel += sigmaParallel;
306
307 std::cout << atmosphere[h].altitude << "\t" << 0.5*(electronDensity[h]+electronDensity[h-1]) << "\t" << sigmap/(2.*halfdx) << "\t" << sigmah/(2.*halfdx) << "\t" << sigmaParallel/(2.*halfdx) << "\t" << qref << std::endl;
308 }
309 std::cerr << std::endl;
310
311 std::cerr << "Integral energy flux: " << integralFlux << " J/m²/s" << std::endl;
312 std::cerr << "Height integrated conductivities:" << std::endl;
313 std::cerr << " SigmaH = " << SigmaH << std::endl;
314 std::cerr << " SigmaP = " << SigmaP << std::endl;
315 std::cerr << " Sigma∥ = " << SigmaParallel << std::endl;
316
317 return 0;
318}
for i
Definition Dispersion.m:24
sqrt(1.0+vA *vA/(c *c))) % Ion-acoustic wave cS
Parameters P
float Real
Definition definitions.h:41
static constexpr int productionNumTemperatures
static constexpr int productionNumParticleEnergies
static constexpr Real productionMinTemperature
static const Real MASS_ELECTRON
static constexpr Real productionMaxAccEnergy
static const Real kB
std::array< Real, productionNumParticleEnergies+1 > particle_energy
static constexpr int productionNumAccEnergies
static constexpr Real productionMaxTemperature
static const Real CHARGE
std::array< Real, productionNumParticleEnergies > differentialFlux
static constexpr Real productionMinAccEnergy
static constexpr int numAtmosphereLevels
static const Real recombAlpha
static Real SergienkoIvanovLambda(Real E0, Real Chi)
std::array< Real, numAtmosphereLevels > productionTable
static Real ReesIsotropicLambda(Real x)
std::array< AtmosphericLayer, numAtmosphereLevels > atmosphere
static const Real MASS_PROTON
int main()
static ARCH_HOSTDEV VecSimple< T > max(VecSimple< T > const &l, VecSimple< T > const &r)