Symmetry Rendering#
When solving on a reduced domain with symmetry boundary conditions, the Symmetry class renders the full solution by applying mirror transformations on the GPU.
Mirror Symmetry#
Render a quarter-domain solution as a full domain:
[1]:
from ngsolve import *
from ngsolve_webgpu import MeshData, FunctionData, CFRenderer, Symmetry
from webgpu.jupyter import Draw
# Solve on a quarter of the domain
mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))
cf = sin(3.14159 * x) * sin(3.14159 * y)
md = MeshData(mesh)
fd = FunctionData(md, cf, order=3)
sym = Symmetry()
sym.mirror_x() # reflect across x=0 -> 2 copies
sym.mirror_y() # reflect across y=0 -> 4 copies (group closure)
cfr = CFRenderer(fd, symmetry=sym)
Draw(cfr)
[1]:
Each call to mirror_x/y/z doubles the number of rendered copies by computing the group closure of all generator combinations. The transforms are applied in the vertex shader — no mesh duplication needed.
Antisymmetric Fields (Parity)#
For fields that change sign under a mirror operation:
[2]:
from ngsolve import *
from ngsolve_webgpu import MeshData, FunctionData, CFRenderer, Symmetry, Colorbar
from webgpu.jupyter import Draw
mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))
cf = x * sin(3.14159 * y) # odd in x
md = MeshData(mesh)
fd = FunctionData(md, cf, order=3)
sym = Symmetry()
sym.mirror_x()
sym_odd = sym.with_parity([-1]) # field is antisymmetric under mirror_x
cfr = CFRenderer(fd, symmetry=sym_odd)
cfr.colormap.set_min(-1)
colorbar = Colorbar(cfr.colormap)
Draw([cfr, colorbar])
[2]:
with_parity([-1]) tells the renderer to negate function values for copies involving that mirror generator. The parity list has one entry per generator in the order they were added.
Vector Symmetry#
Arrow glyphs (SurfaceVectors, ClippingVectors) support the same symmetry system. Positions and directions are expanded on the GPU via a compute shader — no CPU overhead.
[3]:
from ngsolve import *
from ngsolve_webgpu import MeshData, FunctionData, SurfaceVectors, Symmetry
from webgpu.jupyter import Draw
mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))
cf = CF((x, y, 0))
md = MeshData(mesh)
fd = FunctionData(md, cf, order=1)
sym = Symmetry()
sym.mirror_x()
sym.mirror_y()
vecs = SurfaceVectors(fd, symmetry=sym)
Draw(vecs)
Warning: failed to init renderer SurfaceVectors: 'directions_imag'
[3]:
Electromagnetic Symmetry (Polar vs Axial Vectors)#
For electromagnetic simulations, vectors transform differently depending on whether they are polar (E-field, displacement) or axial/pseudovectors (B-field, H-field):
|
Transform |
Use case |
|---|---|---|
|
d' = M · d |
E-field with PMC wall, B-field with PEC wall |
|
d' = det(M) · M · d |
E-field with PEC wall, B-field with PMC wall |
# B-field mirrored at a PMC boundary:
vecs_B = SurfaceVectors(fd_B, symmetry=sym, vector_symmetry="axial")
API Reference#
Symmetry()
.mirror_x(),.mirror_y(),.mirror_z()— add mirror generators (chainable).with_parity(list)— returns new Symmetry with per-generator sign flips for scalar fields.n_copies— total number of symmetry copies (read-only)
Scalar renderers (CFRenderer, ClippingCF, MeshElements2d, MeshElements3d, IsoSurfaceRenderer, NegativeSurfaceRenderer, NegativeClippingRenderer, GeometryRenderer):
Pass
symmetry=symto constructor
Vector renderers (SurfaceVectors, ClippingVectors):
Pass
symmetry=symto constructorPass
vector_symmetry="axial"for pseudovectors (B-field at PMC, E-field at PEC)