Skip to content

Conversation

@krystophny
Copy link
Member

@krystophny krystophny commented Oct 10, 2025

User description

Summary

Integrates libneo's analytical Grad-Shafranov equilibrium solver with TF ripple support into SIMPLE, enabling orbit integration in analytical tokamak fields.

Changes

New Field Type: AnalyticalGSField

  • Module: field_analytical_gs
  • Base Class: Extends MagneticField
  • Backend: Uses libneo's analytical_tokamak_field module directly

Coordinate Transformation

  • Input: SIMPLE flux-like coordinates (r, theta, phi)
    • r = normalized radial position
    • theta = poloidal angle
    • phi = toroidal angle
  • Mapping: Circular tokamak approximation
    • R = R0 + r×a×cos(theta)
    • Z = r×a×sin(theta)
  • Output: Magnetic field in cylindrical basis

Features

  • Full Analytical Equilibrium: Cerfon-Freidberg Grad-Shafranov solution
  • TF Ripple: Optional N-coil ripple perturbation
  • Configurable Shaping: Elongation (kappa) and triangularity (delta)
  • Factory Function: create_analytical_gs_field() with optional parameters

Implementation Pattern

Follows the field_coils pattern:

  • Direct import from libneo module
  • No code duplication
  • Clean separation of concerns

Dependencies

Requires libneo PR #149 (TF ripple support)

Usage Example

use field_analytical_gs

class(AnalyticalGSField), allocatable :: field

! Create circular tokamak with 9-coil ripple
call create_analytical_gs_field( &
    R0=6.2_dp, epsilon=0.32_dp, &
    B0=5.3_dp, A_param=-0.142_dp, &
    Nripple=9, delta0=0.10_dp, &
    field=field)

Test Plan

  • Unit test for field evaluation
  • Orbit integration test
  • Verification against libneo direct evaluation

Notes

This PR is part of the TF ripple feature branch. Tests and verification will be added after confirming build compatibility.


PR Type

Enhancement


Description

  • Add analytical Grad-Shafranov field with TF ripple support

  • Integrate GEQDSK/geoflux coordinate system for tokamak equilibria

  • Extend coordinate transformations with geoflux support

  • Add comprehensive test suite for new field types


Diagram Walkthrough

flowchart LR
  A["GEQDSK File"] --> B["GeofluxField"]
  C["Analytical Parameters"] --> D["AnalyticalGSField"]
  B --> E["magfie_geoflux"]
  D --> E
  E --> F["Orbit Integration"]
  G["Coordinate Transforms"] --> H["geoflux_to_cyl"]
  H --> B
Loading

File Walkthrough

Relevant files
Enhancement
7 files
field_analytical_gs.f90
Add analytical Grad-Shafranov field implementation             
+146/-0 
field_geoflux.f90
Add GEQDSK geoflux field adapter                                                 
+88/-0   
coordinates.f90
Add geoflux coordinate transformation support                       
+41/-0   
magfie.f90
Add geoflux magnetic field evaluation                                       
+209/-3 
simple.f90
Add GEQDSK initialization support                                               
+25/-3   
simple_main.f90
Update field initialization for GEQDSK                                     
+17/-8   
field.F90
Add GEQDSK file detection and field creation                         
+34/-1   
Tests
4 files
test_field_geoflux.f90
Add geoflux field unit tests                                                         
+44/-0   
test_field_vmec.f90
Add VMEC field unit tests                                                               
+33/-0   
test_magfie_geoflux.f90
Add geoflux magfie integration tests                                         
+51/-0   
CMakeLists.txt
Add GEQDSK test data and new test targets                               
+33/-1   
Configuration changes
5 files
Util.cmake
Improve dependency path resolution                                             
+8/-3     
CMakeLists.txt
Add libneo tokamak branch configuration                                   
+14/-0   
Makefile
Add tokamak example build system                                                 
+19/-0   
simple.in
Add tokamak example configuration                                               
+7/-0     
CMakeLists.txt
Add new field modules and dependencies                                     
+19/-0   
Documentation
1 files
TODO.md
Add comprehensive implementation plan                                       
+136/-0 
Dependencies
1 files
fpm.toml
Update libneo dependency to tokamak branch                             
+1/-1     

@qodo-code-review
Copy link

qodo-code-review bot commented Oct 10, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@krystophny krystophny force-pushed the feature/tf-ripple-perturbation branch from 17aa914 to 5df5973 Compare October 10, 2025 10:12
@qodo-code-review
Copy link

qodo-code-review bot commented Oct 10, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect signs in matrix inversion

Correct the signs of several elements in the invert3x3 subroutine's calculation
of the adjugate matrix to ensure the matrix inversion is mathematically correct.

