Complex Fields#

Complex-valued CoefficientFunctions and GridFunctions are automatically detected. Both real and imaginary parts are uploaded to the GPU in a single interleaved buffer, enabling efficient visualization of different views (Real, Imag, Abs, Phase) and phase-sweep animation without any buffer re-upload.

Scalar Complex Field#

A complex grid function is drawn just like a real one. By default, the real part is shown. A "Complex Mode" dropdown and an "Animate" checkbox appear automatically in the GUI.

[1]:
from ngsolve import *
from ngsolve_webgpu import *
from ngsolve_webgpu.cf import CFRenderer
from ngsolve_webgpu.jupyter import Draw

mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))
fes = H1(mesh, order=3, complex=True)
gf = GridFunction(fes)
gf.Set(sin(3*x) + 1j*cos(3*y))

scene = Draw(gf, mesh, order=3)

Switching Complex Visualization Mode#

Use set_complex_mode on the CFRenderer to switch between views. Available modes: "real", "imag", "abs", "phase".

This only updates a small uniform on the GPU — no data re-upload.

[2]:
for ro in scene.render_objects:
    if isinstance(ro, CFRenderer):
        ro.set_complex_mode("abs")
        break

scene.render()
[3]:
for ro in scene.render_objects:
    if isinstance(ro, CFRenderer):
        ro.set_complex_mode("imag")
        break

scene.render()

Phase Animation#

For frequency-domain solutions, the physical field is:

\[u(x, t) = \text{Re}\bigl(u_{\text{complex}}(x) \cdot e^{i\omega t}\bigr)\]

Call animate_phase(scene) to start a smooth phase sweep. Each frame costs only an 8-byte uniform write — the Bernstein coefficient buffers stay untouched.

You can also use the "Animate" checkbox in the GUI.

[4]:
for ro in scene.render_objects:
    if isinstance(ro, CFRenderer):
        ro.animate_phase(scene, speed=0.2)  # 1 full cycle per second
        break
[5]:
# Stop the animation
for ro in scene.render_objects:
    if isinstance(ro, CFRenderer):
        ro.stop_animation()
        break

Complex Vector Field#

Vector-valued complex fields work the same way. Component selection and complex mode are independent.

[6]:
fes_vec = VectorH1(mesh, order=3, complex=True)
gf_vec = GridFunction(fes_vec)
gf_vec.Set(CF((x + 1j*y, 2*x - 1j*y)))

scene2 = Draw(gf_vec, mesh, order=3)
[7]:
# Show imaginary part of component 1
for ro in scene2.render_objects:
    if isinstance(ro, CFRenderer):
        ro.set_component(1)
        ro.set_complex_mode("real")
        break

scene2.render()

Complex Vectors with Phase Animation#

Vector arrows can also be animated. For a complex vector field, the arrow directions rotate smoothly as the phase sweeps, visualizing the time-harmonic solution:

\[\vec{u}(x, t) = \text{Re}\bigl(\vec{u}_{\text{complex}}(x) \cdot e^{i\omega t}\bigr)\]
[8]:
from ngsolve import *
from ngsolve_webgpu.mesh import MeshData
from ngsolve_webgpu.cf import FunctionData
from ngsolve_webgpu.vectors import SurfaceVectors
import webgpu.jupyter as wj

mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))
fes = VectorH1(mesh, order=2, complex=True)
gf = GridFunction(fes)
gf.Set(CF((1j*z, -1j*z, x + 1j*y)))

mesh_data = MeshData(mesh)
function_data = FunctionData(mesh_data, gf, order=2)
vectors = SurfaceVectors(function_data, grid_size=15)
scene3 = wj.Draw([vectors], 600, 600)
[9]:
# Start phase animation — arrows rotate in place
vectors.animate_phase(scene3, speed=0.1)
[10]:
vectors.stop_animation()

Scaling by Value#

With scale_by_value=True, arrow length is proportional to the instantaneous field magnitude. During phase animation, arrows grow and shrink as the field oscillates. Color always shows the current magnitude via the colormap. The scale is auto-calculated from the bounding box and maximum field value.

[11]:
from ngsolve import *
from ngsolve_webgpu.mesh import MeshData
from ngsolve_webgpu.cf import FunctionData
from ngsolve_webgpu.vectors import SurfaceVectors
import webgpu.jupyter as wj

mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))
fes = VectorH1(mesh, order=2, complex=True)
gf = GridFunction(fes)
gf.Set(CF((1j*z, -1j*z, x + 1j*y)))

mesh_data = MeshData(mesh)
function_data = FunctionData(mesh_data, gf, order=2)
vectors = SurfaceVectors(function_data, grid_size=15, scale_by_value=True)
scene4 = wj.Draw([vectors], 600, 600)
[12]:
# Animate: arrows grow/shrink as the phase rotates
vectors.animate_phase(scene4, speed=0.2)
[13]:
vectors.stop_animation()
[ ]: