Skip to content

Conversation

@krystophny
Copy link
Member

@krystophny krystophny commented Dec 1, 2025

User description

Add a minimal example script (examples/compare_gc_coils_rk.py) that runs SIMPLE twice in RK guiding-center mode: once with the VMEC field and once with a coils-based Meiss canonical field. Both runs use VMEC reference coordinates and trace a single particle for 1e-4 s with 1000 output points, then plot s, theta, and phi versus time for comparison.


PR Type

Enhancement, Tests


Description

  • Add example script comparing VMEC vs coils RK guiding-center orbits

  • Runs SIMPLE twice with different field configurations

  • Traces single particle for 1e-4 s with 1000 output points

  • Plots s, theta, phi coordinates versus time for comparison


Diagram Walkthrough

flowchart LR
  A["SIMPLE Config<br/>VMEC RK"] -->|Run 1| B["orbits.nc"]
  C["SIMPLE Config<br/>Coils RK"] -->|Run 2| D["orbits.nc"]
  B -->|Extract| E["VMEC Trajectory<br/>s, theta, phi, time"]
  D -->|Extract| F["Coils Trajectory<br/>s, theta, phi, time"]
  E -->|Compare| G["Comparison Plot<br/>gc_vmec_vs_coils_rk.png"]
  F -->|Compare| G
Loading

File Walkthrough

Relevant files
Tests
compare_gc_coils_rk.py
RK guiding-center VMEC vs coils comparison example             

examples/compare_gc_coils_rk.py

  • New example script demonstrating RK guiding-center orbit comparison
  • Implements run_simple() function to execute SIMPLE with config files
    and extract NetCDF trajectory data
  • Configures two SIMPLE runs: VMEC RK (isw_field_type=1) and coils RK
    with Meiss canonical coordinates (isw_field_type=3)
  • Generates comparison plots of s, theta, phi coordinates versus time
    using matplotlib
  • Includes error handling for missing dependencies (netCDF4, matplotlib)
    and SIMPLE execution failures
+170/-0 

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 1, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Untrusted executable path

Description: The script executes an external binary using subprocess.run with a path derived from
file without validating or verifying the executable (e.g., signature or expected
location), allowing path hijacking if the directory structure is tampered with or
untrusted, which could lead to arbitrary code execution.
compare_gc_coils_rk.py [38-45]

Referred Code
simple_exe = os.path.join(os.path.dirname(__file__), '..', 'build', 'simple.x')
result = subprocess.run(
    [simple_exe, config_path],
    cwd=work_dir,
    capture_output=True,
    text=True,
    timeout=300,
)
Information disclosure

Description: On failure, the script prints the full stdout/stderr from the external tool, which may
leak sensitive environment information or internal paths to logs or consoles not intended
for disclosure; consider redacting or truncating outputs or providing a debug flag.
compare_gc_coils_rk.py [47-51]

Referred Code
if result.returncode != 0:
    print(f"SIMPLE run '{tag}' failed")
    print("STDOUT:", result.stdout[-2000:] if len(result.stdout) > 2000 else result.stdout)
    print("STDERR:", result.stderr[-2000:] if len(result.stderr) > 2000 else result.stderr)
    return None
Unsafe file write path

Description: The script writes plot output to a path constructed from user-provided work_dir without
sanitization, which may allow overwriting files outside the intended directory if work_dir
is set to sensitive locations; enforce safe directories or prompt before overwrite.
compare_gc_coils_rk.py [161-163]

Referred Code
output_png = os.path.join(work_dir, "gc_vmec_vs_coils_rk.png")
plt.savefig(output_png, dpi=150)
plt.close()
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
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No action logging: The example executes an external binary and reads/writes files without structured audit
logs capturing user, timestamp, action, inputs, and outcomes; however, as an example
script this may be acceptable.

Referred Code
result = subprocess.run(
    [simple_exe, config_path],
    cwd=work_dir,
    capture_output=True,
    text=True,
    timeout=300,
)

if result.returncode != 0:
    print(f"SIMPLE run '{tag}' failed")
    print("STDOUT:", result.stdout[-2000:] if len(result.stdout) > 2000 else result.stdout)
    print("STDERR:", result.stderr[-2000:] if len(result.stderr) > 2000 else result.stderr)
    return None

orbits_path = os.path.join(work_dir, 'orbits.nc')
if not os.path.exists(orbits_path):
    print(f"orbits.nc not found for run '{tag}'")
    return None