src/magfie.f90 [415-439]

 subroutine invert3x3(a, ainv, det)
   real(dp), intent(in) :: a(3, 3)
   real(dp), intent(out) :: ainv(3, 3)
   real(dp), intent(out) :: det
 
   det = a(1, 1) * (a(2, 2) * a(3, 3) - a(2, 3) * a(3, 2)) &
       - a(1, 2) * (a(2, 1) * a(3, 3) - a(2, 3) * a(3, 1)) &
       + a(1, 3) * (a(2, 1) * a(3, 2) - a(2, 2) * a(3, 1))
 
   if (abs(det) < 1.0d-16) then
     det = 0.0_dp
     ainv = 0.0_dp
     return
   end if
 
   ainv(1, 1) =  (a(2, 2) * a(3, 3) - a(2, 3) * a(3, 2)) / det
-  ainv(1, 2) = -(a(1, 2) * a(3, 3) - a(1, 3) * a(3, 2)) / det
+  ainv(1, 2) =  (a(1, 3) * a(3, 2) - a(1, 2) * a(3, 3)) / det
   ainv(1, 3) =  (a(1, 2) * a(2, 3) - a(1, 3) * a(2, 2)) / det
-  ainv(2, 1) = -(a(2, 1) * a(3, 3) - a(2, 3) * a(3, 1)) / det
+  ainv(2, 1) =  (a(2, 3) * a(3, 1) - a(2, 1) * a(3, 3)) / det
   ainv(2, 2) =  (a(1, 1) * a(3, 3) - a(1, 3) * a(3, 1)) / det
-  ainv(2, 3) = -(a(1, 1) * a(2, 3) - a(1, 3) * a(2, 1)) / det
+  ainv(2, 3) =  (a(1, 3) * a(2, 1) - a(1, 1) * a(2, 3)) / det
   ainv(3, 1) =  (a(2, 1) * a(3, 2) - a(2, 2) * a(3, 1)) / det
-  ainv(3, 2) = -(a(1, 1) * a(3, 2) - a(1, 2) * a(3, 1)) / det
+  ainv(3, 2) =  (a(1, 2) * a(3, 1) - a(1, 1) * a(3, 2)) / det
   ainv(3, 3) =  (a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1)) / det
 end subroutine invert3x3
  • Apply / Chat
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical bug in the matrix inversion formula, which would produce an incorrect inverse matrix and lead to wrong physics results.

High
Fix incorrect coordinate basis and normalization
Suggestion Impact:The commit updated the documentation to rho/theta/phi, computed cos/sin, transformed cylindrical B components to the (rho, theta, phi) basis, computed covariant components with metric factors, and normalized hcov by Bmod. It also preserved Acov as zero and added a Bmod positivity check.

code diff:

-    !> Note: This field uses cylindrical coordinates (R, phi, Z) instead of
-    !> flux coordinates. Input x is interpreted as:
-    !>   x(1) = (R - R0)/a  (normalized radial distance from axis)
-    !>   x(2) = theta       (poloidal angle, unused for axisymmetric field)
+    !> Note: The equilibrium is defined in cylindrical coordinates (R, phi, Z),
+    !> but the SIMPLE interface supplies flux-like coordinates. Input x is
+    !> interpreted as:
+    !>   x(1) = rho         (normalized minor radius r/a)
+    !>   x(2) = theta       (poloidal angle)
     !>   x(3) = phi         (toroidal angle)
     !>
-    !> Output is magnetic field in cylindrical coordinates
+    !> Output provides covariant components of the unit magnetic field vector
+    !> in the (rho, theta, phi) coordinate basis used by SIMPLE.
     class(AnalyticalGSField), intent(in) :: self
     real(dp), intent(in) :: x(3)
     real(dp), intent(out), dimension(3) :: Acov, hcov
     real(dp), intent(out) :: Bmod
     real(dp), intent(out), optional :: sqgBctr(3)
 
-    real(dp) :: R, phi, Z
+    real(dp) :: rho, theta, phi
+    real(dp) :: cost, sint
+    real(dp) :: R, Z
     real(dp) :: B_R, B_Z, B_phi, B_mod_local
-    real(dp) :: theta
+    real(dp) :: Bcov_rho, Bcov_theta, Bcov_phi
 
     if (.not. self%initialized) then
         error stop 'AnalyticalGSField: field not initialized'
     end if
 
     ! Convert from normalized flux-like coordinates to cylindrical
-    ! x(1) is normalized radial coordinate: rho = (R - R0)/a
+    ! x(1) is normalized minor radius rho = r/a
     ! x(2) is poloidal angle theta
     ! x(3) is toroidal angle phi
 
+    rho = x(1)
     theta = x(2)
     phi = x(3)
+    cost = cos(theta)
+    sint = sin(theta)
 
     ! For circular tokamak: R = R0 + rho*a*cos(theta), Z = rho*a*sin(theta)
-    R = self%R0 + x(1) * self%a * cos(theta)
-    Z = x(1) * self%a * sin(theta)
+    R = self%R0 + rho * self%a * cost
+    Z = rho * self%a * sint
 
     ! Evaluate field in cylindrical coordinates
     call compute_analytical_field_cylindrical(self%eq, R, phi, Z, &
                                               B_R, B_Z, B_phi, B_mod_local)
 
