47#include <unsupported/Eigen/SparseExtra>
51#define Vec3d Eigen::Vector3d
52#define cross_product(av, bv) (av).cross(bv)
53#define dot_product(av, bv) (av).dot(bv)
54#define vector_length(v) (v).norm()
55#define normalize_vector(v) (v).normalized()
58#ifndef DEBUG_IONOSPHERE
59#define DEBUG_IONOSPHERE
62#ifdef DEBUG_SYSBOUNDARY
63#ifndef DEBUG_IONOSPHERE
64#define DEBUG_IONOSPHERE
107 if (
nodes.size() == 0) {
117 for (uint n = 0; n <
nodes.size(); n++) {
118 if (
nodes[n].x[2] > 0) {
127 northSum /= northNum;
128 southSum /= southNum;
130 for (uint n = 0; n <
nodes.size(); n++) {
131 if (
nodes[n].x[2] > 0) {
142 Real L =
sqrt(n.x[0] * n.x[0] + n.x[1] * n.x[1] + n.x[2] * n.x[2]);
143 for (
int c = 0;
c < 3;
c++) {
153 for (uint n = 0; n <
nodes.size(); n++) {
154 nodes[n].numTouchingElements = 0;
156 for (uint e = 0; e <
elements.size(); e++) {
157 for (
int c = 0;
c < 3;
c++) {
159 nodes[n].touchingElements[
nodes[n].numTouchingElements++] = e;
169 const static std::array<uint32_t, 3> seedElements[4] = {
170 {1,2,3}, {1,3,4}, {1,4,2}, {2,4,3}
172 const static std::array<Real, 3> nodeCoords[4] = {
174 { 0., 1.63299, -0.57735},
175 {-1.41421,-0.816497,-0.57735},
176 { 1.41421,-0.816497,-0.57735}
182 for (
const auto& coords : nodeCoords) {
186 nodes.push_back(newNode);
190 for (
const auto& seed : seedElements) {
203 const static std::array<uint32_t, 3> seedElements[8] = {
204 {0,1,2}, {0,2,3}, {0,3,4}, {0,4,1},
205 {5,2,1}, {5,3,2}, {5,4,3}, {5,1,4},
207 const static std::array<Real, 3> nodeCoords[6] = {
219 for (
const auto& coords : nodeCoords) {
223 nodes.push_back(newNode);
227 for (
const auto& seed : seedElements) {
240 const static std::array<uint32_t, 3> seedElements[20] = {
241 { 0, 2, 1}, { 0, 3, 2}, { 0, 4, 3}, { 0, 5, 4},
242 { 0, 1, 5}, { 1, 2, 6}, { 2, 3, 7}, { 3, 4, 8},
243 { 4, 5, 9}, { 5, 1,10}, { 6, 2, 7}, { 7, 3, 8},
244 { 8, 4, 9}, { 9, 5,10}, {10, 1, 6}, { 6, 7,11},
245 { 7, 8,11}, { 8, 9,11}, { 9,10,11}, {10, 6,11}
247 const static std::array<Real, 3> nodeCoords[12] = {
248 { 0., 0., 1.17557}, { 1.05146, 0., 0.525731},
249 { 0.32492, 1.0, 0.525731}, {-0.850651, 0.618034, 0.525731},
250 {-0.850651,-0.618034, 0.525731}, { 0.32492, -1.0, 0.525731},
251 { 0.850651, 0.618034,-0.525731}, {-0.32492, 1.0, -0.525731},
252 {-1.051460, 0. ,-0.525731}, {-0.32492, -1.0, -0.525731},
253 { 0.850651,-0.618034,-0.525731}, { 0. , 0., -1.17557}
259 for (
const auto& coords : nodeCoords) {
263 nodes.push_back(newNode);
267 for (
const auto& seed : seedElements) {
281 phiprof::Timer timer{
"ionosphere-sphericalFibonacci"};
283 const Real Phi = (
sqrt(5) + 1.) / 2.;
285 auto madfrac = [](
Real a,
Real b) ->
float {
return a * b -
floor(a * b); };
288 auto SF = [madfrac, Phi](
int i,
int n) ->
Vec3d {
289 Real phi = 2 * M_PI * madfrac(
i, Phi - 1.);
290 Real z = 1. - (2. *
i + 1.) / n;
292 return {cos(phi) * sinTheta, sin(phi) * sinTheta, z};
297 auto SFDelaunayAdjacency = [SF, Phi](
int j,
int n) -> std::vector<int> {
298 Real cosTheta = 1. - (2. *
j + 1.) / n;
299 Real z =
max(0., round(0.5 * log(n * M_PI *
sqrt(5) * (1. - cosTheta * cosTheta)) / log(Phi)));
301 Vec3d nearestSample = SF(
j, n);
302 std::vector<int> nearestSamples;
306 for (
int i = 0;
i < 12;
i++) {
308 int c = 5 -
abs(5 - r * 2) +
floor((
int)r / 3);
309 int k =
j + (
i < 6 ? +1 : -1) * (
int)round(pow(Phi, z +
c - 2) /
sqrt(5.));
311 Vec3d currentSample = SF(
k, n);
312 Vec3d nearestToCurrentSample = currentSample - nearestSample;
313 Real squaredDistance =
dot_product(nearestToCurrentSample, nearestToCurrentSample);
320 nearestSamples.push_back(
k);
324 std::vector<int> adjacentVertices;
325 for (
int i = 0;
i < (int)nearestSamples.size();
i++) {
326 int k = nearestSamples[
i];
327 int kPrevious = nearestSamples[(
i + nearestSamples.size() - 1) % nearestSamples.size()];
328 int kNext = nearestSamples[(
i + 1) % nearestSamples.size()];
330 Vec3d currentSample = SF(
k, n);
331 Vec3d previousSample = SF(kPrevious, n);
332 Vec3d nextSample = SF(kNext, n);
334 if (
dot_product(previousSample - nextSample, previousSample - nextSample) >
dot_product(currentSample - nearestSample, currentSample - nearestSample)) {
335 adjacentVertices.push_back(nearestSamples[
i]);
341 adjacentVertices.pop_back();
344 return adjacentVertices;
348 for (
int i = 0;
i < n;
i++) {
352 newNode.
x = {pos[0], pos[1], pos[2]};
355 nodes.push_back(newNode);
359 for (
int i = 0;
i < n;
i++) {
360 std::vector<int> neighbours = SFDelaunayAdjacency(
i, n);
363 for (uint
j = 0;
j < neighbours.size();
j++) {
364 if (neighbours[
j] >
i && neighbours[(
j + 1) % neighbours.size()] >
i) {
367 newElement.
corners = {(uint)
i, (uint)neighbours[
j], (uint)neighbours[(
j + 1) % neighbours.size()]};
381 filesystem::path path = pathString;
383 fi.open(pathString.c_str());
385 cerr <<
"(IONOSPHERE) Could not open file: " << pathString << endl;
389 if (path.extension() ==
".obj") {
390 while (getline(fi, line)) {
392 if (!(line.rfind(
"v\t", 0) == 0 || line.rfind(
"v ", 0) == 0 || line.rfind(
"f", 0) == 0)) {
397 while (line.rfind(
"v ", 0) == 0) {
398 istringstream ss(line.substr(1));
399 double num1, num2, num3;
400 if (!(ss >> num1 >> num2 >> num3)) {
401 cerr <<
"(IONOSPHERE) Error reading vertex information of line \"" << line <<
"\" in " << pathString << endl;
405 newNode.
x = {num1, num2, num3};
407 nodes.push_back(newNode);
413 while (line.rfind(
"f", 0) == 0) {
414 istringstream ss(line.substr(1));
416 std::vector<int> vertexIndices;
418 while (ss >> faceArg) {
419 istringstream fss(faceArg);
422 cerr <<
"(IONOSPHERE) Error reading face information of line \"" << line <<
"\" in " << pathString << endl;
431 if (v < 0 || v >=
length) {
432 cerr <<
"(IONOSPHERE) Invalid vertex index (" << v <<
") specified in \"" << line <<
"\" in " << pathString << endl;
435 vertexIndices.push_back(v);
437 if (vertexIndices.size() != 3) {
438 cerr <<
"(IONOSPHERE) Too many vertex indices (" << vertexIndices.size() <<
") specified in \"" << line <<
"\" in " << pathString <<
" (Only triangulated meshes are supported)" << endl;
442 newElement.
corners = std::array<uint32_t, 3>{(uint32_t)vertexIndices[0], (uint32_t)vertexIndices[1], (uint32_t)vertexIndices[2]};
448 if (
nodes.size() == 0) {
449 cerr <<
"(IONOSPHERE) Error reading nodes in \"" << pathString <<
"\", expected a non-zero number of nodes to be specified." << endl;
454 cerr <<
"(IONOSPHERE) Error reading faces in \"" << pathString <<
"\", expected a non-zero number of faces to be specified." << endl;
457 }
else if (path.extension() ==
".vtk") {
458 if (!getline(fi, line)) {
459 cerr <<
"(IONOSPHERE) Error reading version string in " << pathString << endl;
462 if (!(line.rfind(
"# vtk DataFile Version ", 0) == 0)) {
463 cerr <<
"(IONOSPHERE) Expected mandatory VTK version string, obtained \"" << line <<
"\" in " << pathString << endl;
466 float version = stof(line.substr(23));
468 cerr <<
"(IONOSPHERE) VTK version unsupported, expected legacy version less than 4.2, instead obtained " << version <<
" in " << pathString << endl;
471 if (!getline(fi, line)) {
472 cerr <<
"(IONOSPHERE) Error reading mandatory description string in " << pathString << endl;
475 if (!getline(fi, line)) {
476 cerr <<
"(IONOSPHERE) Error reading mandatory data type string in " << pathString <<
", ASCII or BINARY data not specified." << endl;
479 if (line !=
"ASCII") {
480 cerr <<
"(IONOSPHERE) Only ASCII VTK data is supported, obtained " << line << endl;
484 if (getline(fi, line)) {
485 stringstream ss(line);
488 if (ss >> dataset >> data) {
489 if (dataset !=
"DATASET" || data !=
"UNSTRUCTURED_GRID") {
490 cerr <<
"(IONOSPHERE) Could not find DATASET specification in " << pathString << endl;
495 cerr <<
"(IONOSPHERE) Error reading mandatory DATASET string in " << pathString << endl;
499 if (getline(fi, line)) {
500 std::vector<Real> coords;
501 stringstream pss(line);
506 if (!(pss >> points >> size >> type)) {
507 cerr <<
"(IONOSPHERE) Could not read POINTS field \"" << line <<
"\"" <<
" in " << pathString << endl;
511 if (!(points ==
"POINTS")) {
512 cerr <<
"(IONOSPHERE) Mandatory POINTS field not found, obtained " << line <<
"\" in " << pathString << endl;
516 if (type !=
"float" && type !=
"double") {
517 cerr <<
"(IONOSPHERE) Only float or double are supported, obtained \"" << type <<
"\" in " << pathString << endl;
521 while (getline(fi, line) && all_of(line.begin(), line.end(), [](
char c) { return c ==
'e' || c ==
'E' || c ==
'+' || c ==
'.' || c ==
' ' || c ==
'-' || isdigit(c); })) {
522 stringstream css(line);
529 if (coords.size() != size * 3) {
530 cerr <<
"(IONOSPHERE) Number of coordinates in POINTS field (" << size * 3 <<
") does not match number of coordinates found (" << coords.size() <<
") in " << pathString << endl;
534 for (
unsigned int i = 0;
i < coords.size();
i += 3) {
536 newNode.
x = {coords[
i], coords[
i + 1], coords[
i + 2]};
538 nodes.push_back(newNode);
542 cerr <<
"(IONOSPHERE) Could not read POINTS field in " << pathString << endl;
547 stringstream css(line);
549 unsigned int cellNum;
552 if (!(css >> cells >> cellNum >> size)) {
553 cerr <<
"(IONOSPHERE) Could not read CELLS field \"" << line <<
"\"" <<
" in " << pathString << endl;
557 if (!(cells ==
"CELLS")) {
558 cerr <<
"(IONOSPHERE) Mandatory CELLS field not found, obtained " << line <<
"\" in " << pathString << endl;
562 if (!(cellNum * 4 == size)) {
563 cerr <<
"(IONOSPHERE) Incorrect number of entries for the corresponding number of cells, obtained " << line <<
"\" in " << pathString << endl;
567 while (getline(fi, line) && all_of(line.begin(), line.end(), [](
char c) { return c ==
' ' || isdigit(c); })) {
568 stringstream css(line);
569 unsigned int t, a, b,
c;
570 while (css >> t >> a >> b >>
c) {
572 cerr <<
"(IONOSPHERE) Non-triangular cell encountered, \"" << line <<
"\" in " << pathString << endl;
575 for (
int v : {a, b,
c}) {
576 if (v < 0 || (
unsigned int)v >=
nodes.size()) {
577 cerr <<
"(IONOSPHERE) Error vertex number out of bounds, " << v <<
" in \"" << line <<
"\" in " << pathString << endl;
588 cerr <<
"(IONOSPHERE) Number of cells does not match file, expected " << cellNum <<
", obtained " <<
elements.size() <<
" in " << pathString << endl;
592 cerr <<
"(IONOSPHERE) Could not read CELLS field \"" << line <<
"\"" <<
" in " << pathString << endl;
597 cerr <<
"(IONOSPHERE) Unknown ionosphere grid mesh file format " << path.extension() << endl;
643 Real L =
sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
644 for (
int c = 0;
c < 3;
c++) {
649 uint32_t nextNode = 0;
660 std::array<Real, 3> deltaX({x[0] -
nodes[node].x[0], x[1] -
nodes[node].x[1], x[2] -
nodes[node].x[2]});
661 Real minDist =
sqrt(deltaX[0] * deltaX[0] + deltaX[1] * deltaX[1] + deltaX[2] * deltaX[2]);
664 for (uint
i = 0;
i <
nodes[node].numTouchingElements;
i++) {
665 for (
int j = 0;
j < 3;
j++) {
666 uint32_t thatNode =
elements[
nodes[node].touchingElements[
i]].corners[
j];
667 if (thatNode == node || thatNode == nextNode) {
672 deltaX = {x[0] -
nodes[thatNode].x[0], x[1] -
nodes[thatNode].x[1], x[2] -
nodes[thatNode].x[2]};
673 Real thatDist =
sqrt(deltaX[0] * deltaX[0] + deltaX[1] * deltaX[1] + deltaX[2] * deltaX[2]);
674 if (thatDist < minDist) {
682 if (nextNode == node) {
708 phiprof::Timer timer{
"ionosphere-subdivideElement"};
712 std::array<Element, 4> newElements;
713 for (
int i = 0;
i < 4;
i++) {
714 newElements[
i].refLevel = parentElement.
refLevel + 1;
718 std::array<uint32_t, 3> edgeNodes;
719 for (
int i = 0;
i < 3;
i++) {
731 int32_t insertedNode = -1;
735 std::set<uint32_t> candidates;
738 for (
int k = 0;
k < 3;
k++) {
746 for (
int k = 0;
k < 3;
k++) {
753 if (insertedNode == -1) {
754 cerr <<
"(IONOSPHERE) Warning: did not find neighbouring split node when trying to refine "
755 <<
"element " << e <<
" on edge " <<
i <<
" with nodes ("
762 if (
nodes[insertedNode].numTouchingElements != 4) {
763 cerr <<
"(IONOSPHERE) Warning: mesh topology screwup when refining: node " << insertedNode
764 <<
" is touching " <<
nodes[insertedNode].numTouchingElements <<
" elements, should be 4." << endl;
769 nodes[insertedNode].touchingElements[5] =
elements.size() + (
i + 1) % 3;
772 nodes[insertedNode].numTouchingElements = 6;
774 edgeNodes[
i] = insertedNode;
780 for (
int c = 0;
c < 3;
c++) {
781 newNode.
x[
c] = 0.5 * (n1.
x[
c] + n2.
x[
c]);
793 nodes.push_back(newNode);
794 edgeNodes[
i] =
nodes.size() - 1;
799 newElements[0].corners[0] = parentElement.
corners[0];
800 newElements[0].corners[1] = edgeNodes[0];
801 newElements[0].corners[2] = edgeNodes[2];
802 newElements[1].corners[0] = edgeNodes[0];
803 newElements[1].corners[1] = parentElement.
corners[1];
804 newElements[1].corners[2] = edgeNodes[1];
805 newElements[2].corners[0] = edgeNodes[2];
806 newElements[2].corners[1] = edgeNodes[1];
807 newElements[2].corners[2] = parentElement.
corners[2];
808 newElements[3].corners[0] = edgeNodes[0];
809 newElements[3].corners[1] = edgeNodes[1];
810 newElements[3].corners[2] = edgeNodes[2];
814 for (
int n = 0; n < 3; n++) {
826 for (
int i = 0;
i < 3;
i++) {
833 static const Real P[7] = {-11.639, 32.1133, -30.8543, 14.6063, -6.3375, 0.6138, 1.4946};
834 Real lambda = (((((
P[0] * x +
P[1]) * x +
P[2]) * x +
P[3]) * x +
P[4]) * x +
P[5]) * x +
P[6];
835 if (x > 1. || lambda < 0) {
854 {50, 0.0409, 1.072, -0.0641, -1.054},
855 {100, 0.0711, 0.899, -0.171, -0.720},
856 {500, 0.130, 0.674, -0.271, -0.319},
857 {1000,0.142, 0.657, -0.277, -0.268}
865 if (E0 <= SIparameters[0].E) {
866 C1 = SIparameters[0].
C1;
867 C2 = SIparameters[0].
C2;
868 C3 = SIparameters[0].
C3;
869 C4 = SIparameters[0].
C4;
870 }
else if (E0 >= SIparameters[3].E) {
871 C1 = SIparameters[3].
C1;
872 C2 = SIparameters[3].
C2;
873 C3 = SIparameters[3].
C3;
874 C4 = SIparameters[3].
C4;
876 for (
int i = 0;
i < 3;
i++) {
877 if (SIparameters[
i].E < E0 && SIparameters[
i + 1].E > E0) {
878 Real interp = (E0 - SIparameters[
i].
E) / (SIparameters[
i + 1].E - SIparameters[
i].E);
879 C1 = (1. - interp) * SIparameters[
i].C1 + interp * SIparameters[
i + 1].C1;
880 C2 = (1. - interp) * SIparameters[
i].C2 + interp * SIparameters[
i + 1].C2;
881 C3 = (1. - interp) * SIparameters[
i].C3 + interp * SIparameters[
i + 1].C3;
882 C4 = (1. - interp) * SIparameters[
i].C4 + interp * SIparameters[
i + 1].C4;
886 return (C2 + C1 * Chi) * exp(C4 * Chi + C3 * Chi * Chi);
894 phiprof::Timer timer{
"ionosphere-readAtmosphericModelFile"};
898 66, 68, 71, 74, 78, 82, 87, 92, 98, 104, 111,
899 118, 126, 134, 143, 152, 162, 172, 183, 194
904 ifstream in(filename);
906 cerr <<
"(ionosphere) WARNING: Atmospheric Model file " << filename <<
" could not be opened: " << strerror(errno) << endl;
907 cerr <<
"(ionosphere) All atmospheric values will be zero, and there will be no ionization!" << endl;
910 Real integratedDensity = 0;
911 Real prevDensity = 0;
912 Real prevAltitude = 0;
913 std::vector<std::array<Real, 5>> MSISvalues;
915 Real altitude, massdensity, Odensity, N2density, O2density, neutralTemperature;
916 in >> altitude >> Odensity >> N2density >> O2density >> massdensity >> neutralTemperature;
918 integratedDensity += (altitude - prevAltitude) * 1000 * 0.5 * (massdensity + prevDensity);
920 Real nui = 1e-17 * (3.67 * Odensity + 5.14 * N2density + 2.59 * O2density);
922 Real nue = 1e-17 * (8.9 * Odensity + 2.33 * N2density + 18.2 * O2density);
923 prevAltitude = altitude;
924 prevDensity = massdensity;
925 MSISvalues.push_back({altitude, massdensity, nui, nue, integratedDensity});
929 for (
unsigned int i = 1;
i < MSISvalues.size();
i++) {
930 Real altitude = MSISvalues[
i][0];
933 while (altindex < numAtmosphereLevels && altitude >= alt[altindex]) {
934 Real interpolationFactor = (alt[altindex] - MSISvalues[
i - 1][0]) / (MSISvalues[
i][0] - MSISvalues[
i - 1][0]);
938 newLayer.
density = fmax((1.-interpolationFactor) * MSISvalues[
i-1][1] + interpolationFactor * MSISvalues[
i][1], 0.);
939 newLayer.
depth = fmax((1.-interpolationFactor) * MSISvalues[
i-1][4] + interpolationFactor * MSISvalues[
i][4], 0.);
940 newLayer.
nui = fmax((1.-interpolationFactor) * MSISvalues[
i-1][2] + interpolationFactor * MSISvalues[
i][2], 0.);
941 newLayer.
nue = fmax((1.-interpolationFactor) * MSISvalues[
i-1][3] + interpolationFactor * MSISvalues[
i][3], 0.);
953 const Real Bval = 5e-5;
969 std::array<Real, SBC::productionNumParticleEnergies + 1>
particle_energy;
977 const Real eps_ion_keV = 0.035;
981 Real electronRange = 0.;
988 if (
atmosphere[h].depth / electronRange > 1) {
1010 cerr <<
"(IONOSPHERE) Invalid value for Ionization model." << endl;
1038 scatteringRate[e][h] =
max(0., rate);
1079 for (uint n = 0; n <
nodes.size(); n++) {
1090 if (normEnergy < 0) {
1094 if (normTemperature < 0) {
1095 normTemperature = 0;
1100 int energyindex = int(
float(normEnergy));
1101 if (energyindex < 0) {
1109 float t = normEnergy -
floor(normEnergy);
1112 int temperatureindex = int(
float(normTemperature));
1113 float s = normTemperature -
floor(normTemperature);
1114 if (temperatureindex < 0) {
1115 temperatureindex = 0;
1116 normTemperature = 0;
1120 normTemperature = 0;
1124 return (
productionTable[heightindex][energyindex][temperatureindex]*(1.-t) +
1125 productionTable[heightindex][energyindex+1][temperatureindex] * t) * (1.-s) +
1126 (
productionTable[heightindex][energyindex][temperatureindex+1]*(1.-t) +
1127 productionTable[heightindex][energyindex+1][temperatureindex+1] * t) * s ;
1138 for (uint n = 0; n <
nodes.size(); n++) {
1162 phiprof::Timer timer{
"ionosphere-calculateConductivityTensor"};
1165 if (!refillTensorAtRestart) {
1175 const static std::array<Real, 3> SigmaP0d_coefficients = {5.0, -0.8, 60.9};
1176 const static std::array<Real, 3> SigmaP0u_coefficients = {4.2, 1.1, 318.6};
1177 const static std::array<Real, 3> SigmaH0d_coefficients = {7.7, -1.8, 139.0};
1178 const static std::array<Real, 3> SigmaH0u_coefficients = {8.7, 4.6, 327.1};
1180 const static std::array<Real, 3> SigmaP1d_coefficients = {-3.2, -3.6, 21.9};
1181 const static std::array<Real, 3> SigmaP1u_coefficients = { 6.8, -1.5, 184.9};
1182 const static std::array<Real, 3> SigmaH1d_coefficients = {-7.3, 5.6, 100.9};
1183 const static std::array<Real, 3> SigmaH1u_coefficients = {14.8, -10.4, 129.9};
1186 auto interpolate_robinson = [](
const std::array<Real, 3>& variable,
Real MLT) ->
Real {
1187 return variable[0] + variable[1] * cos(variable[2] / 180. * M_PI + MLT);
1193 x = std::clamp((x - a) / (b - a), 0., 1.);
1194 x = x * x * (3 - 2 * x);
1195 return (1. - x) * a + x * b;
1198 for (uint n = 0; n <
nodes.size(); n++) {
1204 for (uint e = 0; e <
nodes[n].numTouchingElements; e++) {
1214 Real SigmaH0 = smoothstep(interpolate_robinson(SigmaH0u_coefficients, MLT), interpolate_robinson(SigmaH0d_coefficients, MLT), FAC / 0.1);
1215 Real SigmaH1 = smoothstep(interpolate_robinson(SigmaH1u_coefficients, MLT), interpolate_robinson(SigmaH1d_coefficients, MLT), FAC / 0.1);
1216 Real SigmaP0 = smoothstep(interpolate_robinson(SigmaP0u_coefficients, MLT), interpolate_robinson(SigmaP0d_coefficients, MLT), FAC / 0.1);
1217 Real SigmaP1 = smoothstep(interpolate_robinson(SigmaP1u_coefficients, MLT), interpolate_robinson(SigmaP1d_coefficients, MLT), FAC / 0.1);
1230 MLT = fmod(MLT, 24.);
1232 Real interpolant = MLT - sector;
1237 MLT = fmod(MLT, 24.);
1239 Real interpolant = MLT - sector;
1244 MLT = fmod(MLT, 24.);
1246 Real interpolant = MLT - sector;
1251 MLT = fmod(MLT, 24.);
1253 Real interpolant = MLT - sector;
1260 Eigen::VectorXd vJ(2 *
elements.size());
1261 Eigen::VectorXd vRHS1(
nodes.size() +
nodes.size());
1262 Eigen::VectorXd vRHS2(
nodes.size() +
nodes.size());
1263 Eigen::SparseMatrix<Real> curlSolverMatrix(vRHS1.size(), vJ.size());
1265 std::vector<Real> elementCorrectionFactors(
elements.size());
1288 if (Eigen::loadMarket(curlSolverMatrix,
"ionosphereSolverMatrix")) {
1290 for (
unsigned int n = 0; n <
nodes.size(); n++) {
1295 for (
unsigned int n = 0; n <
nodes.size(); n++) {
1297 vRHS2[n +
nodes.size()] = 0;
1302 for (uint gridNodeIndex = 0; gridNodeIndex <
nodes.size(); gridNodeIndex++) {
1305 vRHS1[gridNodeIndex] = 0;
1310 for (uint32_t elLocalIndex = 0; elLocalIndex <
nodes[gridNodeIndex].numTouchingElements; elLocalIndex++) {
1314 int gridI = 0, gridJ = 0;
1315 int localC = 0, localI = 0, localJ = 0;
1316 for (
int c = 0;
c < 3;
c++) {
1317 if (element.
corners[
c] == gridNodeIndex) {
1319 localI = (
c + 1) % 3;
1320 gridI = element.
corners[localI];
1321 localJ = (
c + 2) % 3;
1322 gridJ = element.
corners[localJ];
1331 Eigen::Vector3d midpointmi =
commonEdgeMidpoint(
nodes[gridNodeIndex].touchingElements[elLocalIndex], otherElementi);
1332 Real li = (circumcentrem - midpointmi).norm();
1334 Eigen::Vector3d rm(
nodes[gridNodeIndex].x.data());
1335 Eigen::Vector3d ri(
nodes[gridI].x.data());
1336 Eigen::Vector3d rj(
nodes[gridJ].x.data());
1337 Eigen::Vector3d edge = (ri - rm) / (ri - rm).norm();
1339 Eigen::Vector3d normalm =
elementNormal(
nodes[gridNodeIndex].touchingElements[elLocalIndex]);
1340 Eigen::Vector3d edgem = Eigen::Quaterniond::FromTwoVectors(normalm, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge;
1342 curlSolverMatrix.coeffRef(gridNodeIndex, 2 *
nodes[gridNodeIndex].touchingElements[elLocalIndex]) += edgem(0) * li;
1343 curlSolverMatrix.coeffRef(gridNodeIndex, 2 *
nodes[gridNodeIndex].touchingElements[elLocalIndex] + 1) += edgem(1) * li;
1345 Eigen::Vector3d midpointmj =
commonEdgeMidpoint(
nodes[gridNodeIndex].touchingElements[elLocalIndex], otherElementj);
1346 Real lj = (circumcentrem - midpointmj).norm();
1348 edge = (rj - rm) / (rj - rm).norm();
1350 edgem = Eigen::Quaterniond::FromTwoVectors(normalm, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge;
1352 curlSolverMatrix.coeffRef(gridNodeIndex, 2 *
nodes[gridNodeIndex].touchingElements[elLocalIndex]) += edgem(0) * lj;
1353 curlSolverMatrix.coeffRef(gridNodeIndex, 2 *
nodes[gridNodeIndex].touchingElements[elLocalIndex] + 1) += edgem(1) * lj;
1361 for (uint n = 0; n <
nodes.size(); n++) {
1367 vRHS2[
nodes.size() + n] = 0;
1369 for (uint32_t elLocalIndex = 0; elLocalIndex <
nodes[n].numTouchingElements; elLocalIndex++) {
1373 int gridI = 0, gridJ = 0;
1374 int localC = 0, localI = 0, localJ = 0;
1375 for (
int c = 0;
c < 3;
c++) {
1377 localI = (
c + 1) % 3;
1378 gridI = element.
corners[localI];
1379 localJ = (
c + 2) % 3;
1380 gridJ = element.
corners[localJ];
1386 Eigen::Vector3d ri(
nodes[gridI].x.data());
1387 Eigen::Vector3d rj(
nodes[gridJ].x.data());
1388 Eigen::Vector3d rm(
nodes[n].x.data());
1390 Eigen::Vector3d edgemi = (ri - rm) / (ri - rm).norm();
1391 edgemi = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edgemi;
1393 Eigen::Vector3d edgemj = (rj - rm) / (rj - rm).norm();
1394 edgemj = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edgemj;
1396 Real orientation = edgemj.cross(edgemi).dot(normal) > 0 ? 1. : -1.;
1398 Eigen::Vector3d outerEdge = orientation * (rj - ri) / (rj - ri).norm();
1399 Real outerEdgeLength = (rj - ri).norm();
1400 outerEdge = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * outerEdge;
1402 curlSolverMatrix.coeffRef(
nodes.size() + n, 2 *
nodes[n].touchingElements[elLocalIndex]) += outerEdge(0) * outerEdgeLength / 2.;
1403 curlSolverMatrix.coeffRef(
nodes.size() + n, 2 *
nodes[n].touchingElements[elLocalIndex] + 1) += outerEdge(1) * outerEdgeLength / 2.;
1407 Eigen::saveMarket(curlSolverMatrix,
"ionosphereSolverMatrix");
1410 curlSolverMatrix.makeCompressed();
1413 Eigen::LeastSquaresConjugateGradient<Eigen::SparseMatrix<Real>> solver;
1414 solver.compute(curlSolverMatrix);
1415 vJ = solver.solve(vRHS2);
1419 for (uint el = 0; el <
elements.size(); el++) {
1420 std::array<uint32_t, 3>& corners =
elements[el].corners;
1421 Eigen::Vector3d r0(
nodes[corners[0]].x.data());
1422 Eigen::Vector3d r1(
nodes[corners[1]].x.data());
1423 Eigen::Vector3d r2(
nodes[corners[2]].x.data());
1425 Eigen::Vector3d barycentre = (r0 + r1 + r2) / 3.;
1427 Eigen::Vector3d rotatedVJ = Eigen::Quaterniond::FromTwoVectors(Eigen::Vector3d::UnitZ(), barycentre.normalized()).toRotationMatrix() * Eigen::Vector3d(vJ[2 * el], vJ[2 * el + 1], 0);
1430 Real MLT = atan2(barycentre[1], barycentre[0]) * 12 / M_PI + 12;
1434 elementCorrectionFactors[el] = correction;
1439 for (uint n = 0; n <
nodes.size(); n++) {
1442 Real correction = 0;
1444 for (uint32_t el = 0; el <
nodes[n].numTouchingElements; el++) {
1447 correction += elementCorrectionFactors[
nodes[n].touchingElements[el]] * A;
1449 correction /= totalA;
1455 vJ = solver.solve(vRHS1);
1456 for (uint el = 0; el <
elements.size(); el++) {
1457 std::array<uint32_t, 3>& corners =
elements[el].corners;
1459 Eigen::Vector3d r0(
nodes[corners[0]].x.data());
1460 Eigen::Vector3d r1(
nodes[corners[1]].x.data());
1461 Eigen::Vector3d r2(
nodes[corners[2]].x.data());
1463 Eigen::Vector3d barycentre = (r0 + r1 + r2) / 3.;
1465 Eigen::Vector3d rotatedVJ = Eigen::Quaterniond::FromTwoVectors(Eigen::Vector3d::UnitZ(), barycentre.normalized()).toRotationMatrix() * Eigen::Vector3d(vJ[2 * el], vJ[2 * el + 1], 0);
1470 #pragma omp parallel for
1471 for (uint n = 0; n <
nodes.size(); n++) {
1472 Eigen::Vector3d J{0, 0, 0};
1473 Eigen::Vector3d x(
nodes[n].x.data());
1476 for (uint32_t el = 0; el <
nodes[n].numTouchingElements; el++) {
1483 Real MLT = atan2(x[1], x[0]) * 12 / M_PI + 12;
1489 Real SigmaH =
c4H(MLT) * pow(J.norm(),
c5H(MLT));
1490 Real SigmaP =
c4P(MLT) * pow(J.norm(),
c5P(MLT));
1501 for (
unsigned int n = 0; n <
nodes.size(); n++) {
1514 for (
unsigned int n = 0; n <
nodes.size(); n++) {
1518 Eigen::Vector3d x(
nodes[n].x.data());
1520 for (
unsigned int m = 0; m <
nodes[n].numTouchingElements; m++) {
1522 for (
int c = 0;
c < 3;
c++) {
1530 Eigen::Vector3d ox(
nodes[
i].x.data());
1531 Real distance = (ox - x).norm();
1547 Real distance = (ox - x).norm();
1559 #pragma omp parallel for
1560 for (
unsigned int n = 0; n <
nodes.size(); n++) {
1580 Real degrees = fabs(sza) / M_PI * 180;
1583 degrees =
max(0., degrees);
1584 degrees =
min(120., degrees);
1586 int bin = degrees * 10.;
1587 Real interpolant = bin - (degrees * 10.);
1594 Real chi = acos(coschi);
1597 const Real F10_7 = 100;
1598 Real sigmaP_dayside =
c1p * pow(F10_7,
c2p) * pow(qprime,
c3p);
1599 Real sigmaH_dayside =
c1h * pow(F10_7,
c2h) * pow(qprime,
c3h);
1610 static const int epsilon[3][3][3] = {
1611 {{0,0, 0}, { 0,0,1}, {0,-1,0}},
1612 {{0,0,-1}, { 0,0,0}, {1, 0,0}},
1613 {{0,1, 0}, {-1,0,0}, {0, 0,0}}
1617 Eigen::Vector3d b(
nodes[n].x.data());
1619 if (
nodes[n].x[2] >= 0) {
1622 for (
int i = 0;
i < 3;
i++) {
1623 for (
int j = 0;
j < 3;
j++) {
1625 for (
int k = 0;
k < 3;
k++) {
1632 for (uint n = 0; n <
nodes.size(); n++) {
1638 static const int epsilon[3][3][3] = {
1639 {{0,0, 0}, { 0,0,1}, {0,-1,0}},
1640 {{0,0,-1}, { 0,0,0}, {1, 0,0}},
1641 {{0,1, 0}, {-1,0,0}, {0, 0,0}}
1645 Eigen::Vector3d b(
nodes[n].x.data());
1647 if (
nodes[n].x[2] >= 0) {
1650 for (
int i = 0;
i < 3;
i++) {
1651 for (
int j = 0;
j < 3;
j++) {
1653 for (
int k = 0;
k < 3;
k++) {
1666 for (uint n = 0; n <
nodes.size(); n++) {
1670 std::array<Real, numAtmosphereLevels> electronDensity;
1678 Real electronTemp =
nodes[n].electronTemperature();
1680 if (!(std::isfinite(energy_keV) && std::isfinite(temperature_keV))) {
1681 cerr <<
"(ionosphere) NaN or inf encountered in conductivity calculation: " << endl
1682 <<
" `-> DeltaPhi = " <<
nodes[n].deltaPhi() / 1000. <<
" keV" << endl
1683 <<
" `-> energy_keV = " << energy_keV << endl
1684 <<
" `-> ne = " << ne <<
" m^-3" << endl
1685 <<
" `-> electronTemp = " << electronTemp <<
" K" << endl;
1708 static const int epsilon[3][3][3] = {
1709 {{0,0, 0}, { 0,0,1}, {0,-1,0}},
1710 {{0,0,-1}, { 0,0,0}, {1, 0,0}},
1711 {{0,1, 0}, {-1,0,0}, {0, 0,0}}
1716 Real F10_7_p_049 = pow(F10_7, 0.49);
1717 Real F10_7_p_053 = pow(F10_7, 0.53);
1719 for (uint n = 0; n <
nodes.size(); n++) {
1721 std::array<Real, 3>& x =
nodes[n].x;
1738 Real sigmaP_dayside = backgroundIonisation + F10_7_p_049 * (0.34 * coschi + 0.93 *
sqrt(coschi));
1739 Real sigmaH_dayside = backgroundIonisation + F10_7_p_053 * (0.81 * coschi + 0.54 *
sqrt(coschi));
1764 for (
int i = 0;
i < 3;
i++) {
1765 for (
int j = 0;
j < 3;
j++) {
1767 for (
int k = 0;
k < 3;
k++) {
1775 std::array<Real, 3> b = {
dipoleField(x[0], x[1], x[2],
X, 0,
X),
dipoleField(x[0], x[1], x[2],
Y, 0,
Y),
dipoleField(x[0], x[1], x[2],
Z, 0,
Z)};
1776 Real Bnorm =
sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
1781 for (
int i = 0;
i < 3;
i++) {
1782 for (
int j = 0;
j < 3;
j++) {
1784 for (
int k = 0;
k < 3;
k++) {
1791 std::array<Real, 3> b = {
dipoleField(x[0], x[1], x[2],
X, 0,
X),
dipoleField(x[0], x[1], x[2],
Y, 0,
Y),
dipoleField(x[0], x[1], x[2],
Z, 0,
Z)};
1792 Real Bnorm =
sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
1797 for (
int i = 0;
i < 3;
i++) {
1798 for (
int j = 0;
j < 3;
j++) {
1800 for (
int k = 0;
k < 3;
k++) {
1806 cerr <<
"(ionosphere) Error: Undefined conductivity model " <<
Ionosphere::conductivityModel <<
"! Ionospheric Sigma Tensor will be zero." << endl;
1815 phiprof::Timer timer{
"ionosphere-updateIonosphereCommunicator"};
1833 int writingRankInput = 0;
1840 writingRankInput =
fsgrid.getRank();
1844 MPI_Comm_split(MPI_COMM_WORLD, MPI_UNDEFINED, 0, &
communicator);
1849 MPI_Allreduce(&writingRankInput, &
writingRank, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
1863 #pragma omp critical(coupling)
1873 for (
int i = 0;
i < 3;
i++) {
1887 phiprof::Timer timer{
"ionosphere-mapDownMagnetosphere"};
1890 std::vector<double> FACinput(
nodes.size());
1891 std::vector<double> rhoInput(
nodes.size());
1892 std::vector<double> temperatureInput(
nodes.size());
1897 #pragma omp parallel for
1898 for (uint n = 0; n <
nodes.size(); n++) {
1900 Real nodeAreaGeometric = 0;
1903 if (
nodes[n].xMapped[0] == 0. &&
nodes[n].xMapped[1] == 0. &&
nodes[n].xMapped[2] == 0.) {
1909 for (uint e = 0; e <
nodes[n].numTouchingElements; e++) {
1918 nodeAreaGeometric /= 3.;
1920 std::array<Real, 3> curlB;
1924 if(lfsc[0] == -1 || lfsc[1] == -1 || lfsc[2] == -1) {
1935 lfsc[0],lfsc[1],lfsc[2],
1941 std::vector<std::array<double, 3>> sample_pts;
1942 std::vector<double> weights;
1943 std::vector<std::array<fsgrid::FsIndex_t,3>> lfscs;
1945 double weightsum = 0.0;
1946 std::array<double, 3> gridSpacing =
fsgrid.getGridSpacing();
1948 for (
int x = -1; x < 2; ++x){
1949 for (
int y = -1; y < 2; ++y){
1950 for (
int z = -1; z < 2; ++z){
1951 std::array<double, 3> pt = {
nodes[n].xMapped[0] + x*h*gridSpacing[0],
1952 nodes[n].xMapped[1] + y*h*gridSpacing[1],
1953 nodes[n].xMapped[2] + z*h*gridSpacing[2]};
1955 if(lfsc_stencil[0] == -1 || lfsc_stencil[1] == -1 || lfsc_stencil[2] == -1) {
1958 sample_pts.push_back(pt);
1959 lfscs.push_back(lfsc_stencil);
1960 weights.push_back(1.0);
1961 weightsum+=weights.back();
1966 if (sample_pts.size()==0){
1969 for (
unsigned int i = 0;
i < weights.size(); ++
i){
1976 lfscs[
i][0],lfscs[
i][1],lfscs[
i][2],
1979 for(
int ii = 0; ii<3; ++ii){
1980 curlB[ii] += curlB_temp[ii]*weights[
i]/weightsum;
2006 if (
nodes[n].x[2] < 0) {
2011 for (
int c = 0;
c < 3;
c++) {
2013 if (frac[
c] < 0.5) {
2022 const auto stencil =
fsgrid.makeStencil(lfsc[0], lfsc[1], lfsc[2]);
2023 Real couplingSum = 0;
2024 for (
int xoffset : {0, 1}) {
2025 for (
int yoffset : {0, 1}) {
2026 for (
int zoffset : {0, 1}) {
2027 const auto index = stencil.indexFromOffset(xoffset, yoffset, zoffset);
2029 Real coupling = (1. -
abs(xoffset - frac[0])) * (1. -
abs(yoffset - frac[1])) * (1. -
abs(zoffset - frac[2]));
2030 if (coupling < 0. || coupling > 1.) {
2031 cerr <<
"Ionosphere warning: node << " << n <<
" has coupling value " << coupling <<
", which is outside [0,1] at line " << __LINE__ <<
"!" << endl;
2036 couplingSum += coupling;
2043 rhoInput[n] += coupling * thisCellRho;
2052 if (couplingSum > 0) {
2053 rhoInput[n] /= couplingSum;
2054 temperatureInput[n] /= couplingSum;
2060 std::vector<double> FACsum(
nodes.size());
2061 std::vector<double> rhoSum(
nodes.size());
2062 std::vector<double> temperatureSum(
nodes.size());
2063 MPI_Allreduce(&FACinput[0], &FACsum[0],
nodes.size(), MPI_DOUBLE, MPI_SUM,
communicator);
2064 MPI_Allreduce(&rhoInput[0], &rhoSum[0],
nodes.size(), MPI_DOUBLE, MPI_SUM,
communicator);
2066 MPI_Allreduce(&temperatureInput[0], &temperatureSum[0],
nodes.size(), MPI_DOUBLE, MPI_SUM,
communicator);
2068 for (uint n = 0; n <
nodes.size(); n++) {
2074 if (theta > M_PI / 2.) {
2075 theta = M_PI - theta;
2078 Real Chi0 = 0.01 + 0.99 * .5 * (1 + tanh((23. - theta * (180. / M_PI)) / 6));
2080 if (rhoSum[n] == 0 || temperatureSum[n] == 0) {
2122 Vec3d av(a[0], a[1], a[2]);
2123 Vec3d bv(b[0], b[1], b[2]);
2130 return std::array<Real, 3>{result[0], result[1], result[2]};
2136 std::array<Real, 9> retval{0, 0, 0, 0, 0, 0, 0, 0, 0};
2138 for (
int corner = 0; corner < 3; corner++) {
2140 for (
int i = 0;
i < 9;
i++) {
2153 const std::array<Real, 3>& c1 =
nodes[e.corners[0]].x;
2154 const std::array<Real, 3>& c2 =
nodes[e.corners[1]].x;
2155 const std::array<Real, 3>& c3 =
nodes[e.corners[2]].x;
2157 std::array<Real, 3> Ti, Tj;
2183 std::array<Real, 9> sigma =
sigmaAverage(elementIndex);
2187 for (
int n = 0; n < 3; n++) {
2188 for (
int m = 0; m < 3; m++) {
2189 retval += Ti[m] * sigma[3 * n + m] * Tj[n];
2193 for (
int n = 0; n < 3; n++) {
2194 for (
int m = 0; m < 3; m++) {
2195 retval += Ti[n] * sigma[3 * n + m] * Tj[m];
2213 if ((!transposed && node1 == 0) || (transposed && node2 == 0)) {
2214 if (node1 == node2) {
2224 if (node1 == node2) {
2234 for (uint
i = 0;
i < n.numDepNodes;
i++) {
2235 if (n.dependingNodes[
i] == node2) {
2239 n.transposedCoeffs[
i] += coeff;
2241 n.dependingCoeffs[
i] += coeff;
2250 cerr <<
"(ionosphere) Node " << node1 <<
" already has " <<
MAX_DEPENDING_NODES <<
" depending nodes:" << endl;
2253 cerr << n.dependingNodes[
i] <<
", ";
2255 cerr <<
" ]." << endl;
2257 std::set<uint> neighbourNodes;
2258 for (uint e = 0; e <
nodes[node1].numTouchingElements; e++) {
2260 for (
int c = 0;
c < 3;
c++) {
2261 neighbourNodes.emplace(E.
corners[
c]);
2264 cerr <<
" (it has " <<
nodes[node1].numTouchingElements <<
" neighbour elements and " << neighbourNodes.size() - 1 <<
" direct neighbour nodes:" << endl <<
" [ ";
2265 for (
auto& n : neighbourNodes) {
2270 cerr <<
"])." << endl;
2273 n.dependingNodes[n.numDepNodes] = node2;
2275 n.dependingCoeffs[n.numDepNodes] = 0;
2276 n.transposedCoeffs[n.numDepNodes] = coeff;
2278 n.dependingCoeffs[n.numDepNodes] = coeff;
2279 n.transposedCoeffs[n.numDepNodes] = 0;
2287 nodes[nodeIndex].numDepNodes = 1;
2290 nodes[nodeIndex].dependingNodes[0] = nodeIndex;
2291 nodes[nodeIndex].dependingCoeffs[0] = 0;
2292 nodes[nodeIndex].transposedCoeffs[0] = 0;
2294 for (uint t = 0; t <
nodes[nodeIndex].numTouchingElements; t++) {
2299 for (
int c = 0;
c < 3;
c++) {
2300 if (e.corners[
c] == nodeIndex) {
2312 for (
int c = 0;
c < 3;
c++) {
2313 uint neigh = e.corners[
c];
2335 for (uint n = 0; n <
nodes.size(); n++) {
2337 for (uint t = 0; t <
nodes[n].numTouchingElements; t++) {
2342 for (
int c = 0;
c < 3;
c++) {
2343 if (e.corners[
c] == n) {
2356 uint A = 0, B = 0, C = 0;
2357 Real bestColinearity = 0;
2358 for (
int c = 0;
c < 3;
c++) {
2361 Vec3d ab(b.x[0] - a.x[0], b.x[1] - a.x[1], b.x[2] - a.x[2]);
2365 if (dotproduct > 0.9 && dotproduct > bestColinearity) {
2367 B = e.corners[(
c + 1) % 3];
2368 C = e.corners[(
c + 2) % 3];
2369 bestColinearity = dotproduct;
2373 if (bestColinearity == 0) {
2374 cerr <<
"(ionosphere) Stitiching refinement boundaries failed: Element " <<
nodes[n].touchingElements[t] <<
" does not contain node " << n <<
" as a corner, yet matching edge not found." << endl;
2379 if (A == n || B == n || C == n) {
2380 cerr <<
"(ionosphere) ERROR: Trying to split an element at a node that is already it's corner" << endl;
2385 e.corners = {A, n, C};
2389 newElement.
corners = {n, B, C};
2397 nodes[C].touchingElements[
nodes[C].numTouchingElements++] = ne;
2399 cerr <<
"(ionosphere) ERROR: node " << C <<
"'s numTouchingElements (" <<
nodes[C].numTouchingElements <<
") exceeds MAX_TOUCHING_ELEMENTS (= " <<
MAX_TOUCHING_ELEMENTS <<
")" << endl;
2403 nodes[n].touchingElements[
nodes[n].numTouchingElements++] = ne;
2405 cerr <<
"(ionosphere) ERROR: node " << n <<
"'s numTouchingElements [" <<
nodes[n].numTouchingElements <<
"] exceeds MAX_TOUCHING_ELEMENTS (= " <<
MAX_TOUCHING_ELEMENTS <<
")" << endl;
2418 for (
int c = 0;
c < 3;
c++) {
2420 if (nn == A || nn == B || nn == C || nn == n) {
2427 for (uint
j = 0;
j <
nodes[nn].numTouchingElements;
j++) {
2428 if (
nodes[nn].touchingElements[
j] ==
nodes[n].touchingElements[t]) {
2429 nodes[nn].touchingElements[
j] = ne;
2445 phiprof::Timer timer{
"ionosphere-initSolver"};
2448 for (uint n = 0; n <
nodes.size(); n++) {
2456 Real potentialSum = 0;
2457 for (uint n = 0; n <
nodes.size(); n++) {
2465 potentialSum /=
nodes.size();
2474 #pragma omp parallel for
2475 for (uint n = 0; n <
nodes.size(); n++) {
2505 for (uint
i = 0;
i < n.numDepNodes;
i++) {
2506 retval +=
nodes[n.dependingNodes[
i]].parameters[parameter] * n.transposedCoeffs[
i];
2509 for (uint
i = 0;
i < n.numDepNodes;
i++) {
2510 retval +=
nodes[n.dependingNodes[
i]].parameters[parameter] * n.dependingCoeffs[
i];
2526 return n.parameters[parameter] / n.transposedCoeffs[0];
2528 return n.parameters[parameter] / n.dependingCoeffs[0];
2531 return n.parameters[parameter];
2539 if (
nodes.size() == 0) {
2543 minPotentialN = maxPotentialN = minPotentialS = maxPotentialS = 0.;
2552 phiprof::Timer timer{
"ionosphere-solve"};
2559 for (uint n = 0; n <
nodes.size(); n++) {
2570 Eigen::SparseMatrix<Real> potentialSolverMatrix(
nodes.size(),
nodes.size());
2571 Eigen::VectorXd vRightHand(
nodes.size()), vPhi(
nodes.size());
2573 for (uint n = 0; n <
nodes.size(); n++) {
2574 for (uint m = 0; m <
nodes[n].numDepNodes; m++) {
2575 potentialSolverMatrix.insert(n,
nodes[n].dependingNodes[m]) =
nodes[n].dependingCoeffs[m];
2580 potentialSolverMatrix.makeCompressed();
2582 Eigen::BiCGSTAB<Eigen::SparseMatrix<Real>> solver;
2584 solver.compute(potentialSolverMatrix);
2586 vPhi = solver.solve(vRightHand);
2588 for (uint n = 0; n <
nodes.size(); n++) {
2592 nIterations = solver.iterations();
2594 residual = solver.error();
2596 for (uint n = 0; n <
nodes.size(); n++) {
2619 solveInternal(nIterations, nRestarts, residual, minPotentialN, maxPotentialN, minPotentialS, maxPotentialS);
2628 std::vector<iSolverReal> effectiveSource(
nodes.size());
2636 minPotentialN = minPotentialS = std::numeric_limits<iSolverReal>::max();
2637 maxPotentialN = maxPotentialS = std::numeric_limits<iSolverReal>::lowest();
2638#ifdef IONOSPHERE_SORTED_SUMS
2639 std::multiset<iSolverReal> set_neg, set_pos;
2642#ifdef IONOSPHERE_SORTED_SUMS
2643 #pragma omp parallel shared(akden, bknum, potentialInt, sourcenorm, residualnorm, effectiveSource, minPotentialN, maxPotentialN, minPotentialS, maxPotentialS, set_neg, set_pos)
2645 #pragma omp parallel shared(akden, bknum, potentialInt, sourcenorm, residualnorm, effectiveSource, minPotentialN, maxPotentialN, minPotentialS, maxPotentialS)
2651 iSolverReal thread_minerr = std::numeric_limits<iSolverReal>::max();
2652 int thread_iteration = iteration;
2653 int thread_nRestarts = nRestarts;
2654#ifdef IONOSPHERE_SORTED_SUMS
2655 std::multiset<iSolverReal> thread_set_neg, thread_set_pos;
2665#ifdef IONOSPHERE_SORTED_SUMS
2668 #pragma omp for reduction(+ : sourcenorm)
2670 for (uint n = 0; n <
nodes.size(); n++) {
2679 effectiveSource[n] = source;
2682#ifdef IONOSPHERE_SORTED_SUMS
2683 thread_set_pos.insert(source * source);
2685 sourcenorm += source * source;
2696#ifdef IONOSPHERE_SORTED_SUMS
2697 #pragma omp critical
2698 { set_pos.insert(thread_set_pos.begin(), thread_set_pos.end()); }
2703#ifdef IONOSPHERE_SORTED_SUMS
2704 for (
auto it = set_pos.crbegin(); it != set_pos.crend(); it++) {
2708 sourcenorm =
sqrt(sourcenorm);
2710 bool skipSolve =
false;
2712 if (sourcenorm == 0) {
2717 for (uint n = 0; n <
nodes.size(); n++) {
2727 for (uint n = 0; n <
nodes.size(); n++) {
2736#ifdef IONOSPHERE_SORTED_SUMS
2741#ifdef IONOSPHERE_SORTED_SUMS
2742 thread_set_pos.clear();
2743 thread_set_neg.clear();
2746 #pragma omp for reduction(+ : bknum)
2748 for (uint n = 0; n <
nodes.size(); n++) {
2751#ifdef IONOSPHERE_SORTED_SUMS
2753 thread_set_neg.insert(incr);
2756 thread_set_pos.insert(incr);
2763#ifdef IONOSPHERE_SORTED_SUMS
2764 #pragma omp critical
2766 set_neg.insert(thread_set_neg.begin(), thread_set_neg.end());
2767 set_pos.insert(thread_set_pos.begin(), thread_set_pos.end());
2774 for (
auto it = set_neg.cbegin(); it != set_neg.cend(); it++) {
2777 for (
auto it = set_pos.crbegin(); it != set_pos.crend(); it++) {
2780 bknum = bknum_neg + bknum_pos;
2787 for (uint n = 0; n <
nodes.size(); n++) {
2796 for (uint n = 0; n <
nodes.size(); n++) {
2813#ifdef IONOSPHERE_SORTED_SUMS
2818#ifdef IONOSPHERE_SORTED_SUMS
2819 thread_set_neg.clear();
2820 thread_set_pos.clear();
2823 #pragma omp for reduction(+ : akden)
2825 for (uint n = 0; n <
nodes.size(); n++) {
2830#ifdef IONOSPHERE_SORTED_SUMS
2832 thread_set_neg.insert(incr);
2835 thread_set_pos.insert(incr);
2842#ifdef IONOSPHERE_SORTED_SUMS
2843 #pragma omp critical
2845 set_neg.insert(thread_set_neg.begin(), thread_set_neg.end());
2846 set_pos.insert(thread_set_pos.begin(), thread_set_pos.end());
2853 for (
auto it = set_neg.cbegin(); it != set_neg.cend(); it++) {
2856 for (
auto it = set_pos.crbegin(); it != set_pos.crend(); it++) {
2859 akden = akden_neg + akden_pos;
2865 for (uint n = 0; n <
nodes.size(); n++) {
2878 { potentialInt = 0; }
2879 #pragma omp for reduction(+ : potentialInt)
2880 for (uint e = 0; e <
elements.size(); e++) {
2882 Real effPotential = 0;
2883 for (
int c = 0;
c < 3;
c++) {
2887 potentialInt += effPotential * area;
2895 for (uint n = 0; n <
nodes.size(); n++) {
2904#ifdef IONOSPHERE_SORTED_SUMS
2908#ifdef IONOSPHERE_SORTED_SUMS
2909 thread_set_pos.clear();
2912 #pragma omp for reduction(+ : residualnorm)
2914 for (uint n = 0; n <
nodes.size(); n++) {
2933#ifdef IONOSPHERE_SORTED_SUMS
2934 thread_set_pos.insert(newresid * newresid);
2936 residualnorm += newresid * newresid;
2941#ifdef IONOSPHERE_SORTED_SUMS
2942 #pragma omp critical
2943 { set_pos.insert(thread_set_pos.begin(), thread_set_pos.end()); }
2947 for (
auto it = set_pos.crbegin(); it != set_pos.crend(); it++) {
2948 residualnorm += *it;
2954 for (uint n = 0; n <
nodes.size(); n++) {
2960 err =
sqrt(residualnorm) / sourcenorm;
2962 if (err < thread_minerr) {
2965 for (uint n = 0; n <
nodes.size(); n++) {
2969 thread_minerr = err;
2974 for (uint n = 0; n <
nodes.size(); n++) {
2992 threadID = omp_get_thread_num();
2994 if (skipSolve && threadID == 0) {
3002 #pragma omp for reduction(max : maxPotentialN, maxPotentialS) reduction(min : minPotentialN, minPotentialS)
3003 for (uint n = 0; n <
nodes.size(); n++) {
3006 if (N.
x.at(2) > 0) {
3015 if (threadID == 0) {
3016 minerr = thread_minerr;
3017 iteration = thread_iteration;
3018 nRestarts = thread_nRestarts;
3035 Readparameters::add(
"ionosphere.radius",
"Radius of the inner simulation boundary (unit is assumed to be R_E if value < 1000, otherwise m).", 1.0e7);
3037 Readparameters::add(
"ionosphere.geometry",
"Select the geometry of the ionosphere, 0: inf-norm (diamond), 1: 1-norm (square), 2: 2-norm (circle, DEFAULT), 3: 2-norm cylinder aligned with y-axis, use with polar plane/line dipole.", 2);
3038 Readparameters::add(
"ionosphere.precedence",
"Precedence value of the ionosphere system boundary condition (integer), the higher the stronger.", 2);
3039 Readparameters::add(
"ionosphere.reapplyUponRestart",
"If 0 (default), keep going with the state existing in the restart file. If 1, calls again applyInitialState. Can be used to change boundary condition behaviour during a run.", 0);
3040 Readparameters::add(
"ionosphere.baseShape",
"Select the seed mesh geometry for the spherical ionosphere grid. Options are: fromFile, sphericalFibonacci, tetrahedron, octahedron, icosahedron.", std::string(
"sphericalFibonacci"));
3041 Readparameters::add(
"ionosphere.conductivityModel",
"Select ionosphere conductivity tensor construction model. Options are: 0=GUMICS style (Vertical B, only SigmaH and SigmaP), 1=Ridley et al 2004 (1000 mho longitudinal conductivity), 2=Koskinen 2011 full conductivity tensor.", 0);
3042 Readparameters::add(
"ionosphere.ridleyParallelConductivity",
"Constant parallel conductivity value. 1000 mho is given without justification by Ridley et al 2004.", 1000);
3043 Readparameters::add(
"ionosphere.fibonacciNodeNum",
"Number of nodes in the spherical fibonacci mesh.", 256);
3044 Readparameters::add(
"ionosphere.gridFilePath",
"Path to the ionosphere grid mesh OBJ or VTK legacy file, if loading grid from file.",std::string(
""));
3045 Readparameters::addComposing(
"ionosphere.refineMinLatitude",
"Refine the grid polewards of the given latitude. Multiple of these lines can be given for successive refinement, paired up with refineMaxLatitude lines.");
3046 Readparameters::addComposing(
"ionosphere.refineMaxLatitude",
"Refine the grid equatorwards of the given latitude. Multiple of these lines can be given for successive refinement, paired up with refineMinLatitude lines.");
3047 Readparameters::add(
"ionosphere.atmosphericModelFile",
"Filename to read the MSIS atmosphere data from (default: NRLMSIS.dat)", std::string(
"NRLMSIS.dat"));
3048 Readparameters::add(
"ionosphere.recombAlpha",
"Ionospheric recombination parameter (m^3/s)", 2.4e-13);
3049 Readparameters::add(
"ionosphere.ionizationModel",
"Ionospheric electron production rate model. Options are: Rees1963, Rees1989, SergienkoIvanov (default), FixedSigma, Robinson2020, Juusola2025.", std::string(
"SergienkoIvanov"));
3050 Readparameters::add(
"ionosphere.innerBoundaryVDFmode",
"Inner boundary VDF construction method. Options are: FixedMoments, AverageMoments, AverageAllMoments, CopyAndLosscone.", std::string(
"FixedMoments"));
3051 Readparameters::add(
"ionosphere.F10_7",
"Solar 10.7 cm radio flux (sfu = 10^{-22} W/m^2)", 100);
3052 Readparameters::add(
"ionosphere.backgroundIonisation",
"Background ionoisation due to cosmic rays (mho)", 0.5);
3053 Readparameters::add(
"ionosphere.fixedSigmaP",
"Fixed Pedersen conductivity value for the whole shell, if ionizationModel is 'fixedSigma'", 10.);
3054 Readparameters::add(
"ionosphere.fixedSigmaH",
"Fixed Hall conductivity value for the whole shell, if ionizationModel is 'fixedSigma'", 0.);
3055 Readparameters::add(
"ionosphere.useEigenSolver",
"Whether to use Eigen's BiCGSTAB solver over our home-grown BiCG implementation",
false);
3056 Readparameters::add(
"ionosphere.solverMaxIterations",
"Maximum number of iterations for the conjugate gradient solver", 2000);
3057 Readparameters::add(
"ionosphere.solverRelativeL2ConvergenceThreshold",
"Convergence threshold for the relative L2 metric", 1e-6);
3058 Readparameters::add(
"ionosphere.solverMaxFailureCount",
"Maximum number of iterations allowed to diverge before restarting the ionosphere solver", 5);
3059 Readparameters::add(
"ionosphere.solverMaxErrorGrowthFactor",
"Maximum allowed factor of growth with respect to the minimum error before restarting the ionosphere solver", 100);
3060 Readparameters::add(
"ionosphere.solverGaugeFixing",
"Gauge fixing method of the ionosphere solver. Options are: pole, integral, equator", std::string(
"equator"));
3061 Readparameters::add(
"ionosphere.shieldingLatitude",
"Latitude below which the potential is set to zero in the equator gauge fixing scheme (degree)", 70);
3062 Readparameters::add(
"ionosphere.solverPreconditioning",
"Use preconditioning for the solver? (0/1)", 1);
3063 Readparameters::add(
"ionosphere.solverUseMinimumResidualVariant",
"Use minimum residual variant", 0);
3064 Readparameters::add(
"ionosphere.solverToggleMinimumResidualVariant",
"Toggle use of minimum residual variant at every solver restart", 0);
3065 Readparameters::add(
"ionosphere.earthAngularVelocity",
"Angular velocity of inner boundary convection, in rad/s", 7.2921159e-5);
3066 Readparameters::add(
"ionosphere.plasmapauseL",
"L-shell at which the plasmapause resides (for corotation)", 5.);
3067 Readparameters::add(
"ionosphere.downmapRadius",
"Radius from which FACs are coupled down into the ionosphere. Units are assumed to be RE if value < 1000, otherwise m. If -1: use inner boundary cells.", -1.);
3068 Readparameters::add(
"ionosphere.downmapSamplingMode",
"Method for sampling (smoothing) the downmapped quantities (FACs) to the ionosphere. Options are: Pointwise, Boxcar27.", std::string(
"Pointwise"));
3069 Readparameters::add(
"ionosphere.downmapSamplingWidth",
"Width for smoothing the Vlasov grid downmapped parameters; see sampling modes for details.", 1.0);
3071 Readparameters::add(
"ionosphere.unmappedNodeRho",
"Electron density of ionosphere nodes that do not connect to the magnetosphere domain.", 1e4);
3072 Readparameters::add(
"ionosphere.unmappedNodeTe",
"Electron temperature of ionosphere nodes that do not connect to the magnetosphere domain.", 1e6);
3073 Readparameters::add(
"ionosphere.couplingTimescale",
"Magnetosphere->Ionosphere coupling timescale (seconds, 0=immediate coupling", 1.);
3074 Readparameters::add(
"ionosphere.couplingInterval",
"Time interval at which the ionosphere is solved (seconds)", 0);
3079 Readparameters::add(pop +
"_ionosphere.rho",
"Number density of the ionosphere (m^-3)", 0.0);
3081 Readparameters::add(pop +
"_ionosphere.VX0",
"Bulk velocity of ionospheric distribution function in X direction (m/s)", 0.0);
3082 Readparameters::add(pop +
"_ionosphere.VY0",
"Bulk velocity of ionospheric distribution function in Y direction (m/s)", 0.0);
3083 Readparameters::add(pop +
"_ionosphere.VZ0",
"Bulk velocity of ionospheric distribution function in Z direction (m/s)", 0.0);
3111 std::string VDFmodeString;
3113 if (VDFmodeString ==
"FixedMoments") {
3115 }
else if (VDFmodeString ==
"AverageMoments") {
3117 }
else if (VDFmodeString ==
"AverageAllMoments") {
3119 }
else if (VDFmodeString ==
"CopyAndLosscone") {
3122 cerr <<
"(IONOSPHERE) Unknown inner boundary VDF mode \"" << VDFmodeString <<
"\". Aborting." << endl;
3126 std::string downmapFACsamplingModeString;
3128 if(downmapFACsamplingModeString ==
"Pointwise") {
3130 }
else if(downmapFACsamplingModeString ==
"Boxcar27") {
3133 cerr <<
"(IONOSPHERE) Unknown inner boundary downsampling mode \"" << downmapFACsamplingModeString <<
"\". Aborting." << endl;
3147 std::string gaugeFixingString;
3149 if (gaugeFixingString ==
"pole") {
3151 }
else if (gaugeFixingString ==
"integral") {
3153 }
else if (gaugeFixingString ==
"equator") {
3155 }
else if (gaugeFixingString ==
"None") {
3158 cerr <<
"(IONOSPHERE) Unknown solver gauge fixing method \"" << gaugeFixingString <<
"\". Aborting." << endl;
3184 std::string ionizationModelString;
3186 if (ionizationModelString ==
"Rees1963") {
3188 }
else if (ionizationModelString ==
"Rees1989") {
3190 }
else if (ionizationModelString ==
"SergienkoIvanov") {
3192 }
else if (ionizationModelString ==
"Robinson2020") {
3194 }
else if (ionizationModelString ==
"Juusola2025") {
3196 }
else if (ionizationModelString ==
"FixedSigma") {
3199 cerr <<
"(IONOSPHERE) Unknown ionization production model \"" << ionizationModelString <<
"\". Aborting." << endl;
3236 cerr <<
"*************************************************" << endl;
3237 cerr <<
"* BIG FAT IONOSPHERE ERROR: *" << endl;
3238 cerr <<
"* *" << endl;
3239 cerr <<
"* You are trying to run a 2D simulation with an *" << endl;
3240 cerr <<
"* ionosphere inner boundary. This won't work. *" << endl;
3241 cerr <<
"* *" << endl;
3242 cerr <<
"* Most likely, your config file needs to be up- *" << endl;
3243 cerr <<
"* dated, changing all mentions of \"ionosphere\" *" << endl;
3244 cerr <<
"* to \"copysphere\". *" << endl;
3245 cerr <<
"* *" << endl;
3246 cerr <<
"* This simulation will now crash in the friend- *" << endl;
3247 cerr <<
"* liest way possible. *" << endl;
3248 cerr <<
"*************************************************" << endl;
3257 }
else if (
baseShape ==
"tetrahedron") {
3259 }
else if (
baseShape ==
"sphericalFibonacci") {
3264 cerr <<
"(IONOSPHERE) Unknown mesh base shape \"" <<
baseShape <<
"\". Aborting." << endl;
3269 auto refineBetweenLatitudes = [](
Real phi1,
Real phi2) ->
void {
3272 for (uint
i = 0;
i < numElems;
i++) {
3299 refineBetweenLatitudes(lmin, lmax);
3318 r = fabs(x - center[0]) + fabs(y - center[1]) + fabs(z - center[2]);
3322 r =
max(
max(fabs(x - center[0]), fabs(y - center[1])), fabs(z - center[2]));
3326 r =
sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]) + (z - center[2]) * (z - center[2]));
3330 r =
sqrt((x - center[0]) * (x - center[0]) + (z - center[2]) * (z - center[2]));
3333 std::cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
"ionosphere.geometry has to be 0, 1 or 2." << std::endl;
3342 for (uint
i = 0;
i < cells.size();
i++) {
3347 creal*
const cellParams = &(mpiGrid[cells[
i]]->parameters[0]);
3355 if (
getR(x, y, z, this->geometry, this->center) < this->
radius) {
3356 mpiGrid[cells[
i]]->sysBoundaryFlag = this->
getIndex();
3364 for (uint
i = 0;
i < cells.size(); ++
i) {
3371#ifdef DEBUG_VLASIATOR
3374 printf(
"ERROR in vmesh check: %s at %d\n", __FILE__, __LINE__);
3382 phiprof::Timer timer{
"Ionosphere::fieldSolverGetNormalDirection"};
3383 std::array<Real, 3> normalDirection{{0.0, 0.0, 0.0}};
3387 const auto& gridSpacing =
fsgrid.getGridSpacing();
3390 creal dy = gridSpacing[1];
3391 creal dz = gridSpacing[2];
3392 const std::array<fsgrid::FsSize_t, 3> globalIndices =
fsgrid.localToGlobal(
i,
j,
k);
3406 std::cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
"What do you expect to do with a single-cell simulation of ionosphere boundary type? Stop kidding." << std::endl;
3411 normalDirection[2] = zsign;
3416 normalDirection[1] = ysign;
3420 switch (this->geometry) {
3422 normalDirection[1] = DIAG2 * ysign;
3423 normalDirection[2] = DIAG2 * zsign;
3426 if (fabs(y) == fabs(z)) {
3427 normalDirection[1] = ysign * DIAG2;
3428 normalDirection[2] = zsign * DIAG2;
3431 if (fabs(y) > (this->
radius - dy)) {
3432 normalDirection[1] = ysign;
3435 if (fabs(z) > (this->
radius - dz)) {
3436 normalDirection[2] = zsign;
3439 if (fabs(y) > (this->
radius - 2.0 * dy)) {
3440 normalDirection[1] = ysign;
3443 if (fabs(z) > (this->
radius - 2.0 * dz)) {
3444 normalDirection[2] = zsign;
3450 normalDirection[1] = y /
length;
3451 normalDirection[2] = z /
length;
3454 std::cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
"ionosphere.geometry has to be 0, 1 or 2 with this grid shape." << std::endl;
3462 normalDirection[0] = xsign;
3466 switch (this->geometry) {
3468 normalDirection[0] = DIAG2 * xsign;
3469 normalDirection[2] = DIAG2 * zsign;
3472 if (fabs(x) == fabs(z)) {
3473 normalDirection[0] = xsign * DIAG2;
3474 normalDirection[2] = zsign * DIAG2;
3477 if (fabs(x) > (this->
radius -
dx)) {
3478 normalDirection[0] = xsign;
3481 if (fabs(z) > (this->
radius - dz)) {
3482 normalDirection[2] = zsign;
3485 if (fabs(x) > (this->
radius - 2.0 *
dx)) {
3486 normalDirection[0] = xsign;
3489 if (fabs(z) > (this->
radius - 2.0 * dz)) {
3490 normalDirection[2] = zsign;
3497 normalDirection[0] = x /
length;
3498 normalDirection[2] = z /
length;
3501 std::cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
"ionosphere.geometry has to be 0, 1, 2 or 3 with this grid shape." << std::endl;
3508 switch (this->geometry) {
3510 normalDirection[0] = DIAG2 * xsign;
3511 normalDirection[1] = DIAG2 * ysign;
3514 if (fabs(x) == fabs(y)) {
3515 normalDirection[0] = xsign * DIAG2;
3516 normalDirection[1] = ysign * DIAG2;
3519 if (fabs(x) > (this->
radius -
dx)) {
3520 normalDirection[0] = xsign;
3523 if (fabs(y) > (this->
radius - dy)) {
3524 normalDirection[1] = ysign;
3527 if (fabs(x) > (this->
radius - 2.0 *
dx)) {
3528 normalDirection[0] = xsign;
3531 if (fabs(y) > (this->
radius - 2.0 * dy)) {
3532 normalDirection[1] = ysign;
3538 normalDirection[0] = x /
length;
3539 normalDirection[1] = y /
length;
3542 std::cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
"ionosphere.geometry has to be 0, 1 or 2 with this grid shape." << std::endl;
3548 switch (this->geometry) {
3550 normalDirection[0] = DIAG3 * xsign;
3551 normalDirection[1] = DIAG3 * ysign;
3552 normalDirection[2] = DIAG3 * zsign;
3555 if (fabs(x) == fabs(y) && fabs(x) == fabs(z) && fabs(x) > this->
radius -
dx) {
3556 normalDirection[0] = xsign * DIAG3;
3557 normalDirection[1] = ysign * DIAG3;
3558 normalDirection[2] = zsign * DIAG3;
3561 if (fabs(x) == fabs(y) && fabs(x) == fabs(z) && fabs(x) > this->
radius - 2.0 *
dx) {
3562 normalDirection[0] = xsign * DIAG3;
3563 normalDirection[1] = ysign * DIAG3;
3564 normalDirection[2] = zsign * DIAG3;
3567 if (fabs(x) == fabs(y) && fabs(x) > this->
radius -
dx && fabs(z) < this->
radius - dz) {
3568 normalDirection[0] = xsign * DIAG2;
3569 normalDirection[1] = ysign * DIAG2;
3570 normalDirection[2] = 0.0;
3573 if (fabs(y) == fabs(z) && fabs(y) > this->
radius - dy && fabs(x) < this->
radius -
dx) {
3574 normalDirection[0] = 0.0;
3575 normalDirection[1] = ysign * DIAG2;
3576 normalDirection[2] = zsign * DIAG2;
3579 if (fabs(x) == fabs(z) && fabs(x) > this->
radius -
dx && fabs(y) < this->
radius - dy) {
3580 normalDirection[0] = xsign * DIAG2;
3581 normalDirection[1] = 0.0;
3582 normalDirection[2] = zsign * DIAG2;
3585 if (fabs(x) == fabs(y) && fabs(x) > this->
radius - 2.0 *
dx && fabs(z) < this->
radius - 2.0 * dz) {
3586 normalDirection[0] = xsign * DIAG2;
3587 normalDirection[1] = ysign * DIAG2;
3588 normalDirection[2] = 0.0;
3591 if (fabs(y) == fabs(z) && fabs(y) > this->
radius - 2.0 * dy && fabs(x) < this->
radius - 2.0 *
dx) {
3592 normalDirection[0] = 0.0;
3593 normalDirection[1] = ysign * DIAG2;
3594 normalDirection[2] = zsign * DIAG2;
3597 if (fabs(x) == fabs(z) && fabs(x) > this->
radius - 2.0 *
dx && fabs(y) < this->
radius - 2.0 * dy) {
3598 normalDirection[0] = xsign * DIAG2;
3599 normalDirection[1] = 0.0;
3600 normalDirection[2] = zsign * DIAG2;
3603 if (fabs(x) > (this->
radius -
dx)) {
3604 normalDirection[0] = xsign;
3607 if (fabs(y) > (this->
radius - dy)) {
3608 normalDirection[1] = ysign;
3611 if (fabs(z) > (this->
radius - dz)) {
3612 normalDirection[2] = zsign;
3615 if (fabs(x) > (this->
radius - 2.0 *
dx)) {
3616 normalDirection[0] = xsign;
3619 if (fabs(y) > (this->
radius - 2.0 * dy)) {
3620 normalDirection[1] = ysign;
3623 if (fabs(z) > (this->
radius - 2.0 * dz)) {
3624 normalDirection[2] = zsign;
3630 normalDirection[0] = x /
length;
3631 normalDirection[1] = y /
length;
3632 normalDirection[2] = z /
length;
3636 normalDirection[0] = x /
length;
3637 normalDirection[2] = z /
length;
3640 std::cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
"ionosphere.geometry has to be 0, 1, 2 or 3 with this grid shape." << std::endl;
3646 return normalDirection;
3661 const std::array<Real, 3>& gridSpacing,
3662 const std::array<fsgrid::FsSize_t, 3>& globalCoordinates,
3663 const fsgrid::FsStencil& stencil,
3667 const uint32_t bitfield = 1 << component;
3670 static constexpr std::array permutations = {
3682 const std::array permutation = permutations[component];
3684 const std::array<size_t, 6> inds = {
3694 auto bitFieldSet = [&bitfield](
auto& tech) {
return (tech.SOLVE & bitfield) == bitfield; };
3695 auto sbLayerIsOne = [](
auto& tech) {
return tech.sysBoundaryLayer == 1; };
3696 auto averageNeigbours = [&technical, &b, &inds, &permutation, &perbComponent, &bitFieldSet](
auto begin,
auto end,
auto& sum,
auto& nCells) {
3697 for (
size_t i = begin;
i < end;
i++) {
3698 const auto j = inds[permutation[
i]];
3699 if (bitFieldSet(technical[
j])) {
3700 sum += b[
j][perbComponent];
3706 auto averageAllNeighbours = [&stencil, &technical, &b, &perbComponent](
auto predicateLambda,
auto& sum,
auto& nCells) {
3707 for (
const auto&
i : stencil.indices()) {
3708 if (predicateLambda(technical[
i])) {
3709 sum += b[
i][perbComponent];
3717 if (sbLayerIsOne(technical[stencil.ooo()])) {
3718 averageNeigbours(0ul, 2ul, sum, nCells);
3721 averageNeigbours(2ul, 6ul, sum, nCells);
3725 averageAllNeighbours(bitFieldSet, sum, nCells);
3729 averageAllNeighbours(sbLayerIsOne, sum, nCells);
3733 cerr << __FILE__ <<
":" << __LINE__ <<
": ERROR: this should not have fallen through." << endl;
3738 return sum / nCells;
3746 std::array<Real, fsgrids::ehall::N_EHALL>& cp = ehall[stencil.ooo()];
3747 switch (component) {
3767 cerr << __FILE__ <<
":" << __LINE__ <<
":" <<
" Invalid component" << endl;
3794 const Real xcen = 0.5 * (xmin + xmax);
3795 const Real ycen = 0.5 * (ymin + ymax);
3796 const Real zcen = 0.5 * (zmin + zmax);
3797 std::array<std::array<Real, 3>, 6> tracepoints;
3798 tracepoints[0] = {xmin, ycen, zcen};
3799 tracepoints[1] = {xmax, ycen, zcen};
3800 tracepoints[2] = {xcen, ymin, zcen};
3801 tracepoints[3] = {xcen, ymax, zcen};
3802 tracepoints[4] = {xcen, ycen, zmin};
3803 tracepoints[5] = {xcen, ycen, zmax};
3804 std::array<Real, 6> potentials;
3805 for (
int i = 0;
i < 6;
i++) {
3807 potentials[
i] =
ionosphereGrid.interpolateUpmappedPotential(tracepoints[
i]);
3822 Vec3d r(xcen, ycen, zcen);
3830 const Real Bsqr = B[0] * B[0] + B[1] * B[1] + B[2] * B[2];
3850 Real temperature = 0;
3853#pragma GCC diagnostic push
3854#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
3863 Real pressure = 0, vx = 0, vy = 0, vz = 0;
3866 for (
CellID celli : closestCells) {
3874 vx /= closestCells.size();
3875 vy /= closestCells.size();
3876 vz /= closestCells.size();
3877 pressure /= 3.0 * closestCells.size();
3892#pragma GCC diagnostic pop
3901 cell.
clear(popID,
false);
3903 creal initT = temperature;
3904 creal initV0X = vDrift[0];
3905 creal initV0Y = vDrift[1];
3906 creal initV0Z = vDrift[2];
3939 Real blockCoords[6];
3940 vmesh->getBlockInfo(blockGID, &blockCoords[0]);
3941 creal vxBlock = blockCoords[0];
3942 creal vyBlock = blockCoords[1];
3943 creal vzBlock = blockCoords[2];
3944 creal dvxCell = blockCoords[3];
3945 creal dvyCell = blockCoords[4];
3946 creal dvzCell = blockCoords[5];
3948 creal vx = vxBlock + (
i + 0.5) * dvxCell - initV0X;
3949 creal vy = vyBlock + (
j + 0.5) * dvyCell - initV0Y;
3950 creal vz = vzBlock + (
k + 0.5) * dvzCell - initV0Z;
3967 Real vNeighboursX = 0;
3968 Real vNeighboursY = 0;
3969 Real vNeighboursZ = 0;
3973 for (
CellID celli : closestCells) {
3981 pressure /= 3.0 * closestCells.size();
3982 vNeighboursX /= closestCells.size();
3983 vNeighboursY /= closestCells.size();
3984 vNeighboursZ /= closestCells.size();
3994 const Real Bsqr = BX * BX + BY * BY + BZ * BZ;
3999 cell.
clear(popID,
false);
4002 creal initV0X = vDrift[0];
4003 creal initV0Y = vDrift[1];
4004 creal initV0Z = vDrift[2];
4037 Real blockCoords[6];
4038 vmesh->getBlockInfo(blockGID, &blockCoords[0]);
4039 creal vxBlock = blockCoords[0];
4040 creal vyBlock = blockCoords[1];
4041 creal vzBlock = blockCoords[2];
4042 creal dvxCell = blockCoords[3];
4043 creal dvyCell = blockCoords[4];
4044 creal dvzCell = blockCoords[5];
4046 creal vx = vxBlock + (
i + 0.5) * dvxCell;
4047 creal vy = vyBlock + (
j + 0.5) * dvyCell;
4048 creal vz = vzBlock + (
k + 0.5) * dvzCell;
4051 creal mu = (vx * BX + vy * BY + vz * BZ) /
sqrt(Bsqr) /
sqrt(vx * vx + vy * vy + vz * vz);
4053 creal rlength =
sqrt(RX * RX + RY * RY + RZ * RZ);
4054 creal RnormX = RX / rlength;
4055 creal RnormY = RY / rlength;
4056 creal RnormZ = RZ / rlength;
4057 creal vdotr = (vx * RnormX + vy * RnormY * vz * RnormZ);
4061 creal vNeighboursdotr = (vNeighboursX * RnormX + vNeighboursY * RnormY + vNeighboursZ * RnormZ);
4062 creal vNeighboursMirroredX = vNeighboursX - 2 * RnormX * vNeighboursdotr;
4063 creal vNeighboursMirroredY = vNeighboursY - 2 * RnormY * vNeighboursdotr;
4064 creal vNeighboursMirroredZ = vNeighboursZ - 2 * RnormZ * vNeighboursdotr;
4069 if (1 -
mu *
mu <
sqrt(Bsqr) / 5e-5) {
4071 value =
projects::MaxwellianPhaseSpaceDensity(vx - 2 * RnormX * vdotr - vNeighboursMirroredX, vy - 2 * RnormY * vdotr - vNeighboursMirroredY, vz - 2 * RnormZ * vdotr - vNeighboursMirroredZ, temperature,
density, mass);
4094 mpiGrid[cellID]->adjustSingleCellVelocityBlocks(popID,
true);
4117 Real initRho, initT, initV0X, initV0Y, initV0Z;
4158 Real blockCoords[6];
4159 vmesh->getBlockInfo(blockGID, &blockCoords[0]);
4160 creal vxBlock = blockCoords[0];
4161 creal vyBlock = blockCoords[1];
4162 creal vzBlock = blockCoords[2];
4163 creal dvxCell = blockCoords[3];
4164 creal dvyCell = blockCoords[4];
4165 creal dvzCell = blockCoords[5];
4167 creal vx = vxBlock + (
i + 0.5) * dvxCell - initV0X;
4168 creal vy = vyBlock + (
j + 0.5) * dvyCell - initV0Y;
4169 creal vz = vzBlock + (
k + 0.5) * dvzCell - initV0Z;
4184 templateCell.adjustSingleCellVelocityBlocks(popID,
true);
sqrt(1.0+vA *vA/(c *c))) % Ion-acoustic wave cS
#define ARCH_INNER_BODY(...)
void calculateCellMoments(spatial_cell::SpatialCell *cell, const bool &computeSecond, const bool &computePopulationMomentsOnly, const bool &doNotSkip)
static void addComposing(const std::string &name, const std::string &desc)
static void get(const std::string &name, std::string &value)
static void add(const std::string &name, const std::string &desc, const std::string &defValue)
virtual void initSysBoundary(creal &t, Project &project) override
static enum SBC::Ionosphere::downmapSamplingMode downmapFACsamplingMode
virtual void applyInitialState(dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, fsgrids::perbspan perb, fsgrids::bgbspan bgb, Project &project) override
static void addParameters()
static bool useEigenSolver
static std::vector< IonosphereSpeciesParameters > speciesParams
static Real unmappedNodeRho
virtual std::string getName() const override
virtual Real fieldSolverBoundaryCondMagneticField(fsgrids::perbspan b, fsgrids::constbgbspan bgb, fsgrids::consttechnicalspan technical, const std::array< Real, 3 > &gridSpacing, const std::array< fsgrid::FsSize_t, 3 > &globalCoordinates, const fsgrid::FsStencil &stencil, cuint component) override
static Real solverRelativeL2ConvergenceThreshold
virtual uint getIndex() const override
static Real downmapRadius
static Real shieldingLatitude
virtual void getParameters() override
virtual void mapCellPotentialAndGetEXBDrift(std::array< Real, CellParams::N_SPATIAL_CELL_PARAMS > &cellParams) override
virtual void updateState(dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, fsgrids::perbspan perb, fsgrids::bgbspan bgb, creal t) override
virtual void fieldSolverBoundaryCondElectricField(fsgrids::efieldspan e, const fsgrid::FsStencil &stencil, cuint component) override
virtual void getFaces(bool *faces) override
static int solverMaxFailureCount
std::string atmosphericModelFile
static Real unmappedNodeTe
static bool solverPreconditioning
std::vector< Real > refineMaxLatitudes
virtual void assignSysBoundary(dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid) override
static Real couplingInterval
static Real backgroundIonisation
spatial_cell::SpatialCell templateCell
static Real ridleyParallelConductivity
static Real downmapSamplingWidth
std::array< Real, 3 > fieldSolverGetNormalDirection(fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, cint i, cint j, cint k)
static int solverMaxIterations
static Real couplingTimescale
std::vector< Real > refineMinLatitudes
virtual void vlasovBoundaryCondition(dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid, const CellID &cellID, const uint popID, const bool calculate_V_moments) override
void setCellFromTemplate(SpatialCell *cell, const uint popID)
static bool solverToggleMinimumResidualVariant
virtual void fieldSolverBoundaryCondDerivatives(fsgrids::dperbspan dperb, fsgrids::dmomentsspan dmoments, const fsgrid::FsStencil &stencil, cuint RKCase, cuint component) override
IonosphereConductivityModel
static enum SBC::Ionosphere::IonosphereConductivityModel conductivityModel
virtual void fieldSolverBoundaryCondBVOLDerivatives(fsgrids::volspan vols, const fsgrid::FsStencil &stencil, cuint component) override
Real earthAngularVelocity
virtual void fieldSolverBoundaryCondHallElectricField(fsgrids::ehallspan ehall, const fsgrid::FsStencil &stencil, cuint component) override
static bool solverUseMinimumResidualVariant
virtual void fieldSolverBoundaryCondGradPeElectricField(fsgrids::egradpespan EGradPe, const fsgrid::FsStencil &stencil, cuint component) override
static Real solverMaxErrorGrowthFactor
static void setCellBVOLDerivativesToZero(fsgrids::volspan vols, const fsgrid::FsStencil &stencil, cuint component)
virtual void generateTemplateCell()
std::vector< std::array< int, 3 > > getAllClosestNonsysboundaryCells(fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, cint i, cint j, cint k)
void copyCellData(const SpatialCell *from, SpatialCell *to, const bool copyMomentsOnly, const uint popID, const bool copy_V_moments)
static void setCellDerivativesToZero(fsgrids::dperbspan dperb, fsgrids::dmomentsspan dmoments, const fsgrid::FsStencil &stencil, cuint component)
bool checkMesh(const uint popID)
vmesh::VelocityMesh * get_velocity_mesh(const size_t &popID)
vmesh::VelocityBlockContainer * get_velocity_blocks(const size_t &popID)
void clear(const uint popID, bool shrink=false)
void prepare_to_receive_blocks(const uint popID)
void applyReservation(const uint popID)
std::array< Real, CellParams::N_SPATIAL_CELL_PARAMS > parameters
void setReservation(const uint popID, const vmesh::LocalID reservationsize, bool force=false)
vmesh::VelocityBlockContainer * dev_get_velocity_blocks(const size_t &popID)
vmesh::VelocityMesh * dev_get_velocity_mesh(const size_t &popID)
ARCH_HOSTDEV Realf * getData()
const std::vector< CellID > & getLocalCells()
@ N_IONOSPHERE_PARAMETERS
T convert(const T &number)
fsgrid::FsGrid< FS_STENCIL_WIDTH > FieldSolverGrid
std::array< Real, productionNumParticleEnergies+1 > particle_energy
std::array< Real, productionNumParticleEnergies > differentialFlux
#define normalize_vector(v)
#define dot_product(av, bv)
#define cross_product(av, bv)
std::array< Real, 3 > getFractionalFsGridCellForCoord(T &grid, const std::array< Real, 3 > &x)
std::array< fsgrid::FsIndex_t, 3 > getLocalFsGridCellIndexForCoord(T &grid, const std::array< Real, 3 > &x)
Real divideIfNonZero(creal numerator, creal denominator)
Helper function.
std::array< Real, 3 > interpolateCurlB(fsgrids::perbspan perb, fsgrids::constdperbspan dperb, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, std::map< std::array< int, 3 >, std::array< Real, Rec::N_REC_COEFFICIENTS > > &reconstructionCoefficientsCache, cint i, cint j, cint k, const std::array< Real, 3 > x)
Hardcoded lookup- and interpolation tables for semiempirical ionosphere model implementations.
static const Real c4P_values[]
static const Real chapman_euv_table[1201]
static const Real c4H_values[]
static const Real c5P_values[]
static const Real c5H_values[]
std::function< Real(Real)> c5P
std::function< Real(Real)> c4P
ObjectWrapper & getObjectWrapper()
std::function< Real(Real)> c5H
std::function< Real(Real)> c4H
FieldTracingParameters fieldTracingParameters
std::array< std::pair< int, Real >, 3 > calculateIonosphereVlasovGridCoupling(std::array< Real, 3 > x, std::vector< SBC::SphericalTriGrid::Node > &nodes, creal couplingRadius)
vmesh::LocalID findMaxwellianBlocksToInitialize(const uint popID, spatial_cell::SpatialCell &cell, creal &rho, creal &T, creal &VX0, creal &VY0, creal &VZ0)
SBC::findMaxwellianBlocksToInitialize returns a list of blocks to construct the VDF with.
IonosphereBoundaryVDFmode
static constexpr Real productionMinAccEnergy
static Real SergienkoIvanovLambda(Real E0, Real Chi)
static constexpr Real ion_electron_T_ratio
static constexpr Real productionMaxAccEnergy
static constexpr int productionNumAccEnergies
static constexpr int productionNumParticleEnergies
static constexpr Real productionMaxTemperature
SphericalTriGrid ionosphereGrid
static constexpr Real productionMinTemperature
IonosphereBoundaryVDFmode boundaryVDFmode
static const int MAX_DEPENDING_NODES
Real getR(creal x, creal y, creal z, uint geometry, Real center[3])
static constexpr int productionNumTemperatures
static const int MAX_TOUCHING_ELEMENTS
static Real ReesIsotropicLambda(Real x)
static void parallel_reduce(const uint(&limits)[NDim], Lambda loop_body, T &sum)
std::span< std::array< Real, fsgrids::bfield::N_BFIELD > > perbspan
std::span< const std::array< Real, bgbfield::N_BGB > > constbgbspan
std::span< std::array< Real, fsgrids::moments::N_MOMENTS > > momentsspan
std::span< std::array< Real, fsgrids::egradpe::N_EGRADPE > > egradpespan
std::span< std::array< Real, fsgrids::dmoments::N_DMOMENTS > > dmomentsspan
std::span< technical > technicalspan
std::span< std::array< Real, bgbfield::N_BGB > > bgbspan
std::span< std::array< Real, fsgrids::dperb::N_DPERB > > dperbspan
std::span< const technical > consttechnicalspan
std::span< std::array< Real, fsgrids::efield::N_EFIELD > > efieldspan
std::span< std::array< Real, fsgrids::volfields::N_VOL > > volspan
std::span< std::array< Real, fsgrids::ehall::N_EHALL > > ehallspan
std::span< const std::array< Real, fsgrids::dperb::N_DPERB > > constdperbspan
ARCH_HOSTDEV Realf MaxwellianPhaseSpaceDensity(creal &vx, creal &vy, creal &vz, creal &T, creal &rho, creal &mass)
static const Real recombAlpha
std::vector< species::Species > particleSpecies
std::array< uint32_t, 3 > corners
std::array< iSolverReal, N_IONOSPHERE_PARAMETERS > parameters
std::array< uint32_t, MAX_TOUCHING_ELEMENTS > touchingElements
std::array< Real, 9 > sigmaAverage(uint elementIndex)
void mapDownBoundaryData(fsgrids::perbspan perb, fsgrids::constdperbspan dperb, fsgrids::momentsspan moments, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid)
void initializeSphericalFibonacci(int n)
void initializeOctahedron()
Real lookupProductionValue(int heightindex, Real energy_keV, Real temperature_keV)
std::array< Real, 3 > BGB
std::array< std::array< std::array< Real, productionNumTemperatures >, productionNumAccEnergies >, numAtmosphereLevels > productionTable
std::array< Real, 3 > computeGradT(const std::array< Real, 3 > &a, const std::array< Real, 3 > &b, const std::array< Real, 3 > &c)
Real elementArea(uint32_t elementIndex)
void calculatePrecipitation()
uint32_t findNodeAtCoordinates(std::array< Real, 3 > x)
Real interpolateUpmappedPotential(const std::array< Real, 3 > &x)
std::map< std::array< Real, 3 >, std::array< std::pair< int, Real >, 3 > > vlasovGridCoupling
FieldFunction dipoleField
Eigen::Vector3d commonEdgeMidpoint(uint32_t el1, uint32_t el2)
void normalizeRadius(Node &n, Real R)
void readAtmosphericModelFile(const char *filename)
void calculateConductivityTensor(const Real F10_7, const Real recombAlpha, const Real backgroundIonisation, const bool refillTensorAtRestart=false)
void initSolver(bool zeroOut=true)
void initializeGridFromFile(std::string path)
std::vector< Eigen::Vector3d > elementDivFreeCurrent
void addAllMatrixDependencies(uint nodeIndex)
void subdivideElement(uint32_t e)
int32_t findElementNeighbour(uint32_t e, int n1, int n2)
void initializeTetrahedron()
void updateIonosphereCommunicator(dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid)
Real areaInDualPolygon(uint gridNode, uint gridElem)
std::vector< Node > nodes
Eigen::Vector3d elementNormal(uint32_t el)
void updateConnectivity()
void solveInternal(int &iteration, int &nRestarts, Real &residual, Real &minPotentialN, Real &maxPotentialN, Real &minPotentialS, Real &maxPotentialS)
iSolverReal Atimes(uint nodeIndex, int parameter, bool transpose=false)
Eigen::Vector3d elementCircumcentre(uint el)
void stitchRefinementInterfaces()
std::vector< Element > elements
enum SBC::SphericalTriGrid::IonosphereIonizationModel ionizationModel
void initializeIcosahedron()
std::array< AtmosphericLayer, numAtmosphereLevels > atmosphere
Real Asolve(uint nodeIndex, int parameter, bool transpose=false)
static constexpr int numAtmosphereLevels
std::vector< Eigen::Vector3d > elementCurlFreeCurrent
double elementIntegral(uint elementIndex, int i, int j, bool transpose=false)
void solve(int &iteration, int &nRestarts, Real &residual, Real &minPotentialN, Real &maxPotentialN, Real &minPotentialS, Real &maxPotentialS)
void addMatrixDependency(uint node1, uint node2, Real coeff, bool transposed=false)
static bool ionosphereJustSolved
static ARCH_HOSTDEV VecSimple< T > min(VecSimple< T > const &l, VecSimple< T > const &r)
static ARCH_HOSTDEV VecSimple< T > max(VecSimple< T > const &l, VecSimple< T > const &r)
static ARCH_HOSTDEV VecSimple< T > abs(const VecSimple< T > &l)
static ARCH_HOSTDEV VecSimple< T > floor(VecSimple< T > const &a)