| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | module classification | ||
| 2 | use, intrinsic :: iso_fortran_env, only: dp => real64 | ||
| 3 | use finite_check, only: is_finite, quiet_nan | ||
| 4 | !$ use omp_lib | ||
| 5 | use params, only: zstart, zend, times_lost, trap_par, perp_inv, iclass, & | ||
| 6 | ntimstep, confpart_trap, confpart_pass, notrace_passing, contr_pp, & | ||
| 7 | class_plot, ntcut, nturns, fast_class, n_tip_vars, nplagr, nder, npl_half, & | ||
| 8 | class_scores, & | ||
| 9 | nfp, fper, zerolam, num_surf, bmax, bmin, dtaumin, v0, cut_in_per, & | ||
| 10 | integmode, relerr, ntau, should_skip, orbit_exit_code, unresolved_orbits, & | ||
| 11 | max_consecutive_warning_holds, & | ||
| 12 | ORBIT_EXIT_COMPLETED, ORBIT_EXIT_LCFS, ORBIT_EXIT_SKIPPED, & | ||
| 13 | ORBIT_EXIT_NUMERICAL_DOMAIN, ORBIT_EXIT_NUMERICAL_MAXITER, & | ||
| 14 | ORBIT_EXIT_NUMERICAL_LINEAR, ORBIT_EXIT_NUMERICAL_EVENT | ||
| 15 | use util, only: twopi, sqrt2 | ||
| 16 | use velo_mod, only : isw_field_type | ||
| 17 | use orbit_symplectic, only : advance_symplectic_with_retry, & | ||
| 18 | orbit_timestep_sympl, get_val | ||
| 19 | use orbit_symplectic_base, only: SYMPLECTIC_STEP_BOUNDARY, & | ||
| 20 | SYMPLECTIC_STEP_OUTSIDE_DOMAIN, SYMPLECTIC_STEP_MAXITER, & | ||
| 21 | SYMPLECTIC_STEP_LINEAR_SOLVE, symplectic_newton_warning_mode | ||
| 22 | use simple, only : init_sympl, tracer_t | ||
| 23 | use cut_detector, only : fract_dimension | ||
| 24 | use diag_mod, only : icounter | ||
| 25 | use get_can_sub, only : vmec_to_can, can_to_vmec | ||
| 26 | use boozer_sub, only : vmec_to_boozer, boozer_to_vmec | ||
| 27 | use magfie_sub, only : CANFLUX, BOOZER | ||
| 28 | use check_orbit_type_sub, only : check_orbit_type | ||
| 29 | use detect_oneline_mod, only: jpar_variation_rate, rotation_number_drift, & | ||
| 30 | precession_turns, jpar_sample_count, & | ||
| 31 | rotation_half_count, tip_count, & | ||
| 32 | jpar_spread, jpar_ref, topology_margin, & | ||
| 33 | score_status | ||
| 34 | use diag_counters, only: count_event, EVT_WARNING_STEP_SKIP | ||
| 35 | |||
| 36 | implicit none | ||
| 37 | |||
| 38 | ! Classification result type - separates data from I/O | ||
| 39 | ! Note: 0=unclassified means the classification was not computed | ||
| 40 | ! This depends on orbit type (trapped/passing) and class_plot flag | ||
| 41 | type :: classification_result_t | ||
| 42 | logical :: passing ! Trapped (false) or passing (true) | ||
| 43 | logical :: lost ! Orbit lost (true) or confined (false) | ||
| 44 | integer :: fractal ! Fractal: 0=unclassified, 1=regular, 2=chaotic | ||
| 45 | integer :: jpar ! J_parallel: 0=unclassified, 1=regular, 2=stochastic | ||
| 46 | integer :: topology ! Topology: 0=unclassified, 1=ideal, 2=non-ideal | ||
| 47 | integer :: exit_code ! Physical or numerical orbit termination | ||
| 48 | end type classification_result_t | ||
| 49 | |||
| 50 | ! output files: | ||
| 51 | ! iaaa_bou - trapped-passing boundary | ||
| 52 | ! iaaa_pnt - forced regular passing | ||
| 53 | ! iaaa_prp - lossed passing | ||
| 54 | ! iaaa_prt - lossed trapped | ||
| 55 | ! iaaa_rep - regular passing | ||
| 56 | ! iaaa_ret - regular trapped | ||
| 57 | ! iaaa_stp - stochastic passing | ||
| 58 | ! iaaa_stt - stochastic trapped | ||
| 59 | integer, parameter :: iaaa_bou=20000, iaaa_pnt=10000, iaaa_prp=10001, iaaa_prt=10002, & | ||
| 60 | iaaa_rep=10011, iaaa_ret=10012, iaaa_stp=10021, iaaa_stt=10022 | ||
| 61 | |||
| 62 | ! output files: | ||
| 63 | ! iaaa_jre - regular trapped by J_parallel | ||
| 64 | ! iaaa_jst - stochastic trapped by J_parallel | ||
| 65 | ! iaaa_jer - non-classified trapped by J_parallel | ||
| 66 | ! iaaa_ire - ideal trapped by recurrences and monotonicity | ||
| 67 | ! iaaa_ist - non-ideal trapped by recurrences and monotonicity | ||
| 68 | ! iaaa_ier - non-classified trapped by recurrences and monotonicity | ||
| 69 | integer, parameter :: iaaa_jre=40012, iaaa_jst=40022, iaaa_jer=40032, & | ||
| 70 | iaaa_ire=50012, iaaa_ist=50022, iaaa_ier=50032 | ||
| 71 | |||
| 72 | contains | ||
| 73 | |||
| 74 | 48 | subroutine trace_orbit_with_classifiers(anorb, ipart, class_result) | |
| 75 | use find_bminmax_sub, only : get_bminmax | ||
| 76 | use magfie_sub, only : magfie | ||
| 77 | use plag_coeff_sub, only : plag_coeff | ||
| 78 | use alpha_lifetime_sub, only : orbit_timestep_axis | ||
| 79 | |||
| 80 | type(tracer_t), intent(inout) :: anorb | ||
| 81 | integer, intent(in) :: ipart | ||
| 82 | type(classification_result_t), intent(out) :: class_result | ||
| 83 | integer :: ierr | ||
| 84 | real(dp), dimension(5) :: z, z_step_start | ||
| 85 | real(dp) :: bmod,sqrtg | ||
| 86 | real(dp) :: surf_bmin, surf_bmax | ||
| 87 | real(dp), dimension(3) :: bder, hcovar, hctrvr, hcurl | ||
| 88 | integer :: first_unresolved_it, hold_streak, it, ktau, it_f | ||
| 89 | integer(8) :: kt | ||
| 90 | logical :: passing | ||
| 91 | |||
| 92 | integer :: ifp_tip,ifp_per | ||
| 93 | 48 | integer, dimension(:), allocatable :: ipoi | |
| 94 | 48 | real(dp), dimension(:), allocatable :: xp | |
| 95 | 48 | real(dp), dimension(:,:), allocatable :: coef,orb_sten | |
| 96 | 48 | real(dp), dimension(:,:), allocatable :: zpoipl_tip,zpoipl_per,dummy2d | |
| 97 | real(dp), dimension(n_tip_vars) :: var_tip | ||
| 98 | real(dp) :: phiper, alam_prev, par_inv | ||
| 99 | integer :: iper, itip, kper, nfp_tip, nfp_per | ||
| 100 | |||
| 101 | real(dp) :: fraction | ||
| 102 | real(dp) :: r,theta_vmec,varphi_vmec | ||
| 103 | logical :: regular | ||
| 104 | |||
| 105 | ! Variables and settings for classification by J_parallel and ideal orbit condition: | ||
| 106 | integer, parameter :: nfp_dim=3 | ||
| 107 | integer :: nfp_cot,ideal,ijpar,ierr_cot,iangvar | ||
| 108 | real(dp), dimension(nfp_dim) :: fpr_in | ||
| 109 | |||
| 110 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 48 times.
|
288 | zend(:,ipart) = 0d0 |
| 111 | ! | ||
| 112 | 48 | iangvar=2 | |
| 113 | ! End variables and settings for classification by J_parallel and ideal orbit condition | ||
| 114 | ! | ||
| 115 | |||
| 116 | ! Initialize classification result - all unclassified | ||
| 117 | 48 | class_result%passing = .false. | |
| 118 | 48 | class_result%lost = .false. | |
| 119 | 48 | class_result%fractal = 0 | |
| 120 | 48 | class_result%jpar = 0 | |
| 121 | 48 | class_result%topology = 0 | |
| 122 | 48 | class_result%exit_code = ORBIT_EXIT_COMPLETED | |
| 123 | |||
| 124 | ! open(unit=10000+ipart, iostat=stat, status='old') | ||
| 125 | ! if (stat == 0) close(10000+ipart, status='delete') | ||
| 126 | ! open(unit=20000+ipart, iostat=stat, status='old') | ||
| 127 | ! if (stat == 0) close(20000+ipart, status='delete') | ||
| 128 | |||
| 129 | ! Write out trapped-passing boundary at the classification cut: | ||
| 130 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 8 times.
|
48 | if(class_plot) then |
| 131 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 33 times.
|
40 | if(ipart.eq.1) then |
| 132 | 7 | z(1)=zstart(1,ipart) | |
| 133 | 7 | z(3)=cut_in_per*fper | |
| 134 |
2/2✓ Branch 0 taken 7007 times.
✓ Branch 1 taken 7 times.
|
7014 | do kt=0,1000 |
| 135 | 7007 | z(2)=1d-3*twopi*dble(kt) | |
| 136 | 7007 | call magfie(z(1:3),bmod,sqrtg,bder,hcovar,hctrvr,hcurl) | |
| 137 | 7014 | write(iaaa_bou,*) z(2),sqrt(1.d0-bmod/bmax) | |
| 138 | enddo | ||
| 139 | endif | ||
| 140 | endif | ||
| 141 | ! End write out trapped-passing boundary at the classification cut | ||
| 142 | ! | ||
| 143 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 48 times.
|
288 | z = zstart(:, ipart) |
| 144 | 48 | r=z(1) | |
| 145 | 48 | theta_vmec=z(2) | |
| 146 | 48 | varphi_vmec=z(3) | |
| 147 | ! | ||
| 148 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
|
48 | if(isw_field_type .eq. CANFLUX) then |
| 149 | ✗ | call vmec_to_can(r,theta_vmec,varphi_vmec,z(2),z(3)) | |
| 150 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | elseif(isw_field_type .eq. BOOZER) then |
| 151 | 48 | call vmec_to_boozer(r,theta_vmec,varphi_vmec,z(2),z(3)) | |
| 152 | endif | ||
| 153 | |||
| 154 | ! In case of classification plot all starting points are moved to the classification cut: | ||
| 155 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 8 times.
|
48 | if(class_plot) then |
| 156 | 40 | z(3)=cut_in_per*fper | |
| 157 |
2/4✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
|
40 | zstart(2,ipart)=modulo(zstart(2,ipart),twopi) |
| 158 | endif | ||
| 159 | ! End moving starting points to the classification cut | ||
| 160 | |||
| 161 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | if (integmode>0) call init_sympl(anorb%si, anorb%f, z, dtaumin, dtaumin, relerr, integmode) |
| 162 | |||
| 163 | 48 | call magfie(z(1:3),bmod,sqrtg,bder,hcovar,hctrvr,hcurl) | |
| 164 | |||
| 165 | ! Keep surface extrema per particle; the boundary plot reads shared bmax. | ||
| 166 | 96 | !$omp critical | |
| 167 |
2/2✓ Branch 0 taken 34 times.
✓ Branch 1 taken 14 times.
|
48 | if(num_surf > 1) then |
| 168 | 34 | call get_bminmax(z(1), surf_bmin, surf_bmax) | |
| 169 | else | ||
| 170 | 14 | surf_bmin = bmin | |
| 171 | 14 | surf_bmax = bmax | |
| 172 | endif | ||
| 173 | 48 | passing = z(5)**2 .gt. 1.d0 - bmod/surf_bmax | |
| 174 | trap_par(ipart) = ((1.d0 - z(5)**2)*surf_bmax/bmod - 1.d0)*surf_bmin & | ||
| 175 | 48 | /(surf_bmax - surf_bmin) | |
| 176 | 48 | perp_inv(ipart) = z(4)**2*(1.d0-z(5)**2)/bmod | |
| 177 |
2/2✓ Branch 0 taken 144 times.
✓ Branch 1 taken 48 times.
|
192 | iclass(:,ipart) = 0 |
| 178 | !$omp end critical | ||
| 179 | |||
| 180 | ! Store passing status in result | ||
| 181 | 48 | class_result%passing = passing | |
| 182 | |||
| 183 | ! Forced classification of passing as regular: | ||
| 184 |
3/4✓ Branch 0 taken 36 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
|
48 | if(passing .and. should_skip(ipart)) then |
| 185 | 72 | !$omp critical | |
| 186 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 36 times.
|
132 | confpart_pass=confpart_pass+1.d0 |
| 187 |
2/2✓ Branch 0 taken 180 times.
✓ Branch 1 taken 36 times.
|
216 | zend(:, ipart) = zstart(:, ipart) |
| 188 | 36 | times_lost(ipart) = -1.d0 | |
| 189 | !$omp end critical | ||
| 190 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 8 times.
|
36 | if(class_plot) then |
| 191 | 28 | !$omp critical | |
| 192 | 28 | write (iaaa_pnt,*) zstart(2,ipart),zstart(5,ipart),trap_par(ipart) | |
| 193 | !$omp end critical | ||
| 194 | endif | ||
| 195 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 36 times.
|
144 | iclass(:,ipart) = 1 |
| 196 | ! Mark as regular passing (fractal=1) and not lost | ||
| 197 | 36 | class_result%fractal = 1 | |
| 198 | 36 | class_result%lost = .false. | |
| 199 | 36 | class_result%exit_code = ORBIT_EXIT_SKIPPED | |
| 200 | 36 | orbit_exit_code(ipart) = ORBIT_EXIT_SKIPPED | |
| 201 | 36 | return | |
| 202 | endif | ||
| 203 | ! End forced classification of passing as regular | ||
| 204 | |||
| 205 | 24 | !$omp critical | |
| 206 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (.not. allocated(ipoi)) & |
| 207 |
27/54✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 12 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 12 times.
✓ Branch 19 taken 12 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✓ Branch 22 taken 12 times.
✓ Branch 23 taken 12 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 12 times.
✗ Branch 26 not taken.
✓ Branch 27 taken 12 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✓ Branch 30 taken 12 times.
✗ Branch 32 not taken.
✓ Branch 33 taken 12 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 12 times.
✓ Branch 37 taken 12 times.
✗ Branch 38 not taken.
✓ Branch 39 taken 12 times.
✗ Branch 40 not taken.
✓ Branch 41 taken 12 times.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✓ Branch 44 taken 12 times.
✗ Branch 46 not taken.
✓ Branch 47 taken 12 times.
✗ Branch 49 not taken.
✓ Branch 50 taken 12 times.
✓ Branch 51 taken 12 times.
✗ Branch 52 not taken.
✓ Branch 53 taken 12 times.
✗ Branch 54 not taken.
✓ Branch 55 taken 12 times.
✗ Branch 56 not taken.
✗ Branch 57 not taken.
✓ Branch 58 taken 12 times.
✗ Branch 60 not taken.
✓ Branch 61 taken 12 times.
|
120 | allocate(ipoi(nplagr),coef(0:nder,nplagr),orb_sten(6,nplagr),xp(nplagr)) |
| 208 | !$omp end critical | ||
| 209 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 12 times.
|
60 | do it=1,nplagr |
| 210 | 60 | ipoi(it)=it | |
| 211 | enddo | ||
| 212 | |||
| 213 | 12 | nfp_tip=nfp !<= initial array dimension for tips | |
| 214 | 12 | nfp_per=nfp !<= initial array dimension for periods | |
| 215 | 24 | !$omp critical | |
| 216 | if (.not. allocated(zpoipl_tip)) & | ||
| 217 |
10/20✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 12 times.
✓ Branch 16 taken 12 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 12 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 12 times.
|
48 | allocate(zpoipl_tip(2,nfp_tip),zpoipl_per(2,nfp_per)) |
| 218 | !$omp end critical | ||
| 219 | |||
| 220 | ! open(unit=10000+ipart, recl=1024, position='append') | ||
| 221 | ! open(unit=20000+ipart, recl=1024, position='append') | ||
| 222 | |||
| 223 | 12 | ifp_tip=0 !<= initialize footprint counter on tips | |
| 224 | 12 | ifp_per=0 !<= initialize footprint counter on periods | |
| 225 | |||
| 226 | 12 | icounter=0 | |
| 227 | 12 | phiper=0.0d0 | |
| 228 | |||
| 229 | |||
| 230 | 12 | kt = 0 | |
| 231 | 12 | first_unresolved_it = 0 | |
| 232 | 12 | hold_streak = 0 | |
| 233 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (passing) then |
| 234 | ✗ | !$omp atomic | |
| 235 | confpart_pass(1)=confpart_pass(1)+1.d0 | ||
| 236 | else | ||
| 237 | 12 | !$omp atomic | |
| 238 | confpart_trap(1)=confpart_trap(1)+1.d0 | ||
| 239 | end if | ||
| 240 | |||
| 241 | !-------------------------------- | ||
| 242 | ! Initialize tip detector | ||
| 243 | |||
| 244 | 12 | itip=npl_half+1 | |
| 245 | 12 | alam_prev=z(5) | |
| 246 | |||
| 247 | ! End initialize tip detector | ||
| 248 | !-------------------------------- | ||
| 249 | ! Initialize period crossing detector | ||
| 250 | |||
| 251 | 12 | iper=npl_half+1 | |
| 252 | 12 | kper=int(z(3)/fper) | |
| 253 | |||
| 254 | ! End initialize period crossing detector | ||
| 255 | !-------------------------------- | ||
| 256 | ! | ||
| 257 | ! Initialize classification by J_parallel and ideal orbit condition: | ||
| 258 | 12 | nfp_cot=0 | |
| 259 | ! End Initialize classification by J_parallel and ideal orbit condition | ||
| 260 | ! | ||
| 261 | 12 | par_inv = 0d0 | |
| 262 | 12 | regular = .False. | |
| 263 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 12 times.
|
24 | do it=2,ntimstep |
| 264 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (regular) then ! regular orbit, will not be lost |
| 265 | ✗ | if(passing) then | |
| 266 | ✗ | !$omp atomic | |
| 267 | confpart_pass(it)=confpart_pass(it)+1.d0 | ||
| 268 | else | ||
| 269 | ✗ | !$omp atomic | |
| 270 | confpart_trap(it)=confpart_trap(it)+1.d0 | ||
| 271 | endif | ||
| 272 | ✗ | kt = kt+ntau | |
| 273 | ✗ | cycle | |
| 274 | endif | ||
| 275 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 780 times.
|
792 | do ktau=1,ntau |
| 276 | 780 | z_step_start = z | |
| 277 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 780 times.
|
780 | if (integmode <= 0) then |
| 278 | ✗ | call orbit_timestep_axis(z, dtaumin, dtaumin, relerr, ierr) | |
| 279 | ✗ | if (ierr /= 0 .and. ierr /= 1 .and. & | |
| 280 | symplectic_newton_warning_mode) then | ||
| 281 | ✗ | z = z_step_start | |
| 282 | ✗ | if (hold_streak < max_consecutive_warning_holds) then | |
| 283 | ✗ | call count_event(EVT_WARNING_STEP_SKIP) | |
| 284 | ✗ | hold_streak = hold_streak + 1 | |
| 285 | ✗ | ierr = 0 | |
| 286 | end if | ||
| 287 | ✗ | else if (ierr == 0) then | |
| 288 | 780 | hold_streak = 0 | |
| 289 | end if | ||
| 290 | else | ||
| 291 | call advance_symplectic_with_retry(anorb%si, anorb%f, & | ||
| 292 | 780 | orbit_timestep_sympl, ierr) | |
| 293 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 780 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
780 | if (ierr /= 0 .and. ierr /= SYMPLECTIC_STEP_BOUNDARY .and. & |
| 294 | symplectic_newton_warning_mode) then | ||
| 295 | ✗ | z = z_step_start | |
| 296 | ✗ | if (hold_streak < max_consecutive_warning_holds) then | |
| 297 | ✗ | call count_event(EVT_WARNING_STEP_SKIP) | |
| 298 | ✗ | hold_streak = hold_streak + 1 | |
| 299 | ✗ | ierr = 0 | |
| 300 | end if | ||
| 301 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 780 times.
|
780 | else if (ierr == 0) then |
| 302 | 3120 | hold_streak = 0 | |
| 303 |
2/2✓ Branch 0 taken 2340 times.
✓ Branch 1 taken 780 times.
|
3120 | z(1:3) = anorb%si%z(1:3) |
| 304 | z(4) = dsqrt(anorb%f%mu*anorb%f%Bmod + & | ||
| 305 | 780 | 0.5d0*anorb%f%vpar**2) | |
| 306 | 780 | z(5) = anorb%f%vpar/(z(4)*sqrt2) | |
| 307 | end if | ||
| 308 | endif | ||
| 309 | |||
| 310 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 780 times.
|
780 | if(ierr.ne.0) then |
| 311 | call classify_classifier_exit(ierr, integmode, & | ||
| 312 | ✗ | class_result%lost, class_result%exit_code) | |
| 313 | ✗ | if(class_plot .and. class_result%lost) then | |
| 314 | ✗ | call output_lost_orbit_starting_data(ipart, passing) | |
| 315 | endif | ||
| 316 | exit | ||
| 317 | endif | ||
| 318 | 780 | kt = kt+1 | |
| 319 | |||
| 320 | 780 | par_inv = par_inv+z(5)**2*dtaumin ! parallel adiabatic invariant | |
| 321 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 732 times.
|
780 | if(kt.le.nplagr) then !<=first nplagr points to initialize stencil |
| 322 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 48 times.
|
288 | orb_sten(1:5,kt)=z |
| 323 | 48 | orb_sten(6,kt)=par_inv | |
| 324 | else !<=normal case, shift stencil | ||
| 325 |
2/2✓ Branch 0 taken 3660 times.
✓ Branch 1 taken 732 times.
|
4392 | orb_sten(1:5,ipoi(1))=z |
| 326 | 732 | orb_sten(6,ipoi(1))=par_inv | |
| 327 |
6/10✓ Branch 1 taken 732 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2928 times.
✓ Branch 4 taken 732 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 732 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2928 times.
✓ Branch 10 taken 732 times.
|
7320 | ipoi=cshift(ipoi,1) |
| 328 | endif | ||
| 329 | |||
| 330 | ! Tip detection and interpolation | ||
| 331 |
3/4✓ Branch 0 taken 520 times.
✓ Branch 1 taken 260 times.
✓ Branch 2 taken 260 times.
✗ Branch 3 not taken.
|
780 | if(alam_prev.lt.0.d0.and.z(5).gt.0.d0) itip=0 !<=tip has been passed |
| 332 | 780 | itip=itip+1 | |
| 333 | 780 | alam_prev=z(5) | |
| 334 |
2/2✓ Branch 0 taken 732 times.
✓ Branch 1 taken 48 times.
|
780 | if(kt.gt.nplagr) then !<=use only initialized stencil |
| 335 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 732 times.
|
732 | if(itip.eq.npl_half) then !<=stencil around tip is complete, interpolate |
| 336 | ✗ | xp=orb_sten(5,ipoi) | |
| 337 | |||
| 338 | ✗ | call plag_coeff(nplagr,nder,zerolam,xp,coef) | |
| 339 | |||
| 340 | ✗ | var_tip=matmul(orb_sten(:,ipoi),coef(0,:)) | |
| 341 | ✗ | if (.not. all(is_finite(var_tip))) then | |
| 342 | ! A failed numerical step must not become a raw | ||
| 343 | ! NaN/Inf classifier score. Preserve the event as | ||
| 344 | ! an explicitly unresolved particle; consumers | ||
| 345 | ! can then exclude it using the zero sample counts. | ||
| 346 | ✗ | class_scores(:, ipart) = 0.0_dp | |
| 347 | ✗ | iclass(:, ipart) = 0 | |
| 348 | ✗ | class_result%fractal = 0 | |
| 349 | ✗ | class_result%jpar = 0 | |
| 350 | ✗ | class_result%topology = 0 | |
| 351 | ✗ | class_result%exit_code = ORBIT_EXIT_NUMERICAL_EVENT | |
| 352 | ✗ | ierr = 1 | |
| 353 | exit | ||
| 354 | end if | ||
| 355 | ✗ | var_tip(2)=modulo(var_tip(2),twopi) | |
| 356 | ✗ | var_tip(3)=modulo(var_tip(3),twopi) | |
| 357 | |||
| 358 | ! write(10000+ipart,*) var_tip | ||
| 359 | |||
| 360 | ✗ | ifp_tip=ifp_tip+1 | |
| 361 | ✗ | if(ifp_tip.gt.nfp_tip) then !<=increase the buffer for banana tips | |
| 362 | ✗ | !$omp critical | |
| 363 | ✗ | allocate(dummy2d(2,ifp_tip-1)) | |
| 364 | !$omp end critical | ||
| 365 | ✗ | dummy2d=zpoipl_tip(:,1:ifp_tip-1) | |
| 366 | ✗ | !$omp critical | |
| 367 | ✗ | deallocate(zpoipl_tip) | |
| 368 | !$omp end critical | ||
| 369 | ✗ | nfp_tip=nfp_tip+nfp | |
| 370 | ✗ | !$omp critical | |
| 371 | ✗ | allocate(zpoipl_tip(2,nfp_tip)) | |
| 372 | !$omp end critical | ||
| 373 | ✗ | zpoipl_tip(:,1:ifp_tip-1)=dummy2d | |
| 374 | ✗ | !$omp critical | |
| 375 | ✗ | deallocate(dummy2d) | |
| 376 | !$omp end critical | ||
| 377 | endif | ||
| 378 | ✗ | zpoipl_tip(:,ifp_tip)=var_tip(1:2) | |
| 379 | ✗ | par_inv = par_inv - var_tip(6) | |
| 380 | ! | ||
| 381 | ! Classification by J_parallel and ideal orbit conditions: | ||
| 382 | ✗ | fpr_in(1)=var_tip(1) | |
| 383 | ✗ | fpr_in(2)=var_tip(iangvar) | |
| 384 | ✗ | fpr_in(3)=var_tip(6) | |
| 385 | ! | ||
| 386 | ✗ | call check_orbit_type(nturns,nfp_cot,fpr_in,ideal,ijpar,ierr_cot) | |
| 387 | ! | ||
| 388 | ✗ | class_scores(1, ipart) = jpar_variation_rate | |
| 389 | ✗ | class_scores(2, ipart) = rotation_number_drift | |
| 390 | ✗ | class_scores(3, ipart) = precession_turns | |
| 391 | ✗ | class_scores(4, ipart) = dble(jpar_sample_count) | |
| 392 | ✗ | class_scores(5, ipart) = dble(rotation_half_count) | |
| 393 | ✗ | class_scores(6, ipart) = dble(tip_count) | |
| 394 | ✗ | class_scores(7, ipart) = jpar_spread | |
| 395 | ✗ | class_scores(8, ipart) = jpar_ref | |
| 396 | ✗ | class_scores(9, ipart) = topology_margin | |
| 397 | ✗ | class_scores(10, ipart) = dble(score_status) | |
| 398 | ✗ | iclass(1,ipart) = ijpar | |
| 399 | ✗ | iclass(2,ipart) = ideal | |
| 400 | ! Store in classification result | ||
| 401 | ✗ | class_result%jpar = ijpar | |
| 402 | ✗ | class_result%topology = ideal | |
| 403 | ! fast_class without class_plot: stop tracing regular | ||
| 404 | ! orbits early (they are confined). With class_plot, | ||
| 405 | ! continue to ntcut so classification output is written. | ||
| 406 | if(fast_class .and. .not. class_plot & | ||
| 407 | ✗ | .and. ierr_cot /= 0 .and. ijpar == 1) then | |
| 408 | ✗ | regular = .True. | |
| 409 | endif | ||
| 410 | ! | ||
| 411 | ! End classification by J_parallel and ideal orbit conditions | ||
| 412 | endif | ||
| 413 | endif | ||
| 414 | ! End tip detection and interpolation | ||
| 415 | |||
| 416 | ! Periodic boundary footprint detection and interpolation | ||
| 417 |
1/2✓ Branch 0 taken 780 times.
✗ Branch 1 not taken.
|
780 | if(z(3).gt.dble(kper+1)*fper) then |
| 418 | iper=0 !<=periodic boundary has been passed | ||
| 419 | phiper=dble(kper+1)*fper | ||
| 420 | kper=kper+1 | ||
| 421 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 776 times.
|
780 | elseif(z(3).lt.dble(kper)*fper) then |
| 422 | 4 | iper=0 !<=periodic boundary has been passed | |
| 423 | 4 | phiper=dble(kper)*fper | |
| 424 | 4 | kper=kper-1 | |
| 425 | endif | ||
| 426 | 780 | iper=iper+1 | |
| 427 |
2/2✓ Branch 0 taken 732 times.
✓ Branch 1 taken 48 times.
|
780 | if(kt.gt.nplagr) then !<=use only initialized stencil |
| 428 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 732 times.
|
732 | if(iper.eq.npl_half) then !<=stencil around periodic boundary is complete, interpolate |
| 429 | ✗ | xp=orb_sten(3,ipoi)-phiper | |
| 430 | |||
| 431 | ✗ | call plag_coeff(nplagr,nder,zerolam,xp,coef) | |
| 432 | |||
| 433 | ✗ | var_tip=matmul(orb_sten(:,ipoi),coef(0,:)) | |
| 434 | ✗ | var_tip(2)=modulo(var_tip(2),twopi) | |
| 435 | ✗ | var_tip(3)=modulo(var_tip(3),twopi) | |
| 436 | ! write(20000+ipart,*) var_tip | ||
| 437 | ✗ | ifp_per=ifp_per+1 | |
| 438 | ✗ | if(ifp_per.gt.nfp_per) then !<=increase the buffer for periodic boundary footprints | |
| 439 | ✗ | !$omp critical | |
| 440 | ✗ | allocate(dummy2d(2,ifp_per-1)) | |
| 441 | !$omp end critical | ||
| 442 | ✗ | dummy2d=zpoipl_per(:,1:ifp_per-1) | |
| 443 | ✗ | !$omp critical | |
| 444 | ✗ | deallocate(zpoipl_per) | |
| 445 | !$omp end critical | ||
| 446 | ✗ | nfp_per=nfp_per+nfp | |
| 447 | ✗ | !$omp critical | |
| 448 | ✗ | allocate(zpoipl_per(2,nfp_per)) | |
| 449 | !$omp end critical | ||
| 450 | ✗ | zpoipl_per(:,1:ifp_per-1)=dummy2d | |
| 451 | ✗ | !$omp critical | |
| 452 | ✗ | deallocate(dummy2d) | |
| 453 | !$omp end critical | ||
| 454 | endif | ||
| 455 | ✗ | zpoipl_per(:,ifp_per)=var_tip(1:2) | |
| 456 | endif | ||
| 457 | endif | ||
| 458 | ! End periodic boundary footprint detection and interpolation | ||
| 459 | |||
| 460 | ! Cut classification into regular or chaotic | ||
| 461 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 780 times.
|
780 | if (kt == ntcut) then |
| 462 | ✗ | regular = .True. | |
| 463 | |||
| 464 | ✗ | if(ifp_per > 0) then | |
| 465 | |||
| 466 | ✗ | call fract_dimension(ifp_per,zpoipl_per(:,1:ifp_per),fraction) | |
| 467 | |||
| 468 | ✗ | if(fraction.gt.0.2d0) then | |
| 469 | ✗ | print *, ipart, ' chaotic per ', ifp_per | |
| 470 | ✗ | regular = .False. | |
| 471 | else | ||
| 472 | ✗ | print *, ipart, ' regular per', ifp_per | |
| 473 | endif | ||
| 474 | endif | ||
| 475 | |||
| 476 | ✗ | if(ifp_tip > 0) then | |
| 477 | |||
| 478 | ✗ | call fract_dimension(ifp_tip,zpoipl_tip(:,1:ifp_tip),fraction) | |
| 479 | |||
| 480 | ✗ | if(fraction.gt.0.2d0) then | |
| 481 | ✗ | print *, ipart, ' chaotic tip ', ifp_tip | |
| 482 | ✗ | regular = .False. | |
| 483 | ✗ | iclass(3,ipart) = 2 | |
| 484 | else | ||
| 485 | ✗ | print *, ipart, ' regular tip ', ifp_tip | |
| 486 | ✗ | iclass(3,ipart) = 1 | |
| 487 | endif | ||
| 488 | endif | ||
| 489 | |||
| 490 | ! Store fractal classification in result | ||
| 491 | ✗ | if(regular) then | |
| 492 | ✗ | class_result%fractal = 1 | |
| 493 | else | ||
| 494 | ✗ | class_result%fractal = 2 | |
| 495 | endif | ||
| 496 | |||
| 497 | ✗ | if(class_plot) then | |
| 498 | ✗ | call output_minkowsky_class(ipart, regular, passing) | |
| 499 | ✗ | ierr=1 | |
| 500 | endif | ||
| 501 | endif | ||
| 502 | ! | ||
| 503 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 780 times.
|
792 | if(ierr.ne.0) then |
| 504 | ✗ | if(class_plot .and. .not. passing) then | |
| 505 | ✗ | call output_jpar_class(ipart, ijpar) | |
| 506 | ✗ | call output_topological_class(ipart, ideal) | |
| 507 | ✗ | exit | |
| 508 | endif | ||
| 509 | endif | ||
| 510 | ! write(999, *) kt*dtaumin/v0, z | ||
| 511 | enddo | ||
| 512 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if(ierr.ne.0) exit |
| 513 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
24 | if(passing) then |
| 514 | ✗ | !$omp atomic | |
| 515 | confpart_pass(it)=confpart_pass(it)+1.d0 | ||
| 516 | else | ||
| 517 | 12 | !$omp atomic | |
| 518 | confpart_trap(it)=confpart_trap(it)+1.d0 | ||
| 519 | endif | ||
| 520 | enddo | ||
| 521 | |||
| 522 | 24 | !$omp critical | |
| 523 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 12 times.
|
72 | zend(:,ipart) = z |
| 524 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if(isw_field_type .eq. CANFLUX) then |
| 525 | ✗ | call can_to_vmec(z(1),z(2),z(3),zend(2,ipart),zend(3,ipart)) | |
| 526 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | elseif(isw_field_type .eq. BOOZER) then |
| 527 | 12 | call boozer_to_vmec(z(1),z(2),z(3),zend(2,ipart),zend(3,ipart)) | |
| 528 | endif | ||
| 529 | 12 | orbit_exit_code(ipart) = class_result%exit_code | |
| 530 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (class_result%exit_code >= ORBIT_EXIT_NUMERICAL_DOMAIN) then |
| 531 | ✗ | times_lost(ipart) = quiet_nan() | |
| 532 | else | ||
| 533 | 12 | times_lost(ipart) = kt*dtaumin/v0 | |
| 534 | end if | ||
| 535 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
|
12 | deallocate(zpoipl_tip, zpoipl_per) |
| 536 | !$omp end critical | ||
| 537 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (class_result%exit_code >= ORBIT_EXIT_NUMERICAL_DOMAIN) then |
| 538 | ✗ | if (first_unresolved_it == 0) first_unresolved_it = min(it, ntimstep) | |
| 539 | ✗ | do it_f = first_unresolved_it, ntimstep | |
| 540 | ✗ | !$omp atomic update | |
| 541 | unresolved_orbits(it_f) = unresolved_orbits(it_f) + 1 | ||
| 542 | end do | ||
| 543 | end if | ||
| 544 | ! close(unit=10000+ipart) | ||
| 545 | ! close(unit=10000+ipart) | ||
| 546 |
10/10✓ Branch 0 taken 12 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 36 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 36 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 36 times.
✓ Branch 9 taken 12 times.
✓ Branch 10 taken 36 times.
|
48 | end subroutine trace_orbit_with_classifiers |
| 547 | |||
| 548 | 3 | pure subroutine classify_classifier_exit(ierr, mode, lost, exit_code) | |
| 549 | integer, intent(in) :: ierr, mode | ||
| 550 | logical, intent(out) :: lost | ||
| 551 | integer, intent(out) :: exit_code | ||
| 552 | |||
| 553 | 3 | lost = .false. | |
| 554 |
5/6✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 2 times.
|
3 | if ((mode <= 0 .and. ierr == 1) .or. & |
| 555 | ierr == SYMPLECTIC_STEP_BOUNDARY) then | ||
| 556 | 1 | lost = .true. | |
| 557 | 1 | exit_code = ORBIT_EXIT_LCFS | |
| 558 | 1 | return | |
| 559 | end if | ||
| 560 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
|
2 | if (mode <= 0) then |
| 561 | 1 | exit_code = ORBIT_EXIT_NUMERICAL_EVENT | |
| 562 | 1 | return | |
| 563 | end if | ||
| 564 | |||
| 565 | ✗ | select case (ierr) | |
| 566 | case (SYMPLECTIC_STEP_OUTSIDE_DOMAIN) | ||
| 567 | ✗ | exit_code = ORBIT_EXIT_NUMERICAL_DOMAIN | |
| 568 | case (SYMPLECTIC_STEP_MAXITER) | ||
| 569 | 1 | exit_code = ORBIT_EXIT_NUMERICAL_MAXITER | |
| 570 | case (SYMPLECTIC_STEP_LINEAR_SOLVE) | ||
| 571 | ✗ | exit_code = ORBIT_EXIT_NUMERICAL_LINEAR | |
| 572 | case default | ||
| 573 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | exit_code = ORBIT_EXIT_NUMERICAL_EVENT |
| 574 | end select | ||
| 575 | 48 | end subroutine classify_classifier_exit | |
| 576 | |||
| 577 | |||
| 578 | ✗ | subroutine output_lost_orbit_starting_data(ipart, passing) | |
| 579 | integer, intent(in) :: ipart | ||
| 580 | logical, intent(in) :: passing | ||
| 581 | |||
| 582 | ✗ | if(passing) then | |
| 583 | ✗ | call write_output_line(iaaa_prp, ipart) | |
| 584 | else | ||
| 585 | ✗ | call write_output_line(iaaa_prt, ipart) | |
| 586 | endif | ||
| 587 | ✗ | end subroutine output_lost_orbit_starting_data | |
| 588 | |||
| 589 | |||
| 590 | 28 | subroutine output_minkowsky_class(ipart, regular, passing) | |
| 591 | integer, intent(in) :: ipart | ||
| 592 | logical, intent(in) :: regular, passing | ||
| 593 | |||
| 594 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | if(regular) then |
| 595 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | if(passing) then |
| 596 | 28 | call write_output_line(iaaa_rep, ipart) | |
| 597 | else | ||
| 598 | ✗ | call write_output_line(iaaa_ret, ipart) | |
| 599 | endif | ||
| 600 | else | ||
| 601 | ✗ | if(passing) then | |
| 602 | ✗ | call write_output_line(iaaa_stp, ipart) | |
| 603 | else | ||
| 604 | ✗ | call write_output_line(iaaa_stt, ipart) | |
| 605 | endif | ||
| 606 | endif | ||
| 607 | 28 | end subroutine output_minkowsky_class | |
| 608 | |||
| 609 | |||
| 610 | ✗ | subroutine output_jpar_class(ipart, ijpar) | |
| 611 | integer, intent(in) :: ipart, ijpar | ||
| 612 | |||
| 613 | ✗ | select case(ijpar) | |
| 614 | case(0) | ||
| 615 | ✗ | call write_output_line(iaaa_jer, ipart) | |
| 616 | case(1) | ||
| 617 | ✗ | call write_output_line(iaaa_jre, ipart) | |
| 618 | case(2) | ||
| 619 | ✗ | call write_output_line(iaaa_jst, ipart) | |
| 620 | end select | ||
| 621 | ✗ | end subroutine output_jpar_class | |
| 622 | |||
| 623 | |||
| 624 | ✗ | subroutine output_topological_class(ipart, ideal) | |
| 625 | integer, intent(in) :: ipart, ideal | ||
| 626 | |||
| 627 | ✗ | select case(ideal) | |
| 628 | case(0) | ||
| 629 | ✗ | call write_output_line(iaaa_ier, ipart) | |
| 630 | case(1) | ||
| 631 | ✗ | call write_output_line(iaaa_ire, ipart) | |
| 632 | case(2) | ||
| 633 | ✗ | call write_output_line(iaaa_ist, ipart) | |
| 634 | end select | ||
| 635 | ✗ | end subroutine output_topological_class | |
| 636 | |||
| 637 | |||
| 638 | 28 | subroutine write_output_line(iunit, ipart) | |
| 639 | integer, intent(in) :: iunit, ipart | ||
| 640 | |||
| 641 | 56 | !$omp critical | |
| 642 | 28 | write (iunit, *) zstart(2,ipart), zstart(5,ipart), trap_par(ipart) | |
| 643 | !$omp end critical | ||
| 644 | 28 | end subroutine write_output_line | |
| 645 | |||
| 646 | |||
| 647 | ! Write classification results to fort.* files | ||
| 648 | ! This subroutine centralizes all classification file I/O | ||
| 649 | ! Note: Only writes classifications that were computed (non-zero values) | ||
| 650 | 40 | subroutine write_classification_results(ipart, class_result) | |
| 651 | integer, intent(in) :: ipart | ||
| 652 | type(classification_result_t), intent(in) :: class_result | ||
| 653 | logical :: regular | ||
| 654 | |||
| 655 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | if (class_result%exit_code >= ORBIT_EXIT_NUMERICAL_DOMAIN) return |
| 656 | |||
| 657 | ! Write lost orbits | ||
| 658 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if(class_result%lost) then |
| 659 | ✗ | call output_lost_orbit_starting_data(ipart, class_result%passing) | |
| 660 | ✗ | return | |
| 661 | endif | ||
| 662 | |||
| 663 | ! Write fractal classification if computed | ||
| 664 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 12 times.
|
40 | if(class_result%fractal /= 0) then |
| 665 | 28 | regular = (class_result%fractal == 1) | |
| 666 | 28 | call output_minkowsky_class(ipart, regular, class_result%passing) | |
| 667 | endif | ||
| 668 | |||
| 669 | ! Write J_parallel and topological classification if computed | ||
| 670 | ! These are only done for trapped orbits | ||
| 671 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 28 times.
|
40 | if(.not. class_result%passing) then |
| 672 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if(class_result%jpar /= 0) then |
| 673 | ✗ | call output_jpar_class(ipart, class_result%jpar) | |
| 674 | endif | ||
| 675 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if(class_result%topology /= 0) then |
| 676 | ✗ | call output_topological_class(ipart, class_result%topology) | |
| 677 | endif | ||
| 678 | endif | ||
| 679 | |||
| 680 | end subroutine write_classification_results | ||
| 681 | |||
| 682 | ✗ | end module classification | |
| 683 |