Vlasiator ebf0dd394 on dev (v5.4.0 + 1054 commits)
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include <iostream>
2#include <sys/time.h>
3#include "vlsv_writer.h"
4#include "vlsv_reader_parallel.h"
8#include "../../iowrite.h"
9#include "../../ioread.h"
11#include "../../logger.h"
14
15#include <Eigen/Sparse>
16#include <Eigen/Geometry>
17
18#define NODE_CONSTRAINT_REDUCTION 1
19#define ELEMENT_CONSTRAINT_REDUCTION 1
20
21using namespace std;
22using namespace SBC;
23using namespace vlsv;
24
30bool globalflags::doRefine=false;
36
37// Dummy implementations of some functions to make things compile
38std::vector<CellID> localCellDummy;
39const std::vector<CellID>& getLocalCells() { return localCellDummy; }
40void deallocateRemoteCellBlocks(dccrg::Dccrg<spatial_cell::SpatialCell, dccrg::Cartesian_Geometry, std::tuple<>, std::tuple<> >&) {};
41void updateRemoteVelocityBlockLists(dccrg::Dccrg<spatial_cell::SpatialCell, dccrg::Cartesian_Geometry, std::tuple<>, std::tuple<> >&, unsigned int, unsigned int) {};
42void recalculateLocalCellsCache(const dccrg::Dccrg<spatial_cell::SpatialCell, dccrg::Cartesian_Geometry, std::tuple<>, std::tuple<> >&) {};
45
46
47
48Eigen::Vector3d getElementBarycentre(SphericalTriGrid& grid, uint32_t el) {
49 Eigen::Vector3d barycentre(0,0,0);
50
51 SphericalTriGrid::Element& element = grid.elements[el];
52 for(uint i=0; i<3; i++) {
53 Eigen::Vector3d corner(grid.nodes[element.corners[i]].x.data());
54
55 barycentre += corner;
56 }
57 barycentre /= 3.;
58
59 return barycentre;
60}
61
62// Element Circumcentre
63// Calculate the intersection of the perpendicular bisectors of two edges of the triangle
64Eigen::Vector3d getElementCircumcentre(SphericalTriGrid& grid, uint el) {
65 Eigen::Vector3d circumcentre(0,0,0);
66
67 SphericalTriGrid::Element& element = grid.elements[el];
68 uint corner1 = element.corners[0];
69 uint corner2 = element.corners[1];
70 uint corner3 = element.corners[2];
71
72 Eigen::Vector3d a(grid.nodes[corner1].x.data());
73 Eigen::Vector3d b(grid.nodes[corner2].x.data());
74 Eigen::Vector3d c(grid.nodes[corner3].x.data());
75
76 Eigen::Vector3d edge1 = b - a;
77 Eigen::Vector3d edge2 = c - a;
78
79 Eigen::Vector3d edge1Mid = a + edge1 / 2.;
80 Eigen::Vector3d edge2Mid = a + edge2 / 2.;
81
82 Eigen::Vector3d normal = edge1.cross(edge2).normalized();
83
84 if(normal.dot(a) < 0) {
85 normal *= -1.;
86 }
87
88 Eigen::Vector3d edge1Perpendicular = normal.cross(edge1).normalized();
89 Eigen::Vector3d edge2Perpendicular = normal.cross(edge2).normalized();
90
91 Eigen::Matrix<Real, 3, 2> A;
92 A.col(0) = edge1Perpendicular;
93 A.col(1) = - edge2Perpendicular;
94 Eigen::Vector3d bVec = edge2Mid - edge1Mid;
95 Eigen::Vector2d t = A.colPivHouseholderQr().solve(bVec);
96 Eigen::Vector3d residual = A * t - bVec;
97 if (residual.norm() > 1e-6) {
98 cerr << "Circumcentre calculation failed, residual: " << residual.norm() << endl;
99 }
100
101 // Verify that the solution is correct
102 Eigen::Vector3d intersection = edge1Mid + t(0) * edge1Perpendicular;
103 Eigen::Vector3d intersection2 = edge2Mid + t(1) * edge2Perpendicular;
104 if((intersection - intersection2).norm() > 1e-6) {
105 cerr << "Circumcentre calculation failed, intersection points do not match: "
106 << (intersection - intersection2).norm() << endl;
107 }
108 circumcentre = intersection;
109
110 // Check that circumcentre is inside the triangle, use barycentric coordinates
111 Eigen::Vector3d v0 = b - a;
112 Eigen::Vector3d v1 = c - a;
113 Eigen::Vector3d v2 = circumcentre - a;
114
115 double d00 = v0.dot(v0);
116 double d01 = v0.dot(v1);
117 double d11 = v1.dot(v1);
118 double d20 = v2.dot(v0);
119 double d21 = v2.dot(v1);
120
121 double denom = d00 * d11 - d01 * d01;
122 double v = (d11 * d20 - d01 * d21) / denom;
123 double w = (d00 * d21 - d01 * d20) / denom;
124 double u = 1.0 - v - w;
125
126 if (u < 0 || v < 0 || w < 0) {
127 cerr << "Circumcentre is outside the triangle, element: " << el << endl;
128 }
129
130
131 return circumcentre;
132}
133
134// Element Barycentre
135Eigen::Vector3d getElementNormal(SphericalTriGrid& grid, uint32_t el) {
136 Eigen::Vector3d normal(0,0,0);
137
138 SphericalTriGrid::Element& element = grid.elements[el];
139 uint32_t corner1 = element.corners[0];
140 uint32_t corner2 = element.corners[1];
141 uint32_t corner3 = element.corners[2];
142
143 Eigen::Vector3d a(grid.nodes[corner1].x.data());
144 Eigen::Vector3d b(grid.nodes[corner2].x.data());
145 Eigen::Vector3d c(grid.nodes[corner3].x.data());
146
147 Eigen::Vector3d edge1 = b - a;
148 Eigen::Vector3d edge2 = c - a;
149
150 normal = edge1.cross(edge2);
151
152 normal.normalized();
153
154 if(normal.dot(getElementCircumcentre(grid, el)) < 0) {
155 normal *= -1.;
156 }
157
158 return normal;
159}
160
161
162Eigen::Vector3d getCommonEdgeMidpoint(SphericalTriGrid& grid, uint32_t el1, uint32_t el2) {
163 SphericalTriGrid::Element& element1 = grid.elements[el1];
164 SphericalTriGrid::Element& element2 = grid.elements[el2];
165
166 // Get common edge to these two elements
167 for(uint i=0; i<3; i++) {
168 if(element1.corners[i] == element2.corners[0] ||
169 element1.corners[i] == element2.corners[1] ||
170 element1.corners[i] == element2.corners[2]) {
171 for(uint j=0; j<3; j++) {
172 if(i != j && (element1.corners[j] == element2.corners[0] ||
173 element1.corners[j] == element2.corners[1] ||
174 element1.corners[j] == element2.corners[2])) {
175
176 uint corner1 = element1.corners[i];
177 uint corner2 = element1.corners[j];
178
179 Eigen::Vector3d a(grid.nodes[corner1].x.data());
180 Eigen::Vector3d b(grid.nodes[corner2].x.data());
181
182 Eigen::Vector3d midpoint = (a + b) / 2.;
183 return midpoint;
184 }
185 }
186 }
187 }
188 // This should not happen
189 return {0,0,0};
190}
191
193 Real A = 0.;
194
195 for(uint i = 0; i < grid.nodes[gridNode].numTouchingElements; i++){
196 uint32_t gridEl = grid.nodes[gridNode].touchingElements[i];
197 Eigen::Vector3d nodePosition(grid.nodes[gridNode].x.data());
198
199 SphericalTriGrid::Element& element = grid.elements[gridEl];
200
201 int gridI=0,gridJ=0;
202 int localC=0,localI=0,localJ=0;
203 for(int c=0; c < 3; c++) {
204 if(element.corners[c] == gridNode) {
205 localC = c;
206 localI = (c+1)%3;
207 gridI=element.corners[localI];
208 localJ = (c+2)%3;
209 gridJ=element.corners[localJ];
210 break;
211 }
212 }
213
214 uint otherElementi = grid.findElementNeighbour(gridEl, localC, localI);
215 uint otherElementj = grid.findElementNeighbour(gridEl, localC, localJ);
216
217 Eigen::Vector3d midpointi = getCommonEdgeMidpoint(grid, gridEl, otherElementi);
218 Eigen::Vector3d midpointj = getCommonEdgeMidpoint(grid, gridEl, otherElementj);
219
220 Eigen::Vector3d circumcentre = getElementCircumcentre(grid, gridEl);
221
222 Real heighti = (nodePosition - midpointi).norm();
223 Real heightj = (nodePosition - midpointj).norm();
224
225 Real basei = (circumcentre - midpointi).norm();
226 Real basej = (circumcentre - midpointj).norm();
227
228 A += (0.5 * basei * heighti) + (0.5 * basej * heightj);
229 }
230
231 return A;
232}
233
234Real getAreaInDualPolygon(SphericalTriGrid& grid, uint gridNode, uint gridElem) {
235 Real A = 0.;
236
237 Eigen::Vector3d nodePosition(grid.nodes[gridNode].x.data());
238 SphericalTriGrid::Element& element = grid.elements[gridElem];
239
240 int localC=0,localI=0,localJ=0;
241 for(int c=0; c < 3; c++) {
242 if(element.corners[c] == gridNode) {
243 localC = c;
244 localI = (c+1)%3;
245 localJ = (c+2)%3;
246 break;
247 }
248 }
249
250 uint otherElementi = grid.findElementNeighbour(gridElem, localC, localI);
251 uint otherElementj = grid.findElementNeighbour(gridElem, localC, localJ);
252
253 Eigen::Vector3d midpointi = getCommonEdgeMidpoint(grid, gridElem, otherElementi);
254 Eigen::Vector3d midpointj = getCommonEdgeMidpoint(grid, gridElem, otherElementj);
255
256 Eigen::Vector3d circumcentre = getElementCircumcentre(grid, gridElem);
257
258 Real heighti = (nodePosition - midpointi).norm();
259 Real heightj = (nodePosition - midpointj).norm();
260
261 Real basei = (circumcentre - midpointi).norm();
262 Real basej = (circumcentre - midpointj).norm();
263
264 A += (0.5 * basei * heighti) + (0.5 * basej * heightj);
265
266 return A;
267}
268
269// Ionosoheric Sigma calculation function from
270// Juusola et al. 2025
271// Coefficients are in ionosphere_tables.h
272// Note: MLT is in hours
273std::function<Real(Real)> c4P = [](Real MLT) {
274 MLT = fmod(MLT, 24.);
275 int sector = MLT;
276 Real interpolant = MLT - sector;
277 return (1.-interpolant)*c4P_values[sector] + interpolant * c4P_values[(sector+1)%24];
278};
279
280std::function<Real(Real)> c5P = [](Real MLT) {
281 MLT = fmod(MLT, 24.);
282 int sector = MLT;
283 Real interpolant = MLT - sector;
284 return (1.-interpolant)*c5P_values[sector] + interpolant * c5P_values[(sector+1)%24];
285};
286
287std::function<Real(Real)> c4H = [](Real MLT) {
288 MLT = fmod(MLT, 24.);
289 int sector = MLT;
290 Real interpolant = MLT - sector;
291 return (1.-interpolant)*c4H_values[sector] + interpolant * c4H_values[(sector+1)%24];
292};
293
294
295std::function<Real(Real)> c5H = [](Real MLT) {
296 MLT = fmod(MLT, 24.);
297 int sector = MLT;
298 Real interpolant = MLT - sector;
299 return (1.-interpolant)*c5H_values[sector] + interpolant * c5H_values[(sector+1)%24];
300};
301
302// Drop-in replacement for cosine function for describing plasma production at the height of max plasma production
303// using the Chapman function (which assumes the earth is round, not flat).
304//
305// The advantage of this approach is that the conductance gradient at the terminator is
306// more realistic. This is important since conductance gradients appear in the equations that
307// relate electric and magnetic fields. In addition, conductances above 90° sza are positive.
308// The code is based on table lookup, and does not calculate the Chapman function.
309// Author: S. M. Hatch (2024)
311 Real degrees = fabs(sza) / M_PI * 180;
312
313 // Clamp to table lookup range
314 degrees = max(0.,degrees);
315 degrees = min(120.,degrees);
316
317 int bin = degrees * 10.;
318 Real interpolant = bin - (degrees * 10.);
319 return (1.-interpolant) * chapman_euv_table[bin] + interpolant * chapman_euv_table[bin+1];
320}
321
322void assignConductivityTensor(std::vector<SphericalTriGrid::Node>& nodes, Real sigmaP, Real sigmaH) {
323 static const char epsilon[3][3][3] = {
324 {{0,0,0},{0,0,1},{0,-1,0}},
325 {{0,0,-1},{0,0,0},{1,0,0}},
326 {{0,1,0},{-1,0,0},{0,0,0}}
327 };
328
329 for(uint n=0; n<nodes.size(); n++) {
330 std::array<Real, 3> b = {nodes[n].x[0] / Ionosphere::innerRadius, nodes[n].x[1] / Ionosphere::innerRadius, nodes[n].x[2] / Ionosphere::innerRadius};
331 if(nodes[n].x[2] >= 0) {
332 b[0] *= -1;
333 b[1] *= -1;
334 b[2] *= -1;
335 }
336 for(int i=0; i<3; i++) {
337 for(int j=0; j<3; j++) {
338 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] = sigmaP * (((i==j)? 1. : 0.) - b[i]*b[j]);
339 for(int k=0; k<3; k++) {
340 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] -= sigmaH * epsilon[i][j][k]*b[k];
341 }
342 }
343 }
344 }
345}
346
347void assignConductivityTensorFromLoadedData(std::vector<SphericalTriGrid::Node>& nodes) {
348 static const char epsilon[3][3][3] = {
349 {{0,0,0},{0,0,1},{0,-1,0}},
350 {{0,0,-1},{0,0,0},{1,0,0}},
351 {{0,1,0},{-1,0,0},{0,0,0}}
352 };
353
354 for(uint n=0; n<nodes.size(); n++) {
355 Real sigmaH = nodes[n].parameters[ionosphereParameters::SIGMAH];
356 Real sigmaP = nodes[n].parameters[ionosphereParameters::SIGMAP];
357 std::array<Real, 3> b = {nodes[n].x[0] / Ionosphere::innerRadius, nodes[n].x[1] / Ionosphere::innerRadius, nodes[n].x[2] / Ionosphere::innerRadius};
358 if(nodes[n].x[2] >= 0) {
359 b[0] *= -1;
360 b[1] *= -1;
361 b[2] *= -1;
362 }
363 for(int i=0; i<3; i++) {
364 for(int j=0; j<3; j++) {
365 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] = sigmaP * (((i==j)? 1. : 0.) - b[i]*b[j]);
366 for(int k=0; k<3; k++) {
367 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] -= sigmaH * epsilon[i][j][k]*b[k];
368 }
369 }
370 }
371 }
372}
373
374std::vector<Real> edgeLength;
376
377// Unique lookup of edges given a pair of nodes.
378std::tuple<uint,int> getEdgeIndexOrientation(uint32_t a, uint32_t b) {
379
380 int orientation = 0;
381
382 // Edges are sorted by adjacent node index (directed to go from smaller to larger index)
383 uint32_t low = std::min(a,b);
384 uint32_t high = std::max(a,b);
385
386 // If a->b is the natural ordering of this edge, return 1 for orientation, otherwise -1
387 if(low == a) {
388 orientation = 1;
389 } else {
390 orientation = -1;
391 }
392
393 // We use a 64bit value of both edges as the hash value
394 uint64_t hash = high;
395 hash <<= 32;
396 hash |= low;
397
398 if(edgeIndex.count(hash) == 0) {
399 // Add entry into array
400 edgeIndex[hash] = edgeLength.size();
401 edgeLength.push_back(0.);
402 }
403
404 return {edgeIndex[hash], orientation};
405}
406
407// Interpolate edge-based quantity to elements (barycentres) using Whitney 1-forms.
408// (DOI: 10.1145/1141911.1141991)
409Eigen::Vector3d whitneyInterpolate(SphericalTriGrid& grid, uint32_t el, std::vector<Real> edgeValue) {
410 std::array<uint32_t, 3>& corners = grid.elements[el].corners;
411 Real A = grid.elementArea(el);
412
413 auto [e1,o1] = getEdgeIndexOrientation(corners[0],corners[1]);
414 auto [e2,o2] = getEdgeIndexOrientation(corners[1],corners[2]);
415 auto [e3,o3] = getEdgeIndexOrientation(corners[2],corners[0]);
416
417 Eigen::Vector3d r0(grid.nodes[corners[0]].x.data());
418 Eigen::Vector3d r1(grid.nodes[corners[1]].x.data());
419 Eigen::Vector3d r2(grid.nodes[corners[2]].x.data());
420
421 Eigen::Vector3d barycentre = (r0+r1+r2)/3.;
422
423 // Barycentric coordinates
424 auto lambda1 = [&r0,&r1,&r2,&A](const Eigen::Vector3d& p) {
425 return ((r0-p).cross(r1-p)).norm() / (2*A);
426 };
427 auto lambda2 = [&r0,&r1,&r2,&A](const Eigen::Vector3d& p) {
428 return ((r1-p).cross(r2-p)).norm() / (2*A);
429 };
430 auto lambda3 = [&r0,&r1,&r2,&A](const Eigen::Vector3d& p) {
431 return ((r2-p).cross(r0-p)).norm() / (2*A);
432 };
433
434 // Barycentric gradients (these are constant per element)
435 Eigen::Vector3d gradLambda1 = edgeLength[e1] / (2 * A) * (r1-r0).cross(r2-r0).cross(r1-r0).normalized();
436 Eigen::Vector3d gradLambda2 = edgeLength[e2] / (2 * A) * (r2-r1).cross(r0-r1).cross(r2-r1).normalized();
437 Eigen::Vector3d gradLambda3 = edgeLength[e3] / (2 * A) * (r2-r0).cross(r1-r0).cross(r2-r0).normalized();
438
439 // Whitney 1-form basis functions
440 auto w1 = [&](const Eigen::Vector3d& p) {
441 return lambda2(p) * gradLambda3 - lambda3(p) * gradLambda2;
442 };
443 auto w2 = [&](const Eigen::Vector3d& p) {
444 return lambda3(p) * gradLambda1 - lambda1(p) * gradLambda3;
445 };
446 auto w3 = [&](const Eigen::Vector3d& p) {
447 return lambda1(p) * gradLambda2 - lambda2(p) * gradLambda1;
448 };
449
450 // Effective interpolated value this element
451 return o1*edgeLength[e1]*edgeValue[e1] * w1(barycentre) + o2*edgeLength[e2]*edgeValue[e2] * w2(barycentre) + o3*edgeLength[e3]*edgeValue[e3] *w3(barycentre);
452}
453
454// Calculate neighbor's Barycentre and dual polygon - edge - intersection point.
455std::tuple<Eigen::Vector3d, Eigen::Vector3d> connectingSegmentLengths(SphericalTriGrid& grid, uint32_t el1, uint32_t el2) {
456 SphericalTriGrid::Element& element1 = grid.elements[el1];
457 SphericalTriGrid::Element& element2 = grid.elements[el2];
458
459 Eigen::Vector3d barycentre1 = getElementBarycentre(grid,el1);
460 Eigen::Vector3d barycentre2 = getElementBarycentre(grid,el2);
461
462 // Get common edge to these two elements
463 for(uint i=0; i<3; i++) {
464 if(element1.corners[i] == element2.corners[0] ||
465 element1.corners[i] == element2.corners[1] ||
466 element1.corners[i] == element2.corners[2]) {
467 for(uint j=0; j<3; j++) {
468 if(i != j && (element1.corners[j] == element2.corners[0] ||
469 element1.corners[j] == element2.corners[1] ||
470 element1.corners[j] == element2.corners[2])) {
471
472 uint corner1 = element1.corners[i];
473 uint corner2 = element1.corners[j];
474
475 Eigen::Vector3d normal1 = getElementNormal(grid,el1);
476 Eigen::Vector3d normal2 = getElementNormal(grid,el2);
477
478 Eigen::Vector3d rotatedBarycentre2 = Eigen::Vector3d(grid.nodes[corner1].x.data()) +
479 Eigen::Quaternion<Real>::FromTwoVectors(normal2, normal1).toRotationMatrix() *
480 (barycentre2 - Eigen::Vector3d(grid.nodes[corner1].x.data()));
481
482 Eigen::Vector3d corner1Position(grid.nodes[corner1].x.data());
483 Eigen::Vector3d corner2Position(grid.nodes[corner2].x.data());
484
485 Eigen::Vector3d barycentre1ToBarycentre2 = (rotatedBarycentre2 - barycentre1).normalized();
486 Eigen::Vector3d corner1ToCorner2 = (corner2Position - corner1Position).normalized();
487
488 // Get intersection of line between barycenters and line between corners
489 Eigen::Matrix<double, 3, 2> A;
490 A.col(0) = barycentre1ToBarycentre2;
491 A.col(1) = - corner1ToCorner2;
492 Eigen::Vector3d b = corner1Position - barycentre1;
493 Eigen::Vector2d t = A.colPivHouseholderQr().solve(b);
494 Eigen::Vector3d intersection = barycentre1 + t(0) * barycentre1ToBarycentre2;
495
496 return std::make_tuple(barycentre2, intersection);
497 }
498 }
499 }
500 }
501
502 // Not found, something went bananas.
503 abort();
504}
505
506// Interpolate edge-based quantity to nodes, by bisecting the node in coordinate-orthohonal planes and calculating the fluxes through those planes
507Eigen::Vector3d interpolateEdgeToNode(SphericalTriGrid& grid, uint32_t n, std::vector<Real> edgeValue) {
508
509 Eigen::Vector3d Jl(0,0,0), Jr(0,0,0);
510
511 // Sum incoming and outgoing edge vectors coordinate-component wise
512 Eigen::Vector3d summedPathl(0,0,0), summedPathr(0,0,0);
513 for(uint32_t el=0; el< grid.nodes[n].numTouchingElements; el++) {
514 int32_t elementn = grid.nodes[n].touchingElements[el];
515 SphericalTriGrid::Element& element = grid.elements[elementn];
516 // Find the two other nodes on this element
517 int i=0,j=0;
518 int cn=0,ci=0,cj=0;
519 for(int c=0; c< 3; c++) {
520 if(element.corners[c] == n) {
521 cn = c;
522 ci = (c+1)%3;
523 i=element.corners[ci];
524 cj = (c+2)%3;
525 j=element.corners[cj];
526 break;
527 }
528 }
529 Eigen::Vector3d ri(grid.nodes[i].x.data());
530 Eigen::Vector3d rj(grid.nodes[j].x.data());
531 Eigen::Vector3d rn(grid.nodes[n].x.data());
532
533 int32_t otherElementi = grid.findElementNeighbour(elementn, cn, ci);
534 int32_t otherElementj = grid.findElementNeighbour(elementn, cn, cj);
535
536 auto [barycentrei,intersectioni] = connectingSegmentLengths(ionosphereGrid, elementn, otherElementi);
537 auto [barycentrej,intersectionj] = connectingSegmentLengths(ionosphereGrid, elementn, otherElementj);
538
539 Eigen::Vector3d barycentren = getElementBarycentre(ionosphereGrid, elementn);
540 auto [e,orientation] = getEdgeIndexOrientation(n,i);
541 Eigen::Vector3d vi = (ri-rn).normalized();
542 Eigen::Vector3d segmenti = barycentren-intersectioni;
543 for(int c =0; c<3; c++) {
544 Eigen::Vector3d ec(0,0,0);
545 ec[c]=1;
546
547 // Sum coordinate-negative and coordinate-positive currents separately
548 Real projectedPath = (segmenti - segmenti[c]*ec).norm();
549 if(vi[c] > 0) {
550 Jr[c] += edgeValue[e] * orientation * vi[c] * projectedPath;
551 summedPathr[c] += projectedPath;
552 } else {
553 Jr[c] += edgeValue[e] * orientation * vi[c] * projectedPath;
554 summedPathl[c] += projectedPath;
555 }
556 }
557
558 std::tie(e,orientation) = getEdgeIndexOrientation(n,j);
559 Eigen::Vector3d vj = (rj-rn).normalized();
560 Eigen::Vector3d segmentj = barycentren-intersectionj;
561 for(int c =0; c<3; c++) {
562 Eigen::Vector3d ec(0,0,0);
563 ec[c]=1;
564
565 // Sum coordinate-negative and coordinate-positive currents separately
566 Real projectedPath = (segmentj - segmentj[c]*ec).norm();
567 if(vj[c] > 0) {
568 Jr[c] += edgeValue[e] * orientation * vj[c] * projectedPath;
569 summedPathr[c] += projectedPath;
570 } else {
571 Jr[c] += edgeValue[e] * orientation * vj[c] * projectedPath;
572 summedPathl[c] += projectedPath;
573 }
574 }
575 }
576 Jr = Jr.array() / summedPathr.array();
577 Jl = Jl.array() / summedPathl.array();
578
579 return (Jl+Jr)/2;
580}
581
582int main(int argc, char** argv) {
583
584 // Init MPI
585 int required=MPI_THREAD_FUNNELED;
586 int provided;
587 int myRank;
588 MPI_Init_thread(&argc,&argv,required,&provided);
589 if (required > provided){
590 MPI_Comm_rank(MPI_COMM_WORLD,&myRank);
592 cerr << "(MAIN): MPI_Init_thread failed! Got " << provided << ", need "<<required <<endl;
593 exit(1);
594 }
595 const int masterProcessID = 0;
596 logFile.open(MPI_COMM_WORLD, masterProcessID, "logfile.txt");
597
598
599 // Parse parameters
600 int numNodes = 64;
601 std::string baseShapeString = "sphericalFibonacci";
602 std::string gridFilePath;
603 std::string sigmaString="identity";
604 std::string facString="constant";
605 std::string gaugeFixString="pole";
606 std::string inputFile;
607 std::string outputFilename("output.vlsv");
608 std::string meshDescription="";
609 std::string meshFormatString;
610 std::vector<std::pair<double, double>> refineExtents;
612 bool doPrecondition = true;
613 bool writeSolverMatrix = false;
614 bool writeMesh = false;
615 bool quiet = false;
616 bool runCurlJSolver = false;
617 int multipoleL = 0;
618 int multipolem = 0;
619 if(argc ==1) {
620 cerr << "Running with default options. Run main --help to see available settings." << endl;
621 }
622 for(int i=1; i<argc; i++) {
623 if(!strcmp(argv[i], "-baseShape")) {
624 meshDescription += " -baseShape " + std::string(argv[i+1]);
625 baseShapeString = argv[++i];
626 continue;
627 }
628 if(!strcmp(argv[i], "-gridFilePath")) {
629 meshDescription += " -gridFilePath " + std::string(argv[i+1]);
630 gridFilePath = argv[++i];
631 continue;
632 }
633 if(!strcmp(argv[i], "-N")) {
634 meshDescription += " -N " + std::string(argv[i+1]);
635 numNodes = atoi(argv[++i]);
636 continue;
637 }
638 if(!strcmp(argv[i], "-r")) {
639 meshDescription += " -r " + std::string(argv[i+1]) + " " + std::string(argv[i+2]);
640 double minLat = atof(argv[++i]);
641 double maxLat = atof(argv[++i]);
642 refineExtents.push_back(std::pair<double,double>(minLat, maxLat));
643 continue;
644 }
645 if(!strcmp(argv[i], "-sigma")) {
646 sigmaString = argv[++i];
647 continue;
648 }
649 if(!strcmp(argv[i], "-fac")) {
650 facString = argv[++i];
651
652 // Special handling for multipoles
653 if(facString == "multipole") {
654 multipoleL = atoi(argv[++i]);
655 multipolem = atoi(argv[++i]);
656 }
657 continue;
658 }
659 if(!strcmp(argv[i], "-gaugeFix")) {
660 gaugeFixString = argv[++i];
661 continue;
662 }
663 if(!strcmp(argv[i], "-np")) {
664 doPrecondition = false;
665 continue;
666 }
667 if(!strcmp(argv[i], "-infile")) {
668 inputFile = argv[++i];
669 continue;
670 }
671 if(!strcmp(argv[i], "-maxIter")) {
672 Ionosphere::solverMaxIterations = atoi(argv[++i]);
673 continue;
674 }
675 if(!strcmp(argv[i], "-o")) {
676 outputFilename = argv[++i];
677 continue;
678 }
679 if(!strcmp(argv[i], "-matrix")) {
680 writeSolverMatrix = true;
681 continue;
682 }
683 if(!strcmp(argv[i], "-omesh")) {
684 writeMesh = true;
685 meshFormatString = argv[++i];
686 continue;
687 }
688 if(!strcmp(argv[i], "-q")) {
689 quiet = true;
690 continue;
691 }
692 cerr << "Unknown command line option \"" << argv[i] << "\"" << endl;
693 cerr << endl;
694 cerr << "main [-baseShape (sphericalFibonacci|icosahedron|tetrahedron|fromFile)] [-gridFilePath <filepath>] [-N num] [-r <lat0> <lat1>] [-sigma (identity|random|35|53|curlJ|file)] [-fac (constant|dipole|quadrupole|octopole|hexadecapole||file)] [-facfile <filename>] [-gaugeFix equator|equator40|equator45|equator50|equator60|pole|integral|none] [-np]" << endl;
695 cerr << "Paramters:" << endl;
696 cerr << " -baseShape: Select the seed mesh geometry for the spherical ionosphere grid. (default: sphericalFibonacci)" << endl;
697 cerr << " options are:" << endl;
698 cerr << " sphericalFibonacci - Spherical fibonacci base grid with arbitrary number of nodes n>8" << endl;
699 cerr << " icosahedron - Icosahedron grid on a sphere" << endl;
700 cerr << " tetrahedron - Tetrahedron grid on a sphere" << endl;
701 cerr << " fromFile - Load grid from a VTK or OBJ file" << endl;
702 cerr << " -gridFilePath: Path to the grid file" << endl;
703 cerr << " -N <num>: Number of nodes in the spherical Fibonacci grid (default: 64)" << endl;
704 cerr << " -r: Refine grid between the given latitudes (can be specified multiple times)" << endl;
705 cerr << " -sigma: Conductivity matrix contents (default: identity)" << endl;
706 cerr << " options are:" << endl;
707 cerr << " identity - identity matrix w/ conductivity 1" << endl;
708 cerr << " ponly - Constant pedersen conductivitu"<< endl;
709 cerr << " 10 - Sigma_H = 0, Sigma_P = 10" << endl;
710 cerr << " 35 - Sigma_H = 3, Sigma_P = 5" << endl;
711 cerr << " 53 - Sigma_H = 5, Sigma_P = 3" << endl;
712 cerr << " 100 - Sigma_H = 100, Sigma_P=20" << endl;
713 cerr << " file - Read from vlsv input file " << endl;
714 cerr << " -fac: FAC pattern on the sphere (default: constant)" << endl;
715 cerr << " options are:" << endl;
716 cerr << " constant - Constant value of 1" << endl;
717 cerr << " dipole - north/south dipole" << endl;
718 cerr << " quadrupole - east/west quadrupole (L=2, m=1)" << endl;
719 cerr << " octopole - octopole (L=3, m=2)" << endl;
720 cerr << " hexadecapole - hexadecapole (L=4, m=3)" << endl;
721 cerr << " multipole <L> <m> - generic multipole, L and m given separately." << endl;
722 cerr << " merkin2010 - eq13 of Merkin et al (2010)" << endl;
723 cerr << " file - read FAC distribution from vlsv input file" << endl;
724 cerr << " pole - testcase: FACs are nonzero only at the north pole" << endl;
725 cerr << " -infile: Read FACs from this input file" << endl;
726 cerr << " -gaugeFix: Solver gauge fixing method (default: pole)" << endl;
727 cerr << " options are:" << endl;
728 cerr << " pole - Fix potential in a single node at the north pole" << endl;
729 cerr << " equator - Fix potential on all nodes +- 10 degrees of the equator" << endl;
730 cerr << " equator40 - Fix potential on all nodes +- 40 degrees of the equator" << endl;
731 cerr << " equator45 - Fix potential on all nodes +- 45 degrees of the equator" << endl;
732 cerr << " equator50 - Fix potential on all nodes +- 50 degrees of the equator" << endl;
733 cerr << " equator60 - Fix potential on all nodes +- 60 degrees of the equator" << endl;
734 cerr << " -np: DON'T use the matrix preconditioner (default: do)" << endl;
735 cerr << " -maxIter: Maximum number of solver iterations" << endl;
736 cerr << " -o <filename>: Output filename (default: \"output.vlsv\")" << endl;
737 cerr << " -matrix: Write solver dependency matrix to solverMatrix.txt (default: don't.)" << endl;
738 cerr << " -omesh: Write the mesh to the file ionosphereMesh using a specified format (default: don't)" << endl;
739 cerr << " options are:" << endl;
740 cerr << " obj - Wavefront OBJ file format" << endl;
741 cerr << " vtk - Visualization Toolkit legacy file format" << endl;
742 cerr << " -q: Quiet mode (only output residual value" << endl;
743
744 return 1;
745 }
746
747 phiprof::initialize();
748
749 // Initialize ionosphere grid
751 if(baseShapeString == "sphericalFibonacci") {
752 if(numNodes < 8) {
753 cerr << "Spherical Fibonacci grid requires at least 8 nodes" << endl;
754 return 1;
755 }
756 ionosphereGrid.initializeSphericalFibonacci(numNodes);
757 } else if(baseShapeString == "icosahedron") {
758 ionosphereGrid.initializeIcosahedron();
759 } else if(baseShapeString == "tetrahedron") {
760 ionosphereGrid.initializeTetrahedron();
761 } else if(baseShapeString == "fromFile") {
762 if(gridFilePath.empty()) {
763 cerr << "No grid file path specified for base shape fromFile" << endl;
764 return 1;
765 }
766 ionosphereGrid.initializeGridFromFile(gridFilePath);
767 } else {
768 cerr << "Unknown mesh base shape \"" << baseShapeString << "\"" << endl;
769 return 1;
770 }
771
772 if(gaugeFixString == "pole") {
774 } else if (gaugeFixString == "integral") {
776 } else if (gaugeFixString == "equator") {
779 } else if (gaugeFixString == "equator40") {
782 } else if (gaugeFixString == "equator45") {
785 } else if (gaugeFixString == "equator50") {
788 } else if (gaugeFixString == "equator60") {
791 } else if (gaugeFixString == "none") {
793 } else {
794 cerr << "Unknown gauge fixing method " << gaugeFixString << endl;
795 return 1;
796 }
797
798 // Refine the base shape to acheive desired resolution
799 auto refineBetweenLatitudes = [](Real phi1, Real phi2) -> void {
800 uint numElems=ionosphereGrid.elements.size();
801
802 for(uint i=0; i< numElems; i++) {
803 Real mean_z = 0;
804 mean_z = ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[0]].x[2];
805 mean_z += ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[1]].x[2];
806 mean_z += ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[2]].x[2];
807 mean_z /= 3.;
808
809 if(fabs(mean_z) >= sin(phi1 * M_PI / 180.) * Ionosphere::innerRadius &&
810 fabs(mean_z) <= sin(phi2 * M_PI / 180.) * Ionosphere::innerRadius) {
811 ionosphereGrid.subdivideElement(i);
812 }
813 }
814 };
815
816 if(refineExtents.size() > 0) {
817 for(unsigned int i=0; i< refineExtents.size(); i++) {
818 refineBetweenLatitudes(refineExtents[i].first, refineExtents[i].second);
819 }
820 ionosphereGrid.stitchRefinementInterfaces();
821 }
822
823
824 std::vector<SphericalTriGrid::Node>& nodes = ionosphereGrid.nodes;
825 std::vector< Real > elementCorrectionFactors(ionosphereGrid.elements.size());
826 std::vector< Eigen::Vector3d > elementCurlFreeCurrent(ionosphereGrid.elements.size());
827 std::vector< Eigen::Vector3d > elementDivFreeCurrent(ionosphereGrid.elements.size());
828
829 // Set FACs
830 if(facString == "constant") {
831 for(uint n=0; n<nodes.size(); n++) {
832 if(n == 16){
833 nodes[n].parameters[ionosphereParameters::SOURCE] = 1;
834 } else {
835 nodes[n].parameters[ionosphereParameters::SOURCE] = 0;
836 }
837
839
840 nodes[n].parameters[ionosphereParameters::SOURCE] *= area;
841 }
842 } else if(facString == "dipole") {
843 for(uint n=0; n<nodes.size(); n++) {
844 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
845 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
846
848 nodes[n].parameters[ionosphereParameters::SOURCE] = sph_legendre(1,0,theta) * cos(0*phi) * area;
849 }
850 } else if(facString == "quadrupole") {
851 for(uint n=0; n<nodes.size(); n++) {
852 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
853 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
854
855
857
858 nodes[n].parameters[ionosphereParameters::SOURCE] = sph_legendre(2,1,theta) * cos(1*phi) * area;
859 }
860 } else if(facString == "octopole") {
861 for(uint n=0; n<nodes.size(); n++) {
862 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
863 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
864
865
867
868 nodes[n].parameters[ionosphereParameters::SOURCE] = sph_legendre(3,2,theta) * cos(2*phi) * area;
869 }
870 } else if(facString == "hexadecapole") {
871 for(uint n=0; n<nodes.size(); n++) {
872 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
873 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
874
875
877
878 nodes[n].parameters[ionosphereParameters::SOURCE] = sph_legendre(4,3,theta) * cos(3*phi) * area;
879 }
880 } else if(facString == "multipole") {
881 for(uint n=0; n<nodes.size(); n++) {
882 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
883 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
884
885
887
888 nodes[n].parameters[ionosphereParameters::SOURCE] = sph_legendre(multipoleL,fabs(multipolem),theta) * cos(multipolem*phi) * area;
889 }
890 } else if(facString == "merkin2010") {
891
892 // From Merkin et al (2010), LFM's conductivity map test setup (Figure3 / eq 13):
893 const double j_0 = 1e-6;
894 const double theta_0 = 22. / 180 * M_PI;
895 const double deltaTheta = 12. / 180 * M_PI;
896
897 for(uint n=0; n<nodes.size(); n++) {
898 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
899 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
900
901
903
904 double j_parallel=0;
905
906 // Merkin et al specifies colatitude as degrees-from-the-pole
907 if(fabs(theta) >= theta_0 && fabs(theta) < theta_0 + deltaTheta) {
908 j_parallel = j_0 * sin(M_PI/2 - fabs(theta)) * sin(phi);
909 }
910 nodes[n].parameters[ionosphereParameters::SOURCE] = j_parallel * area;
911 }
912 } else if(facString == "file") {
913 vlsv::ParallelReader inVlsv;
914 //print out that file is being read
915 if(!quiet) {
916 cerr << "Reading FAC from VLSV file " << inputFile << endl;
917 }
918 inVlsv.open(inputFile,MPI_COMM_WORLD,masterProcessID);
920 if(!quiet) {
921 cerr << "Read file." << endl;
922 }
923 for(uint i=0; i<ionosphereGrid.nodes.size(); i++) {
924 // Use the same (inaccurate) area as ig_fac
925 Real area = 0;
926 for (uint e = 0; e < ionosphereGrid.nodes[i].numTouchingElements; e++) {
927 area += ionosphereGrid.elementArea(ionosphereGrid.nodes[i].touchingElements[e]);
928 }
929 area /= 3.;
930 ionosphereGrid.nodes[i].parameters[ionosphereParameters::SOURCE] *= area;
931 // ionosphereGrid.nodes[i].parameters[ionosphereParameters::SOURCE] *= area;
932 cout << ionosphereGrid.nodes[i].parameters[ionosphereParameters::SOURCE] << endl;
933 }
934 // Also read open/closed information from the file, if it exists.
935 // (We use PPARAM as temporary storage here)
936
938 for(uint i=0; i<ionosphereGrid.nodes.size(); i++) {
939 ionosphereGrid.nodes[i].openFieldLine = ionosphereGrid.nodes[i].parameters[ionosphereParameters::PPARAM];
940 }
941 } else if(facString == "pole") {
942 for(uint i=0; i<ionosphereGrid.nodes.size(); i++) {
943 if(ionosphereGrid.nodes[i].x[2] >= Ionosphere::innerRadius * 0.95) {
944 ionosphereGrid.nodes[i].parameters[ionosphereParameters::SOURCE] = 1;
945 } else if(ionosphereGrid.nodes[i].x[2] <= -Ionosphere::innerRadius * 0.95) {
946 ionosphereGrid.nodes[i].parameters[ionosphereParameters::SOURCE] = -1;
947 } else {
948 ionosphereGrid.nodes[i].parameters[ionosphereParameters::SOURCE] = 0;
949 }
950 }
951 } else {
952 cerr << "FAC pattern " << sigmaString << " not implemented!" << endl;
953 return 1;
954 }
955
956 // Count number of elements below a certain latitude
957 uint numEquatorialElements = 0;
958 // for(uint i=0; i<ionosphereGrid.elements.size(); i++) {
959 // Real mean_z = 0;
960 // mean_z = ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[0]].x[2];
961 // mean_z += ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[1]].x[2];
962 // mean_z += ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[2]].x[2];
963 // mean_z /= 3.;
964
965 // if(fabs(mean_z) < sin(0. * M_PI / 180.) * Ionosphere::innerRadius) {
966 // numEquatorialElements++;
967 // }
968
969 // }
970
971 // Eigen vector and matrix for solving
972 Eigen::VectorXd vJ(2 * ionosphereGrid.elements.size()); // 2 * ionosphereGrid.elements.size() because we have two components of J in every element
973 Eigen::VectorXd vRHS1(ionosphereGrid.nodes.size() + ionosphereGrid.nodes.size() + 2 * numEquatorialElements); // Right hand side for divergence-free system
974 Eigen::VectorXd vRHS2(ionosphereGrid.nodes.size() + ionosphereGrid.nodes.size() + 2 * numEquatorialElements); // Right hand side for curl-free system
975 Eigen::SparseMatrix<Real> curlSolverMatrix(vRHS1.size(), vJ.size());
976
977 // uint fixed = 0;
978 // for(uint i=0; i<ionosphereGrid.elements.size(); i++) {
979
980 // Real mean_z = 0;
981 // mean_z = ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[0]].x[2];
982 // mean_z += ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[1]].x[2];
983 // mean_z += ionosphereGrid.nodes[ionosphereGrid.elements[i].corners[2]].x[2];
984 // mean_z /= 3.;
985
986 // if(fabs(mean_z) < sin(0. * M_PI / 180.) * Ionosphere::innerRadius) {
987 // curlSolverMatrix.coeffRef(2*ionosphereGrid.nodes.size() + fixed, 2*i) = 1;
988 // curlSolverMatrix.coeffRef(2*ionosphereGrid.nodes.size() + fixed + 1, 2*i + 1) = 1;
989 // vRHS1[2*ionosphereGrid.nodes.size() + fixed] = 0;
990 // vRHS1[2*ionosphereGrid.nodes.size() + fixed + 1] = 0;
991 // vRHS2[2*ionosphereGrid.nodes.size() + fixed] = 0;
992 // vRHS2[2*ionosphereGrid.nodes.size() + fixed + 1] = 0;
993 // fixed+=2;
994 // }
995
996 // }
997
998 // Set conductivity tensors
999 if(sigmaString == "identity") {
1000 for(uint n=0; n<nodes.size(); n++) {
1001 for(int i=0; i<3; i++) {
1002 for(int j=0; j<3; j++) {
1003 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] = ((i==j)? 1. : 0.);
1004 }
1005 }
1006 }
1007 } else if(sigmaString == "file") {
1008 vlsv::ParallelReader inVlsv;
1009 inVlsv.open(inputFile,MPI_COMM_WORLD,masterProcessID);
1010 // Try to read the sigma tensor directly
1012
1013 // If that doesn't exist, reconstruct it from the sigmaH and sigmaP components
1014 // (This assumes that the input file was run with the "GUMICS" conductivity model. Which is reasonable,
1015 // because the others don't work very well)
1016 if(!quiet) {
1017 cerr << "Reading conductivity tensor from ig_sigmah, ig_sigmap." << endl;
1018 }
1021 //readIonosphereNodeVariable(inVlsv, "ig_sigmaparallel", ionosphereGrid, ionosphereParameters::SIGMAPARALLEL);
1023 }
1024 } else if(sigmaString == "ponly") {
1025 Real sigmaP=3.;
1026 Real sigmaH=0.;
1027 assignConductivityTensor(nodes, sigmaP, sigmaH);
1028 } else if(sigmaString == "10") {
1029 Real sigmaP=10.;
1030 Real sigmaH=0.;
1031 assignConductivityTensor(nodes, sigmaP, sigmaH);
1032 } else if(sigmaString == "35") {
1033 Real sigmaP=3.;
1034 Real sigmaH=5.;
1035 assignConductivityTensor(nodes, sigmaP, sigmaH);
1036 } else if(sigmaString == "53") {
1037 Real sigmaP=5.;
1038 Real sigmaH=3.;
1039 assignConductivityTensor(nodes, sigmaP, sigmaH);
1040 } else if(sigmaString == "10") {
1041 Real sigmaP=10.;
1042 Real sigmaH=0.;
1043 assignConductivityTensor(nodes, sigmaP, sigmaH);
1044 } else if(sigmaString == "100") {
1045 Real sigmaP=20.;
1046 Real sigmaH=100.;
1047 assignConductivityTensor(nodes, sigmaP, sigmaH);
1048 // REMINDER: IonizationModel EBEC
1049 } else if(sigmaString == "curlJ") {
1050 runCurlJSolver = true;
1051
1052 // First, solve curl-free inplane current system.
1053 // Use those currents to estimate sigma ratio.
1054 // Then, solve divergence-free part.
1055 // Finally, estimate Sigmas.
1056
1057 // This formalism uses a cirumcentre-based current-density vector field.
1058 // For more details, see Hirani (2003) "Discrete Exterior Calculus" PhD
1059 // thesis.
1060 //
1061 // To calculate the divergence at a specific node, the current density is
1062 // first interpolated to all the edges subtended by this node by weighing
1063 // the current-density of the elements subtended by a particular edge by
1064 // the proportion of the distances from the circumcentres to the midpoint
1065 // of that edge, to the line connecting the two circumcentres. This line
1066 // will always be the perpendicular bisector of the common edge, thanks to
1067 // the fact that circumcentres are equidistant from the corners of a
1068 // triangle. Then, the dot product of the edge-interpolated current
1069 // densities with the edge parallel is taken, multiplied by the length of
1070 // the dual to this edge, and summed over all edges subtended by the node.
1071 //
1072 // Since the mesh is not flat, the edge vectors are be transformed to a
1073 // common coordinate system (XY plane at the north pole) before the dot
1074 // product is taken
1075
1076 if(!quiet) {
1077 cerr << "Using curlJ solver." << endl;
1078 }
1079
1080 if(!quiet) {
1081 cout << "Building curl solver matrix." << endl;
1082 }
1083
1084 if(!quiet) {
1085 cout << "Adding divergence constraints." << endl;
1086 }
1087
1088 // Divergence constraints
1089 for(uint gridNodeIndex=0; gridNodeIndex<ionosphereGrid.nodes.size(); gridNodeIndex++) {
1090 if(!quiet && (gridNodeIndex % 100) == 0) {
1091 cout << "Adding divergence constraints: " << gridNodeIndex << "/" << ionosphereGrid.nodes.size() << endl;
1092 }
1093
1094 // Divergence of divergence-free current
1095 vRHS1[gridNodeIndex] = 0;
1096
1097 //Divergence of curl-free current
1098 vRHS2[gridNodeIndex] = nodes[gridNodeIndex].parameters[ionosphereParameters::SOURCE];
1099
1100 for(uint32_t elLocalIndex=0; elLocalIndex<nodes[gridNodeIndex].numTouchingElements; elLocalIndex++) {
1101 SphericalTriGrid::Element& element = ionosphereGrid.elements[nodes[gridNodeIndex].touchingElements[elLocalIndex]];
1102
1103 // Find the two other nodes on this element
1104 int gridI=0,gridJ=0;
1105 int localC=0,localI=0,localJ=0;
1106 for(int c=0; c< 3; c++) {
1107 if(element.corners[c] == gridNodeIndex) {
1108 localC = c;
1109 localI = (c+1)%3;
1110 gridI=element.corners[localI];
1111 localJ = (c+2)%3;
1112 gridJ=element.corners[localJ];
1113 break;
1114 }
1115 }
1116
1117
1118
1119 int32_t otherElementi = ionosphereGrid.findElementNeighbour(nodes[gridNodeIndex].touchingElements[elLocalIndex], localC, localI);
1120 int32_t otherElementj = ionosphereGrid.findElementNeighbour(nodes[gridNodeIndex].touchingElements[elLocalIndex], localC, localJ);
1121
1122 if(otherElementi < 0 || otherElementj < 0) {
1123 cerr << "Error: Element " << nodes[gridNodeIndex].touchingElements[elLocalIndex] << " does not have neighbour with nodes " << gridI << " and " << gridJ << endl;
1124 return 1;
1125 }
1126
1127 Eigen::Vector3d circumcentrem = getElementCircumcentre(ionosphereGrid, nodes[gridNodeIndex].touchingElements[elLocalIndex]);
1128 Eigen::Vector3d midpointmi = getCommonEdgeMidpoint(ionosphereGrid, nodes[gridNodeIndex].touchingElements[elLocalIndex], otherElementi);
1129 Real li = (circumcentrem - midpointmi).norm();
1130
1131 Eigen::Vector3d rm(nodes[gridNodeIndex].x.data());
1132 Eigen::Vector3d ri(nodes[gridI].x.data());
1133 Eigen::Vector3d rj(nodes[gridJ].x.data());
1134 Eigen::Vector3d edge = (ri - rm) / (ri - rm).norm();
1135
1136 Eigen::Vector3d normalm = getElementNormal(ionosphereGrid, nodes[gridNodeIndex].touchingElements[elLocalIndex]);
1137 Eigen::Vector3d edgem = Eigen::Quaterniond::FromTwoVectors(normalm, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge;
1138
1139 // check if z value of edges exceeds 1e-6
1140 if(std::abs(edgem(2)) > 1e-6) {
1141 cerr << "Error: Z component of edgem is not zero! edgem = [" << edgem(0) << ", " << edgem(1) << ", " << edgem(2) << "]" << endl;
1142 }
1143
1144 curlSolverMatrix.coeffRef(gridNodeIndex, 2 * nodes[gridNodeIndex].touchingElements[elLocalIndex]) += edgem(0) * li;
1145 curlSolverMatrix.coeffRef(gridNodeIndex, 2 * nodes[gridNodeIndex].touchingElements[elLocalIndex] + 1) += edgem(1) * li;
1146
1147 Eigen::Vector3d midpointmj = getCommonEdgeMidpoint(ionosphereGrid, nodes[gridNodeIndex].touchingElements[elLocalIndex], otherElementj);
1148 Real lj = (circumcentrem - midpointmj).norm();
1149
1150 edge = (rj - rm) / (rj - rm).norm();
1151
1152 edgem = Eigen::Quaterniond::FromTwoVectors(normalm, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge;
1153
1154 // check if z value of edges exceeds 1e-6
1155 if(std::abs(edgem(2)) > 1e-6) {
1156 cerr << "Error: Z component of edgem is not zero! edgem = [" << edgem(0) << ", " << edgem(1) << ", " << edgem(2) << "]" << endl;
1157 }
1158
1159 curlSolverMatrix.coeffRef(gridNodeIndex, 2 * nodes[gridNodeIndex].touchingElements[elLocalIndex]) += edgem(0) * lj;
1160 curlSolverMatrix.coeffRef(gridNodeIndex, 2 * nodes[gridNodeIndex].touchingElements[elLocalIndex] + 1) += edgem(1) * lj;
1161
1162 }
1163 }
1164
1165 if(!quiet) {
1166 cout << "Done." << endl;
1167 }
1168 if(!quiet) {
1169 cout << "Adding curl constraints." << endl;
1170 }
1171
1172 // The curl at a specific node is calculated by taking half the dot product
1173 // of the edges opposite to the node with the current-density of the
1174 // elements subtended by the node, multiplied by a consistent orientation.
1175
1176 // Curl constraints
1177 for(uint n=0; n<ionosphereGrid.nodes.size(); n++) {
1178 if(!quiet && (n % 100) == 0) {
1179 cout << "Adding curl constraints: " << n << "/" << ionosphereGrid.nodes.size() << endl;
1180 }
1181
1182 // Curl of divergence-free current
1183 vRHS1[ionosphereGrid.nodes.size() + n] = ionosphereGrid.nodes[n].parameters[ionosphereParameters::SOURCE];
1184
1185 // Curl of curl-free current
1186 vRHS2[ionosphereGrid.nodes.size() + n] = 0;
1187
1188 for(uint32_t elLocalIndex=0; elLocalIndex<ionosphereGrid.nodes[n].numTouchingElements; elLocalIndex++) {
1189 SphericalTriGrid::Element& element = ionosphereGrid.elements[ionosphereGrid.nodes[n].touchingElements[elLocalIndex]];
1190
1191 // Find the two other nodes on this element
1192 int gridI=0,gridJ=0;
1193 int localC=0,localI=0,localJ=0;
1194 for(int c=0; c< 3; c++) {
1195 if(element.corners[c] == n) {
1196 localC = c;
1197 localI = (c+1)%3;
1198 gridI=element.corners[localI];
1199 localJ = (c+2)%3;
1200 gridJ=element.corners[localJ];
1201 break;
1202 }
1203 }
1204
1205 Eigen::Vector3d normal = getElementNormal(ionosphereGrid, ionosphereGrid.nodes[n].touchingElements[elLocalIndex]);
1206 Eigen::Vector3d ri(nodes[gridI].x.data());
1207 Eigen::Vector3d rj(nodes[gridJ].x.data());
1208 Eigen::Vector3d rm(nodes[n].x.data());
1209
1210 Eigen::Vector3d edgemi = (ri - rm) / (ri - rm).norm();
1211 edgemi = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edgemi;
1212
1213 if(std::abs(edgemi(2)) > 1e-6) {
1214 cerr << "Error: Z component of edgemi is not zero! edgemi = [" << edgemi(0) << ", " << edgemi(1) << ", " << edgemi(2) << "]" << endl;
1215 }
1216
1217 Eigen::Vector3d edgemj = (rj - rm) / (rj - rm).norm();
1218 edgemj = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edgemj;
1219
1220 if(std::abs(edgemj(2)) > 1e-6) {
1221 cerr << "Error: Z component of edgemj is not zero! edgemj = [" << edgemj(0) << ", " << edgemj(1) << ", " << edgemj(2) << "]" << endl;
1222 }
1223
1224 Real orientation = edgemj.cross(edgemi).dot(normal) > 0 ? 1. : -1.;
1225
1226 Eigen::Vector3d outerEdge = orientation * (rj - ri) / (rj - ri).norm();
1227 Real outerEdgeLength = (rj - ri).norm();
1228 outerEdge = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * outerEdge;
1229
1230 if(outerEdge(2) > 1e-6) {
1231 cerr << "Error: Outer edge vector is not in the XY plane! outerEdge = [" << outerEdge(0) << ", " << outerEdge(1) << ", " << outerEdge(2) << "]" << endl;
1232 }
1233
1234 curlSolverMatrix.coeffRef(ionosphereGrid.nodes.size() + n, 2 * ionosphereGrid.nodes[n].touchingElements[elLocalIndex]) += outerEdge(0) * outerEdgeLength / 2.;
1235 curlSolverMatrix.coeffRef(ionosphereGrid.nodes.size() + n, 2 * ionosphereGrid.nodes[n].touchingElements[elLocalIndex] + 1) += outerEdge(1) * outerEdgeLength / 2.;
1236 }
1237
1238
1239 }
1240
1241 curlSolverMatrix.makeCompressed();
1242
1243 if(writeSolverMatrix) {
1244 ofstream matrixOut("JSolverMatrix.txt");
1245 for(uint n=0; n<ionosphereGrid.nodes.size() + ionosphereGrid.nodes.size(); n++) {
1246 for(uint m=0; m<2*ionosphereGrid.elements.size(); m++) {
1247
1248 Real val=0;
1249 val = curlSolverMatrix.coeffRef(n, m);
1250
1251 matrixOut << val << "\t";
1252 }
1253 matrixOut << endl;
1254 }
1255 if(!quiet) {
1256 cout << "--- CURL SOLVER MATRIX WRITTEN TO JSolverMatrix.txt ---" << endl;
1257 }
1258 }
1259
1260
1261 // Verify Euler characteristic of the mesh
1262 int Chi = nodes.size() - edgeLength.size() + ionosphereGrid.elements.size();
1263 cout << "Mesh has an euler characteristic of " << Chi << endl;
1264
1265 cout << nodes.size() << " nodes, " << edgeLength.size() << " edges, " << ionosphereGrid.elements.size() << " elements." << endl;
1266
1267 // Solve curl-free currents.
1268 cout << "Solving divJ system" << endl;
1269#if 1 //NODE_CONSTRAINT_REDUCTION+ELEMENT_CONSTRAINT_REDUCTION != 2
1270 Eigen::LeastSquaresConjugateGradient<Eigen::SparseMatrix<Real>> solver;
1271#else
1272 Eigen::BiCGSTAB<Eigen::SparseMatrix<Real>> solver;
1273#endif
1274 solver.compute(curlSolverMatrix);
1275 vJ = solver.solve(vRHS2);
1276 cout << "... done with " << solver.iterations() << " iterations and remaining error " << solver.error() << "\n";
1277
1278 for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1279 // cout << "Calculating curl-free current for element " << el << "/" << ionosphereGrid.elements.size() << endl;
1280 std::array<uint32_t, 3>& corners = ionosphereGrid.elements[el].corners;
1281 Real A = ionosphereGrid.elementArea(el);
1282 Eigen::Vector3d r0(ionosphereGrid.nodes[corners[0]].x.data());
1283 Eigen::Vector3d r1(ionosphereGrid.nodes[corners[1]].x.data());
1284 Eigen::Vector3d r2(ionosphereGrid.nodes[corners[2]].x.data());
1285
1286 Eigen::Vector3d barycentre = getElementBarycentre(ionosphereGrid, el);
1287 Eigen::Vector3d rotatedVJ = Eigen::Quaterniond::FromTwoVectors(Eigen::Vector3d::UnitZ(), barycentre.normalized()).toRotationMatrix() * Eigen::Vector3d(vJ[2*el], vJ[2*el+1], 0);
1288 elementCurlFreeCurrent[el] = rotatedVJ;
1289
1290 Real MLT = atan2(barycentre[1], barycentre[0]) * 12 / M_PI + 12;
1291
1292 // Note: The coefficients want to be looked up in A/km, so we multiply by 1000
1293 Real correction = pow(c4H(MLT)/c4P(MLT) * 1000*elementCurlFreeCurrent[el].norm(),1./(1.+c5P(MLT)-c5H(MLT))) / (1000*elementCurlFreeCurrent[el].norm());
1294 elementCorrectionFactors[el] = correction;
1295 }
1296
1297 // Apply correction to RHS for divergence-free current density (vRHS1)
1298 // Interpolate from elements to nodes via proportion of dual polygon contained
1299 for(uint n=0; n<nodes.size(); n++) {
1300
1301 Real totalA = 0;
1302 Real correction = 0;
1303
1304 for(uint32_t el=0; el< nodes[n].numTouchingElements; el++) {
1305 Real A = getAreaInDualPolygon(ionosphereGrid, n, nodes[n].touchingElements[el]);
1306 totalA += A;
1307 correction += elementCorrectionFactors[nodes[n].touchingElements[el]] * A;
1308 }
1309 correction /= totalA;
1310 if(totalA - getDualPolygonArea(ionosphereGrid, n) > 1e-6) {
1311 cerr << "Warning: Dual polygon area for node " << n << " is not equal to the sum of areas of touching elements! " << totalA << " != " << getDualPolygonArea(ionosphereGrid, n) << endl;
1312 }
1313 // cout << x[2] << endl;
1314 vRHS1[nodes.size()+n] = vRHS1[nodes.size()+n]*correction;
1315 }
1316
1317
1318
1319
1320 cout << "Solving curlJ system with " << nodes.size() << " nodes, " << ionosphereGrid.elements.size() << " elements and " << edgeLength.size() << " edges.\n";
1321 Eigen::LeastSquaresConjugateGradient<Eigen::SparseMatrix<Real>> solver2;
1322 solver2.compute(curlSolverMatrix);
1323 vJ = solver2.solve(vRHS1);
1324 cout << "... done with " << solver2.iterations() << " iterations and remaining error " << solver2.error() << "\n";
1325
1326 for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1327 std::array<uint32_t, 3>& corners = ionosphereGrid.elements[el].corners;
1328 Real A = ionosphereGrid.elementArea(el);
1329
1330 Eigen::Vector3d r0(ionosphereGrid.nodes[corners[0]].x.data());
1331 Eigen::Vector3d r1(ionosphereGrid.nodes[corners[1]].x.data());
1332 Eigen::Vector3d r2(ionosphereGrid.nodes[corners[2]].x.data());
1333
1334 Eigen::Vector3d barycentre = (r0+r1+r2)/3.;
1335
1336 Eigen::Vector3d rotatedVJ = Eigen::Quaterniond::FromTwoVectors(Eigen::Vector3d::UnitZ(), barycentre.normalized()).toRotationMatrix() * Eigen::Vector3d(vJ[2*el], vJ[2*el+1], 0);
1337 elementDivFreeCurrent[el] = rotatedVJ;
1338 }
1339
1340 // Next, evaluate Sigma as a function of inplane-J and MLT
1341 #pragma omp parallel for
1342 for(uint n=0; n < nodes.size(); n++) {
1343 Eigen::Vector3d J{0,0,0};
1344 Eigen::Vector3d x(nodes[n].x.data());
1345
1346 Real totalA=0;
1347 for(uint32_t el=0; el< nodes[n].numTouchingElements; el++) {
1348 Real A = getAreaInDualPolygon(ionosphereGrid, n, nodes[n].touchingElements[el]);
1349 totalA += A;
1350 J += elementDivFreeCurrent[nodes[n].touchingElements[el]] * A;
1351 }
1352 J/=totalA;
1353
1354 Real MLT = atan2(x[1], x[0]) * 12 / M_PI + 12;
1355
1356 // Formula 33 from Juusola et al 2025
1357 // (in A/km)
1358 J *= 1000;
1359 // cout << "J: " << J.norm() << endl;
1360 Real SigmaH = c4H(MLT) * pow(J.norm(), c5H(MLT));
1361 Real SigmaP = c4P(MLT) * pow(J.norm(), c5P(MLT));
1362
1363 nodes[n].parameters[ionosphereParameters::SIGMAP] = SigmaP;
1364 nodes[n].parameters[ionosphereParameters::SIGMAH] = SigmaH;
1365 }
1366
1367 // Read open/closed boundary from input file, to adjust sigmas in the polar regions
1368 vlsv::ParallelReader inVlsv;
1369 inVlsv.open(inputFile,MPI_COMM_WORLD,masterProcessID);
1370 readIonosphereNodeVariable(inVlsv, "ig_openclosed", ionosphereGrid, ionosphereParameters::ZPARAM); // NOTE: Abusing ZPARAM here, since the solver won't need it
1371
1372 // Perform distance transform on the mesh
1373 // Here we have, as temporary variables:
1374 // ZPARAM -> Openclosed 1/0
1375 // ZZPARAM -> index of closest node (so far)
1376 // PPARAM -> distance to boundary
1377 std::cerr << "Distance transform!" << std::endl << "[";
1378 for(uint n=0; n<nodes.size(); n++) {
1379 if(nodes[n].parameters[ionosphereParameters::ZPARAM] < 1.5) {
1380 nodes[n].parameters[ionosphereParameters::ZZPARAM] = n;
1381 nodes[n].parameters[ionosphereParameters::PPARAM] = 0;
1382 } else {
1383 nodes[n].parameters[ionosphereParameters::ZZPARAM] = -1;
1384 nodes[n].parameters[ionosphereParameters::PPARAM] = 6371e3;
1385 }
1386 }
1387
1388 bool done=false;
1389 while(!done) {
1390 done = true;
1391 for(uint n=0; n<nodes.size(); n++) {
1392 if(nodes[n].parameters[ionosphereParameters::ZPARAM] < 1.5) {
1393 continue; // Skip closed nodes
1394 }
1395 Eigen::Vector3d x(nodes[n].x.data());
1396
1397 for(uint m=0; m<nodes[n].numTouchingElements; m++) {
1398 SphericalTriGrid::Element& element = ionosphereGrid.elements[nodes[n].touchingElements[m]];
1399 for(int c=0; c<3; c++) {
1400 uint i = element.corners[c];
1401 if(i == n) {
1402 continue;
1403 }
1404
1405 if(nodes[i].parameters[ionosphereParameters::ZPARAM] < 1.5) {
1406 // Closed nodes can be probed directly
1407 Eigen::Vector3d ox(nodes[i].x.data());
1408 Real distance = (ox - x).norm();
1409 if(distance < nodes[n].parameters[ionosphereParameters::PPARAM]) {
1410 nodes[n].parameters[ionosphereParameters::PPARAM] = distance;
1411 nodes[n].parameters[ionosphereParameters::ZZPARAM] = i;
1412 done = false;
1413 }
1414 } else {
1415 // Open nodes require inferred distance
1416 // TODO: This should actually be geodetic distance, but maybe we can afford not to care
1417 if(nodes[i].parameters[ionosphereParameters::ZZPARAM] == -1) {
1418 // This node doesn't even have a distance yet, skipping.
1419 //done = false;
1420 continue;
1421 }
1422
1423 Eigen::Vector3d ox(nodes[ nodes[i].parameters[ionosphereParameters::ZZPARAM] ].x.data());
1424 Real distance = (ox - x).norm();
1425 if(distance < nodes[n].parameters[ionosphereParameters::PPARAM]) {
1426 nodes[n].parameters[ionosphereParameters::PPARAM] = distance;
1427 nodes[n].parameters[ionosphereParameters::ZZPARAM] = nodes[i].parameters[ionosphereParameters::ZZPARAM];
1428 done = false;
1429 }
1430 }
1431 }
1432 }
1433 }
1434 }
1435 std::cerr << "]\nDistance transform done!" << std::endl;
1436
1437 #pragma omp parallel for
1438 for(uint n=0; n<nodes.size(); n++) {
1439
1440 // Adjust sigmas based on distance value
1441 if(nodes[n].parameters[ionosphereParameters::PPARAM] > 300e3) { // TODO: Hardcoded 300km here
1442 Real alpha = (nodes[n].parameters[ionosphereParameters::PPARAM] - 300e3) / 300e3;
1443 nodes[n].parameters[ionosphereParameters::SIGMAP] *= exp(-alpha);
1444 nodes[n].parameters[ionosphereParameters::SIGMAH] *= exp(-alpha);
1445 }
1446
1447 // Also add solar contribution
1448 // Solar incidence parameter for calculating UV ionisation on the dayside
1449 Real coschi = nodes[n].x[0] / Ionosphere::innerRadius;
1450 Real chi = acos(coschi);
1451 Real qprime = altcos(chi);
1452
1453 const Real F10_7 = 100;
1454 Real sigmaP_dayside = c1p * pow(F10_7, c2p) * pow(qprime, c3p);
1455 Real sigmaH_dayside = c1h * pow(F10_7, c2h) * pow(qprime, c3h);
1456
1457 Real SigmaP = nodes[n].parameters[ionosphereParameters::SIGMAP];
1458 Real SigmaH = nodes[n].parameters[ionosphereParameters::SIGMAH];
1459
1460 nodes[n].parameters[ionosphereParameters::SIGMAP] = sqrt(SigmaP*SigmaP + sigmaP_dayside*sigmaP_dayside +0.625*0.625);
1461 nodes[n].parameters[ionosphereParameters::SIGMAH] = sqrt(SigmaH*SigmaH + sigmaH_dayside*sigmaH_dayside +0.894*0.894);
1462
1463 // TODO: We could instead directly calculate element conductivities using Whitney forms
1464 // and don't need to go via sigma averaging here.
1465 static const char epsilon[3][3][3] = {
1466 {{0,0,0},{0,0,1},{0,-1,0}},
1467 {{0,0,-1},{0,0,0},{1,0,0}},
1468 {{0,1,0},{-1,0,0},{0,0,0}}
1469 };
1470
1471 Eigen::Vector3d b(nodes[n].x.data());
1472 b.normalized();
1473 if(nodes[n].x[2] >= 0) {
1474 b *= -1;
1475 }
1476 for(int i=0; i<3; i++) {
1477 for(int j=0; j<3; j++) {
1478 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] = SigmaP * (((i==j)? 1. : 0.) - b[i]*b[j]);
1479 for(int k=0; k<3; k++) {
1480 nodes[n].parameters[ionosphereParameters::SIGMA + i*3 + j] -= SigmaH * epsilon[i][j][k]*b[k];
1481 }
1482 }
1483 }
1484 }
1485
1486 } else {
1487 cerr << "Conductivity tensor " << sigmaString << " not implemented!" << endl;
1488 return 1;
1489 }
1490
1491 if(writeMesh){
1492 if(meshFormatString == "vtk"){
1493 ofstream meshOut("ionosphereMesh.vtk");
1494 meshOut << "# vtk DataFile Version 3.0" << endl;
1495 meshOut << "Ionosphere mesh exported from Vlasiator, Mesh arguments: " << meshDescription << endl;
1496 meshOut << "ASCII" << endl;
1497 meshOut << "DATASET UNSTRUCTURED_GRID" << endl;
1498 meshOut << "POINTS " << ionosphereGrid.nodes.size() << " double" << endl;
1499 for(uint n = 0; n < ionosphereGrid.nodes.size(); n++){
1500 Eigen::Vector3d pos(ionosphereGrid.nodes[n].x.data());
1501 meshOut << fixed << pos(0) << " " << pos(1) << " " << pos(2) << endl;
1502 }
1503 meshOut << "CELLS " << ionosphereGrid.elements.size() << " " << 4*ionosphereGrid.elements.size() << endl;
1504 for(uint el = 0; el < ionosphereGrid.elements.size(); el++){
1505 // Order of vertices in face definition defines face normal
1506 std::array<uint32_t, 3>& corners = ionosphereGrid.elements[el].corners;
1507 Eigen::Vector3d normal = getElementNormal(ionosphereGrid, el);
1508 Eigen::Vector3d r0(ionosphereGrid.nodes[corners[0]].x.data());
1509 Eigen::Vector3d r1(ionosphereGrid.nodes[corners[1]].x.data());
1510 Eigen::Vector3d r2(ionosphereGrid.nodes[corners[2]].x.data());
1511
1512 Eigen::Vector3d edge01 = (r1 - r0) / (r1 - r0).norm();
1513 edge01 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge01;
1514
1515 Eigen::Vector3d edge12 = (r2 - r1) / (r2 - r1).norm();
1516 edge12 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge12;
1517
1518 Real orientation = edge01.cross(edge12).dot(Eigen::Vector3d::UnitZ()) > 0 ? 1. : -1.;
1519
1520 if (orientation > 0) {
1521 meshOut << "3 " << corners[0] << " " << corners[1] << " " << corners[2] << endl;
1522 } else {
1523 meshOut << "3 " << corners[0] << " " << corners[2] << " " << corners[1] << endl;
1524 }
1525 }
1526 meshOut << "CELL_TYPES " << ionosphereGrid.elements.size() << endl;
1527 for(uint el = 0; el < ionosphereGrid.elements.size(); el++){
1528 meshOut << 5 << endl;
1529 }
1530 meshOut << "POINT_DATA " << ionosphereGrid.nodes.size() << endl;
1531 meshOut << "SCALARS node_id int 1" << endl;
1532 meshOut << "LOOKUP_TABLE default" << endl;
1533 for(uint n = 0; n < ionosphereGrid.nodes.size(); n++){
1534 meshOut << n << endl;
1535 }
1536 meshOut << "CELL_DATA " << ionosphereGrid.elements.size() << endl;
1537 meshOut << "SCALARS face_id int 1" << endl;
1538 meshOut << "LOOKUP_TABLE default" << endl;
1539 for(uint el = 0; el < ionosphereGrid.elements.size(); el++){
1540 meshOut << el << endl;
1541 }
1542 meshOut << "NORMALS normals double" << endl;
1543 for(uint el = 0; el < ionosphereGrid.elements.size(); el++){
1544 Eigen::Vector3d normal = getElementNormal(ionosphereGrid, el).normalized();
1545 meshOut << normal(0) << " " << normal(1) << " " << normal(2) << endl;
1546 }
1547 if(!quiet){
1548 cout << "--- MESH WRITTEN TO ionosphereMesh.vtk ---" << endl;
1549 }
1550 } else if (meshFormatString == "obj") {
1551 ofstream meshOut("ionosphereMesh.obj");
1552 meshOut << "# Ionosphere mesh exported from Vlasiator" << endl;
1553 meshOut << "# Mesh arguments:" << meshDescription << endl;
1554
1555 for(uint n = 0; n < ionosphereGrid.nodes.size(); n++){
1556 Eigen::Vector3d pos(ionosphereGrid.nodes[n].x.data());
1557 meshOut << "v " << pos(0) << " " << pos(1) << " " << pos(2) << endl;
1558 }
1559 for(uint el = 0; el < ionosphereGrid.elements.size(); el++){
1560 Eigen::Vector3d normal = getElementNormal(ionosphereGrid, el);
1561 meshOut << "vn " << normal(0) << " " << normal(1) << " " << normal(2) << endl;
1562 }
1563 for(uint el = 0; el < ionosphereGrid.elements.size(); el++){
1564 // Order of vertices in face definition defines face normal
1565
1566 std::array<uint32_t, 3>& corners = ionosphereGrid.elements[el].corners;
1567 Eigen::Vector3d normal = getElementNormal(ionosphereGrid, el);
1568 Eigen::Vector3d r0(ionosphereGrid.nodes[corners[0]].x.data());
1569 Eigen::Vector3d r1(ionosphereGrid.nodes[corners[1]].x.data());
1570 Eigen::Vector3d r2(ionosphereGrid.nodes[corners[2]].x.data());
1571
1572 Eigen::Vector3d edge01 = (r1 - r0) / (r1 - r0).norm();
1573 edge01 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge01;
1574
1575 Eigen::Vector3d edge12 = (r2 - r1) / (r2 - r1).norm();
1576 edge12 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge12;
1577
1578 Real orientation = edge01.cross(edge12).dot(Eigen::Vector3d::UnitZ()) > 0 ? 1. : -1.;
1579
1580 if(orientation > 0){
1581 meshOut << "f " << corners[0]+1 << "//" << el+1 << " "
1582 << corners[1]+1 << "//" << el+1 << " "
1583 << corners[2]+1 << "//" << el+1 << endl;
1584 } else {
1585 meshOut << "f " << corners[2]+1 << "//" << el+1 << " "
1586 << corners[1]+1 << "//" << el+1 << " "
1587 << corners[0]+1 << "//" << el+1 << endl;
1588 }
1589 }
1590 if(!quiet){
1591 cout << "--- MESH WRITTEN TO ionosphereMesh.obj ---" << endl;
1592 }
1593 } else {
1594 cerr << "Unknown mesh file format \'" << meshFormatString << "\'" << endl;
1595 }
1596
1597 }
1598
1599 // Write solver dependency matrix.
1600 // if(writeSolverMatrix) {
1601 // ofstream matrixOut("solverMatrix.txt");
1602 // for(uint n=0; n<nodes.size(); n++) {
1603 // for(uint m=0; m<nodes.size(); m++) {
1604
1605 // Real val=0;
1606 // for(unsigned int d=0; d<nodes[n].numDepNodes; d++) {
1607 // if(nodes[n].dependingNodes[d] == m) {
1608 // if(doPrecondition) {
1609 // val=nodes[n].dependingCoeffs[d] / nodes[n].dependingCoeffs[0];
1610 // } else {
1611 // val=nodes[n].dependingCoeffs[d];
1612 // }
1613 // }
1614 // }
1615
1616 // matrixOut << val << "\t";
1617 // }
1618 // matrixOut << endl;
1619 // }
1620 // if(!quiet) {
1621 // cout << "--- SOLVER DEPENDENCY MATRIX WRITTEN TO solverMatrix.txt ---" << endl;
1622 // }
1623 // }
1624
1625 ionosphereGrid.initSolver(true);
1626
1627 // // Try to solve the system.
1628 ionosphereGrid.isCouplingInwards=true;
1629 Ionosphere::solverPreconditioning = doPrecondition;
1631 ionosphereGrid.rank = 0;
1632 int iterations, nRestarts;
1633 Real residual = std::numeric_limits<Real>::max(), minPotentialN, minPotentialS, maxPotentialN, maxPotentialS;
1634
1635 // // Measure solver timing
1636 timeval tStart, tEnd;
1637 gettimeofday(&tStart, NULL);
1638 ionosphereGrid.solve(iterations, nRestarts, residual, minPotentialN, maxPotentialN, minPotentialS, maxPotentialS);
1639 gettimeofday(&tEnd, NULL);
1640 // double solverTime = 0;//(tEnd.tv_sec - tStart.tv_sec) + (tEnd.tv_usec - tStart.tv_usec) / 1000000.0;
1641 // cout << "Own solver took " << solverTime << " seconds.\n";
1642
1643 // // Do the same solution using Eigen solver
1644 // Eigen::SparseMatrix<Real> potentialSolverMatrix(nodes.size(), nodes.size());
1645 // Eigen::VectorXd vRightHand(nodes.size()), vPhi(nodes.size());
1646 // for(uint n=0; n<nodes.size(); n++) {
1647
1648 // for(uint m=0; m<nodes[n].numDepNodes; m++) {
1649 // potentialSolverMatrix.insert(n, nodes[n].dependingNodes[m]) = nodes[n].dependingCoeffs[m];
1650 // }
1651
1652 // vRightHand[n] = nodes[n].parameters[ionosphereParameters::SOURCE];
1653 // }
1654 // gettimeofday(&tStart, NULL);
1655 // potentialSolverMatrix.makeCompressed();
1656 // Eigen::BiCGSTAB<Eigen::SparseMatrix<Real> > solver;
1657 // solver.compute(potentialSolverMatrix);
1658 // vPhi = solver.solve(vRightHand);
1659 // gettimeofday(&tEnd, NULL);
1660 // cout << "... done with " << solver.iterations() << " iterations and remaining error " << solver.error() << "\n";
1661 // solverTime = (tEnd.tv_sec - tStart.tv_sec) + (tEnd.tv_usec - tStart.tv_usec) / 1000000.0;
1662 // cout << "Eigen solver took " << solverTime << " seconds.\n";
1663
1664 if(!quiet) {
1665 // cout << "Ionosphere solver: iterations " << iterations << " restarts " << nRestarts
1666 // << " residual " << std::scientific << residual << std::defaultfloat
1667 // << " potential min N = " << minPotentialN << " S = " << minPotentialS
1668 // << " max N = " << maxPotentialN << " S = " << maxPotentialS
1669 // << " difference N = " << maxPotentialN - minPotentialN << " S = " << maxPotentialS - minPotentialS
1670 // << endl;
1671 } else {
1672 if(multipoleL == 0) {
1673 cout << std::scientific << residual << std::defaultfloat << std::endl;
1674 } else {
1675 // Actually corellate with our input multipole
1676 Real correlate=0;
1677 Real selfNorm=0;
1678 Real sphNorm =0;
1679 Real totalArea = 0;
1680 for(uint n=0; n<nodes.size(); n++) {
1681 double theta = acos(nodes[n].x[2] / sqrt(nodes[n].x[0]*nodes[n].x[0] + nodes[n].x[1]*nodes[n].x[1] + nodes[n].x[2]*nodes[n].x[2])); // Latitude
1682 double phi = atan2(nodes[n].x[0], nodes[n].x[1]); // Longitude
1683
1684 Real area = 0;
1685 for(uint e=0; e<ionosphereGrid.nodes[n].numTouchingElements; e++) {
1686 area += ionosphereGrid.elementArea(ionosphereGrid.nodes[n].touchingElements[e]);
1687 }
1688 area /= 3.; // As every element has 3 corners, don't double-count areas
1689
1690 totalArea += area;
1691 selfNorm += pow(nodes[n].parameters[ionosphereParameters::SOLUTION],2.) * area;
1692 sphNorm += pow(sph_legendre(multipoleL,fabs(multipolem),theta) * cos(multipolem*phi), 2.) * area;
1693 correlate += nodes[n].parameters[ionosphereParameters::SOLUTION] * sph_legendre(multipoleL,fabs(multipolem),theta) * cos(multipolem*phi) * area;
1694 }
1695
1696 selfNorm = sqrt(selfNorm/totalArea);
1697 sphNorm = sqrt(sphNorm/totalArea);
1698 correlate /= totalArea * selfNorm * sphNorm;
1699
1700 cout << std::scientific << correlate << std::defaultfloat << std::endl;
1701 }
1702 }
1703
1704 // Write output
1705 vlsv::Writer outputFile;
1706 outputFile.open(outputFilename,MPI_COMM_WORLD,masterProcessID);
1707 ionosphereGrid.communicator = MPI_COMM_WORLD;
1708 ionosphereGrid.writingRank = 0;
1709 P::systemWriteName = std::vector<std::string>({"potato potato"});
1710 writeIonosphereGridMetadata(outputFile);
1711
1712 // Data reducers
1713 DataReducer outputDROs;
1714 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_facelement", [](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1715 std::vector<Real> retval(grid.elements.size());
1716
1717 for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1718
1719 // Distribute FACs by area ratios
1720 SphericalTriGrid::Element& element = ionosphereGrid.elements[el];
1721 Real A = ionosphereGrid.elementArea(el);
1722
1723 int i=element.corners[0],j=element.corners[1],k=element.corners[2];
1724
1728
1729
1730 retval[el] = (nodes[element.corners[0]].parameters[ionosphereParameters::SOURCE] * A/A1
1731 + nodes[element.corners[1]].parameters[ionosphereParameters::SOURCE] * A/A2
1732 + nodes[element.corners[2]].parameters[ionosphereParameters::SOURCE] * A/A3);
1733 }
1734
1735 return retval;
1736 }));
1737
1738 // outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_rowsum", [&](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1739 // std::vector<Real> retval(grid.elements.size());
1740
1741 // //sum of row corresponding to element in matrix curlSolverMatrix
1742 // for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1743 // retval[el] = 0;
1744
1745 // for(uint m=0; m<2*ionosphereGrid.elements.size(); m++) {
1746 // retval[el] += std::abs(curlSolverMatrix.coeff(el,m));
1747 // }
1748 // }
1749
1750 // return retval;
1751 // }));
1752
1753 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_vRHS2", [&](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1754 std::vector<Real> retval(grid.elements.size());
1755
1756 //sum of row corresponding to element in matrix curlSolverMatrix
1757 for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1758 retval[el] = vRHS2[el];
1759 }
1760
1761 return retval;
1762 }));
1763 // outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_ratio", [&](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1764 // std::vector<Real> retval(grid.elements.size());
1765
1766 // for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1767 // retval[el] = 0;
1768 // for(uint m=0; m<2*ionosphereGrid.elements.size(); m++) {
1769 // retval[el] += curlSolverMatrix.coeff(el,m)*curlSolverMatrix.coeff(el,m);
1770 // }
1771 // }
1772
1773 // for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1774 // retval[el] /= vRHS2[el];
1775 // }
1776
1777 // return retval;
1778 // }));
1779 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_source", [](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1780 std::vector<Real> retval(grid.nodes.size());
1781
1782 for (uint i = 0; i < grid.nodes.size(); i++) {
1783 retval[i] = grid.nodes[i].parameters[ionosphereParameters::SOURCE];
1784 }
1785
1786 return retval;
1787 }));
1788
1789 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_analyticdipole", [](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1790 std::vector<Real> retval(grid.elements.size());
1791
1792 for (uint i = 0; i < grid.elements.size(); i++) {
1793 Eigen::Vector3d pos(getElementCircumcentre(grid, i).data());
1794 Real theta = acos(pos[2] / pos.norm());
1795 retval[i] = 1.5809222875130877e6 * sin(theta);
1796 }
1797
1798 return retval;
1799 }));
1800
1801 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_dualarea", [](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1802 std::vector<Real> retval(grid.nodes.size());
1803
1804 for (uint i = 0; i < grid.nodes.size(); i++) {
1805 retval[i] = getDualPolygonArea(grid, i);
1806 }
1807
1808 return retval;
1809 }));
1810
1811 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_circumcentre", [](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1812 std::vector<Real> retval(grid.elements.size() * 3);
1813
1814 for (uint i = 0; i < grid.elements.size(); i++) {
1815 Eigen::Vector3d pos(getElementCircumcentre(grid, i).data());
1816 retval[3*i] = pos[0];
1817 retval[3*i+1] = pos[1];
1818 retval[3*i+2] = pos[2];
1819 }
1820
1821 return retval;
1822 }));
1823 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_openclosed", [](SBC::SphericalTriGrid& grid) -> std::vector<Real> {
1824 std::vector<Real> retval(grid.nodes.size());
1825
1826 for (uint i = 0; i < grid.nodes.size(); i++) {
1827 retval[i] = grid.nodes[i].openFieldLine;
1828 }
1829
1830 return retval;
1831 }));
1832 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_potential", [](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1833
1834 std::vector<Real> retval(grid.nodes.size());
1835
1836 for(uint i=0; i<grid.nodes.size(); i++) {
1837 retval[i] = grid.nodes[i].parameters[ionosphereParameters::SOLUTION];
1838 }
1839
1840 return retval;
1841 }));
1842 // outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_EigenPotential", [&vPhi](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1843
1844 // std::vector<Real> retval(grid.nodes.size());
1845
1846 // for(uint i=0; i<grid.nodes.size(); i++) {
1847 // retval[i] = vPhi[i];
1848 // }
1849
1850 // return retval;
1851 // }));
1852 // outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_residual", [](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1853
1854 // std::vector<Real> retval(grid.nodes.size());
1855
1856 // for(uint i=0; i<grid.nodes.size(); i++) {
1857 // retval[i] = grid.nodes[i].parameters[ionosphereParameters::RESIDUAL];
1858 // }
1859
1860 // return retval;
1861 // }));
1862 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_sigmah", [](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1863
1864 std::vector<Real> retval(grid.nodes.size());
1865
1866 for(uint i=0; i<grid.nodes.size(); i++) {
1867 retval[i] = grid.nodes[i].parameters[ionosphereParameters::SIGMAH];
1868 }
1869
1870 return retval;
1871 }));
1872 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_sigmap", [](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1873
1874 std::vector<Real> retval(grid.nodes.size());
1875
1876 for(uint i=0; i<grid.nodes.size(); i++) {
1877 retval[i] = grid.nodes[i].parameters[ionosphereParameters::SIGMAP];
1878 }
1879
1880 return retval;
1881 }));
1882 if(runCurlJSolver) {
1883 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_jFromCurlJ", [&edgeIndex, &edgeLength,&elementDivFreeCurrent](
1884 SBC::SphericalTriGrid& grid)->std::vector<Real> {
1885
1886 std::vector<Real> retval(3*grid.elements.size());
1887
1888 for(uint el=0; el<grid.elements.size(); el++) {
1889 Eigen::Vector3d J = elementDivFreeCurrent[el];
1890
1891 retval[3*el] = J[0];
1892 retval[3*el+1] = J[1];
1893 retval[3*el+2] = J[2];
1894 }
1895
1896 return retval;
1897 }));
1898 }
1899
1900 // outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_jFromCurlJNode", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1901
1902 // std::vector<Real> retval(3*grid.nodes.size());
1903
1904 // for(uint n=0; n<grid.nodes.size(); n++) {
1905 // Eigen::Vector3d J(0,0,0);
1906
1907 // Real totalA=0;
1908 // // Sum all incoming edges
1909 // for(uint32_t el=0; el< nodes[n].numTouchingElements; el++) {
1910 // Real A = grid.elementArea(nodes[n].touchingElements[el]);
1911 // totalA += A;
1912 // J += elementDivFreeCurrent[nodes[n].touchingElements[el]] * A;
1913 // }
1914 // J/=totalA;
1915
1916
1917 // retval[3*n] = J[0];
1918 // retval[3*n+1] = J[1];
1919 // retval[3*n+2] = J[2];
1920 // }
1921
1922 // return retval;
1923 // }));
1924 // outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_jFromDivJNode", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1925
1926 // std::vector<Real> retval(3*grid.nodes.size());
1927 // for(uint n=0; n<grid.nodes.size(); n++) {
1928 // Eigen::Vector3d J(0,0,0);
1929 // int numEdges=0;
1930 // for(uint32_t el=0; el< nodes[n].numTouchingElements; el++) {
1931 // SphericalTriGrid::Element& element = ionosphereGrid.elements[nodes[n].touchingElements[el]];
1932
1933 // int i=0, j=0;
1934 // for(int c=0; c<3; c++) {
1935 // if(element.corners[c] == n) {
1936 // i = element.corners[(c+1)%3];
1937 // j = element.corners[(c+2)%3];
1938 // break;
1939 // }
1940 // }
1941
1942 // Eigen::Vector3d rn(nodes[n].x.data());
1943 // Eigen::Vector3d ri(nodes[i].x.data());
1944 // Eigen::Vector3d rj(nodes[j].x.data());
1945
1946 // auto [e,orientation] = getEdgeIndexOrientation(i,n);
1947 // J += 0.5 * edgeJDiv[e] * orientation * (rn-ri).normalized();
1948
1949 // std::tie(e,orientation) = getEdgeIndexOrientation(j,n);
1950 // J += 0.5 * edgeJDiv[e] * orientation * (rn-rj).normalized();
1951 // numEdges++;
1952 // }
1953
1954 // J /= numEdges;
1955
1956 // retval[3*n] = J[0];
1957 // retval[3*n+1] = J[1];
1958 // retval[3*n+2] = J[2];
1959 // }
1960 // return retval;
1961 // }));
1962 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_jFromDivJ", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1963
1964 std::vector<Real> retval(3*grid.elements.size());
1965
1966 for(uint el=0; el<grid.elements.size(); el++) {
1967 Eigen::Vector3d J = elementCurlFreeCurrent[el];
1968
1969 retval[3*el] = J[0];
1970 retval[3*el+1] = J[1];
1971 retval[3*el+2] = J[2];
1972 }
1973
1974 return retval;
1975 }));
1976 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_normals", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1977
1978 std::vector<Real> retval(3*grid.elements.size());
1979
1980 for(uint el=0; el<grid.elements.size(); el++) {
1981 Eigen::Vector3d N = getElementNormal(grid, el);
1982
1983 retval[3*el] = N(0);
1984 retval[3*el+1] = N(1);
1985 retval[3*el+2] = N(2);
1986 }
1987
1988 return retval;
1989 }));
1990 //outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_correctionFactor", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
1991
1992 // std::vector<Real> retval(ionosphereGrid.elements.size());
1993
1994 // for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
1995 // retval[el]= elementCorrectionFactors[el];
1996 // }
1997 // return retval;
1998 //}));
1999 //outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereNode("ig_dualPolygonArea", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
2000 // std::vector<Real> retval(grid.nodes.size());
2001
2002 // for(uint n=0; n<grid.nodes.size(); n++) {
2003 // Real dualPolygonArea = 0;
2004 // for(uint32_t el=0; el< grid.nodes[n].numTouchingElements; el++) {
2005 // Real A = ionosphereGrid.elementArea(grid.nodes[n].touchingElements[el]);
2006 // dualPolygonArea += A / 3.;
2007 // }
2008 // retval[n] = dualPolygonArea;
2009 // }
2010
2011 // return retval;
2012 //}));
2013 outputDROs.addOperator(new DRO::DataReductionOperatorIonosphereElement("ig_elementArea", [&](SBC::SphericalTriGrid& grid)->std::vector<Real> {
2014
2015 std::vector<Real> retval(ionosphereGrid.elements.size());
2016
2017 for(uint el=0; el<ionosphereGrid.elements.size(); el++) {
2018 retval[el] = ionosphereGrid.elementArea(el);
2019 }
2020 return retval;
2021 }));
2022 // }
2023
2024 for(unsigned int i=0; i<outputDROs.size(); i++) {
2025 outputDROs.writeIonosphereGridData(ionosphereGrid, "ionosphere", i, outputFile);
2026 }
2027
2028 outputFile.close();
2029 if(!quiet) {
2030 cout << "--- OUTPUT WRITTEN TO " << outputFilename << " ---" << endl;
2031 }
2032
2033 //cout << "--- DONE. ---" << endl;
2034 return 0;
2035}
for i
Definition Dispersion.m:24
sqrt(1.0+vA *vA/(c *c))) % Ion-acoustic wave cS
Constants c
Definition Dispersion.m:45
unsigned int size() const
bool addOperator(DRO::DataReductionOperator *op)
bool writeIonosphereGridData(SBC::SphericalTriGrid &grid, const std::string &meshName, const unsigned int operatorID, vlsv::Writer &vlsvWriter)
static Real innerRadius
Definition ionosphere.h:622
static Real shieldingLatitude
Definition ionosphere.h:631
static int solverMaxFailureCount
Definition ionosphere.h:626
static bool solverPreconditioning
Definition ionosphere.h:628
static int solverMaxIterations
Definition ionosphere.h:624
SysBoundary()
Definition main.cpp:43
~SysBoundary()
Destructor for class SysBoundary.
Definition main.cpp:44
@ SOURCE
Definition common.h:459
@ SIGMAP
Definition common.h:464
@ SOLUTION
Definition common.h:472
@ PPARAM
Definition common.h:477
@ ZZPARAM
Definition common.h:476
@ ZPARAM
Definition common.h:476
@ SIGMAH
Definition common.h:465
@ SIGMA
Definition common.h:460
#define MASTER_RANK
Definition common.h:67
float Real
Definition definitions.h:41
const Realf intersection
int myRank
Definition gpu_base.cpp:48
Logger logFile
Definition main.cpp:25
const int vi
const int j
const int k
Hardcoded lookup- and interpolation tables for semiempirical ionosphere model implementations.
static const Real c2h
static const Real c2p
static const Real c4P_values[]
static const Real chapman_euv_table[1201]
static const Real c3h
static const Real c1h
static const Real c3p
static const Real c4H_values[]
static const Real c5P_values[]
static const Real c1p
static const Real c5H_values[]
Eigen::Vector3d whitneyInterpolate(SphericalTriGrid &grid, uint32_t el, std::vector< Real > edgeValue)
Definition main.cpp:409
std::tuple< Eigen::Vector3d, Eigen::Vector3d > connectingSegmentLengths(SphericalTriGrid &grid, uint32_t el1, uint32_t el2)
Definition main.cpp:455
Eigen::Vector3d getElementCircumcentre(SphericalTriGrid &grid, uint el)
Definition main.cpp:64
const std::vector< CellID > & getLocalCells()
Definition main.cpp:39
std::vector< Real > edgeLength
Definition main.cpp:374
std::function< Real(Real)> c5P
Definition main.cpp:280
Eigen::Vector3d getCommonEdgeMidpoint(SphericalTriGrid &grid, uint32_t el1, uint32_t el2)
Definition main.cpp:162
Eigen::Vector3d getElementNormal(SphericalTriGrid &grid, uint32_t el)
Definition main.cpp:135
Eigen::Vector3d interpolateEdgeToNode(SphericalTriGrid &grid, uint32_t n, std::vector< Real > edgeValue)
Definition main.cpp:507
std::function< Real(Real)> c4P
Definition main.cpp:273
std::tuple< uint, int > getEdgeIndexOrientation(uint32_t a, uint32_t b)
Definition main.cpp:378
Real getAreaInDualPolygon(SphericalTriGrid &grid, uint gridNode, uint gridElem)
Definition main.cpp:234
ObjectWrapper objectWrapper
Definition main.cpp:32
void recalculateLocalCellsCache(const dccrg::Dccrg< spatial_cell::SpatialCell, dccrg::Cartesian_Geometry, std::tuple<>, std::tuple<> > &)
Definition main.cpp:42
void assignConductivityTensorFromLoadedData(std::vector< SphericalTriGrid::Node > &nodes)
Definition main.cpp:347
void deallocateRemoteCellBlocks(dccrg::Dccrg< spatial_cell::SpatialCell, dccrg::Cartesian_Geometry, std::tuple<>, std::tuple<> > &)
Definition main.cpp:40
ObjectWrapper & getObjectWrapper()
Definition main.cpp:33
Eigen::Vector3d getElementBarycentre(SphericalTriGrid &grid, uint32_t el)
Definition main.cpp:48
std::function< Real(Real)> c5H
Definition main.cpp:295
std::vector< CellID > localCellDummy
Definition main.cpp:38
std::function< Real(Real)> c4H
Definition main.cpp:287
Real altcos(Real sza)
Definition main.cpp:310
Real getDualPolygonArea(SphericalTriGrid &grid, uint gridNode)
Definition main.cpp:192
void updateRemoteVelocityBlockLists(dccrg::Dccrg< spatial_cell::SpatialCell, dccrg::Cartesian_Geometry, std::tuple<>, std::tuple<> > &, unsigned int, unsigned int)
Definition main.cpp:41
void assignConductivityTensor(std::vector< SphericalTriGrid::Node > &nodes, Real sigmaP, Real sigmaH)
Definition main.cpp:322
OpenBucketHashtable< uint64_t, uint > edgeIndex
Definition main.cpp:375
bool readIonosphereNodeVariable(vlsv::ParallelReader &file, const string &variableName, SBC::SphericalTriGrid &grid, ionosphereParameters index)
Definition ioread.cpp:1570
Logger diagnostic
Definition ioread.cpp:59
bool writeIonosphereGridMetadata(vlsv::Writer &vlsvWriter)
Definition iowrite.cpp:1496
SphericalTriGrid ionosphereGrid
const Real R_E
Definition common.h:575
static std::vector< std::string > systemWriteName
Definition parameters.h:82
std::array< uint32_t, 3 > corners
Definition ionosphere.h:81
static bool balanceLoad
Definition common.h:541
static bool ionosphereJustSolved
Definition common.h:543
static int bailingOut
Definition common.h:538
static bool doRefine
Definition common.h:542
static bool writeRecover
Definition common.h:540
static bool writeRestart
Definition common.h:539
int main()
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)