Skip to content

FDTD: E_z on the axis of a closed cylinder counted one alpha current twice - #248

Open
MrHighVoltage wants to merge 1 commit into
thliebig:masterfrom
MrHighVoltage:cylinder-axis-currents
Open

MrHighVoltage wants to merge 1 commit into
thliebig:masterfrom
MrHighVoltage:cylinder-axis-currents

Conversation

@MrHighVoltage

@MrHighVoltage MrHighVoltage commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

AI: generated with Claude Opus 5.5 (Claude Code), reviewed and tested by me.

On a closed cylindrical mesh that includes r=0, the E_z update on the axis adds the alpha current of alpha line 0 twice. Its loop runs over one alpha line too many, and that line is a copy of line 0 past the seam. The capacitance of the same update counts every line once. The loop is in the host extension, which the basic, multithreaded, CPU and gpu-reference engines run, and in the HIP and Metal kernels.

Where it happens (links to master 1d710c2):

  1. A closed alpha mesh gets two extra lines past the seam, in Operator_Cylinder::SetupCSXGrid() (operator_cylinder.cpp#L524-L526): the last line is set to α0+2π, and a line at α1+2π is added. So numLines[1] is the number of distinct alpha lines plus 2. For 17 lines from -π to π:

    index      0    1    2   ...  15   |  16      17
    position   α0   α1   α2  ...  α15  |  α0+2π   α1+2π
                                       |  = 0     = 1
    
  2. Engine_Ext_Cylinder::BoxImpl() closes the seam by copying fields (after the voltage update, after the current update): E_r and E_z of line last_A_Line = numLines[1]-2 go to line 0, and H_r and H_z of line 0 go to line last_A_Line. H_alpha lies on its alpha line and depends only on E_r and E_z of that line, so line last_A_Line holds the same alpha current as line 0.

  3. E_z on the axis is updated from the alpha currents on the ring around it (C dV/dt = sum of I_alpha):

The fix is the limit last_A_Line (N.ny-2 in the kernels).

Effect:

  • A field without E_z on the axis, e.g. of two opposite sources or any cos(α) dependence: the ring currents cancel, except for the extra copy of line 0. It puts 0.51 % (16 alpha cells) to 0.26 % (32) of the source voltage on the axis; after the fix, below 1e-7.
  • A rotationally symmetric field, e.g. TM010: with N alpha cells, the axis gets (N+1)/N of the current sum. In Cylindrical_Cavity.py, TM010 moves by one 1 MHz bin, from 2.2940 to 2.2930 GHz (analytic 2.2949 GHz), and the deviation of its radial profile from J0 drops from 3e-4 to 1e-4.

Test: the new python/Tests/Cylindrical_Axis.py drives two opposite z-directed sources at r=3 mm on a closed 16-cell mesh, and checks that E_z on the axis stays below 1e-5 of the source voltage. Like the other simulation tests, it runs the default engine, or the one of run_testsuite.py --engine. I ran it on the default (multithreaded), basic, cpu, gpu-reference and HIP (RX 6800) engines: each fails on master (5.1e-3) and passes with the fix (6e-8 to 8e-8). Engine_Compare.py, which compares the engines with basic on a closed mesh including r=0 (cylinder_closed) and on the multi-grid cases, passes with --engine=cpu and --engine=gpu; so do Cylindrical_Cavity.py and the C++ unit tests. Metal has the same one-line change but was not compiled (no Mac).

Note for the gpu-cellmodel branch: the branch writes the device extensions once, and Op_Cylinder mode 0 in kernels/ext_ops.h#L186 (fc20a33) copies the loop for the reference, HIP and Metal backends. The HIP and Metal hunks here become:

-			for (uint64_t a=0; a<ny-1; ++a)
+			for (uint64_t a=0; a<ny-2; ++a)

Its Engine_Compare.py compares gpu-reference with the CPU engine bit for bit, so both sides have to change together: with only ext_ops.h fixed, cylinder_closed fails (et, et_axis, ht); with both, Engine_Compare.py --engine=gpu and Cylindrical_Axis.py on gpu and gpu-reference pass. I have the commit on a local branch on fc20a33.

On a closed alpha mesh including r=0, E_z on the axis is updated from the
alpha currents on the ring around it. The alpha lines numLines-2 and
numLines-1 are copies of the lines 0 and 1 past the seam, but the sum
ran up to numLines-2, so the current of line 0 entered twice, while the
capacitance of vi_R0 counts every line once. A field odd under a
rotation by pi, without E_z on the axis, put 0.26 % (32 alpha cells) to
0.5 % (16) of its source voltage there. The same sum is in the host
extension (Engine_Ext_Cylinder::BoxImpl(), which the basic,
multithreaded, CPU and gpu-reference engines run) and in the HIP and
Metal kernels.

python/Tests/Cylindrical_Axis.py checks the axis voltage of such a field
with the default engine, or the one of run_testsuite.py --engine;
Engine_Compare.py compares every engine with the basic one on a closed
mesh including r=0.

Generated-by: Claude Opus 5.5 (Claude Code)
Signed-off-by: Georg Zachl <it@zachlge.org>
@MrHighVoltage
MrHighVoltage force-pushed the cylinder-axis-currents branch from 34783ca to 8954284 Compare November 9, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant