XGC1
follow_psi_gradients.hpp
Go to the documentation of this file.
1 #ifndef FOLLOW_PSI_GRADIENTS_HPP
2 #define FOLLOW_PSI_GRADIENTS_HPP
3 
4 #include "magnetic_field.hpp"
5 #include "plane.hpp"
6 
7 // RK2 routine to follow the Psi gradient a given distance dPsi in Psi space
8 KOKKOS_INLINE_FUNCTION void get_next_point_gl(const MagneticField<DeviceType>& magnetic_field, double x_in_r, double x_in_z, double phi, double psi_in, double dpsi, int direction, double& x_out_r, double& x_out_z, double& dist){
9  constexpr int maxsteps = 20;
10 
11  double x2_r = x_in_r;
12  double x2_z = x_in_z;
13 
14  // Plan for maxsteps/2 iterations -->
15  double dp = direction*dpsi/(maxsteps/2);
16  dist = 0.0;
17 
18  for (int i=0; i<maxsteps; i++){
19  double x1_r = x2_r;
20  double x1_z = x2_z;
21 
22  double psi,dpsi_dr,dpsi_dz,dpsi_dphi;
23 
24  // Predictor step: dl_perp=dpsi/|grad(Psi)|
25  // ==> dl_perp uvector_perp = dpsi*grad(Psi)/|grad(Psi)|^2
26  magnetic_field.get_psi_and_derivs(x1_r, x1_z, phi, psi,dpsi_dr,dpsi_dz,dpsi_dphi );
27  double gradpsi2 = (dpsi_dr*dpsi_dr + dpsi_dz*dpsi_dz); // |grad(Psi)|^2
28  x2_r = x1_r + (0.5*dp)*dpsi_dr/gradpsi2;
29  x2_z = x1_z + (0.5*dp)*dpsi_dz/gradpsi2;
30 
31  // I need to implement a fallback in case we are on the separatrix:
32  if(gradpsi2<1.0e-13){
33  DEVICE_PRINTF("get_next_point_gl: Warning, gradient smaller than threshold!\n");
34  }
35 
36  // Corrector step
37  magnetic_field.get_psi_and_derivs(x2_r, x2_z, phi, psi,dpsi_dr,dpsi_dz,dpsi_dphi );
38  gradpsi2 = (dpsi_dr*dpsi_dr + dpsi_dz*dpsi_dz);
39  x2_r = x1_r + dp*dpsi_dr/gradpsi2;
40  x2_z = x1_z + dp*dpsi_dz/gradpsi2;
41 
42  psi = magnetic_field.get_psi(x2_r, x2_z, phi);
43  if(abs(psi-psi_in)>=dpsi){
44  if(abs(psi-psi_in)>1.5*dpsi){
45  // We have gone too far, go one step back
46  x2_r = x1_r;
47  x2_z = x1_z;
48  } else {
49  dist += sqrt((x2_r-x1_r)*(x2_r-x1_r) + (x2_z-x1_z)*(x2_z-x1_z));
50  }
51  // exit the loop
52  x_out_r = x2_r;
53  x_out_z = x2_z;
54  break;
55  }
56  dist += sqrt((x2_r-x1_r)*(x2_r-x1_r) + (x2_z-x1_z)*(x2_z-x1_z));
57  }
58 }
59 
60 // RK2 routine to follow the Psi gradient a given distance dlperp
61 KOKKOS_INLINE_FUNCTION void get_next_point_perp(const MagneticField<DeviceType>& magnetic_field, double x_in_r, double x_in_z, double phi, double dlperp, int direction, double& x_out_r, double& x_out_z, double& dist){
62  constexpr int maxsteps = 20;
63 
64  double x2_r = x_in_r;
65  double x2_z = x_in_z;
66 
67  // Plan for maxsteps/2 iterations -->
68  double dlp = direction*dlperp/(maxsteps/2);
69  dist = 0.0;
70  double dlc = 0;
71 
72  for (int i=0; i<maxsteps; i++){
73  double x1_r = x2_r;
74  double x1_z = x2_z;
75 
76  double psi,dpsi_dr,dpsi_dz,dpsi_dphi;
77 
78  // Predictor step
79  magnetic_field.get_psi_and_derivs(x1_r, x1_z, phi, psi,dpsi_dr,dpsi_dz,dpsi_dphi );
80  double gradpsi = sqrt(dpsi_dr*dpsi_dr + dpsi_dz*dpsi_dz);
81  double inv_gradpsi = 1.0/gradpsi;
82  x2_r = x1_r + (0.5*dlp)*dpsi_dr*inv_gradpsi;
83  x2_z = x1_z + (0.5*dlp)*dpsi_dz*inv_gradpsi;
84 
85  // I need to implement a fallback in case we are on the separatrix:
86  if(gradpsi<1.0e-8 && abs(x1_r - magnetic_field.equil.axis.r)>1.0e-4 && abs(x1_z - magnetic_field.equil.axis.z)>1.0e-9){
87  DEVICE_PRINTF("get_next_point_perp: Warning, gradient smaller than threshold! %1.15e %1.15e %1.15e %1.15e\n",x1_r,x1_z,
88  abs(x1_r - magnetic_field.equil.axis.r), abs(x1_z - magnetic_field.equil.axis.z));
89  }
90 
91  // Corrector step
92  magnetic_field.get_psi_and_derivs(x2_r, x2_z, phi, psi,dpsi_dr,dpsi_dz,dpsi_dphi );
93  gradpsi = sqrt(dpsi_dr*dpsi_dr + dpsi_dz*dpsi_dz);
94  inv_gradpsi = 1.0/gradpsi;
95  x2_r = x1_r + dlp*dpsi_dr*inv_gradpsi;
96  x2_z = x1_z + dlp*dpsi_dz*inv_gradpsi;
97 
98  dlc += sqrt((x2_r-x1_r)*(x2_r-x1_r) + (x2_z-x1_z)*(x2_z-x1_z));
99  if(dlc>=dlperp){
100  if(dlc>1.5*dlperp){
101  // We have gone too far, go one step back
102  dist = dlc - sqrt((x2_r-x1_r)*(x2_r-x1_r) + (x2_z-x1_z)*(x2_z-x1_z));
103  x2_r = x1_r;
104  x2_z = x1_z;
105  } else {
106  dist = dlc;
107  }
108  // exit the loop
109  x_out_r = x2_r;
110  x_out_z = x2_z;
111  break;
112  }
113  dist = dlc;
114  }
115 }
116 
117 KOKKOS_INLINE_FUNCTION void single_tang_step(const MagneticField<DeviceType>& magnetic_field, double r, double z, double phi, double& rp, double& zp){
118  double psi,dpsi_dr,dpsi_dz,dpsi_dphi;
119  magnetic_field.get_psi_and_derivs(r, z, phi, psi,dpsi_dr,dpsi_dz,dpsi_dphi );
120  double inv_gradpsi = 1.0/sqrt(dpsi_dr*dpsi_dr + dpsi_dz*dpsi_dz);
121  //double inv_gradpsi = 1.0/gradpsi;
122  rp = -dpsi_dz*inv_gradpsi;
123  zp = dpsi_dr*inv_gradpsi;
124 
125  // If gradpsi is small, set to derivatives to 0
126  //constexpr double GRADPSI_MIN = 1.0e-12;
127  //rp = gradpsi<GRADPSI_MIN ? 0.0 : rp;
128  //zp = gradpsi<GRADPSI_MIN ? 0.0 : zp;
129 }
130 
131 // RK4 routine to follow the flux surface
132 KOKKOS_INLINE_FUNCTION void get_next_point_tang(const MagneticField<DeviceType>& magnetic_field, double x_r_in, double x_z_in, double phi, double dltheta, int direction, double& x_r_out, double& x_z_out, double& dist){
133  constexpr int maxsteps = 50;
134 
135  double x_r5 = x_r_in;
136  double x_z5 = x_z_in;
137 
138  // Plan for maxsteps/2 iterations -->
139  double dlt = direction*dltheta/(maxsteps/2);
140  dist = 0.0;
141  double dlc = 0;
142 
143  for (int i=0; i<maxsteps; i++){
144  // RK step 1
145  double x_r1 = x_r5;
146  double x_z1 = x_z5;
147  double xp_r1, xp_z1;
148  single_tang_step(magnetic_field, x_r1, x_z1, phi, xp_r1, xp_z1);
149 
150  // RK step 2
151  double x_r2 = x_r1 + (0.5*dlt)*xp_r1;
152  double x_z2 = x_z1 + (0.5*dlt)*xp_z1;
153  double xp_r2, xp_z2;
154  single_tang_step(magnetic_field, x_r2, x_z2, phi, xp_r2, xp_z2);
155 
156  // RK step 3
157  double x_r3 = x_r1 + (0.5*dlt)*xp_r2;
158  double x_z3 = x_z1 + (0.5*dlt)*xp_z2;
159  double xp_r3, xp_z3;
160  single_tang_step(magnetic_field, x_r3, x_z3, phi, xp_r3, xp_z3);
161 
162  // RK step 4
163  double x_r4 = x_r1 + dlt*xp_r3;
164  double x_z4 = x_z1 + dlt*xp_z3;
165  double xp_r4, xp_z4;
166  single_tang_step(magnetic_field, x_r4, x_z4, phi, xp_r4, xp_z4);
167 
168  // Corrector step
169  x_r5 = x_r1 + (dlt/6.0)*(xp_r1 + 2.0*(xp_r2 + xp_r3) + xp_r4);
170  x_z5 = x_z1 + (dlt/6.0)*(xp_z1 + 2.0*(xp_z2 + xp_z3) + xp_z4);
171 
172  dlc += sqrt((x_r5-x_r1)*(x_r5-x_r1) + (x_z5-x_z1)*(x_z5-x_z1));
173  if(dlc>=dltheta){
174  if(dlc>1.5*dltheta){
175  // We have gone too far, go one step back
176  dist = dlc - sqrt((x_r5-x_r1)*(x_r5-x_r1) + (x_z5-x_z1)*(x_z5-x_z1));
177  x_r5 = x_r1;
178  x_z5 = x_z1;
179  } else {
180  dist = dlc;
181  }
182  // exit the loop
183  x_r_out = x_r5;
184  x_z_out = x_z5;
185  break;
186  }
187  dist = dlc;
188  }
189 }
190 // This routine uses get_next_point_gl to find a valid point in grad(psi)
191 // direction using a psi-difference. It reduces the input distance until it finds a valid point
192 // or the target distance is less than 10% of the input distance
193 KOKKOS_INLINE_FUNCTION void get_node_psi(const MagneticField<DeviceType>& magnetic_field, const Plane<DeviceType>& plane, double x_r, double x_z, double psi_in, double dpsi_in, int direction, double& dist_out, int& itr, double (&p)[3]){
194 
195  double dpsi = dpsi_in;
196  itr = -1;
197 
198  double x2_r = -1.0;
199  double x2_z = -1.0;
200 
201  while(itr<1 && dpsi>=0.1*dpsi_in){
202  get_next_point_gl(magnetic_field, x_r, x_z, plane.plane_phi, psi_in,dpsi,direction, x2_r, x2_z, dist_out);
203  double psi = magnetic_field.get_psi(x2_r, x2_z, plane.plane_phi);
204  plane.search_tr2_no_precheck(x2_r,x2_z,itr,p);
205 #if defined(XGCA) || defined(NEOCLASSICAL_TEST)
206  plane.t_coeff_mod(magnetic_field, x2_r,x2_z,psi,itr,p);
207 #endif
208 
209  if(itr<1){
210  dpsi *= 0.4;
211  } else {
212  break;
213  }
214  }
215  if(itr<1){
216  // This is not an error, although it should not happen.
217  // This is likely a node close to the wall.
218  dist_out = 0.0;
219  // if (sml_mype==0) then
220  // print *,'init_grid_deriv: No base point found in Psi-direction'
221  // print '(a11,i3)', 'Direction: ', direction
222  // print '(a13,2f16.6)', 'Coordinates: ',x(1),x(2)
223  // endif
224  }
225 
226 }
227 
228 // This routine uses get_next_point_perp to find a valid point in grad(psi)
229 // direction using a distance in m. It reduces the input distance until it finds a valid point
230 // or the target distance is less than 10% of the input distance
231 KOKKOS_INLINE_FUNCTION void get_node_perp(const MagneticField<DeviceType>& magnetic_field, const Plane<DeviceType>& plane, double x_r, double x_z, double dlperp_in, int direction, double& dist_out, int& itr, double (&p)[3]){
232 
233  double dlperp = dlperp_in;
234  itr = -1;
235 
236  double x2_r = -1.0;
237  double x2_z = -1.0;
238  while(itr<1 && dlperp>=0.1*dlperp_in){
239  get_next_point_perp(magnetic_field, x_r,x_z,plane.plane_phi, dlperp,direction, x2_r, x2_z, dist_out);
240  double psi = magnetic_field.get_psi(x2_r, x2_z, plane.plane_phi);
241  plane.search_tr2_no_precheck(x2_r,x2_z,itr,p);
242 #if defined(XGCA) || defined(NEOCLASSICAL_TEST)
243  plane.t_coeff_mod(magnetic_field,x2_r,x2_z,psi,itr,p);
244 #endif
245 
246  if(itr<1){
247  dlperp *= 0.4;
248  } else {
249  break;
250  }
251  }
252  if(itr<1){
253  // This is not an error, although it should not happen.
254  // This is likely a node close to the wall.
255  dist_out = 0.0;
256  // if (sml_mype==0) then
257  // print *,'init_grid_deriv: No base point found in Psi-direction'
258  // print '(a11,i3)', 'Direction: ', direction
259  // print '(a13,2f16.6)', 'Coordinates: ',x(1),x(2)
260  // endif
261  }
262 
263 }
264 
265 // This routine uses get_next_point_tang to find a valid point in grad(theta)
266 // direction using a distance in m. It reduces the input distance until it finds a valid point
267 // or the target distance is less than 10% of the input distance
268 KOKKOS_INLINE_FUNCTION void get_node_theta(const MagneticField<DeviceType>& magnetic_field, const Plane<DeviceType>& plane, double x_r, double x_z, double dltheta_in, int direction, double& dist_out, int& itr, double (&p)[3]){
269  double dltheta = dltheta_in;
270  itr = -1;
271 
272  double x2_r = -1.0;
273  double x2_z = -1.0;
274  while(itr<1 && dltheta>=0.1*dltheta_in){
275  get_next_point_tang(magnetic_field, x_r, x_z, plane.plane_phi, dltheta, direction, x2_r, x2_z, dist_out);
276  double psi = magnetic_field.get_psi(x2_r, x2_z, plane.plane_phi);
277  plane.search_tr2_no_precheck(x2_r,x2_z,itr,p);
278 #if defined(XGCA) || defined(NEOCLASSICAL_TEST)
279  plane.t_coeff_mod(magnetic_field,x2_r,x2_z,psi,itr,p);
280 #endif
281  if(itr<1){
282  dltheta *= 0.4;
283  } else {
284  break;
285  }
286  }
287  if(itr<1){
288  // This is not an error, although it should not happen.
289  // This is likely a node close to the wall.
290  dist_out = 0.0;
291  }
292 }
293 
294 // This routine uses get_next_point_tang to find a valid point in grad(theta)
295 // direction using a distance in m. It reduces the input distance until it finds a valid point
296 // or the target distance is less than 10% of the input distance
297 // Same as above, but input coordinates are provided by itr0 and p0 rather than x_r and x_z
298 KOKKOS_INLINE_FUNCTION void get_node_theta(const MagneticField<DeviceType>& magnetic_field, const Plane<DeviceType>& plane, int& itr0, double (&p0)[3], double dltheta_in, int direction, double& dist_out, int& itr, double (&p)[3]){
299  double dltheta = dltheta_in;
300  itr = -1;
301 
302  // Get rz coordinates since itr0 and p0 are provided rather than x_r and x_z directly
303  double x_r, x_z;
304  plane.get_rz_coordinates(itr0, p0, x_r, x_z);
305 
306  double x2_r = -1.0;
307  double x2_z = -1.0;
308  while(itr<1 && dltheta>=0.1*dltheta_in){
309  get_next_point_tang(magnetic_field, x_r, x_z, plane.plane_phi, dltheta, direction, x2_r, x2_z, dist_out);
310  double psi = magnetic_field.get_psi(x2_r, x2_z, plane.plane_phi);
311  plane.search_tr2_no_precheck(x2_r,x2_z,itr,p);
312 #if defined(XGCA) || defined(NEOCLASSICAL_TEST)
313  plane.t_coeff_mod(magnetic_field,x2_r,x2_z,psi,itr,p);
314 #endif
315  if(itr<1){
316  dltheta *= 0.5;
317  } else {
318  break;
319  }
320  }
321  if(itr<1){
322  // This is not an error, although it should not happen.
323  // This is likely a node close to the wall.
324  dist_out = 0.0;
325  }
326 }
327 
328 #endif
Definition: magnetic_field.hpp:12
Definition: plane.hpp:17
KOKKOS_INLINE_FUNCTION void search_tr2_no_precheck(const double r, const double z, int &itr, double(&p)[3]) const
Definition: plane.tpp:13
double plane_phi
phi coordinate of this plane
Definition: plane.hpp:261
KOKKOS_INLINE_FUNCTION void t_coeff_mod(const MagneticField< Device > &magnetic_field, const double r, const double z, const double psiin, const int itr, double(&p)[3]) const
Definition: plane.tpp:139
KOKKOS_INLINE_FUNCTION void get_rz_coordinates(const int inode, double &r, double &z) const
Definition: plane.tpp:1003
KOKKOS_INLINE_FUNCTION void get_node_psi(const MagneticField< DeviceType > &magnetic_field, const Plane< DeviceType > &plane, double x_r, double x_z, double psi_in, double dpsi_in, int direction, double &dist_out, int &itr, double(&p)[3])
Definition: follow_psi_gradients.hpp:193
KOKKOS_INLINE_FUNCTION void get_next_point_perp(const MagneticField< DeviceType > &magnetic_field, double x_in_r, double x_in_z, double phi, double dlperp, int direction, double &x_out_r, double &x_out_z, double &dist)
Definition: follow_psi_gradients.hpp:61
KOKKOS_INLINE_FUNCTION void get_next_point_gl(const MagneticField< DeviceType > &magnetic_field, double x_in_r, double x_in_z, double phi, double psi_in, double dpsi, int direction, double &x_out_r, double &x_out_z, double &dist)
Definition: follow_psi_gradients.hpp:8
KOKKOS_INLINE_FUNCTION void get_node_theta(const MagneticField< DeviceType > &magnetic_field, const Plane< DeviceType > &plane, double x_r, double x_z, double dltheta_in, int direction, double &dist_out, int &itr, double(&p)[3])
Definition: follow_psi_gradients.hpp:268
KOKKOS_INLINE_FUNCTION void get_node_perp(const MagneticField< DeviceType > &magnetic_field, const Plane< DeviceType > &plane, double x_r, double x_z, double dlperp_in, int direction, double &dist_out, int &itr, double(&p)[3])
Definition: follow_psi_gradients.hpp:231
KOKKOS_INLINE_FUNCTION void get_next_point_tang(const MagneticField< DeviceType > &magnetic_field, double x_r_in, double x_z_in, double phi, double dltheta, int direction, double &x_r_out, double &x_z_out, double &dist)
Definition: follow_psi_gradients.hpp:132
KOKKOS_INLINE_FUNCTION void single_tang_step(const MagneticField< DeviceType > &magnetic_field, double r, double z, double phi, double &rp, double &zp)
Definition: follow_psi_gradients.hpp:117
integer, parameter dp
Definition: module.F90:2453
real(8), parameter phi
Definition: load_balance_constraint_mod.F90:18
Definition: magnetic_field.F90:1
#define DEVICE_PRINTF(...)
Definition: space_settings.hpp:86