-    ! For now, return field components in cylindrical basis
-    ! (transformation to flux coordinates would require knowing the actual flux surfaces)
-    Acov = 0.0_dp  ! Vector potential not implemented
-
-    ! Magnetic field unit vector in cylindrical coordinates
-    hcov(1) = B_R
-    hcov(2) = B_phi
-    hcov(3) = B_Z
+    ! Vector potential is not yet provided for this field
+    Acov = 0.0_dp
 
     Bmod = B_mod_local
+    if (Bmod <= 0.0_dp) then
+        error stop 'AnalyticalGSField: |B| must be positive'
+    end if
+
+    ! Convert cylindrical components to covariant basis vectors g_rho, g_theta, g_phi
+    Bcov_rho = self%a * (cost * B_R + sint * B_Z)
+    Bcov_theta = rho * self%a * (-sint * B_R + cost * B_Z)
+    Bcov_phi = R * B_phi
+
+    ! Normalize to obtain covariant components of unit vector along B
+    hcov(1) = Bcov_rho / Bmod
+    hcov(2) = Bcov_theta / Bmod
+    hcov(3) = Bcov_phi / Bmod
 
     if (present(sqgBctr)) then
         error stop 'sqgBctr not implemented for AnalyticalGSField'

Fix the evaluate subroutine by correcting the coordinate basis of the returned
magnetic field vector, normalizing it to a unit vector, and updating the
documentation to match the implementation.

src/field/field_analytical_gs.f90 [26-70]

-!>   x(1) = (R - R0)/a  (normalized radial distance from axis)
-!>   x(2) = theta       (poloidal angle, unused for axisymmetric field)
+!>   x(1) = rho         (normalized minor radius, r/a)
+!>   x(2) = theta       (poloidal angle)
 !>   x(3) = phi         (toroidal angle)
 !>
-!> Output is magnetic field in cylindrical coordinates
+!> Output is magnetic field in the (rho, theta, phi) basis.
 class(AnalyticalGSField), intent(in) :: self
 real(dp), intent(in) :: x(3)
 real(dp), intent(out), dimension(3) :: Acov, hcov
 real(dp), intent(out) :: Bmod
 real(dp), intent(out), optional :: sqgBctr(3)
 
 real(dp) :: R, phi, Z
 real(dp) :: B_R, B_Z, B_phi, B_mod_local
-real(dp) :: theta
+real(dp) :: rho, theta
+real(dp) :: cost, sint
+real(dp) :: B_rho, B_theta
 
 if (.not. self%initialized) then
     error stop 'AnalyticalGSField: field not initialized'
 end if
 
 ! Convert from normalized flux-like coordinates to cylindrical
-! x(1) is normalized radial coordinate: rho = (R - R0)/a
+! x(1) is normalized minor radius: rho = r/a
 ! x(2) is poloidal angle theta
 ! x(3) is toroidal angle phi
 
+rho = x(1)
 theta = x(2)
 phi = x(3)
+cost = cos(theta)
+sint = sin(theta)
 
-! For circular tokamak: R = R0 + rho*a*cos(theta), Z = rho*a*sin(theta)
-R = self%R0 + x(1) * self%a * cos(theta)
-Z = x(1) * self%a * sin(theta)
+! For circular tokamak: R = R0 + r*cos(theta), Z = r*sin(theta)
+R = self%R0 + rho * self%a * cost
+Z = rho * self%a * sint
 
 ! Evaluate field in cylindrical coordinates
 call compute_analytical_field_cylindrical(self%eq, R, phi, Z, &
                                           B_R, B_Z, B_phi, B_mod_local)
 
-! For now, return field components in cylindrical basis
-! (transformation to flux coordinates would require knowing the actual flux surfaces)
+! Transform B from cylindrical (R,Z,phi) to local polar (rho,theta,phi)
+! B_rho = B_R*cos(theta) + B_Z*sin(theta)
+! B_theta = -B_R*sin(theta) + B_Z*cos(theta)
+! The covariant components are (a*B_rho, a*rho*B_theta, R*B_phi)
+! but hcov should be a unit vector.
+B_rho = B_R * cost + B_Z * sint
+B_theta = -B_R * sint + B_Z * cost
+
+! For now, return field components in the input (rho, theta, phi) basis
 Acov = 0.0_dp  ! Vector potential not implemented
 
-! Magnetic field unit vector in cylindrical coordinates
-hcov(1) = B_R
-hcov(2) = B_phi
-hcov(3) = B_Z
+! Magnetic field unit vector in (rho, theta, phi) coordinates
+hcov(1) = B_rho
+hcov(2) = B_theta
+hcov(3) = B_phi
 
 Bmod = B_mod_local
+if (Bmod > 1.0e-12_dp) then
+    hcov = hcov / Bmod
+else
+    hcov = 0.0_dp
+end if

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies multiple critical bugs in the new evaluate subroutine, including an incorrect coordinate basis for the output vector and a failure to normalize it, which would lead to incorrect physics calculations.

High
High-level
Refactor GEQDSK integration for clarity

