582int main(
int argc,
char** argv) {
585 int required=MPI_THREAD_FUNNELED;
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;
595 const int masterProcessID = 0;
596 logFile.open(MPI_COMM_WORLD, masterProcessID,
"logfile.txt");
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;
616 bool runCurlJSolver =
false;
620 cerr <<
"Running with default options. Run main --help to see available settings." << endl;
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];
628 if(!strcmp(argv[
i],
"-gridFilePath")) {
629 meshDescription +=
" -gridFilePath " + std::string(argv[
i+1]);
630 gridFilePath = argv[++
i];
633 if(!strcmp(argv[
i],
"-N")) {
634 meshDescription +=
" -N " + std::string(argv[
i+1]);
635 numNodes = atoi(argv[++
i]);
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));
645 if(!strcmp(argv[
i],
"-sigma")) {
646 sigmaString = argv[++
i];
649 if(!strcmp(argv[
i],
"-fac")) {
650 facString = argv[++
i];
653 if(facString ==
"multipole") {
654 multipoleL = atoi(argv[++
i]);
655 multipolem = atoi(argv[++
i]);
659 if(!strcmp(argv[
i],
"-gaugeFix")) {
660 gaugeFixString = argv[++
i];
663 if(!strcmp(argv[
i],
"-np")) {
664 doPrecondition =
false;
667 if(!strcmp(argv[
i],
"-infile")) {
668 inputFile = argv[++
i];
671 if(!strcmp(argv[
i],
"-maxIter")) {
675 if(!strcmp(argv[
i],
"-o")) {
676 outputFilename = argv[++
i];
679 if(!strcmp(argv[
i],
"-matrix")) {
680 writeSolverMatrix =
true;
683 if(!strcmp(argv[
i],
"-omesh")) {
685 meshFormatString = argv[++
i];
688 if(!strcmp(argv[
i],
"-q")) {
692 cerr <<
"Unknown command line option \"" << argv[
i] <<
"\"" << 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;
747 phiprof::initialize();
751 if(baseShapeString ==
"sphericalFibonacci") {
753 cerr <<
"Spherical Fibonacci grid requires at least 8 nodes" << endl;
757 }
else if(baseShapeString ==
"icosahedron") {
759 }
else if(baseShapeString ==
"tetrahedron") {
761 }
else if(baseShapeString ==
"fromFile") {
762 if(gridFilePath.empty()) {
763 cerr <<
"No grid file path specified for base shape fromFile" << endl;
768 cerr <<
"Unknown mesh base shape \"" << baseShapeString <<
"\"" << endl;
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") {
794 cerr <<
"Unknown gauge fixing method " << gaugeFixString << endl;
799 auto refineBetweenLatitudes = [](
Real phi1,
Real phi2) ->
void {
802 for(uint
i=0;
i< numElems;
i++) {
816 if(refineExtents.size() > 0) {
817 for(
unsigned int i=0;
i< refineExtents.size();
i++) {
818 refineBetweenLatitudes(refineExtents[
i].first, refineExtents[
i].second);
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());
830 if(facString ==
"constant") {
831 for(uint n=0; n<nodes.size(); n++) {
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]));
845 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
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]));
853 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
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]));
863 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
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]));
873 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
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]));
883 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
890 }
else if(facString ==
"merkin2010") {
893 const double j_0 = 1e-6;
894 const double theta_0 = 22. / 180 * M_PI;
895 const double deltaTheta = 12. / 180 * M_PI;
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]));
899 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
907 if(fabs(theta) >= theta_0 && fabs(theta) < theta_0 + deltaTheta) {
908 j_parallel = j_0 * sin(M_PI/2 - fabs(theta)) * sin(phi);
912 }
else if(facString ==
"file") {
913 vlsv::ParallelReader inVlsv;
916 cerr <<
"Reading FAC from VLSV file " << inputFile << endl;
918 inVlsv.open(inputFile,MPI_COMM_WORLD,masterProcessID);
921 cerr <<
"Read file." << endl;
926 for (uint e = 0; e <
ionosphereGrid.nodes[
i].numTouchingElements; e++) {
941 }
else if(facString ==
"pole") {
952 cerr <<
"FAC pattern " << sigmaString <<
" not implemented!" << endl;
957 uint numEquatorialElements = 0;
975 Eigen::SparseMatrix<Real> curlSolverMatrix(vRHS1.size(), vJ.size());
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++) {
1007 }
else if(sigmaString ==
"file") {
1008 vlsv::ParallelReader inVlsv;
1009 inVlsv.open(inputFile,MPI_COMM_WORLD,masterProcessID);
1017 cerr <<
"Reading conductivity tensor from ig_sigmah, ig_sigmap." << endl;
1024 }
else if(sigmaString ==
"ponly") {
1028 }
else if(sigmaString ==
"10") {
1032 }
else if(sigmaString ==
"35") {
1036 }
else if(sigmaString ==
"53") {
1040 }
else if(sigmaString ==
"10") {
1044 }
else if(sigmaString ==
"100") {
1049 }
else if(sigmaString ==
"curlJ") {
1050 runCurlJSolver =
true;
1077 cerr <<
"Using curlJ solver." << endl;
1081 cout <<
"Building curl solver matrix." << endl;
1085 cout <<
"Adding divergence constraints." << endl;
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;
1095 vRHS1[gridNodeIndex] = 0;
1100 for(uint32_t elLocalIndex=0; elLocalIndex<nodes[gridNodeIndex].numTouchingElements; elLocalIndex++) {
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) {
1110 gridI=element.
corners[localI];
1112 gridJ=element.
corners[localJ];
1119 int32_t otherElementi =
ionosphereGrid.findElementNeighbour(nodes[gridNodeIndex].touchingElements[elLocalIndex], localC, localI);
1120 int32_t otherElementj =
ionosphereGrid.findElementNeighbour(nodes[gridNodeIndex].touchingElements[elLocalIndex], localC, localJ);
1122 if(otherElementi < 0 || otherElementj < 0) {
1123 cerr <<
"Error: Element " << nodes[gridNodeIndex].touchingElements[elLocalIndex] <<
" does not have neighbour with nodes " << gridI <<
" and " << gridJ << endl;
1129 Real li = (circumcentrem - midpointmi).norm();
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();
1137 Eigen::Vector3d edgem = Eigen::Quaterniond::FromTwoVectors(normalm, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge;
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;
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;
1148 Real lj = (circumcentrem - midpointmj).norm();
1150 edge = (rj - rm) / (rj - rm).norm();
1152 edgem = Eigen::Quaterniond::FromTwoVectors(normalm, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge;
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;
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;
1166 cout <<
"Done." << endl;
1169 cout <<
"Adding curl constraints." << endl;
1178 if(!quiet && (n % 100) == 0) {
1179 cout <<
"Adding curl constraints: " << n <<
"/" <<
ionosphereGrid.nodes.size() << endl;
1188 for(uint32_t elLocalIndex=0; elLocalIndex<
ionosphereGrid.nodes[n].numTouchingElements; elLocalIndex++) {
1192 int gridI=0,gridJ=0;
1193 int localC=0,localI=0,localJ=0;
1194 for(
int c=0;
c< 3;
c++) {
1198 gridI=element.
corners[localI];
1200 gridJ=element.
corners[localJ];
1206 Eigen::Vector3d ri(nodes[gridI].x.data());
1207 Eigen::Vector3d rj(nodes[gridJ].x.data());
1208 Eigen::Vector3d rm(nodes[n].x.data());
1210 Eigen::Vector3d edgemi = (ri - rm) / (ri - rm).norm();
1211 edgemi = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edgemi;
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;
1217 Eigen::Vector3d edgemj = (rj - rm) / (rj - rm).norm();
1218 edgemj = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edgemj;
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;
1224 Real orientation = edgemj.cross(edgemi).dot(normal) > 0 ? 1. : -1.;
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;
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;
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.;
1241 curlSolverMatrix.makeCompressed();
1243 if(writeSolverMatrix) {
1244 ofstream matrixOut(
"JSolverMatrix.txt");
1249 val = curlSolverMatrix.coeffRef(n, m);
1251 matrixOut << val <<
"\t";
1256 cout <<
"--- CURL SOLVER MATRIX WRITTEN TO JSolverMatrix.txt ---" << endl;
1263 cout <<
"Mesh has an euler characteristic of " << Chi << endl;
1265 cout << nodes.size() <<
" nodes, " <<
edgeLength.size() <<
" edges, " <<
ionosphereGrid.elements.size() <<
" elements." << endl;
1268 cout <<
"Solving divJ system" << endl;
1270 Eigen::LeastSquaresConjugateGradient<Eigen::SparseMatrix<Real>> solver;
1272 Eigen::BiCGSTAB<Eigen::SparseMatrix<Real>> solver;
1274 solver.compute(curlSolverMatrix);
1275 vJ = solver.solve(vRHS2);
1276 cout <<
"... done with " << solver.iterations() <<
" iterations and remaining error " << solver.error() <<
"\n";
1280 std::array<uint32_t, 3>& corners =
ionosphereGrid.elements[el].corners;
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;
1290 Real MLT = atan2(barycentre[1], barycentre[0]) * 12 / M_PI + 12;
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;
1299 for(uint n=0; n<nodes.size(); n++) {
1302 Real correction = 0;
1304 for(uint32_t el=0; el< nodes[n].numTouchingElements; el++) {
1307 correction += elementCorrectionFactors[nodes[n].touchingElements[el]] * A;
1309 correction /= totalA;
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;
1314 vRHS1[nodes.size()+n] = vRHS1[nodes.size()+n]*correction;
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";
1327 std::array<uint32_t, 3>& corners =
ionosphereGrid.elements[el].corners;
1334 Eigen::Vector3d barycentre = (r0+r1+r2)/3.;
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;
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());
1347 for(uint32_t el=0; el< nodes[n].numTouchingElements; el++) {
1350 J += elementDivFreeCurrent[nodes[n].touchingElements[el]] * A;
1354 Real MLT = atan2(x[1], x[0]) * 12 / M_PI + 12;
1360 Real SigmaH =
c4H(MLT) * pow(J.norm(),
c5H(MLT));
1361 Real SigmaP =
c4P(MLT) * pow(J.norm(),
c5P(MLT));
1368 vlsv::ParallelReader inVlsv;
1369 inVlsv.open(inputFile,MPI_COMM_WORLD,masterProcessID);
1377 std::cerr <<
"Distance transform!" << std::endl <<
"[";
1378 for(uint n=0; n<nodes.size(); n++) {
1391 for(uint n=0; n<nodes.size(); n++) {
1395 Eigen::Vector3d x(nodes[n].x.data());
1397 for(uint m=0; m<nodes[n].numTouchingElements; m++) {
1399 for(
int c=0;
c<3;
c++) {
1407 Eigen::Vector3d ox(nodes[
i].x.data());
1408 Real distance = (ox - x).norm();
1424 Real distance = (ox - x).norm();
1435 std::cerr <<
"]\nDistance transform done!" << std::endl;
1437 #pragma omp parallel for
1438 for(uint n=0; n<nodes.size(); n++) {
1450 Real chi = acos(coschi);
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);
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}}
1471 Eigen::Vector3d b(nodes[n].x.data());
1473 if(nodes[n].x[2] >= 0) {
1476 for(
int i=0;
i<3;
i++) {
1477 for(
int j=0;
j<3;
j++) {
1479 for(
int k=0;
k<3;
k++) {
1487 cerr <<
"Conductivity tensor " << sigmaString <<
" not implemented!" << endl;
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;
1501 meshOut << fixed << pos(0) <<
" " << pos(1) <<
" " << pos(2) << endl;
1506 std::array<uint32_t, 3>& corners =
ionosphereGrid.elements[el].corners;
1512 Eigen::Vector3d edge01 = (r1 - r0) / (r1 - r0).norm();
1513 edge01 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge01;
1515 Eigen::Vector3d edge12 = (r2 - r1) / (r2 - r1).norm();
1516 edge12 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge12;
1518 Real orientation = edge01.cross(edge12).dot(Eigen::Vector3d::UnitZ()) > 0 ? 1. : -1.;
1520 if (orientation > 0) {
1521 meshOut <<
"3 " << corners[0] <<
" " << corners[1] <<
" " << corners[2] << endl;
1523 meshOut <<
"3 " << corners[0] <<
" " << corners[2] <<
" " << corners[1] << endl;
1526 meshOut <<
"CELL_TYPES " <<
ionosphereGrid.elements.size() << endl;
1528 meshOut << 5 << endl;
1531 meshOut <<
"SCALARS node_id int 1" << endl;
1532 meshOut <<
"LOOKUP_TABLE default" << endl;
1534 meshOut << n << endl;
1536 meshOut <<
"CELL_DATA " <<
ionosphereGrid.elements.size() << endl;
1537 meshOut <<
"SCALARS face_id int 1" << endl;
1538 meshOut <<
"LOOKUP_TABLE default" << endl;
1540 meshOut << el << endl;
1542 meshOut <<
"NORMALS normals double" << endl;
1545 meshOut << normal(0) <<
" " << normal(1) <<
" " << normal(2) << endl;
1548 cout <<
"--- MESH WRITTEN TO ionosphereMesh.vtk ---" << endl;
1550 }
else if (meshFormatString ==
"obj") {
1551 ofstream meshOut(
"ionosphereMesh.obj");
1552 meshOut <<
"# Ionosphere mesh exported from Vlasiator" << endl;
1553 meshOut <<
"# Mesh arguments:" << meshDescription << endl;
1557 meshOut <<
"v " << pos(0) <<
" " << pos(1) <<
" " << pos(2) << endl;
1561 meshOut <<
"vn " << normal(0) <<
" " << normal(1) <<
" " << normal(2) << endl;
1566 std::array<uint32_t, 3>& corners =
ionosphereGrid.elements[el].corners;
1572 Eigen::Vector3d edge01 = (r1 - r0) / (r1 - r0).norm();
1573 edge01 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge01;
1575 Eigen::Vector3d edge12 = (r2 - r1) / (r2 - r1).norm();
1576 edge12 = Eigen::Quaterniond::FromTwoVectors(normal, Eigen::Vector3d::UnitZ()).toRotationMatrix() * edge12;
1578 Real orientation = edge01.cross(edge12).dot(Eigen::Vector3d::UnitZ()) > 0 ? 1. : -1.;
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;
1585 meshOut <<
"f " << corners[2]+1 <<
"//" << el+1 <<
" "
1586 << corners[1]+1 <<
"//" << el+1 <<
" "
1587 << corners[0]+1 <<
"//" << el+1 << endl;
1591 cout <<
"--- MESH WRITTEN TO ionosphereMesh.obj ---" << endl;
1594 cerr <<
"Unknown mesh file format \'" << meshFormatString <<
"\'" << endl;
1632 int iterations, nRestarts;
1633 Real residual = std::numeric_limits<Real>::max(), minPotentialN, minPotentialS, maxPotentialN, maxPotentialS;
1636 timeval tStart, tEnd;
1637 gettimeofday(&tStart, NULL);
1638 ionosphereGrid.solve(iterations, nRestarts, residual, minPotentialN, maxPotentialN, minPotentialS, maxPotentialS);
1639 gettimeofday(&tEnd, NULL);
1672 if(multipoleL == 0) {
1673 cout << std::scientific << residual << std::defaultfloat << std::endl;
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]));
1682 double phi = atan2(nodes[n].x[0], nodes[n].x[1]);
1685 for(uint e=0; e<
ionosphereGrid.nodes[n].numTouchingElements; e++) {
1692 sphNorm += pow(sph_legendre(multipoleL,fabs(multipolem),theta) * cos(multipolem*phi), 2.) * area;
1696 selfNorm =
sqrt(selfNorm/totalArea);
1697 sphNorm =
sqrt(sphNorm/totalArea);
1698 correlate /= totalArea * selfNorm * sphNorm;
1700 cout << std::scientific << correlate << std::defaultfloat << std::endl;
1705 vlsv::Writer outputFile;
1706 outputFile.open(outputFilename,MPI_COMM_WORLD,masterProcessID);
1715 std::vector<Real> retval(grid.elements.size());
1754 std::vector<Real> retval(grid.elements.size());
1758 retval[el] = vRHS2[el];
1780 std::vector<Real> retval(grid.nodes.size());
1782 for (uint
i = 0;
i < grid.nodes.size();
i++) {
1790 std::vector<Real> retval(grid.elements.size());
1792 for (uint
i = 0;
i < grid.elements.size();
i++) {
1794 Real theta = acos(pos[2] / pos.norm());
1795 retval[
i] = 1.5809222875130877e6 * sin(theta);
1802 std::vector<Real> retval(grid.nodes.size());
1804 for (uint
i = 0;
i < grid.nodes.size();
i++) {
1812 std::vector<Real> retval(grid.elements.size() * 3);
1814 for (uint
i = 0;
i < grid.elements.size();
i++) {
1816 retval[3*
i] = pos[0];
1817 retval[3*
i+1] = pos[1];
1818 retval[3*
i+2] = pos[2];
1824 std::vector<Real> retval(grid.nodes.size());
1826 for (uint
i = 0;
i < grid.nodes.size();
i++) {
1827 retval[
i] = grid.nodes[
i].openFieldLine;
1834 std::vector<Real> retval(grid.nodes.size());
1836 for(uint
i=0;
i<grid.nodes.size();
i++) {
1864 std::vector<Real> retval(grid.nodes.size());
1866 for(uint
i=0;
i<grid.nodes.size();
i++) {
1874 std::vector<Real> retval(grid.nodes.size());
1876 for(uint
i=0;
i<grid.nodes.size();
i++) {
1882 if(runCurlJSolver) {
1886 std::vector<Real> retval(3*grid.elements.size());
1888 for(uint el=0; el<grid.elements.size(); el++) {
1889 Eigen::Vector3d J = elementDivFreeCurrent[el];
1891 retval[3*el] = J[0];
1892 retval[3*el+1] = J[1];
1893 retval[3*el+2] = J[2];
1964 std::vector<Real> retval(3*grid.elements.size());
1966 for(uint el=0; el<grid.elements.size(); el++) {
1967 Eigen::Vector3d J = elementCurlFreeCurrent[el];
1969 retval[3*el] = J[0];
1970 retval[3*el+1] = J[1];
1971 retval[3*el+2] = J[2];
1978 std::vector<Real> retval(3*grid.elements.size());
1980 for(uint el=0; el<grid.elements.size(); el++) {
1983 retval[3*el] = N(0);
1984 retval[3*el+1] = N(1);
1985 retval[3*el+2] = N(2);
2024 for(
unsigned int i=0;
i<outputDROs.
size();
i++) {
2030 cout <<
"--- OUTPUT WRITTEN TO " << outputFilename <<
" ---" << endl;