Vlasiator ebf0dd394 on dev (v5.4.0 + 1054 commits)
Loading...
Searching...
No Matches
fieldtracing.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Vlasiator.
3 *
4 *
5 * For details of usage, see the COPYING file and read the "Rules of the Road"
6 * at http://www.physics.helsinki.fi/vlasiator/
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
26
28#include "fieldtracing.h"
29
30#include <Eigen/Dense>
31
32#define Vec3d Eigen::Vector3d
33#define cross_product(av, bv) (av).cross(bv)
34#define dot_product(av, bv) (av).dot(bv)
35#define vector_length(v) (v).norm()
36#define normalize_vector(v) (v).normalized()
37
38namespace FieldTracing {
40
41 /* Call the heavier operations for DROs to be called only if needed, before an IO.
42 */
46 dccrg::Dccrg<SpatialCell, dccrg::Cartesian_Geometry>& mpiGrid,
47 std::vector<SBC::SphericalTriGrid::Node>& nodes) {
48 if (fieldTracingParameters.doTraceOpenClosed) {
49 traceOpenClosedConnection(technical, fsgrid, perb, dperb, nodes);
50 }
51 if (fieldTracingParameters.doTraceFullBox) {
52 traceFullBoxConnectionAndFluxRopes(technical, fsgrid, perb, dperb, mpiGrid);
53 }
54 }
55
64 std::vector<SBC::SphericalTriGrid::Node>& nodes, creal couplingRadius) {
65
66 // we don't need to do anything if we have no nodes
67 if (nodes.size() == 0) {
68 return;
69 }
70
71 phiprof::Timer timer {"fieldtracing-ionosphere-fsgridCoupling"};
72 // Pick an initial stepsize
73 creal stepSize = min(100e3, fsgrid.getGridSpacing()[0] / 2.);
74 std::vector<Real> nodeTracingStepSize(nodes.size(), stepSize); // In-flight storage of step size, needed when crossing into next MPI domain
75 std::vector<Real> reducedNodeTracingStepSize(nodes.size());
76
77 std::vector<Real> nodeDistance(nodes.size(), std::numeric_limits<Real>::max()); // For reduction of node coordinate in case of multiple hits
78 std::vector<int> nodeNeedsContinuedTracing(nodes.size(), 1); // Flag, whether tracing needs to continue on another task
79 std::vector<std::array<Real, 3>> nodeTracingCoordinates(nodes.size()); // In-flight node upmapping coordinates (for global reduction)
80 for (uint n = 0; n < nodes.size(); n++) {
81 nodeTracingCoordinates.at(n) = nodes.at(n).x;
82 nodes.at(n).haveCouplingData = 0;
83 for (uint c = 0; c < 3; c++) {
84 nodes.at(n).xMapped.at(c) = 0;
85 nodes.at(n).parameters.at(ionosphereParameters::UPMAPPED_BX + c) = 0;
86 }
87 }
88 bool anyNodeNeedsTracing;
89
90 TracingFieldFunction<Real> tracingFullField = [&perb, &dperb, &technical, &fsgrid](std::array<Real, 3>& r, const bool alongB, std::array<Real, 3>& b) -> bool {
91 return traceFullFieldFunction(perb, dperb, technical, fsgrid, r, alongB, b);
92 };
93
94 int itCount = 0;
95 do {
96 itCount++;
97 anyNodeNeedsTracing = false;
98
99 #pragma omp parallel
100 {
101 // Trace node coordinates outwards until a non-sysboundary cell is encountered or the local fsgrid domain has been left.
102 #pragma omp for schedule(dynamic)
103 for (uint n = 0; n < nodes.size(); n++) {
104
105 if (!nodeNeedsContinuedTracing[n]) {
106 // This node has already found its target, no need for us to do anything about it.
107 continue;
108 }
109 SBC::SphericalTriGrid::Node& no = nodes[n];
110
111 std::array<Real, 3> x = nodeTracingCoordinates[n];
112 std::array<Real, 3> v({0,0,0});
113
114 while (true) {
115
116 // Check if the current coordinates (pre-step) are in our own domain.
117 std::array<fsgrid::FsIndex_t, 3> fsgridCell = getLocalFsGridCellIndexForCoord(fsgrid, x);
118 // If it is not in our domain, somebody else takes care of it.
119 if (fsgridCell[0] == -1) {
120 nodeNeedsContinuedTracing[n] = 0;
121 nodeTracingCoordinates[n] = {0,0,0};
122 nodeTracingStepSize[n] = 0;
123 break;
124 }
125
126 // Make one step along the fieldline
127 stepFieldLine(x, v, nodeTracingStepSize[n], fieldTracingParameters.min_tracer_dx_full_box, fsgrid.getGridSpacing()[0] / 2, fieldTracingParameters.tracingMethod, tracingFullField, (no.x[2] < 0));
128
129 // If we map back into the ionosphere, we obviously don't couple out to SBC::Ionosphere::downmapRadius.
130 if (x.at(0)*x.at(0) + x.at(1)*x.at(1) + x.at(2)*x.at(2) < SBC::Ionosphere::innerRadius * SBC::Ionosphere::innerRadius) {
131 nodeNeedsContinuedTracing.at(n) = 0;
132 nodeTracingCoordinates.at(n) = {0,0,0};
133 break;
134 }
135
136 // Store the cells mapped coordinates and upmapped magnetic field at exact crossing point
137 if (x[0]*x[0] + x[1]*x[1] + x[2]*x[2] > SBC::Ionosphere::downmapRadius * SBC::Ionosphere::downmapRadius) {
138 const std::array<Real, 3> x_out = x;
139
140 // Take a step back and find the downmapRadius crossing point
141 stepFieldLine(x, v, nodeTracingStepSize[n], fieldTracingParameters.min_tracer_dx_full_box, fsgrid.getGridSpacing()[0] / 2, fieldTracingParameters.tracingMethod, tracingFullField, !(no.x[2] < 0));
142 Real r_out = sqrt(x_out[0] * x_out[0] + x_out[1] * x_out[1] + x_out[2] * x_out[2]);
143 Real r_in = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
144 Real alpha = (SBC::Ionosphere::downmapRadius - r_out) / (r_in - r_out);
145 alpha = std::fmax(std::fmin(alpha, 1.0), 0.0);
146 if (fabs(r_out - r_in) < 0.01 * fieldTracingParameters.min_tracer_dx_full_box) {
147 alpha = 0.5;
148 }
149 Real xi = x[0]-x_out[0];
150 Real yi = x[1]-x_out[1];
151 Real zi = x[2]-x_out[2];
152 no.xMapped[0] = x_out[0] + xi*alpha;
153 no.xMapped[1] = x_out[1] + yi*alpha;
154 no.xMapped[2] = x_out[2] + zi*alpha;
155
156 // Look up the fsgrid cell belonging to these coordinates, including ghost IDs as we might have
157 // stepped back across the domain edge
159
160 // Interpolate and record upmapped B at final xMapped ccordinates
161 const std::array<Real, 3> perB = interpolatePerturbedB(
162 perb,
163 dperb,
164 technical,
165 fsgrid,
166 fieldTracingParameters.reconstructionCoefficientsCache,
167 fsgridCell[0],
168 fsgridCell[1],
169 fsgridCell[2],
170 no.xMapped
171 );
172
173 no.parameters[ionosphereParameters::UPMAPPED_BX] = SBC::ionosphereGrid.dipoleField(x[0], x[1], x[2], X, 0, X) + SBC::ionosphereGrid.BGB[0] + perB[0];
174 no.parameters[ionosphereParameters::UPMAPPED_BY] = SBC::ionosphereGrid.dipoleField(x[0], x[1], x[2], Y, 0, Y) + SBC::ionosphereGrid.BGB[1] + perB[1];
175 no.parameters[ionosphereParameters::UPMAPPED_BZ] = SBC::ionosphereGrid.dipoleField(x[0], x[1], x[2], Z, 0, Z) + SBC::ionosphereGrid.BGB[2] + perB[2];
176
177 no.haveCouplingData = 1;
178 nodeDistance[n] = sqrt((no.xMapped[0]-no.x[0])*(no.xMapped[0]-no.x[0]) + (no.xMapped[1]-no.x[1])*(no.xMapped[1]-no.x[1]) + (no.xMapped[2]-no.x[2])*(no.xMapped[2]-no.x[2]));
179 nodeNeedsContinuedTracing[n] = 0;
180 nodeTracingCoordinates[n] = {0,0,0};
181 break;
182 }
183
184 // Look up the fsgrid cell belonging to these coordinates, again only local (no ghosts)
186 // Now, after stepping, if it is no longer in our domain, another MPI rank will pick up later.
187 if (fsgridCell[0] == -1) {
188 nodeNeedsContinuedTracing[n] = 1;
189 nodeTracingCoordinates[n] = x;
190 break;
191 }
192 } // while(true)
193 } // for
194 } // pragma omp parallel
195
196 // Globally reduce whether any node still needs to be picked up and traced onwards
197 std::vector<int> sumNodeNeedsContinuedTracing(nodes.size());
198 std::vector<std::array<Real, 3>> sumNodeTracingCoordinates(nodes.size());
199 MPI_Allreduce(nodeNeedsContinuedTracing.data(), sumNodeNeedsContinuedTracing.data(), nodes.size(), MPI_INT, MPI_SUM, MPI_COMM_WORLD);
200 if (sizeof(Real) == sizeof(double)) {
201 MPI_Allreduce(nodeTracingCoordinates.data(), sumNodeTracingCoordinates.data(), 3*nodes.size(), MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
202 MPI_Allreduce(nodeTracingStepSize.data(), reducedNodeTracingStepSize.data(), nodes.size(), MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
203 } else {
204 MPI_Allreduce(nodeTracingCoordinates.data(), sumNodeTracingCoordinates.data(), 3*nodes.size(), MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
205 MPI_Allreduce(nodeTracingStepSize.data(), reducedNodeTracingStepSize.data(), nodes.size(), MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
206 }
207 for (uint n = 0; n < nodes.size(); n++) {
208 if (sumNodeNeedsContinuedTracing[n] > 0) {
209 anyNodeNeedsTracing = true;
210 nodeNeedsContinuedTracing[n] = 1;
211
212 // Update that nodes' tracing coordinates
213 nodeTracingCoordinates[n][0] = sumNodeTracingCoordinates[n][0] / sumNodeNeedsContinuedTracing[n];
214 nodeTracingCoordinates[n][1] = sumNodeTracingCoordinates[n][1] / sumNodeNeedsContinuedTracing[n];
215 nodeTracingCoordinates[n][2] = sumNodeTracingCoordinates[n][2] / sumNodeNeedsContinuedTracing[n];
216 }
217 nodeTracingStepSize[n] = reducedNodeTracingStepSize[n];
218 }
219
220 } while (anyNodeNeedsTracing);
221
222 logFile << "(fieldtracing) fsgrid coupling traced in " << itCount << " iterations of the tracing loop." << endl;
223
224 std::vector<Real> reducedNodeDistance(nodes.size());
225 if (sizeof(Real) == sizeof(double)) {
226 MPI_Allreduce(nodeDistance.data(), reducedNodeDistance.data(), nodes.size(), MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
227 } else {
228 MPI_Allreduce(nodeDistance.data(), reducedNodeDistance.data(), nodes.size(), MPI_FLOAT, MPI_MIN, MPI_COMM_WORLD);
229 }
230
231 // Reduce upmapped magnetic field to be consistent on all nodes
232 std::vector<Real> sendUpmappedB(3*nodes.size());
233 std::vector<Real> reducedUpmappedB(3*nodes.size());
234 // Likewise, reduce upmapped coordinates
235 std::vector<Real> sendxMapped(3*nodes.size());
236 std::vector<Real> reducedxMapped(3*nodes.size());
237 // And coupling rank number
238 std::vector<int> sendCouplingNum(nodes.size());
239 std::vector<int> reducedCouplingNum(nodes.size());
240
241 for (uint n = 0; n < nodes.size(); n++) {
242 SBC::SphericalTriGrid::Node& no = nodes[n];
243 // Discard false hits from cells that are further out from the node
244 if (nodeDistance[n] > reducedNodeDistance[n]) {
245 no.haveCouplingData = 0;
246 for (int c = 0; c < 3; c++) {
248 no.xMapped[c] = 0;
249 }
250 } else {
251 // Cell found, add association.
252 SBC::ionosphereGrid.isCouplingInwards = true;
253 }
254
255 sendUpmappedB[3*n] = no.parameters[ionosphereParameters::UPMAPPED_BX];
256 sendUpmappedB[3*n+1] = no.parameters[ionosphereParameters::UPMAPPED_BY];
257 sendUpmappedB[3*n+2] = no.parameters[ionosphereParameters::UPMAPPED_BZ];
258 sendxMapped[3*n] = no.xMapped[0];
259 sendxMapped[3*n+1] = no.xMapped[1];
260 sendxMapped[3*n+2] = no.xMapped[2];
261 sendCouplingNum[n] = no.haveCouplingData;
262 }
263 if (sizeof(Real) == sizeof(double)) {
264 MPI_Allreduce(sendUpmappedB.data(), reducedUpmappedB.data(), 3*nodes.size(), MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
265 MPI_Allreduce(sendxMapped.data(), reducedxMapped.data(), 3*nodes.size(), MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
266 } else {
267 MPI_Allreduce(sendUpmappedB.data(), reducedUpmappedB.data(), 3*nodes.size(), MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
268 MPI_Allreduce(sendxMapped.data(), reducedxMapped.data(), 3*nodes.size(), MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
269 }
270 MPI_Allreduce(sendCouplingNum.data(), reducedCouplingNum.data(), nodes.size(), MPI_INT, MPI_SUM, MPI_COMM_WORLD);
271
272 for (uint n = 0; n < nodes.size(); n++) {
273 SBC::SphericalTriGrid::Node& no = nodes[n];
274
275 // We don't even care about nodes that couple nowhere.
276 if (reducedCouplingNum[n] == 0) {
277 continue;
278 }
279 no.parameters[ionosphereParameters::UPMAPPED_BX] = reducedUpmappedB[3*n] / reducedCouplingNum[n];
280 no.parameters[ionosphereParameters::UPMAPPED_BY] = reducedUpmappedB[3*n+1] / reducedCouplingNum[n];
281 no.parameters[ionosphereParameters::UPMAPPED_BZ] = reducedUpmappedB[3*n+2] / reducedCouplingNum[n];
282 no.xMapped[0] = reducedxMapped[3*n] / reducedCouplingNum[n];
283 no.xMapped[1] = reducedxMapped[3*n+1] / reducedCouplingNum[n];
284 no.xMapped[2] = reducedxMapped[3*n+2] / reducedCouplingNum[n];
285 }
286 }
287
296 std::array<std::pair<int, Real>, 3> calculateIonosphereVlasovGridCoupling(
297 std::array<Real, 3> x,
298 std::vector<SBC::SphericalTriGrid::Node>& nodes,
299 creal couplingRadius
300 ) {
301
302 std::array<std::pair<int, Real>, 3> coupling;
303
304 Real stepSize = 100e3;
305 std::array<Real,3> v;
306 // For tracing towards the vlasov boundary, we only require the dipole field.
307 TracingFieldFunction<Real> dipoleFieldOnly = [](std::array<Real, 3>& r, const bool outwards, std::array<Real, 3>& b) -> bool {
308 // Get field direction
309 b[0] = SBC::ionosphereGrid.dipoleField(r[0], r[1], r[2], X, 0, X) + SBC::ionosphereGrid.BGB[0];
310 b[1] = SBC::ionosphereGrid.dipoleField(r[0], r[1], r[2], Y, 0, Y) + SBC::ionosphereGrid.BGB[1];
311 b[2] = SBC::ionosphereGrid.dipoleField(r[0], r[1], r[2], Z, 0, Z) + SBC::ionosphereGrid.BGB[2];
312
313 // Normalize
314 Real norm = 1. / sqrt(b[0]*b[0] + b[1]*b[1] + b[2]*b[2]);
315 for (int c = 0; c < 3; c++) {
316 b[c] = b[c] * norm;
317 }
318
319 // Make sure motion is outwards. Flip b if dot(r,b) < 0
320 if (outwards) {
321 if (b[0]*r[0] + b[1]*r[1] + b[2]*r[2] < 0) {
322 b[0] *= -1;
323 b[1] *= -1;
324 b[2] *= -1;
325 }
326 } else {
327 if (b[0]*r[0] + b[1]*r[1] + b[2]*r[2] > 0) {
328 b[0] *= -1;
329 b[1] *= -1;
330 b[2] *= -1;
331 }
332 }
333 return true;
334 };
335
336 while (x[0]*x[0] + x[1]*x[1] + x[2]*x[2] > SBC::Ionosphere::innerRadius * SBC::Ionosphere::innerRadius) {
337
338 // Make one step along the fieldline
339 stepFieldLine(x, v, stepSize, fieldTracingParameters.min_tracer_dx_ionospere_coupling, fieldTracingParameters.max_tracer_dx_ionospere_coupling, fieldTracingParameters.tracingMethod, dipoleFieldOnly, false);
340
341 // If the field lines is moving even further outwards, abort.
342 // (this shouldn't happen under normal magnetospheric conditions, but who
343 // knows what crazy driving this will be run with)
344 if (x[0]*x[0] + x[1]*x[1] + x[2]*x[2] > 1.5*1.5 * couplingRadius*couplingRadius) {
345 cerr << "(fieldtracing) Warning: coupling of Vlasov grid cell failed due to weird magnetic field topology." << endl;
346
347 // Return a coupling that has 0 value and results in zero potential
348 return coupling;
349 }
350 }
351
352 const std::array<Real, 3> x_in = x;
353 Real r_in = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
354 // Take a step back and find the innerRadius crossing point
355 stepFieldLine(x, v, stepSize, fieldTracingParameters.min_tracer_dx_ionospere_coupling, fieldTracingParameters.max_tracer_dx_ionospere_coupling, fieldTracingParameters.tracingMethod, dipoleFieldOnly, true);
356 Real r_out = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
357 Real alpha = (SBC::Ionosphere::innerRadius - r_in) / (r_out - r_in);
358 alpha = std::fmax(std::fmin(alpha, 1.0), 0.0);
359 if (fabs(r_out - r_in) < 0.01 * fieldTracingParameters.min_tracer_dx_ionospere_coupling) {
360 alpha = 0.5;
361 }
362 Real xi = x[0]-x_in[0];
363 Real yi = x[1]-x_in[1];
364 Real zi = x[2]-x_in[2];
365 x[0] = x_in[0] + xi*alpha;
366 x[1] = x_in[1] + yi*alpha;
367 x[2] = x_in[2] + zi*alpha;
368
369 // Determine the nearest ionosphere node to this point.
370 uint32_t nearestNode = SBC::ionosphereGrid.findNodeAtCoordinates(x);
371 int32_t elementIndex = nodes[nearestNode].touchingElements[0];
372 int32_t oldElementIndex;
373
374 std::unordered_set<int32_t> elementHistory;
375 bool override = false;
376
377 for (uint toto = 0; toto < 15; toto++) {
378 const SBC::SphericalTriGrid::Element& el = SBC::ionosphereGrid.elements[elementIndex];
379 oldElementIndex = elementIndex;
380
381 if (elementHistory.find(elementIndex) == elementHistory.end()) {
382 elementHistory.insert(elementIndex);
383 } else {
384 // This element was already seen, entering a loop, let's get out
385 // It happens when the projection rx is left seen from the right triangle and right seen from the left
386 // triangle.
387 cerr << "Entered a loop, taking the current element " << elementIndex << "." << endl;
388 override = true;
389 }
390
391 // Calculate barycentric coordinates for x in this element.
392 Vec3d r1(nodes[el.corners[0]].x.data());
393 Vec3d r2(nodes[el.corners[1]].x.data());
394 Vec3d r3(nodes[el.corners[2]].x.data());
395
396 Vec3d rx(x[0], x[1], x[2]);
397
398 cint handedness = sign(dot_product(cross_product(r2-r1, r3-r1), r1));
399
400 creal kappa1 = handedness * sign(dot_product(cross_product(r1, r2-r1), rx-r1));
401 creal kappa2 = handedness * sign(dot_product(cross_product(r2, r3-r2), rx-r2));
402 creal kappa3 = handedness * sign(dot_product(cross_product(r3, r1-r3), rx-r3));
403
404 if (override || (kappa1 > 0 && kappa2 > 0 && kappa3 > 0)) {
405 // Total area
406 Real A = vector_length(cross_product(r2-r1, r3-r1));
407
408 // Project x into the plane of this triangle
409 Vec3d normal = normalize_vector(cross_product(r2-r1, r3-r1));
410 rx -= normal * dot_product(rx-r1, normal);
411
412 // Area of the sub-triangles
413 Real lambda1 = vector_length(cross_product(r2-rx, r3-rx)) / A;
414 Real lambda2 = vector_length(cross_product(r1-rx, r3-rx)) / A;
415 Real lambda3 = vector_length(cross_product(r1-rx, r2-rx)) / A;
416
417 coupling[0] = {el.corners[0], lambda1};
418 coupling[1] = {el.corners[1], lambda2};
419 coupling[2] = {el.corners[2], lambda3};
420 return coupling;
421 } else if (kappa1 > 0 && kappa2 > 0 && kappa3 < 0) {
422 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,0,2);
423 } else if (kappa2 > 0 && kappa3 > 0 && kappa1 < 0) {
424 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,0,1);
425 } else if (kappa3 > 0 && kappa1 > 0 && kappa2 < 0) {
426 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,1,2);
427 } else if (kappa1 < 0 && kappa2 < 0 && kappa3 > 0) {
428 if (handedness > 0) {
429 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,0,1);
430 } else {
431 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,1,2);
432 }
433 } else if (kappa1 < 0 && kappa2 > 0 && kappa3 < 0) {
434 if (handedness > 0) {
435 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,0,2);
436 } else {
437 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,0,1);
438 }
439 } else if (kappa1 > 0 && kappa2 < 0 && kappa3 < 0) {
440 if (handedness > 0) {
441 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,1,2);
442 } else {
443 elementIndex = SBC::ionosphereGrid.findElementNeighbour(elementIndex,0,2);
444 }
445 } else {
446 cerr << "This fell through, strange."
447 << " kappas " << kappa1 << " " << kappa2 << " " << kappa3
448 << " handedness " << handedness
449 << " r1 " << r1[0] << " " << r1[1] << " " << r1[2]
450 << " r2 " << r2[0] << " " << r2[1] << " " << r2[2]
451 << " r3 " << r3[0] << " " << r3[1] << " " << r3[2]
452 << " rx " << rx[0] << " " << rx[1] << " " << rx[2] << endl;
453 }
454 if (elementIndex == -1) {
455 cerr << __FILE__ << ":" << __LINE__ << ": invalid elementIndex returned for coordinate "
456 << x[0] << " " << x[1] << " " << x[2] << " projected to rx " << rx[0] << " " << rx[1] << " " << rx[2]
457 << ". Last valid elementIndex: " << oldElementIndex << "." << endl;
458 return coupling;
459 }
460 }
461
462 // If we arrived here, we did not find an element to couple to (why?)
463 // Return an empty coupling instead
464 cerr << "(fieldtracing) Failed to find an ionosphere element to couple to for coordinate "
465 << x[0] << " " << x[1] << " " << x[2] << endl;
466 return coupling;
467 }
468
474 std::vector<SBC::SphericalTriGrid::Node>& nodes) {
475
476 // we don't need to do anything if we have no nodes
477 if (nodes.size() == 0) {
478 return;
479 }
480
481 phiprof::Timer tracingTimer{"fieldtracing-ionosphere-openclosedTracing"};
482 // Pick an initial stepsize
483 const TReal stepSize = min(1000e3, fsgrid.getGridSpacing()[0] / 2.);
484 std::vector<TReal> nodeTracingStepSize(
485 nodes.size(), stepSize); // In-flight storage of step size, needed when crossing into next MPI domain
486 std::vector<TReal> reducedNodeTracingStepSize(nodes.size());
487 std::array<fsgrid::FsSize_t, 3> gridSize = fsgrid.getGlobalSize();
488 uint64_t maxTracingSteps = 8 * (gridSize[0]*fsgrid.getGridSpacing()[0] + gridSize[1]*fsgrid.getGridSpacing()[1] + gridSize[2]*fsgrid.getGridSpacing()[2]) / stepSize;
489
490 std::vector<int> nodeMapping(nodes.size(), TracingLineEndType::UNPROCESSED);
491 std::vector<uint64_t> nodeStepCounter(nodes.size());
492 std::vector<int> nodeNeedsContinuedTracing(nodes.size(), 1);
493 std::vector<std::array<TReal, 3>> nodeTracingCoordinates(nodes.size());
494
495 std::vector<int> nodeTracingStepCount(nodes.size());
496
497 for (uint n = 0; n < nodes.size(); n++) {
498 // Real -> TReal hence 3 lines...
499 nodeTracingCoordinates.at(n)[0] = nodes.at(n).x[0];
500 nodeTracingCoordinates.at(n)[1] = nodes.at(n).x[1];
501 nodeTracingCoordinates.at(n)[2] = nodes.at(n).x[2];
502 }
503 bool anyNodeNeedsTracing;
504
505 TracingFieldFunction<TReal> tracingFullField = [&perb, &dperb, &technical, &fsgrid](std::array<TReal, 3>& r, const bool alongB, std::array<TReal, 3>& b) -> bool {
506 return traceFullFieldFunction(perb, dperb, technical, fsgrid, r, alongB, b);
507 };
508
509 int itCount = 0;
510 bool warnMaxStepsExceeded = false;
511 do {
512 anyNodeNeedsTracing = false;
513 itCount++;
514
515 #pragma omp parallel
516 {
517 // Trace node coordinates outwards until a non-sysboundary cell is encountered or the local fsgrid domain has been left.
518 #pragma omp for schedule(dynamic)
519 for (uint n = 0; n < nodes.size(); n++) {
520
521 if (!nodeNeedsContinuedTracing[n]) {
522 // This node has already found its target, no need for us to do anything about it.
523 continue;
524 }
525 SBC::SphericalTriGrid::Node& no = nodes[n];
526
527 std::array<TReal, 3> x = nodeTracingCoordinates[n];
528 std::array<TReal, 3> v({0,0,0});
529
530 while (true) {
531 nodeStepCounter[n]++;
532
533 // Check if the current coordinates (pre-step) are in our own domain.
534 std::array<fsgrid::FsIndex_t, 3> fsgridCell = getLocalFsGridCellIndexForCoord(fsgrid, {(TReal)x[0], (TReal)x[1], (TReal)x[2]});
535 // If it is not in our domain, somebody else takes care of it.
536 if (fsgridCell[0] == -1) {
537 nodeNeedsContinuedTracing[n] = 0;
538 nodeTracingCoordinates[n] = {0,0,0};
539 nodeTracingStepSize[n] = 0;
540 break;
541 }
542
543 if (nodeStepCounter[n] > maxTracingSteps) {
544 nodeNeedsContinuedTracing[n] = 0;
545 nodeTracingCoordinates[n] = {0,0,0};
546 #pragma omp critical
547 {
548 warnMaxStepsExceeded = true;
549 }
550 break;
551 }
552
553 // Make one step along the fieldline
554 // If the node is in the North, trace along -B (false for last argument), in the South, trace along B
555 stepFieldLine(x, v, nodeTracingStepSize[n], (TReal)fieldTracingParameters.min_tracer_dx_full_box, (TReal)fsgrid.getGridSpacing()[0] / 2, fieldTracingParameters.tracingMethod, tracingFullField, (no.x[2] < 0));
556 nodeTracingStepCount[n]++;
557
558 // Look up the fsgrid cell belonging to these coordinates
559 fsgridCell = getLocalFsGridCellIndexForCoord(fsgrid, {(TReal)x[0], (TReal)x[1], (TReal)x[2]});
560
561 // If we map into the ionosphere, this node is on a closed field line.
562 if (x.at(0)*x.at(0) + x.at(1)*x.at(1) + x.at(2)*x.at(2) < SBC::Ionosphere::innerRadius*SBC::Ionosphere::innerRadius) {
563 nodeNeedsContinuedTracing[n] = 0;
564 nodeTracingCoordinates[n] = {0,0,0};
565 nodeMapping[n] = TracingLineEndType::CLOSED;
566 break;
567 }
568
569 // If we map out of the box, this node is on an open field line.
570 if (x[0] > fieldTracingParameters.x_max || x[0] < fieldTracingParameters.x_min ||
571 x[1] > fieldTracingParameters.y_max || x[1] < fieldTracingParameters.y_min ||
572 x[2] > fieldTracingParameters.z_max || x[2] < fieldTracingParameters.z_min) {
573 nodeNeedsContinuedTracing[n] = 0;
574 nodeTracingCoordinates[n] = {0,0,0};
575 nodeMapping[n] = TracingLineEndType::OPEN;
576 break;
577 }
578
579 // Now, after stepping, if it is no longer in our domain, another MPI rank will pick up later.
580 if (fsgridCell[0] == -1) {
581 nodeNeedsContinuedTracing[n] = 1;
582 nodeTracingCoordinates[n] = x;
583 break;
584 }
585 }
586 } // pragma omp parallel
587 }
588
589 // Globally reduce whether any node still needs to be picked up and traced onwards
590 std::vector<int> sumNodeNeedsContinuedTracing(nodes.size());
591 std::vector<std::array<TReal, 3>> sumNodeTracingCoordinates(nodes.size());
592 std::vector<uint64_t> maxNodeStepCounter(nodes.size());
593 MPI_Allreduce(nodeNeedsContinuedTracing.data(), sumNodeNeedsContinuedTracing.data(), nodes.size(), MPI_INT, MPI_SUM, MPI_COMM_WORLD);
594 MPI_Allreduce(nodeStepCounter.data(), maxNodeStepCounter.data(), nodes.size(), MPI_UINT64_T, MPI_MAX, MPI_COMM_WORLD);
595 if (sizeof(TReal) == sizeof(double)) {
596 MPI_Allreduce(nodeTracingCoordinates.data(), sumNodeTracingCoordinates.data(), 3*nodes.size(), MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
597 MPI_Allreduce(nodeTracingStepSize.data(), reducedNodeTracingStepSize.data(), nodes.size(), MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
598 } else {
599 MPI_Allreduce(nodeTracingCoordinates.data(), sumNodeTracingCoordinates.data(), 3*nodes.size(), MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
600 MPI_Allreduce(nodeTracingStepSize.data(), reducedNodeTracingStepSize.data(), nodes.size(), MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
601 }
602 for (uint n = 0; n < nodes.size(); n++) {
603 if (sumNodeNeedsContinuedTracing[n] > 0) {
604 anyNodeNeedsTracing = true;
605 nodeNeedsContinuedTracing[n] = 1;
606
607 // Update that nodes' tracing coordinates
608 nodeTracingCoordinates[n][0] = sumNodeTracingCoordinates[n][0] / sumNodeNeedsContinuedTracing[n];
609 nodeTracingCoordinates[n][1] = sumNodeTracingCoordinates[n][1] / sumNodeNeedsContinuedTracing[n];
610 nodeTracingCoordinates[n][2] = sumNodeTracingCoordinates[n][2] / sumNodeNeedsContinuedTracing[n];
611
612 nodeStepCounter[n] = maxNodeStepCounter[n];
613 }
614 nodeTracingStepSize[n] = reducedNodeTracingStepSize[n];
615 }
616 } while (anyNodeNeedsTracing);
617
618 logFile << "(fieldtracing) open-closed tracing traced in " << itCount << " iterations of the tracing loop." << endl;
619
620 bool redWarning = false;
621 MPI_Allreduce(&warnMaxStepsExceeded, &redWarning, 1, MPI_C_BOOL, MPI_LOR, MPI_COMM_WORLD);
622 int rank;
623 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
624 if (redWarning && rank == MASTER_RANK) {
625 logFile << "(fieldtracing) Warning: reached the maximum number of tracing steps " << maxTracingSteps << " allowed for open-closed ionosphere tracing." << endl;
626 }
627
628 std::vector<int> reducedNodeMapping(nodes.size());
629 std::vector<int> reducedNodeTracingStepCount(nodes.size());
630 MPI_Allreduce(nodeMapping.data(), reducedNodeMapping.data(), nodes.size(), MPI_INT, MPI_MAX, MPI_COMM_WORLD);
631 MPI_Allreduce(nodeTracingStepCount.data(), reducedNodeTracingStepCount.data(), nodes.size(), MPI_INT, MPI_SUM, MPI_COMM_WORLD);
632
633 for (uint n = 0; n < nodes.size(); n++) {
634 nodes[n].openFieldLine = reducedNodeMapping.at(n);
635 }
636 }
637
644 TracingFieldFunction<TReal>& tracingFullField,
645 const std::vector<std::array<TReal, 3>>& cellInitialCoordinates,
646 const std::vector<TReal>& cellCurvatureRadius,
647 std::vector<std::array<TReal, 3>>& cellTracingCoordinates,
648 std::vector<TReal>& cellTracingStepSize, std::vector<TReal>& cellRunningDistance,
649 std::vector<TReal>& cellMaxExtension, std::vector<signed char>& cellConnection,
650 bool& warnMaxDistanceExceeded, const TReal maxTracingDistance, cuint DIRECTION) {
651 std::array<TReal, 3> x = cellTracingCoordinates[n];
652 std::array<TReal, 3> v({0,0,0});
653 while (true) {
654 // Check if the current coordinates (pre-step) are in our own domain.
655 std::array<fsgrid::FsIndex_t, 3> fsgridCell = getLocalFsGridCellIndexForCoord(fsgrid, {(Real)x[0], (Real)x[1], (Real)x[2]});
656 // If it is not in our domain, somebody else takes care of it.
657 if (fsgridCell[0] == -1) {
658 cellTracingCoordinates[n] = {0,0,0};
659 cellTracingStepSize[n] = 0;
660 break;
661 }
662
663 // Make one step along the fieldline
664 // Forward tracing means true for last argument
665 stepFieldLine(x, v, cellTracingStepSize[n], (TReal)100e3, (TReal)fsgrid.getGridSpacing()[0] / 2, fieldTracingParameters.tracingMethod, tracingFullField, (DIRECTION == Direction::FORWARD));
666 cellRunningDistance[n] += cellTracingStepSize[n];
667
668 // Look up the fsgrid cell belonging to these coordinates
669 fsgridCell = getLocalFsGridCellIndexForCoord(fsgrid, {(Real)x[0], (Real)x[1], (Real)x[2]});
670
671 // If we map into the ionosphere, discard this field line.
672 if (x.at(0)*x.at(0) + x.at(1)*x.at(1) + x.at(2)*x.at(2) < fieldTracingParameters.innerBoundaryRadius*fieldTracingParameters.innerBoundaryRadius) {
673 cellTracingCoordinates[n] = x;
674 cellConnection[n] += TracingLineEndType::CLOSED;
675
676 // Take a step back and find the innerRadius crossing point
677 stepFieldLine(x, v, cellTracingStepSize[n], (TReal)fieldTracingParameters.min_tracer_dx_full_box, (TReal)fsgrid.getGridSpacing()[0] / 2, fieldTracingParameters.tracingMethod, tracingFullField, !(DIRECTION == Direction::FORWARD));
678 Real r_in = sqrt(cellTracingCoordinates[n][0]*cellTracingCoordinates[n][0] + cellTracingCoordinates[n][1]*cellTracingCoordinates[n][1] + cellTracingCoordinates[n][2]*cellTracingCoordinates[n][2]);
679 Real r_out = sqrt(x[0]*x[0] + x[1]*x[1] + x[2]*x[2]);
680 Real alpha = (fieldTracingParameters.innerBoundaryRadius - r_in) / (r_out - r_in);
681 alpha = std::fmax(std::fmin(alpha, 1.0), 0.0);
682 if (fabs(r_out - r_in) < 0.01 * fieldTracingParameters.min_tracer_dx_full_box) {
683 alpha = 0.5;
684 }
685 TReal xi = x[0]-cellTracingCoordinates[n][0];
686 TReal yi = x[1]-cellTracingCoordinates[n][1];
687 TReal zi = x[2]-cellTracingCoordinates[n][2];
688 cellTracingCoordinates[n][0] += xi*alpha;
689 cellTracingCoordinates[n][1] += yi*alpha;
690 cellTracingCoordinates[n][2] += zi*alpha;
691 cellRunningDistance[n] -= cellTracingStepSize[n] * alpha;
692 break;
693 }
694
695 // If we map out of the box, discard this field line.
696 if (x[0] > fieldTracingParameters.x_max || x[0] < fieldTracingParameters.x_min ||
697 x[1] > fieldTracingParameters.y_max || x[1] < fieldTracingParameters.y_min ||
698 x[2] > fieldTracingParameters.z_max || x[2] < fieldTracingParameters.z_min) {
699 cellTracingCoordinates[n] = x;
700 cellConnection[n] += TracingLineEndType::OPEN;
701 break;
702 }
703
704 // If we exceed the max tracing distance we're probably looping
705 if (cellRunningDistance[n] > maxTracingDistance) {
706 cellTracingCoordinates[n] = x;
707 cellConnection[n] += TracingLineEndType::DANGLING;
708 #pragma omp critical
709 {
710 warnMaxDistanceExceeded = true;
711 }
712 break;
713 }
714
715 // See the longer comment for the function traceFullBoxConnectionAndFluxRopes for details.
716 // If we are still in the race for flux rope...
717 if (cellConnection[n] < TracingLineEndType::N_TYPES) {
718 const TReal extension =
719 sqrt((x[0]-(cellInitialCoordinates[n])[0]) * (x[0]-(cellInitialCoordinates[n])[0]) +
720 (x[1]-(cellInitialCoordinates[n])[1]) * (x[1]-(cellInitialCoordinates[n])[1]) +
721 (x[2]-(cellInitialCoordinates[n])[2]) * (x[2]-(cellInitialCoordinates[n])[2]));
722 cellMaxExtension[n] = max(cellMaxExtension[n], extension);
723 // ...and if we traced too far from the seed, this is not a flux rope candidate and we do a single +=
724 if (extension > fieldTracingParameters.fluxrope_max_curvature_radii_extent * cellCurvatureRadius[n]) {
725 cellConnection[n] += TracingLineEndType::N_TYPES;
726 } else if (cellRunningDistance[n] > fieldTracingParameters.fluxrope_max_curvature_radii_to_trace * cellCurvatureRadius[n]) {
727 // If we're still in the game and reach this limit we have a hit and we do a double +=
728 cellConnection[n] += 2 * TracingLineEndType::N_TYPES;
729 }
730 }
731
732 // Now, after stepping, if it is no longer in our domain, another MPI rank will pick up later.
733 if (fsgridCell[0] == -1) {
734 cellTracingCoordinates[n] = x;
735 break;
736 }
737 } // while true
738 }
739
807 dccrg::Dccrg<SpatialCell, dccrg::Cartesian_Geometry>& mpiGrid) {
808 phiprof::Timer fluxTracingTimer {"fieldtracing-fullAndFluxTracing"};
809
810 std::vector<CellID> localDccrgCells = getLocalCells();
811 int localDccrgSize = localDccrgCells.size();
812 int globalDccrgSize;
813 MPI_Allreduce(&localDccrgSize, &globalDccrgSize, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
814 int commSize;
815 MPI_Comm_size(MPI_COMM_WORLD, &commSize);
816 std::vector<int> amounts(commSize);
817 std::vector<int> displacements(commSize);
818 std::vector<CellID> allDccrgCells(globalDccrgSize);
819 MPI_Allgather(&localDccrgSize, 1, MPI_INT, amounts.data(), 1, MPI_INT, MPI_COMM_WORLD);
820 for (int i = 1; i < commSize; i++) {
821 displacements[i] = displacements[i - 1] + amounts[i - 1];
822 }
823 MPI_Allgatherv(localDccrgCells.data(), localDccrgSize, MPI_UINT64_T, allDccrgCells.data(), amounts.data(), displacements.data(), MPI_UINT64_T, MPI_COMM_WORLD);
824
825 // Pick an initial stepsize
826 const TReal stepSize = min(1000e3, fsgrid.getGridSpacing()[0] / 2.);
827 std::vector<TReal> cellFWTracingStepSize(globalDccrgSize, stepSize); // In-flight storage of step size, needed when crossing into next MPI domain
828 std::vector<TReal> cellBWTracingStepSize(globalDccrgSize, stepSize); // In-flight storage of step size, needed when crossing into next MPI domain
829
830 std::array<fsgrid::FsSize_t, 3> gridSize = fsgrid.getGlobalSize();
831 // If fullbox_and_fluxrope_max_distance is unset, use this heuristic considering how far an IMF+dipole combo can
832 // sensibly stretch in the box before we're safe to assume it's rolled up more or less pathologically.
833 const TReal maxTracingDistance = fieldTracingParameters.fullbox_and_fluxrope_max_distance > 0 ?
834 fieldTracingParameters.fullbox_and_fluxrope_max_distance :
835 gridSize[0]*fsgrid.getGridSpacing()[0] + gridSize[1]*fsgrid.getGridSpacing()[1] + gridSize[2]*fsgrid.getGridSpacing()[2];
836
837 std::vector<TReal> cellCurvatureRadius(globalDccrgSize);
838 std::vector<TReal> reducedCellCurvatureRadius(globalDccrgSize);
839
840 std::vector<signed char> cellFWConnection(globalDccrgSize, TracingLineEndType::UNPROCESSED);
841 std::vector<signed char> cellBWConnection(globalDccrgSize, TracingLineEndType::UNPROCESSED);
842
843 std::vector<std::array<TReal, 3>> cellFWTracingCoordinates(globalDccrgSize);
844 std::vector<std::array<TReal, 3>> cellBWTracingCoordinates(globalDccrgSize);
845 std::vector<TReal> cellFWRunningDistance(globalDccrgSize);
846 std::vector<TReal> cellBWRunningDistance(globalDccrgSize);
847
848 // This we need only once and not forward and backward separately as we'll only record the max
849 std::vector<TReal> cellMaxExtension(globalDccrgSize);
850
851 // These guys are needed in the reductions
852 std::vector<std::array<TReal, 3>> sumCellFWTracingCoordinates(globalDccrgSize);
853 std::vector<std::array<TReal, 3>> sumCellBWTracingCoordinates(globalDccrgSize);
854 std::vector<TReal> reducedCellFWRunningDistance(globalDccrgSize);
855 std::vector<TReal> reducedCellBWRunningDistance(globalDccrgSize);
856 std::vector<TReal> reducedCellFWTracingStepSize(globalDccrgSize);
857 std::vector<TReal> reducedCellBWTracingStepSize(globalDccrgSize);
858 std::vector<signed char> storedCellFWConnection(globalDccrgSize);
859 std::vector<signed char> storedCellBWConnection(globalDccrgSize);
860
861 phiprof::Timer initializationTimer {"initialization-loop"};
862 for (int n = 0; n < globalDccrgSize; n++) {
863 const CellID id = allDccrgCells[n];
864 const std::array<Real, 3> ctr = mpiGrid.get_center(id);
865 cellFWTracingCoordinates.at(n) = {(TReal)ctr[0], (TReal)ctr[1], (TReal)ctr[2]};
866 cellBWTracingCoordinates.at(n) = cellFWTracingCoordinates.at(n);
867 if (mpiGrid.is_local(id)) {
868 if ((mpiGrid[id]->sysBoundaryFlag != sysboundarytype::NOT_SYSBOUNDARY)
869 || cellFWTracingCoordinates[n][0] > fieldTracingParameters.x_max
870 || cellFWTracingCoordinates[n][0] < fieldTracingParameters.x_min
871 || cellFWTracingCoordinates[n][1] > fieldTracingParameters.y_max
872 || cellFWTracingCoordinates[n][1] < fieldTracingParameters.y_min
873 || cellFWTracingCoordinates[n][2] > fieldTracingParameters.z_max
874 || cellFWTracingCoordinates[n][2] < fieldTracingParameters.z_min
875 ) {
876 cellFWConnection[n] = TracingLineEndType::OUTSIDE;
877 cellBWConnection[n] = TracingLineEndType::OUTSIDE;
878 cellFWTracingCoordinates[n] = {0,0,0};
879 cellBWTracingCoordinates[n] = {0,0,0};
880 cellFWTracingStepSize[n] = 0;
881 cellBWTracingStepSize[n] = 0;
882 } else {
883 cellCurvatureRadius[n] = 1 / sqrt(mpiGrid[id]->parameters[CellParams::CURVATUREX] * mpiGrid[id]->parameters[CellParams::CURVATUREX] + mpiGrid[id]->parameters[CellParams::CURVATUREY] * mpiGrid[id]->parameters[CellParams::CURVATUREY] + mpiGrid[id]->parameters[CellParams::CURVATUREZ] * mpiGrid[id]->parameters[CellParams::CURVATUREZ]);
884 if (fieldTracingParameters.fluxrope_max_curvature_radii_to_trace * cellCurvatureRadius[n] > maxTracingDistance) {
885 cellCurvatureRadius[n] = 0; // This will stop fluxrope tracing for these field lines in the first iteration below.
886 }
887 }
888 }
889 }
890 initializationTimer.stop();
891
892 // The FW, BW and this copy of coordinates were created using mpiGrid.get_center() which is for all cells, not just
893 // local ones, so no need to reduce them now.
894 const std::vector<std::array<TReal,3>> cellInitialCoordinates = cellFWTracingCoordinates;
895
896 // We use the connection type array to track what needs to be traced, and we need to store the previous iteration's values.
897 MPI_Allreduce(cellFWConnection.data(), storedCellFWConnection.data(), globalDccrgSize, MPI_SIGNED_CHAR, MPI_MAX, MPI_COMM_WORLD);
898 MPI_Allreduce(cellBWConnection.data(), storedCellBWConnection.data(), globalDccrgSize, MPI_SIGNED_CHAR, MPI_MAX, MPI_COMM_WORLD);
899 if (sizeof(TReal) == sizeof(double)) {
900 MPI_Allreduce(cellFWTracingStepSize.data(), reducedCellFWTracingStepSize.data(), globalDccrgSize, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
901 MPI_Allreduce(cellBWTracingStepSize.data(), reducedCellBWTracingStepSize.data(), globalDccrgSize, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
902 MPI_Allreduce(cellCurvatureRadius.data(), reducedCellCurvatureRadius.data(), globalDccrgSize, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
903 } else {
904 MPI_Allreduce(cellFWTracingStepSize.data(), reducedCellFWTracingStepSize.data(), globalDccrgSize, MPI_FLOAT, MPI_MIN, MPI_COMM_WORLD);
905 MPI_Allreduce(cellBWTracingStepSize.data(), reducedCellBWTracingStepSize.data(), globalDccrgSize, MPI_FLOAT, MPI_MIN, MPI_COMM_WORLD);
906 MPI_Allreduce(cellCurvatureRadius.data(), reducedCellCurvatureRadius.data(), globalDccrgSize, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
907 }
908 // Don't swap the first two as the stored guys are used below
909 cellFWConnection = storedCellFWConnection;
910 cellBWConnection = storedCellBWConnection;
911 cellFWTracingStepSize.swap(reducedCellFWTracingStepSize);
912 cellBWTracingStepSize.swap(reducedCellBWTracingStepSize);
913 cellCurvatureRadius.swap(reducedCellCurvatureRadius);
914
915 TracingFieldFunction<TReal> tracingFullField = [&perb, &dperb, &technical, &fsgrid](std::array<TReal, 3>& r, const bool alongB, std::array<TReal, 3>& b) -> bool {
916 return traceFullFieldFunction(perb, dperb, technical, fsgrid, r, alongB, b);
917 };
918 int itCount = 0;
919 bool warnMaxDistanceExceeded = false;
920 int cellsToDoFullBox, cellsToDoFluxRopes;
921 int smallSizeFW, smallSizeBW;
922
923 std::vector<int> indicesToReduceFW, indicesToReduceBW;
924 std::vector<std::array<TReal, 3>> smallCellFWTracingCoordinates, smallCellBWTracingCoordinates;
925 std::vector<TReal> smallCellFWRunningDistance, smallCellBWRunningDistance;
926 std::vector<TReal> smallCellFWTracingStepSize, smallCellBWTracingStepSize;
927 std::vector<signed char> smallCellFWConnection, smallCellBWConnection;
928
929 std::vector<std::array<TReal, 3>> smallSumCellFWTracingCoordinates, smallSumCellBWTracingCoordinates;
930 std::vector<TReal> smallReducedCellFWRunningDistance, smallReducedCellBWRunningDistance;
931 std::vector<TReal> smallReducedCellFWTracingStepSize, smallReducedCellBWTracingStepSize;
932 std::vector<signed char> smallReducedCellFWConnection, smallReducedCellBWConnection;
933
934 int mpi_timer{phiprof::initializeTimer("MPI-loop")};
935 phiprof::Timer loopTimer{"loop"};
936 #pragma omp parallel shared(cellsToDoFullBox, cellsToDoFluxRopes)
937 {
938 do { // while(either leftover fraction is not achieved
939 #pragma omp single
940 {
941 itCount++;
942 }
943 // Trace node coordinates forward and backwards until a
944 // non-sysboundary cell is encountered or the local fsgrid domain
945 // has been left.
946 #pragma omp for schedule(dynamic)
947 for (int n = 0; n < globalDccrgSize; n++) {
949 stepCellAcrossTaskDomain(n, technical, fsgrid, tracingFullField, cellInitialCoordinates, cellCurvatureRadius,
950 cellFWTracingCoordinates, cellFWTracingStepSize, cellFWRunningDistance,
951 cellMaxExtension, cellFWConnection, warnMaxDistanceExceeded, maxTracingDistance,
953 }
955 stepCellAcrossTaskDomain(n, technical, fsgrid, tracingFullField, cellInitialCoordinates, cellCurvatureRadius,
956 cellBWTracingCoordinates, cellBWTracingStepSize, cellBWRunningDistance,
957 cellMaxExtension, cellBWConnection, warnMaxDistanceExceeded, maxTracingDistance,
959 }
960 } // for
961
962 // Globally reduce whether any node still needs to be picked up and traced onwards
963 phiprof::Timer timer{mpi_timer};
964 #pragma omp master
965 {
966 indicesToReduceFW.clear();
967 indicesToReduceBW.clear();
968 smallCellFWTracingCoordinates.clear();
969 smallCellBWTracingCoordinates.clear();
970 smallCellFWRunningDistance.clear();
971 smallCellBWRunningDistance.clear();
972 smallCellFWTracingStepSize.clear();
973 smallCellBWTracingStepSize.clear();
974 smallCellFWConnection.clear();
975 smallCellBWConnection.clear();
976 for (int n = 0; n < globalDccrgSize; n++) {
977 if (storedCellFWConnection[n] % TracingLineEndType::N_TYPES ==
978 TracingLineEndType::UNPROCESSED) { // This still has the allreduce MPI_SUM results from the previous
979 // round, so that all globally continuing cells are accounted for.
980 indicesToReduceFW.push_back(n);
981 smallCellFWTracingCoordinates.push_back(cellFWTracingCoordinates[n]);
982 smallCellFWRunningDistance.push_back(cellFWRunningDistance[n]);
983 smallCellFWTracingStepSize.push_back(cellFWTracingStepSize[n]);
984 smallCellFWConnection.push_back(cellFWConnection[n]);
985 }
986 if (storedCellBWConnection[n] % TracingLineEndType::N_TYPES ==
987 TracingLineEndType::UNPROCESSED) { // This still has the allreduce MPI_SUM results from the previous
988 // round, so that all globally continuing cells are accounted for.
989 indicesToReduceBW.push_back(n);
990 smallCellBWTracingCoordinates.push_back(cellBWTracingCoordinates[n]);
991 smallCellBWRunningDistance.push_back(cellBWRunningDistance[n]);
992 smallCellBWTracingStepSize.push_back(cellBWTracingStepSize[n]);
993 smallCellBWConnection.push_back(cellBWConnection[n]);
994 }
995 }
996 smallSizeFW = indicesToReduceFW.size();
997 smallSizeBW = indicesToReduceBW.size();
998
999 smallSumCellFWTracingCoordinates.resize(smallSizeFW);
1000 smallSumCellBWTracingCoordinates.resize(smallSizeBW);
1001 smallReducedCellFWRunningDistance.resize(smallSizeFW);
1002 smallReducedCellBWRunningDistance.resize(smallSizeBW);
1003 smallReducedCellFWTracingStepSize.resize(smallSizeFW);
1004 smallReducedCellBWTracingStepSize.resize(smallSizeBW);
1005 smallReducedCellFWConnection.resize(smallSizeFW);
1006 smallReducedCellBWConnection.resize(smallSizeBW);
1007
1008 MPI_Allreduce(smallCellFWConnection.data(), smallReducedCellFWConnection.data(), smallSizeFW, MPI_SIGNED_CHAR, MPI_MAX, MPI_COMM_WORLD);
1009 MPI_Allreduce(smallCellBWConnection.data(), smallReducedCellBWConnection.data(), smallSizeBW, MPI_SIGNED_CHAR, MPI_MAX, MPI_COMM_WORLD);
1010 if (sizeof(TReal) == sizeof(double)) {
1011 MPI_Allreduce(smallCellFWTracingCoordinates.data(), smallSumCellFWTracingCoordinates.data(), 3*smallSizeFW, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
1012 MPI_Allreduce(smallCellBWTracingCoordinates.data(), smallSumCellBWTracingCoordinates.data(), 3*smallSizeBW, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
1013 MPI_Allreduce(smallCellFWTracingStepSize.data(), smallReducedCellFWTracingStepSize.data(), smallSizeFW, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
1014 MPI_Allreduce(smallCellBWTracingStepSize.data(), smallReducedCellBWTracingStepSize.data(), smallSizeBW, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
1015 MPI_Allreduce(smallCellFWRunningDistance.data(), smallReducedCellFWRunningDistance.data(), smallSizeFW, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
1016 MPI_Allreduce(smallCellBWRunningDistance.data(), smallReducedCellBWRunningDistance.data(), smallSizeBW, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
1017 } else {
1018 MPI_Allreduce(smallCellFWTracingCoordinates.data(), smallSumCellFWTracingCoordinates.data(), 3*smallSizeFW, MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
1019 MPI_Allreduce(smallCellBWTracingCoordinates.data(), smallSumCellBWTracingCoordinates.data(), 3*smallSizeBW, MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
1020 MPI_Allreduce(smallCellFWTracingStepSize.data(), smallReducedCellFWTracingStepSize.data(), smallSizeFW, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
1021 MPI_Allreduce(smallCellBWTracingStepSize.data(), smallReducedCellBWTracingStepSize.data(), smallSizeBW, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
1022 MPI_Allreduce(smallCellFWRunningDistance.data(), smallReducedCellFWRunningDistance.data(), smallSizeFW, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
1023 MPI_Allreduce(smallCellBWRunningDistance.data(), smallReducedCellBWRunningDistance.data(), smallSizeBW, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
1024 }
1025 }
1026 #pragma omp barrier
1027 #pragma omp for schedule(dynamic)
1028 for (int n = 0; n < smallSizeFW; n++) {
1029 cellFWTracingStepSize[indicesToReduceFW[n]] = smallReducedCellFWTracingStepSize[n];
1030 cellFWRunningDistance[indicesToReduceFW[n]] = smallReducedCellFWRunningDistance[n];
1031 cellFWConnection[indicesToReduceFW[n]] = smallReducedCellFWConnection[n];
1032 cellFWTracingCoordinates[indicesToReduceFW[n]] = smallSumCellFWTracingCoordinates[n];
1033 }
1034 #pragma omp for schedule(dynamic)
1035 for (int n = 0; n < smallSizeBW; n++) {
1036 cellBWTracingStepSize[indicesToReduceBW[n]] = smallReducedCellBWTracingStepSize[n];
1037 cellBWRunningDistance[indicesToReduceBW[n]] = smallReducedCellBWRunningDistance[n];
1038 cellBWConnection[indicesToReduceBW[n]] = smallReducedCellBWConnection[n];
1039 cellBWTracingCoordinates[indicesToReduceBW[n]] = smallSumCellBWTracingCoordinates[n];
1040 }
1041 timer.stop();
1042 #pragma omp single
1043 {
1044 cellsToDoFullBox = 0;
1045 cellsToDoFluxRopes = 0;
1046 // These are used for the small arrays ~70 lines up.
1047 storedCellFWConnection = cellFWConnection;
1048 storedCellBWConnection = cellBWConnection;
1049 }
1050 #pragma omp for schedule(dynamic) reduction(+ : cellsToDoFullBox) reduction(+ : cellsToDoFluxRopes)
1051 for (int n = 0; n < globalDccrgSize; n++) {
1052 if (cellFWConnection[n] % TracingLineEndType::N_TYPES == TracingLineEndType::UNPROCESSED ||
1054 cellsToDoFullBox++;
1055 if (cellFWConnection[n] == TracingLineEndType::UNPROCESSED ||
1056 cellBWConnection[n] == TracingLineEndType::UNPROCESSED) {
1057 cellsToDoFluxRopes++;
1058 }
1059 }
1060 }
1061 #pragma omp barrier
1062 } while (!(cellsToDoFullBox <= fieldTracingParameters.fullbox_max_incomplete_cells * globalDccrgSize &&
1063 cellsToDoFluxRopes <= fieldTracingParameters.fluxrope_max_incomplete_cells * globalDccrgSize));
1064 } // pragma omp parallel
1065 loopTimer.stop();
1066
1067 logFile << "(fieldtracing) combined flux rope + full box tracing traced in " << itCount
1068 << " iterations of the tracing loop with flux rope " << cellsToDoFluxRopes << ", full box "
1069 << cellsToDoFullBox << " remaining incomplete field lines (total spatial cells " << globalDccrgSize << ")."
1070 << endl;
1071
1072 bool redWarning = false;
1073 MPI_Allreduce(&warnMaxDistanceExceeded, &redWarning, 1, MPI_C_BOOL, MPI_LOR, MPI_COMM_WORLD);
1074 int rank;
1075 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
1076 if (redWarning && rank == MASTER_RANK) {
1077 logFile << "(fieldtracing) Warning: reached the maximum tracing distance " << maxTracingDistance
1078 << " m allowed for combined flux rope + full box tracing." << endl;
1079 }
1080
1081 // Now we're all done we want to reduce the max extension so we can store it
1082 std::vector<TReal> reducedCellMaxExtension(globalDccrgSize);
1083 if (sizeof(TReal) == sizeof(double)) {
1084 MPI_Allreduce(cellMaxExtension.data(), reducedCellMaxExtension.data(), globalDccrgSize, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
1085 } else {
1086 MPI_Allreduce(cellMaxExtension.data(), reducedCellMaxExtension.data(), globalDccrgSize, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD);
1087 }
1088
1089 phiprof::Timer finalLoopTimer {"final-loop"};
1090 for (int n = 0; n < globalDccrgSize; n++) {
1091 const CellID id = allDccrgCells.at(n);
1092 if (mpiGrid.is_local(id)) {
1093 // Handle flux ropes
1094 mpiGrid[id]->parameters[CellParams::FLUXROPE] = 0;
1095 // Earlier, if we marked nothing (e.g. hit a wall or ionosphere before making a call) cellXWConnection[n] is
1096 // less than N_TYPES. If we went beyond the thresholds we did += N_TYPES, which is also not a positive hit. If
1097 // we identified a flux rope we did a double += by N_TYPES and we pick them out with this.
1098 if (cellFWConnection[n] >= 2 * TracingLineEndType::N_TYPES &&
1099 cellBWConnection[n] >= 2 * TracingLineEndType::N_TYPES) {
1100 mpiGrid[id]->parameters[CellParams::FLUXROPE] = reducedCellMaxExtension[n] / cellCurvatureRadius[n];
1101 }
1102
1103 // Now remove the flux rope mark so we're left with UNPROCESSED, OPEN, CLOSED, DANGLING, OUTSIDE.
1104 cellFWConnection[n] %= TracingLineEndType::N_TYPES;
1105 cellBWConnection[n] %= TracingLineEndType::N_TYPES;
1106
1107 // Handle full box connection
1109 if (cellFWConnection[n] == TracingLineEndType::CLOSED && cellBWConnection[n] == TracingLineEndType::CLOSED) {
1111 }
1112 if (cellFWConnection[n] == TracingLineEndType::CLOSED && cellBWConnection[n] == TracingLineEndType::OPEN) {
1114 }
1115 if (cellFWConnection[n] == TracingLineEndType::OPEN && cellBWConnection[n] == TracingLineEndType::CLOSED) {
1117 }
1118 if (cellFWConnection[n] == TracingLineEndType::OPEN && cellBWConnection[n] == TracingLineEndType::OPEN) {
1120 }
1121 if (cellFWConnection[n] == TracingLineEndType::CLOSED && cellBWConnection[n] == TracingLineEndType::DANGLING) {
1123 }
1124 if (cellFWConnection[n] == TracingLineEndType::DANGLING && cellBWConnection[n] == TracingLineEndType::CLOSED) {
1126 }
1127 if (cellFWConnection[n] == TracingLineEndType::OPEN && cellBWConnection[n] == TracingLineEndType::DANGLING) {
1129 }
1130 if (cellFWConnection[n] == TracingLineEndType::DANGLING && cellBWConnection[n] == TracingLineEndType::OPEN) {
1132 }
1133 if (cellFWConnection[n] == TracingLineEndType::DANGLING && cellBWConnection[n] == TracingLineEndType::DANGLING) {
1135 }
1136 mpiGrid[id]->parameters[CellParams::CONNECTION_FW_X] = cellFWTracingCoordinates[n][0];
1137 mpiGrid[id]->parameters[CellParams::CONNECTION_FW_Y] = cellFWTracingCoordinates[n][1];
1138 mpiGrid[id]->parameters[CellParams::CONNECTION_FW_Z] = cellFWTracingCoordinates[n][2];
1139 mpiGrid[id]->parameters[CellParams::CONNECTION_BW_X] = cellBWTracingCoordinates[n][0];
1140 mpiGrid[id]->parameters[CellParams::CONNECTION_BW_Y] = cellBWTracingCoordinates[n][1];
1141 mpiGrid[id]->parameters[CellParams::CONNECTION_BW_Z] = cellBWTracingCoordinates[n][2];
1142 }
1143 }
1144 finalLoopTimer.stop();
1145 }
1146} // namespace FieldTracing
for i
Definition Dispersion.m:24
sqrt(1.0+vA *vA/(c *c))) % Ion-acoustic wave cS
Constants c
Definition Dispersion.m:45
static Real innerRadius
Definition ionosphere.h:622
static Real downmapRadius
Definition ionosphere.h:638
const std::vector< CellID > & getLocalCells()
Definition main.cpp:39
@ UPMAPPED_BX
Definition common.h:471
@ UPMAPPED_BZ
Definition common.h:471
@ UPMAPPED_BY
Definition common.h:471
#define MASTER_RANK
Definition common.h:67
int sign(const T &value)
Definition common.h:528
const uint32_t cuint
Definition definitions.h:50
float Real
Definition definitions.h:41
const int cint
Definition definitions.h:45
uint64_t CellID
Definition definitions.h:54
fsgrid::FsGrid< FS_STENCIL_WIDTH > FieldSolverGrid
Definition definitions.h:78
const float creal
Definition definitions.h:42
#define normalize_vector(v)
#define Vec3d
#define dot_product(av, bv)
#define cross_product(av, bv)
#define vector_length(v)
std::array< fsgrid::FsIndex_t, 3 > getLocalFsGridCellIndexWithGhostsForCoord(T &grid, const std::array< Real, 3 > &x)
float TReal
std::array< fsgrid::FsIndex_t, 3 > getLocalFsGridCellIndexForCoord(T &grid, const std::array< Real, 3 > &x)
std::array< Real, 3 > interpolatePerturbedB(fsgrids::perbspan perb, fsgrids::constdperbspan dperb, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, std::map< std::array< int, 3 >, std::array< Real, Rec::N_REC_COEFFICIENTS > > &reconstructionCoefficientsCache, cint i, cint j, cint k, const std::array< Real, 3 > x)
@ Y
Definition functions.hpp:28
@ X
Definition functions.hpp:28
@ Z
Definition functions.hpp:28
Logger logFile
Definition main.cpp:25
@ CURVATUREZ
Definition common.h:213
@ CONNECTION_BW_Y
Definition common.h:209
@ CONNECTION_BW_X
Definition common.h:208
@ CONNECTION_FW_X
Definition common.h:205
@ CURVATUREX
Definition common.h:211
@ CURVATUREY
Definition common.h:212
@ CONNECTION_FW_Z
Definition common.h:207
@ CONNECTION_FW_Y
Definition common.h:206
@ CONNECTION
Definition common.h:204
@ CONNECTION_BW_Z
Definition common.h:210
void traceOpenClosedConnection(fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, fsgrids::perbspan perb, fsgrids::constdperbspan dperb, std::vector< SBC::SphericalTriGrid::Node > &nodes)
void traceFullBoxConnectionAndFluxRopes(fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, fsgrids::perbspan perb, fsgrids::constdperbspan dperb, dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid)
void calculateIonosphereFsgridCoupling(fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, fsgrids::perbspan perb, fsgrids::constdperbspan dperb, std::vector< SBC::SphericalTriGrid::Node > &nodes, creal couplingRadius)
void stepFieldLine(std::array< REAL, 3 > &x, std::array< REAL, 3 > &v, REAL &stepsize, const REAL minStepSize, const REAL maxStepSize, TracingMethod method, TracingFieldFunction< REAL > &BFieldFunction, const bool outwards)
bool traceFullFieldFunction(fsgrids::perbspan perb, fsgrids::constdperbspan dperb, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, std::array< REAL, 3 > &r, const bool alongB, std::array< REAL, 3 > &b)
void stepCellAcrossTaskDomain(cint n, fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, TracingFieldFunction< TReal > &tracingFullField, const std::vector< std::array< TReal, 3 > > &cellInitialCoordinates, const std::vector< TReal > &cellCurvatureRadius, std::vector< std::array< TReal, 3 > > &cellTracingCoordinates, std::vector< TReal > &cellTracingStepSize, std::vector< TReal > &cellRunningDistance, std::vector< TReal > &cellMaxExtension, std::vector< signed char > &cellConnection, bool &warnMaxDistanceExceeded, const TReal maxTracingDistance, cuint DIRECTION)
Trace magnetic field lines forward and backward from each DCCRG cell to record the connectivity and d...
std::function< bool(std::array< REAL, 3 > &, const bool, std::array< REAL, 3 > &)> TracingFieldFunction
void reduceData(fsgrids::technicalspan technical, FieldSolverGrid &fsgrid, fsgrids::perbspan perb, fsgrids::constdperbspan dperb, dccrg::Dccrg< SpatialCell, dccrg::Cartesian_Geometry > &mpiGrid, std::vector< SBC::SphericalTriGrid::Node > &nodes)
FieldTracingParameters fieldTracingParameters
std::array< std::pair< int, Real >, 3 > calculateIonosphereVlasovGridCoupling(std::array< Real, 3 > x, std::vector< SBC::SphericalTriGrid::Node > &nodes, creal couplingRadius)
SphericalTriGrid ionosphereGrid
std::span< std::array< Real, fsgrids::bfield::N_BFIELD > > perbspan
Definition common.h:434
std::span< technical > technicalspan
Definition common.h:452
std::span< const std::array< Real, fsgrids::dperb::N_DPERB > > constdperbspan
Definition common.h:443
std::array< uint32_t, 3 > corners
Definition ionosphere.h:81
std::array< Real, 3 > xMapped
Definition ionosphere.h:100
std::array< iSolverReal, N_IONOSPHERE_PARAMETERS > parameters
Definition ionosphere.h:102
std::array< Real, 3 > x
Definition ionosphere.h:99
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)