GCC Code Coverage Report


Directory: ./
File: src/field/field_boozer_chartmap.f90
Date: 2026-10-22 05:02:02
Coverage Exec Excl Total
Lines: 93.9% 92 0 98
Functions: 100.0% 7 0 7
Branches: 62.4% 116 0 186

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