The integration of GEQDSK files should be refactored to use a distinct
initialization path and field type, rather than overloading existing VMEC
components. This will improve code clarity and maintainability.

Examples:

src/simple.f90 [71-83]
    if (is_geqdsk(vmec_file)) then
      call initialize_geoflux_field(vmec_file)
      call geoflux_get_axis(R_axis, Z_axis)
      nper = 1
      rmajor = R_axis
      fper = twopi
      vmec_B_scale = 1.0d0
      vmec_RZ_scale = 1.0d0
      call splint_geoflux_field(0.0_dp, 0.0_dp, 0.0_dp, Acov_axis, hcov_axis, B_axis, sqg_axis)
      print *, 'GEQDSK equilibrium loaded. R_axis = ', R_axis, ' cm, fper = ', fper

 ... (clipped 3 lines)
src/magfie.f90 [52-57]
case(VMEC)
    if (geoflux_ready) then
      magfie => magfie_geoflux
    else
      magfie => magfie_vmec
    end if

Solution Walkthrough:

Before:

// in src/simple.f90
subroutine init_vmec(vmec_file, ...)
  if (is_geqdsk(vmec_file)) then
    call initialize_geoflux_field(vmec_file)
    // ... set geoflux-specific parameters
    return
  end if
  call spline_vmec_data ! original VMEC logic
  ...
end subroutine

// in src/magfie.f90
subroutine init_magfie(id)
  select case(id)
  case(VMEC) // id = 1
    if (geoflux_ready) then
      magfie => magfie_geoflux
    else
      magfie => magfie_vmec
    end if
  ...
  end select
end subroutine

After:

// in src/simple.f90
subroutine init_geoflux(geqdsk_file, ...)
  call initialize_geoflux_field(geqdsk_file)
  // ... set geoflux-specific parameters
end subroutine

subroutine init_vmec(vmec_file, ...)
  call spline_vmec_data ! original VMEC logic
  ...
end subroutine

// in src/magfie.f90
subroutine init_magfie(id)
  select case(id)
  case(VMEC) // id = 1
    magfie => magfie_vmec
  case(GEOFLUX) // id = 5
    magfie => magfie_geoflux
  ...
  end select
end subroutine
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a major design flaw where GEQDSK support is confusingly merged into the VMEC initialization path, harming code clarity and maintainability.

High
  • Update

@qodo-code-review
Copy link

qodo-code-review bot commented Oct 10, 2025

CI Feedback 🧐

(Feedback updated until commit 817b4e6)

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Run Regression Tests

Failed stage: Build SIMPLE [❌]

Failure summary:

The action failed during the build step due to Fortran compilation errors in src/simple_main.f90:
-
File: /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/simple_main.f90
- Line 354:
Error: Unexpected data declaration statement at (1) for real(dp) :: time_val, trap_val, perp_val,
zend_row(5)
- Lines 356–359: Subsequent errors that perp_val, time_val, trap_val, and zend_row
have no IMPLICIT type, indicating these variables were used without valid prior declaration due to
the failed declaration at line 354.
- As a result, target
src/CMakeFiles/simple.dir/simple_main.f90.o failed, Ninja stopped, and make exited with code 2.
-
Upstream warnings (e.g., missing optional source files for f2py wrappers) did not stop the build;
the definitive failure was the Fortran syntax/placement error in simple_main.f90.

Relevant error logs:
1:  Runner name: 'debian-2'
2:  Runner group name: 'default'
...

25:  persist-credentials: true
26:  clean: true
27:  sparse-checkout-cone-mode: true
28:  fetch-depth: 1
29:  fetch-tags: false
30:  show-progress: true
31:  lfs: false
32:  submodules: false
33:  set-safe-directory: true
34:  env:
35:  LIBNEO_BRANCH: feature/tf-ripple-perturbation
36:  ##[endgroup]
37:  Syncing repository: itpplasma/SIMPLE
38:  ##[group]Getting Git version info
39:  Working directory is '/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE'
40:  Unexpected error attempting to determine if executable file exists '/home/ert/.npm-global/bin/git': Error: EACCES: permission denied, stat '/home/ert/.npm-global/bin/git'
41:  Unexpected error attempting to determine if executable file exists '/home/ert/code/.venv/bin/git': Error: EACCES: permission denied, stat '/home/ert/code/.venv/bin/git'
42:  Unexpected error attempting to determine if executable file exists '/home/ert/code/scripts/git': Error: EACCES: permission denied, stat '/home/ert/code/scripts/git'
43:  [command]/usr/bin/git version
...

