| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | module field_boozer_chartmap | ||
| 2 | !> Boozer field from extended chartmap NetCDF file. | ||
| 3 | !> | ||
| 4 | !> Reads an extended chartmap file containing Boozer coordinate geometry | ||
| 5 | !> (X, Y, Z on a Boozer angle grid) plus magnetic field data: | ||
| 6 | !> - A_phi(s) : toroidal vector potential (1D flux profile) | ||
| 7 | !> - B_theta(rho) : covariant poloidal B component (1D, surface function) | ||
| 8 | !> - B_phi(rho) : covariant toroidal B component (1D, surface function) | ||
| 9 | !> - Bmod(rho,theta,zeta) : field magnitude (3D) | ||
| 10 | !> - torflux : total toroidal flux (scalar attribute) | ||
| 11 | !> | ||
| 12 | !> This type serves as a magnetic_field_t for the non-canonical path and | ||
| 13 | !> provides data to populate the Boozer batch splines for the symplectic path. | ||
| 14 | |||
| 15 | use, intrinsic :: iso_fortran_env, only: dp => real64 | ||
| 16 | use field_base, only: magnetic_field_t | ||
| 17 | use interpolate, only: BatchSplineData1D, BatchSplineData3D, & | ||
| 18 | construct_batch_splines_1d, construct_batch_splines_3d, & | ||
| 19 | evaluate_batch_splines_1d_der2, & | ||
| 20 | evaluate_batch_splines_3d, & | ||
| 21 | destroy_batch_splines_1d, destroy_batch_splines_3d | ||
| 22 | use new_vmec_stuff_mod, only: vmec_B_scale, vmec_RZ_scale, nper, rmajor | ||
| 23 | use boozer_chartmap_io, only: boozer_chartmap_data_t, read_boozer_chartmap | ||
| 24 | use netcdf | ||
| 25 | use scaled_chartmap_coordinates, only: wrap_scaled_chartmap_coordinate_system | ||
| 26 | |||
| 27 | implicit none | ||
| 28 | |||
| 29 | private | ||
| 30 | public :: boozer_chartmap_field_t, create_boozer_chartmap_field, & | ||
| 31 | is_boozer_chartmap, has_boozer_rk_tables | ||
| 32 | |||
| 33 | type, extends(magnetic_field_t) :: boozer_chartmap_field_t | ||
| 34 | type(BatchSplineData1D) :: aphi_spline | ||
| 35 | type(BatchSplineData1D) :: bcovar_spline | ||
| 36 | type(BatchSplineData3D) :: bmod_spline | ||
| 37 | real(dp) :: torflux = 0.0_dp | ||
| 38 | integer :: nfp = 1 | ||
| 39 | integer :: ns = 0 | ||
| 40 | integer :: ntheta = 0 | ||
| 41 | integer :: nphi = 0 | ||
| 42 | real(dp) :: hs = 0.0_dp | ||
| 43 | real(dp) :: h_theta = 0.0_dp | ||
| 44 | real(dp) :: h_phi = 0.0_dp | ||
| 45 | character(len=512) :: filename = '' | ||
| 46 | logical :: initialized = .false. | ||
| 47 | contains | ||
| 48 | procedure :: evaluate => boozer_chartmap_evaluate | ||
| 49 | final :: boozer_chartmap_cleanup | ||
| 50 | end type boozer_chartmap_field_t | ||
| 51 | |||
| 52 | contains | ||
| 53 | |||
| 54 | 430 | function is_boozer_chartmap(filename) result(is_bc) | |
| 55 | character(len=*), intent(in) :: filename | ||
| 56 | logical :: is_bc | ||
| 57 | integer :: ncid, status, ival | ||
| 58 | |||
| 59 | 430 | is_bc = .false. | |
| 60 | 430 | status = nf90_open(trim(filename), nf90_nowrite, ncid) | |
| 61 |
1/2✓ Branch 0 taken 430 times.
✗ Branch 1 not taken.
|
430 | if (status /= nf90_noerr) return |
| 62 | |||
| 63 | 430 | status = nf90_get_att(ncid, nf90_global, "boozer_field", ival) | |
| 64 |
3/4✓ Branch 0 taken 379 times.
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
|
430 | if (status == nf90_noerr .and. ival == 1) is_bc = .true. |
| 65 | |||
| 66 | 430 | status = nf90_close(ncid) | |
| 67 | 430 | end function is_boozer_chartmap | |
| 68 | |||
| 69 | !> True for a Boozer chartmap carrying the compact RK tables (rk_Bmod, ...) | ||
| 70 | !> that native CUDA tracing needs (libneo booz_xform_to_boozer_chartmap). | ||
| 71 | 1 | function has_boozer_rk_tables(filename) result(has_tables) | |
| 72 | character(len=*), intent(in) :: filename | ||
| 73 | logical :: has_tables | ||
| 74 | integer :: ncid, status, varid | ||
| 75 | |||
| 76 | 1 | has_tables = .false. | |
| 77 |
1/2✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
|
1 | if (.not. is_boozer_chartmap(filename)) return |
| 78 | ✗ | status = nf90_open(trim(filename), nf90_nowrite, ncid) | |
| 79 | ✗ | if (status /= nf90_noerr) return | |
| 80 | ✗ | has_tables = nf90_inq_varid(ncid, "rk_Bmod", varid) == nf90_noerr | |
| 81 | ✗ | status = nf90_close(ncid) | |
| 82 | ✗ | end function has_boozer_rk_tables | |
| 83 | |||
| 84 | 10 | subroutine create_boozer_chartmap_field(filename, field) | |
| 85 | use libneo_coordinates, only: coordinate_system_t, & | ||
| 86 | make_chartmap_coordinate_system | ||
| 87 | |||
| 88 | character(len=*), intent(in) :: filename | ||
| 89 | type(boozer_chartmap_field_t), allocatable, intent(out) :: field | ||
| 90 | |||
| 91 | 10 | type(boozer_chartmap_data_t) :: d | |
| 92 | 10 | real(dp), allocatable :: y_aphi(:, :), y_bcovar(:, :), y_bmod(:, :, :, :) | |
| 93 | real(dp) :: torflux_val | ||
| 94 | real(dp) :: b_scale, rz_scale, covar_scale, flux_scale | ||
| 95 | integer, parameter :: spline_order_1d = 5 | ||
| 96 | integer, parameter :: spline_order_3d(3) = [5, 5, 5] | ||
| 97 | logical, parameter :: periodic_3d(3) = [.false., .true., .true.] | ||
| 98 | real(dp) :: x_min_3d(3), x_max_3d(3) | ||
| 99 | 30 | class(coordinate_system_t), allocatable :: cs | |
| 100 | |||
| 101 |
15/16✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 4 taken 30 times.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 30 times.
✓ Branch 7 taken 10 times.
✓ Branch 8 taken 30 times.
✓ Branch 9 taken 10 times.
✓ Branch 10 taken 30 times.
✓ Branch 11 taken 10 times.
✓ Branch 12 taken 30 times.
✓ Branch 13 taken 10 times.
✓ Branch 14 taken 30 times.
✓ Branch 15 taken 10 times.
✓ Branch 16 taken 30 times.
✓ Branch 17 taken 10 times.
|
220 | allocate (field) |
| 102 | |||
| 103 | ! Single shared parse: base-unit arrays plus internal periodic endpoints. | ||
| 104 | 10 | call read_boozer_chartmap(filename, d) | |
| 105 | |||
| 106 | 10 | b_scale = vmec_B_scale | |
| 107 | 10 | rz_scale = vmec_RZ_scale | |
| 108 | 10 | covar_scale = b_scale*rz_scale | |
| 109 | 10 | flux_scale = covar_scale*rz_scale | |
| 110 | |||
| 111 |
2/2✓ Branch 0 taken 203 times.
✓ Branch 1 taken 10 times.
|
213 | d%A_phi = flux_scale*d%A_phi |
| 112 |
2/2✓ Branch 0 taken 203 times.
✓ Branch 1 taken 10 times.
|
213 | d%B_theta = covar_scale*d%B_theta |
| 113 |
2/2✓ Branch 0 taken 203 times.
✓ Branch 1 taken 10 times.
|
213 | d%B_phi = covar_scale*d%B_phi |
| 114 |
6/6✓ Branch 0 taken 387 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 13320 times.
✓ Branch 3 taken 387 times.
✓ Branch 4 taken 322668 times.
✓ Branch 5 taken 13320 times.
|
336385 | d%Bmod = b_scale*d%Bmod |
| 115 | 10 | torflux_val = flux_scale*d%torflux | |
| 116 | |||
| 117 |
7/14✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 10 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 10 times.
|
30 | allocate (y_aphi(d%n_s, 1)) |
| 118 |
2/2✓ Branch 0 taken 203 times.
✓ Branch 1 taken 10 times.
|
213 | y_aphi(:, 1) = d%A_phi |
| 119 | call construct_batch_splines_1d(d%s(1), d%s(d%n_s), y_aphi, & | ||
| 120 | 10 | spline_order_1d, .false., field%aphi_spline) | |
| 121 | |||
| 122 | ! Build B_theta, B_phi spline over rho_tor | ||
| 123 |
7/14✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 10 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 10 times.
|
30 | allocate (y_bcovar(d%n_rho, 2)) |
| 124 |
2/2✓ Branch 0 taken 203 times.
✓ Branch 1 taken 10 times.
|
213 | y_bcovar(:, 1) = d%B_theta |
| 125 |
2/2✓ Branch 0 taken 203 times.
✓ Branch 1 taken 10 times.
|
213 | y_bcovar(:, 2) = d%B_phi |
| 126 | call construct_batch_splines_1d(d%rho_min, d%rho_max, y_bcovar, spline_order_1d, & | ||
| 127 | 10 | .false., field%bcovar_spline) | |
| 128 | |||
| 129 | ! Build Bmod 3D spline over (rho_tor, theta_B, phi_B). The reader | ||
| 130 | ! appended exact endpoint planes, so x_max spans the full periods. | ||
| 131 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | x_min_3d = [d%rho_min, 0.0_dp, 0.0_dp] |
| 132 | x_max_3d = [d%rho_max, real(d%n_theta - 1, dp)*d%h_theta, & | ||
| 133 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | real(d%n_phi - 1, dp)*d%h_phi] |
| 134 | |||
| 135 |
11/22✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 10 times.
✓ Branch 10 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 10 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 10 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✓ Branch 17 taken 10 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 10 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 10 times.
|
50 | allocate (y_bmod(d%n_rho, d%n_theta, d%n_phi, 1)) |
| 136 |
6/6✓ Branch 0 taken 387 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 13320 times.
✓ Branch 3 taken 387 times.
✓ Branch 4 taken 322668 times.
✓ Branch 5 taken 13320 times.
|
336385 | y_bmod(:, :, :, 1) = d%Bmod |
| 137 | call construct_batch_splines_3d(x_min_3d, x_max_3d, y_bmod, & | ||
| 138 | spline_order_3d, periodic_3d, & | ||
| 139 | 10 | field%bmod_spline) | |
| 140 | |||
| 141 | ! Store metadata | ||
| 142 | 10 | field%torflux = torflux_val | |
| 143 | 10 | field%nfp = d%nfp | |
| 144 | 10 | field%ns = d%n_rho | |
| 145 | 10 | field%ntheta = d%n_theta | |
| 146 | 10 | field%nphi = d%n_phi | |
| 147 | 10 | field%hs = d%h_rho | |
| 148 | 10 | field%h_theta = d%h_theta | |
| 149 | 10 | field%h_phi = d%h_phi | |
| 150 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
|
10 | field%filename = filename |
| 151 | |||
| 152 | ! Restore equilibrium periods/major radius so stevvo and params_init | ||
| 153 | ! produce the correct dphi/dtaumin/fper for a VMEC-free chartmap run. | ||
| 154 | 10 | nper = d%nfp | |
| 155 | 10 | rmajor = d%rmajor*rz_scale | |
| 156 | |||
| 157 | ! Set up coordinate system from the same chartmap file | ||
| 158 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
10 | call make_chartmap_coordinate_system(cs, filename) |
| 159 | 10 | call wrap_scaled_chartmap_coordinate_system(cs, rz_scale) | |
| 160 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
|
10 | allocate (field%coords, source=cs) |
| 161 | |||
| 162 | 10 | field%initialized = .true. | |
| 163 | |||
| 164 | 10 | print *, 'Loaded Boozer chartmap field from ', trim(filename) | |
| 165 | 10 | print *, ' nfp=', d%nfp, ' ns=', d%n_rho, ' ntheta=', d%n_theta, & | |
| 166 | 20 | ' nphi=', d%n_phi | |
| 167 | 10 | print *, ' torflux=', torflux_val | |
| 168 |
14/28✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 10 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 10 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 10 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 10 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 10 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 10 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✓ Branch 23 taken 10 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 10 times.
✓ Branch 26 taken 10 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 10 times.
✗ Branch 29 not taken.
|
40 | end subroutine create_boozer_chartmap_field |
| 169 | |||
| 170 | 4 | subroutine boozer_chartmap_evaluate(self, x, Acov, hcov, Bmod, sqgBctr) | |
| 171 | !> Evaluate field at x = (rho, theta_B, phi_B) in chartmap coordinates. | ||
| 172 | !> Input x(1) = rho = sqrt(s), matching the chartmap coordinate system. | ||
| 173 | !> Returns covariant components in (rho, theta_B, phi_B) coordinates. | ||
| 174 | class(boozer_chartmap_field_t), intent(in) :: self | ||
| 175 | real(dp), intent(in) :: x(3) | ||
| 176 | real(dp), intent(out) :: Acov(3), hcov(3), Bmod | ||
| 177 | real(dp), intent(out), optional :: sqgBctr(3) | ||
| 178 | |||
| 179 | real(dp) :: rho_tor, s, theta_B, phi_B | ||
| 180 | real(dp) :: A_phi_val, A_theta_val | ||
| 181 | real(dp) :: y1d_aphi(1), dy1d_aphi(1), d2y1d_aphi(1) | ||
| 182 | real(dp) :: y1d_bc(2), dy1d_bc(2), d2y1d_bc(2) | ||
| 183 | real(dp) :: x_3d(3), y_bmod(1) | ||
| 184 | real(dp) :: B_theta_val, B_phi_val, Bmod_val | ||
| 185 | real(dp), parameter :: twopi = 8.0_dp * atan(1.0_dp) | ||
| 186 | |||
| 187 | 4 | rho_tor = x(1) | |
| 188 | 4 | theta_B = x(2) | |
| 189 | 4 | phi_B = x(3) | |
| 190 | 4 | s = rho_tor**2 | |
| 191 | |||
| 192 | ! A_theta = torflux * s (linear, by definition) | ||
| 193 | ! In rho coordinates: A_theta(rho) = torflux * rho^2 | ||
| 194 | ! Covariant component transforms: A_rho = A_s * ds/drho = A_s * 2*rho | ||
| 195 | ! But A_s = 0 (gauge), so A_rho = 0 regardless. | ||
| 196 | 4 | A_theta_val = self%torflux * s | |
| 197 | |||
| 198 | call evaluate_batch_splines_1d_der2(self%aphi_spline, s, & | ||
| 199 | 4 | y1d_aphi, dy1d_aphi, d2y1d_aphi) | |
| 200 | 4 | A_phi_val = y1d_aphi(1) | |
| 201 | |||
| 202 | ! B_theta, B_phi from 1D spline at rho_tor | ||
| 203 | call evaluate_batch_splines_1d_der2(self%bcovar_spline, rho_tor, y1d_bc, & | ||
| 204 | 4 | dy1d_bc, d2y1d_bc) | |
| 205 | 4 | B_theta_val = y1d_bc(1) | |
| 206 | 4 | B_phi_val = y1d_bc(2) | |
| 207 | |||
| 208 | ! Bmod from 3D spline at (rho_tor, theta_B, phi_B) | ||
| 209 | 4 | x_3d(1) = rho_tor | |
| 210 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
4 | x_3d(2) = modulo(theta_B, twopi) |
| 211 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
4 | x_3d(3) = modulo(phi_B, twopi / real(self%nfp, dp)) |
| 212 | |||
| 213 | 4 | call evaluate_batch_splines_3d(self%bmod_spline, x_3d, y_bmod) | |
| 214 | 4 | Bmod_val = y_bmod(1) | |
| 215 | |||
| 216 | 4 | Bmod = Bmod_val | |
| 217 | |||
| 218 | ! Covariant vector potential in (rho, theta, phi) coordinates. | ||
| 219 | ! A_theta and A_phi are the same in (s, theta, phi) and (rho, theta, phi) | ||
| 220 | ! since the angular coordinates are unchanged. A_rho = 0. | ||
| 221 | 4 | Acov(1) = 0.0_dp | |
| 222 | 4 | Acov(2) = A_theta_val | |
| 223 | 4 | Acov(3) = A_phi_val | |
| 224 | |||
| 225 | ! Covariant unit field direction: h = B/|B| | ||
| 226 | ! B_theta and B_phi are covariant and independent of the radial | ||
| 227 | ! coordinate choice (they refer to the angular basis vectors). | ||
| 228 | 4 | hcov(1) = 0.0_dp | |
| 229 | 4 | hcov(2) = B_theta_val / Bmod_val | |
| 230 | 4 | hcov(3) = B_phi_val / Bmod_val | |
| 231 | |||
| 232 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (present(sqgBctr)) then |
| 233 | ✗ | error stop "sqgBctr not implemented for boozer_chartmap_field_t" | |
| 234 | end if | ||
| 235 | 10 | end subroutine boozer_chartmap_evaluate | |
| 236 | |||
| 237 | 6 | subroutine boozer_chartmap_cleanup(self) | |
| 238 | type(boozer_chartmap_field_t), intent(inout) :: self | ||
| 239 | |||
| 240 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (self%initialized) then |
| 241 | 6 | call destroy_batch_splines_1d(self%aphi_spline) | |
| 242 | 6 | call destroy_batch_splines_1d(self%bcovar_spline) | |
| 243 | 6 | call destroy_batch_splines_3d(self%bmod_spline) | |
| 244 | 6 | self%initialized = .false. | |
| 245 | end if | ||
| 246 | 6 | end subroutine boozer_chartmap_cleanup | |
| 247 | |||
| 248 |
16/32__field_boozer_chartmap_MOD___copy_field_boozer_chartmap_Boozer_chartmap_field_t:
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
__field_boozer_chartmap_MOD___final_field_boozer_chartmap_Boozer_chartmap_field_t:
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✓ Branch 11 taken 6 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 6 times.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 6 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 6 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 6 times.
|
24 | end module field_boozer_chartmap |
| 249 |