with nc.Dataset(orbits_path, 'r') as ds:
    # Variables are stored as (timestep, particle) on main


 ... (clipped 7 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Limited error context: Subprocess failures and missing files print minimal messages without raising exceptions or
structured context, potentially hindering debugging though acceptable for an example
script.

Referred Code
if result.returncode != 0:
    print(f"SIMPLE run '{tag}' failed")
    print("STDOUT:", result.stdout[-2000:] if len(result.stdout) > 2000 else result.stdout)
    print("STDERR:", result.stderr[-2000:] if len(result.stderr) > 2000 else result.stderr)
    return None

orbits_path = os.path.join(work_dir, 'orbits.nc')
if not os.path.exists(orbits_path):
    print(f"orbits.nc not found for run '{tag}'")
    return None

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Verbose stdout/stderr: On failure the script prints up to 2000 characters of subprocess stdout/stderr which may
reveal internal details; as a local example this might be acceptable.

Referred Code
print("STDOUT:", result.stdout[-2000:] if len(result.stdout) > 2000 else result.stdout)
print("STDERR:", result.stderr[-2000:] if len(result.stderr) > 2000 else result.stderr)
return None

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured logs: The script uses print statements with raw subprocess outputs which are unstructured and
could inadvertently include sensitive data from external tools.

Referred Code
if result.returncode != 0:
    print(f"SIMPLE run '{tag}' failed")
    print("STDOUT:", result.stdout[-2000:] if len(result.stdout) > 2000 else result.stdout)
    print("STDERR:", result.stderr[-2000:] if len(result.stderr) > 2000 else result.stderr)
    return None

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
External exec input: The script executes an external binary with file paths from argv/work_dir without
validation or sandboxing, which could pose risks if used beyond controlled example
contexts.

Referred Code
def main():
    if len(sys.argv) < 2:
        work_dir = os.path.join(os.getcwd(), "examples")
    else:
        work_dir = sys.argv[1]

    work_dir = os.path.abspath(work_dir)
    print(f"Working directory: {work_dir}")

Learn more about managing compliance generic rules or creating your own custom rules

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

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 1, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incomplete NaN masking for plotting

To prevent potential plotting errors, update the NaN masking to check all
coordinates (s, theta, phi) instead of just s.

examples/compare_gc_coils_rk.py [136-158]

-# Mask out NaNs
-mask_vmec = ~np.isnan(vmec_traj["s"])
-mask_coils = ~np.isnan(coils_traj["s"])
+# Mask out NaNs from any coordinate
+mask_vmec = ~np.isnan(vmec_traj["s"]) & ~np.isnan(vmec_traj["theta"]) & ~np.isnan(vmec_traj["phi"])
+mask_coils = ~np.isnan(coils_traj["s"]) & ~np.isnan(coils_traj["theta"]) & ~np.isnan(coils_traj["phi"])
 
 fig, axes = plt.subplots(3, 1, figsize=(8, 10), sharex=True)
 
 axes[0].plot(vmec_traj["time"][mask_vmec], vmec_traj["s"][mask_vmec], "b-", label="VMEC RK")
 axes[0].plot(coils_traj["time"][mask_coils], coils_traj["s"][mask_coils], "r--", label="Coils RK")
 axes[0].set_ylabel("s")
 axes[0].set_title("Guiding-center RK: VMEC vs coils (VMEC reference coords)")
 axes[0].legend()
 axes[0].grid(True, alpha=0.3)
 
 axes[1].plot(vmec_traj["time"][mask_vmec], vmec_traj["theta"][mask_vmec], "b-")
 axes[1].plot(coils_traj["time"][mask_coils], coils_traj["theta"][mask_coils], "r--")
 axes[1].set_ylabel("theta")
 axes[1].grid(True, alpha=0.3)
 
 axes[2].plot(vmec_traj["time"][mask_vmec], vmec_traj["phi"][mask_vmec], "b-")
 axes[2].plot(coils_traj["time"][mask_coils], coils_traj["phi"][mask_coils], "r--")
 axes[2].set_ylabel("phi")
 axes[2].set_xlabel("time")
 axes[2].grid(True, alpha=0.3)
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential ValueError during plotting if NaN values appear in coordinates other than s, and provides a robust fix by creating a comprehensive mask for all plotted coordinates.

Medium
Make default working directory robust

To ensure the script runs consistently regardless of the execution directory,
change the default work_dir to be based on the script's file path instead of the
current working directory.

examples/compare_gc_coils_rk.py [69-72]

 if len(sys.argv) < 2:
-    work_dir = os.path.join(os.getcwd(), "examples")
+    work_dir = os.path.dirname(os.path.abspath(__file__))
 else:
     work_dir = sys.argv[1]
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue where the default work_dir is constructed incorrectly depending on the execution path, and proposes a robust fix using the script's own location.

Medium
High-level
Avoid hardcoding the executable path

The path to the simple.x executable is hardcoded, which limits portability. It
should be made more flexible by searching the system PATH or allowing the user
to specify the path via an environment variable or command-line argument.

Examples:

examples/compare_gc_coils_rk.py [38]
    simple_exe = os.path.join(os.path.dirname(__file__), '..', 'build', 'simple.x')

Solution Walkthrough:

Before:

def run_simple(config_text: str, work_dir: str, tag: str):
    """Write a config file, run SIMPLE, and return trajectory from orbits.nc."""
    # ...
    simple_exe = os.path.join(os.path.dirname(__file__), '..', 'build', 'simple.x')
    result = subprocess.run(
        [simple_exe, config_path],
        cwd=work_dir,
        capture_output=True,
        text=True,
        timeout=300,
    )
    # ...

After:

import shutil

def run_simple(config_text: str, work_dir: str, tag: str):
    """Write a config file, run SIMPLE, and return trajectory from orbits.nc."""
    # ...
    simple_exe = os.environ.get("SIMPLE_EXE_PATH") or shutil.which("simple.x")
    if not simple_exe:
        print("Error: simple.x not found in PATH or specified via SIMPLE_EXE_PATH env var.")
        return None

    result = subprocess.run(
        [simple_exe, config_path],
        cwd=work_dir,
        capture_output=True,
        text=True,
        timeout=300,
    )
    # ...
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a hardcoded path to simple.x which makes the example script brittle. Improving this would significantly enhance the script's usability and robustness for users with different build configurations.

Low
  • Update

Include vacuum NCSX VMEC equilibrium (wout_ncsx_vacuum.nc) in test data
so tests can run without needing separate VMEC runs.

Add four example configurations for GC orbit comparisons:
- vmec_vacuum: VMEC field (isw_field_type=1)
- meiss_vacuum: Meiss canonical coords from coils (isw_field_type=3)
- meiss_vmec_vacuum: Meiss canonical coords from VMEC (isw_field_type=3)
- coils_vacuum: Cartesian coils GC (isw_field_type=5)

All use NCSX c09r00 coils and consistent parameters for reproducible
orbit comparisons between field representations.
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