346:  [141/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_examples.f90-pp.f90
347:  [142/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_coordinate_validation.f90-pp.f90
348:  [143/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_contour_regions.f90-pp.f90
349:  [144/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_media.f90-pp.f90
350:  [145/799] Building Fortran object _deps/libneo-build/src/field/CMakeFiles/neo_field.dir/mesh_2d.f90.o
351:  [146/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_output.f90-pp.f90
352:  [147/799] Building Fortran object _deps/libneo-build/CMakeFiles/neo.dir/src/system_utility.f90.o
353:  [148/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_files.f90-pp.f90
354:  [149/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_paths.f90-pp.f90
355:  [150/799] Building Fortran object _deps/libneo-build/src/efit_to_boozer/CMakeFiles/efit_to_boozer.dir/efit_to_boozer_mod.f90.o
356:  [151/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_source.f90-pp.f90
357:  [152/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_text.f90-pp.f90
358:  [153/799] Building Fortran object _deps/libneo-build/src/field/CMakeFiles/neo_field.dir/mesh.f90.o
359:  [154/799] Building Fortran object _deps/libneo-build/CMakeFiles/neo.dir/src/solve_systems.f90.o
360:  [155/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_processor.f90-pp.f90
361:  [156/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_errors.f90-pp.f90
362:  [157/799] Building Fortran preprocessed _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_fast_io.f90-pp.f90
...

499:  [292/799] Building Fortran object _deps/libneo-build/CMakeFiles/neo.dir/src/vmecinm_m.f90.o
500:  [293/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_doc_constants.f90.o
501:  [294/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_constants.f90.o
502:  [295/799] Building C object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_c_interface.c.o
503:  cc1: warning: command-line option ‘-fbacktrace’ is valid for Fortran but not for C
504:  [296/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_markers.f90.o
505:  [297/799] Building Fortran object _deps/libneo-build/src/field/CMakeFiles/neo_field.dir/polylag_field.f90.o
506:  [298/799] Building C object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_mkdir_windows.c.o
507:  cc1: warning: command-line option ‘-fbacktrace’ is valid for Fortran but not for C
508:  [299/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_bmp.f90.o
509:  [300/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_layout.f90.o
510:  [301/799] Building Fortran object _deps/libneo-build/src/species/CMakeFiles/species.dir/species.f90.o
511:  [302/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_contour_algorithms.f90.o
512:  [303/799] Building C object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_pipe_timeout.c.o
513:  cc1: warning: command-line option ‘-fbacktrace’ is valid for Fortran but not for C
514:  [304/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_errors.f90.o
515:  [305/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_logging.f90.o
...

693:  [338/799] Generating Fortran dyndep file _deps/libneo-build/CMakeFiles/mc_efit.dir/Fortran.dd
694:  [339/799] Generating Fortran dyndep file _deps/libneo-build/CMakeFiles/standardise_geqdsk.x.dir/Fortran.dd
695:  [340/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_windows_performance.f90.o
696:  [341/799] Building Fortran object _deps/libneo-build/src/interpolate/CMakeFiles/interpolate.dir/__/spl_three_to_five.f90.o
697:  [342/799] Generating Fortran dyndep file _deps/libneo-build/CMakeFiles/coil_convert.x.dir/Fortran.dd
698:  [343/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_streamline_placement.f90.o
699:  [344/799] Generating Fortran dyndep file _deps/libneo-build/CMakeFiles/vacfield.x.dir/Fortran.dd
700:  [345/799] Generating Fortran dyndep file _deps/libneo-build/CMakeFiles/efit_to_boozer.x.dir/Fortran.dd
701:  [346/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_vector.f90.o
702:  [347/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_streamline_integrator.f90.o
703:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_streamline_integrator.f90:80:29:
704:  80 |         real(wp) :: tolerance, h_new
705:  |                             1
706:  Warning: Unused variable ‘tolerance’ declared at (1) [-Wunused-variable]
707:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_streamline_integrator.f90:77:37:
708:  77 |         real(wp) :: x, y, t, h, x_new, y_new, error_est
709:  |                                     1
710:  Warning: Unused variable ‘x_new’ declared at (1) [-Wunused-variable]
711:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_streamline_integrator.f90:77:44:
712:  77 |         real(wp) :: x, y, t, h, x_new, y_new, error_est
713:  |                                            1
...

960:  [392/799] Building Fortran object _deps/libneo-build/extra/MyMPILib/CMakeFiles/MyMPILib.dir/Tools/intList_module.f90.o
961:  [393/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_security.f90.o
962:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_security.f90:397:25:
963:  397 |         logical :: exists
964:  |                         1
965:  Warning: Unused variable ‘exists’ declared at (1) [-Wunused-variable]
966:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_security.f90:680:4:
967:  680 |     function is_development_environment_enabled() result(enabled)
968:  |    ^
969:  Warning: ‘is_development_environment_enabled’ defined but not used [-Wunused-function]
970:  [394/799] Building Fortran object _deps/libneo-build/extra/MyMPILib/CMakeFiles/MyMPILib.dir/Generic/workunit_module.f90.o
971:  [395/799] Linking Fortran executable test/tests/test_tokamak_alpha_example.x
972:  [396/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_text.f90.o
973:  [397/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_streamplot_matplotlib.f90.o
974:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_streamplot_matplotlib.f90:257:16:
975:  257 |             if (error == 0.0_wp) then
976:  |                1
...

1913:  126 |     subroutine savefig(filename, dpi, transparent, bbox_inches)
1914:  |                                                 1
1915:  Warning: Unused dummy argument ‘transparent’ at (1) [-Wunused-dummy-argument]
1916:  [491/799] Building Fortran object _deps/libneo-build/src/field/CMakeFiles/neo_field.dir/field.f90.o
1917:  [492/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_animation_pipeline.f90.o
1918:  [493/799] Linking Fortran static library _deps/libneo-build/src/field/libneo_field.a
1919:  [494/799] Linking Fortran static library _deps/libneo-build/libneo.a
1920:  [495/799] Linking Fortran static library _deps/libneo-build/src/species/libspecies.a
1921:  [496/799] Linking Fortran static library _deps/libneo-build/src/collisions/libcollision_freqs.a
1922:  [497/799] Building Fortran object _deps/fortplot-build/CMakeFiles/fortplot.dir/src/fortplot_matplotlib_plotting.f90.o
1923:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:333:59:
1924:  333 |     subroutine add_3d_plot(x, y, z, label, linestyle, color, linewidth, marker, markersize)
1925:  |                                                           1
1926:  Warning: Unused dummy argument ‘color’ at (1) [-Wunused-dummy-argument]
1927:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:259:91:
1928:  259 |     subroutine add_errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
1929:  |                                                                                           1
...

1985:  |                    1
1986:  Warning: Unused variable ‘i’ declared at (1) [-Wunused-variable]
1987:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:54:57:
1988:  54 |     subroutine bar(x, height, width, bottom, label, color, edgecolor, align)
1989:  |                                                         1
1990:  Warning: Unused dummy argument ‘color’ at (1) [-Wunused-dummy-argument]
1991:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:54:68:
1992:  54 |     subroutine bar(x, height, width, bottom, label, color, edgecolor, align)
1993:  |                                                                    1
1994:  Warning: Unused dummy argument ‘edgecolor’ at (1) [-Wunused-dummy-argument]
1995:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:65:20:
1996:  65 |         integer :: i
1997:  |                    1
1998:  Warning: Unused variable ‘i’ declared at (1) [-Wunused-variable]
1999:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:41:61:
2000:  41 |     subroutine errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
2001:  |                                                             1
2002:  Warning: Unused dummy argument ‘capsize’ at (1) [-Wunused-dummy-argument]
2003:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:41:87:
2004:  41 |     subroutine errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
2005:  |                                                                                       1
2006:  Warning: Unused dummy argument ‘color’ at (1) [-Wunused-dummy-argument]
2007:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:41:45:
2008:  41 |     subroutine errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
2009:  |                                             1
2010:  Warning: Unused dummy argument ‘fmt’ at (1) [-Wunused-dummy-argument]
2011:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:41:80:
2012:  41 |     subroutine errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
2013:  |                                                                                1
2014:  Warning: Unused dummy argument ‘marker’ at (1) [-Wunused-dummy-argument]
2015:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:41:34:
2016:  41 |     subroutine errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
2017:  |                                  1
2018:  Warning: Unused dummy argument ‘xerr’ at (1) [-Wunused-dummy-argument]
2019:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/fortplot-src/src/fortplot_matplotlib_plotting.f90:41:40:
2020:  41 |     subroutine errorbar(x, y, xerr, yerr, fmt, label, capsize, linestyle, marker, color)
2021:  |                                        1
...

2510:  spl_reg(h,splcoe,[ns,n])
2511:  Generating possibly empty wrappers"
2512:  Maybe empty "_magfie-f2pywrappers.f"
2513:  Constructing wrapper function "magfie_vmec"...
2514:  bmod,sqrtg,bder,hcovar,hctrvr,hcurl = magfie_vmec(x)
2515:  Constructing F90 module support for "f2py_vmec_wrappers"...
2516:  Constructing wrapper function "f2py_vmec_wrappers.splint_vmec_data_wrapper"...
2517:  a_phi,a_theta,da_phi_ds,da_theta_ds,aiota,r,z,alam,dr_ds,dr_dt,dr_dp,dz_ds,dz_dt,dz_dp,dl_ds,dl_dt,dl_dp = splint_vmec_data_wrapper(s,theta,varphi)
2518:  Constructing wrapper function "f2py_vmec_wrappers.vmec_field_wrapper"...
2519:  a_theta,a_phi,da_theta_ds,da_phi_ds,aiota,sqg,alam,dl_ds,dl_dt,dl_dp,bctrvr_vartheta,bctrvr_varphi,bcovar_s,bcovar_vartheta,bcovar_varphi = vmec_field_wrapper(s,theta,varphi)
2520:  Constructing wrapper function "f2py_vmec_wrappers.vmec_field_cylindrical_wrapper"...
2521:  br,bphi,bz,bmag = vmec_field_cylindrical_wrapper(s,theta,varphi)
2522:  Wrote C/API module "_magfie" to file "./_magfiemodule.c"
2523:  Fortran 90 wrappers are saved to "./_magfie-f2pywrappers2.f90"
2524:  [667/799] Generating _efit_to_boozermodule.c, _efit_to_boozer-f2pywrappers.f, _efit_to_boozer-f2pywrappers2.f90
2525:  OSError: [Errno 2] No such file or directory: '/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/magfie/field_divB0.f90'. Skipping file "/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/magfie/field_divB0.f90".
2526:  OSError: [Errno 2] No such file or directory: '/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/magfie/field_eq_mod.f90'. Skipping file "/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/magfie/field_eq_mod.f90".
2527:  OSError: [Errno 2] No such file or directory: '/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/spl_three_to_five.f90'. Skipping file "/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/spl_three_to_five.f90".
2528:  OSError: [Errno 2] No such file or directory: '/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/plag_coeff.f90'. Skipping file "/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/plag_coeff.f90".
2529:  OSError: [Errno 2] No such file or directory: '/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/binsrc.f90'. Skipping file "/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/binsrc.f90".
2530:  get_useparameters: mapping for {'only': 1, 'map': {'psimax': 'psimax'}} not impl.
...

3483:  f951: note: unrecognized command-line option ‘-Wno-external-argument-mismatch’ may have been intended to silence earlier diagnostics
3484:  [746/799] Building Fortran object test/tests/CMakeFiles/test_samplers.x.dir/test_samplers.f90.o
3485:  [747/799] Building Fortran object src/CMakeFiles/simple.dir/diag/diag_orbit.f90.o
3486:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/diag/diag_orbit.f90:136:60:
3487:  136 |     integer :: it, ktau, point_idx, newton_iters, ierr_orbit
3488:  |                                                            1
3489:  Warning: Unused variable ‘ierr_orbit’ declared at (1) [-Wunused-variable]
3490:  f951: note: unrecognized command-line option ‘-Wno-external-argument-mismatch’ may have been intended to silence earlier diagnostics
3491:  [748/799] Building Fortran object src/CMakeFiles/simple.dir/classification.f90.o
3492:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/classification.f90:56:30:
3493:  56 |     integer :: ierr, ierr_coll
3494:  |                              1
3495:  Warning: Unused variable ‘ierr_coll’ declared at (1) [-Wunused-variable]
3496:  f951: note: unrecognized command-line option ‘-Wno-external-argument-mismatch’ may have been intended to silence earlier diagnostics
3497:  [749/799] Building Fortran object src/CMakeFiles/simple.dir/simple_main.f90.o
3498:  FAILED: src/CMakeFiles/simple.dir/simple_main.f90.o include/simple_main.mod 
3499:  /usr/bin/gfortran -I/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src -I/usr/include -I/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/include -I/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/_deps/libneo-build/include -I/home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/build/modules -O3 -Jinclude -fPIC -fdiagnostics-color=always -g -fbacktrace -Wall -Wextra -fPIC -Wimplicit-interface -Wno-external-argument-mismatch -fmax-errors=5 -Wno-unused-dummy-argument -ffree-line-length-132 -O3 -ffast-math -ffp-contract=fast -funroll-loops -mcpu=native -Wtrampolines -Werror=trampolines -fopenmp -fpreprocessed -c src/CMakeFiles/simple.dir/simple_main.f90-pp.f90 -o src/CMakeFiles/simple.dir/simple_main.f90.o
3500:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/simple_main.f90:354:59:
3501:  354 |       real(dp) :: time_val, trap_val, perp_val, zend_row(5)
3502:  |                                                           1
3503:  Error: Unexpected data declaration statement at (1)
3504:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/simple_main.f90:358:14:
3505:  358 |       perp_val = sanitize_scalar(perp_inv(i))
3506:  |              1
3507:  Error: Symbol ‘perp_val’ at (1) has no IMPLICIT type
3508:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/simple_main.f90:356:14:
3509:  356 |       time_val = sanitize_scalar(times_lost(i))
3510:  |              1
3511:  Error: Symbol ‘time_val’ at (1) has no IMPLICIT type
3512:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/simple_main.f90:357:14:
3513:  357 |       trap_val = sanitize_scalar(trap_par(i))
3514:  |              1
3515:  Error: Symbol ‘trap_val’ at (1) has no IMPLICIT type
3516:  /home/github-runner/actions-runner-2/_work/SIMPLE/SIMPLE/src/simple_main.f90:359:14:
3517:  359 |       zend_row = sanitize_vector(zend(:,i))
3518:  |              1
3519:  Error: Symbol ‘zend_row’ at (1) has no IMPLICIT type
3520:  f951: note: unrecognized command-line option ‘-Wno-external-argument-mismatch’ may have been intended to silence earlier diagnostics
3521:  ninja: build stopped: subcommand failed.
3522:  make: *** [Makefile:21: build] Error 1
3523:  ##[error]Process completed with exit code 2.
3524:  ##[group]Run actions/upload-artifact@v4
3525:  with:
3526:  name: regression-test-results
3527:  path: build/Testing/
3528:  
3529:  if-no-files-found: warn
3530:  compression-level: 6
3531:  overwrite: false
3532:  include-hidden-files: false
3533:  env:
3534:  LIBNEO_BRANCH: feature/tf-ripple-perturbation
3535:  ##[endgroup]
3536:  ##[warning]No files were found with the provided path: build/Testing/. No artifacts will be uploaded.
3537:  Post job cleanup.
3538:  Unexpected error attempting to determine if executable file exists '/home/ert/.npm-global/bin/git': Error: EACCES: permission denied, stat '/home/ert/.npm-global/bin/git'
3539:  Unexpected error attempting to determine if executable file exists '/home/ert/code/.venv/bin/git': Error: EACCES: permission denied, stat '/home/ert/code/.venv/bin/git'
3540:  Unexpected error attempting to determine if executable file exists '/home/ert/code/scripts/git': Error: EACCES: permission denied, stat '/home/ert/code/scripts/git'
3541:  [command]/usr/bin/git version

krystophny and others added 22 commits October 28, 2025 10:13
- Add comprehensive TODO for tokamak GEQDSK support using geoflux coordinates
- Reference Simpson rule implementation in KAMEL fix-integration branch
- Document 3D field superposition via field_divB0 module
- Note that 3D perturbations affect field but not reference coordinates
Document field_divB0 perturbation modes:
- Vacuum perturbation workflow (ipert=1) with Biot-Savart coil fields
- Plasma response modes (ipert=2,3) with Fourier representation
- Coil file formats (AUG, GPEC, Nemov, STELLOPT)
- vacfield.x program for generating field files from coil geometries
- Phased implementation plan for SIMPLE+GEQDSK

Note: libneo has full 3D capability but no regression tests yet
Add comprehensive summary section covering:
- 4 perturbation modes (ipert=0/1/2/3) with use cases
- Vacuum perturbation workflow using vacfield.x
- Plasma response mode requirements
- Available tools (coil_tools, coil_convert, Python interfaces)
- Current status: fully implemented but not regression tested
- 3-phase integration plan for SIMPLE+GEQDSK
- Example field_divB0.inp configuration

This provides quick reference for understanding libneo's 3D capabilities
before diving into detailed implementation phases.
Provides SIMPLE field interface for libneo's analytical Grad-Shafranov
equilibrium solver with optional TF ripple perturbation.

The adapter converts between SIMPLE's flux-like coordinates (r, theta, phi)
and libneo's cylindrical coordinates (R, phi, Z) using a simple circular
tokamak mapping: R = R0 + r*a*cos(theta), Z = r*a*sin(theta).

Features:
- Full Cerfon-Freidberg analytical equilibrium
- Optional TF coil ripple (Nripple parameter)
- Configurable shaping (kappa, delta)
- Direct import from libneo analytical_tokamak_field module

Usage pattern matches field_coils (direct libneo import).
Task 1: Add ANALYTICAL=6 constant to field type enums
- Added to magfie.f90 alongside existing GEOFLUX, MEISS, etc.

Task 2: Create tokamak.in namelist files
- examples/tokamak/tokamak.in: circular equilibrium without ripple
- examples/tokamak/tokamak_ripple.in: 9-coil ripple configuration
- Parameters documented with comments

Task 3: Add tokamak parameters to params module
- Added module variables for equilibrium and ripple parameters
- Extended config namelist to include tokamak_input and all tok_* params
- Added read_tokamak_config() subroutine
- Auto-loads tokamak.in when field_input contains "analytical"

Next: Rewrite field_analytical_gs for proper coordinate system interface
- Document libneo test hierarchy (unit + integration tests complete)
- Outline SIMPLE system test plan
- Alpha confinement example: 128 particles, s=0.3, ITER-size, Meiss coords
- System test: verify zero losses in tokamak without ripple
Create example for system test of alpha particle confinement in
ITER-size analytical tokamak without ripple.

Configuration:
- 128 alpha particles at s=0.3
- Meiss coordinates on analytical geoflux
- 1 ms integration time
- Expected: zero losses

This establishes baseline for ripple-induced transport tests.
Create test_tokamak_alpha_confinement to verify perfect particle
confinement in analytical tokamak without ripple.

Test scaffold ready but marked WILL_FAIL until:
- field.F90 updated with analytical_geoflux_field integration
- Analytical field initialization working in SIMPLE

Once integration complete, test will verify:
- Zero particles lost in axisymmetric configuration
- Particles remain near starting flux surface s=0.3
- Meiss coordinates work on analytical geoflux
Mark example and system test as complete:
- Example directory created with all config files
- System test scaffold integrated with CMake
- Both ready and waiting for Phase 3 field integration

Next step: Update field.F90 to initialize analytical geoflux
Document full implementation status:
- What works now: libneo complete, SIMPLE infrastructure ready
- What's needed: Only field.F90 integration (10 lines of code)
- Detailed phase-by-phase breakdown with commits and test results
- Clear critical path: field.F90 → system test → merge
- Quick reference section for the exact changes needed

All infrastructure complete and tested. Ready for field integration.
@krystophny krystophny force-pushed the feature/tf-ripple-perturbation branch from 6300884 to 2776210 Compare October 28, 2025 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants