{ "cells": [ { "cell_type": "markdown", "id": "c1", "metadata": {}, "source": [ "# Complex Fields\n", "\n", "Complex-valued `CoefficientFunction`s and `GridFunction`s are automatically detected.\n", "Both real and imaginary parts are uploaded to the GPU in a single interleaved buffer,\n", "enabling efficient visualization of different views (Real, Imag, Abs, Phase) and\n", "phase-sweep animation without any buffer re-upload." ] }, { "cell_type": "markdown", "id": "c2", "metadata": {}, "source": [ "## Scalar Complex Field\n", "\n", "A complex grid function is drawn just like a real one. By default, the real part is shown.\n", "A \"Complex Mode\" dropdown and an \"Animate\" checkbox appear automatically in the GUI." ] }, { "cell_type": "code", "execution_count": null, "id": "c3", "metadata": {}, "outputs": [], "source": [ "from ngsolve import *\n", "from ngsolve_webgpu import *\n", "from ngsolve_webgpu.cf import CFRenderer\n", "from ngsolve_webgpu.jupyter import Draw\n", "\n", "mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))\n", "fes = H1(mesh, order=3, complex=True)\n", "gf = GridFunction(fes)\n", "gf.Set(sin(3*x) + 1j*cos(3*y))\n", "\n", "scene = Draw(gf, mesh, order=3)" ] }, { "cell_type": "markdown", "id": "c4", "metadata": {}, "source": [ "## Switching Complex Visualization Mode\n", "\n", "Use `set_complex_mode` on the `CFRenderer` to switch between views.\n", "Available modes: `\"real\"`, `\"imag\"`, `\"abs\"`, `\"phase\"`.\n", "\n", "This only updates a small uniform on the GPU — no data re-upload." ] }, { "cell_type": "code", "execution_count": null, "id": "c5", "metadata": {}, "outputs": [], "source": [ "for ro in scene.render_objects:\n", " if isinstance(ro, CFRenderer):\n", " ro.set_complex_mode(\"abs\")\n", " break\n", "\n", "scene.render()" ] }, { "cell_type": "code", "execution_count": null, "id": "c6", "metadata": {}, "outputs": [], "source": [ "for ro in scene.render_objects:\n", " if isinstance(ro, CFRenderer):\n", " ro.set_complex_mode(\"imag\")\n", " break\n", "\n", "scene.render()" ] }, { "cell_type": "markdown", "id": "c7", "metadata": {}, "source": [ "## Phase Animation\n", "\n", "For frequency-domain solutions, the physical field is:\n", "\n", "$$u(x, t) = \\text{Re}\\bigl(u_{\\text{complex}}(x) \\cdot e^{i\\omega t}\\bigr)$$\n", "\n", "Call `animate_phase(scene)` to start a smooth phase sweep.\n", "Each frame costs only an 8-byte uniform write — the Bernstein coefficient buffers stay untouched.\n", "\n", "You can also use the \"Animate\" checkbox in the GUI." ] }, { "cell_type": "code", "execution_count": null, "id": "c8", "metadata": {}, "outputs": [], "source": [ "for ro in scene.render_objects:\n", " if isinstance(ro, CFRenderer):\n", " ro.animate_phase(scene, speed=0.2) # 1 full cycle per second\n", " break" ] }, { "cell_type": "code", "execution_count": null, "id": "c8b", "metadata": {}, "outputs": [], "source": [ "# Stop the animation\n", "for ro in scene.render_objects:\n", " if isinstance(ro, CFRenderer):\n", " ro.stop_animation()\n", " break" ] }, { "cell_type": "markdown", "id": "c9", "metadata": {}, "source": [ "## Complex Vector Field\n", "\n", "Vector-valued complex fields work the same way. Component selection and complex mode are independent." ] }, { "cell_type": "code", "execution_count": null, "id": "c10", "metadata": {}, "outputs": [], "source": [ "fes_vec = VectorH1(mesh, order=3, complex=True)\n", "gf_vec = GridFunction(fes_vec)\n", "gf_vec.Set(CF((x + 1j*y, 2*x - 1j*y)))\n", "\n", "scene2 = Draw(gf_vec, mesh, order=3)" ] }, { "cell_type": "code", "execution_count": null, "id": "c11", "metadata": {}, "outputs": [], "source": [ "# Show imaginary part of component 1\n", "for ro in scene2.render_objects:\n", " if isinstance(ro, CFRenderer):\n", " ro.set_component(1)\n", " ro.set_complex_mode(\"real\")\n", " break\n", "\n", "scene2.render()" ] }, { "cell_type": "markdown", "id": "c12", "metadata": {}, "source": [ "## Complex Vectors with Phase Animation\n", "\n", "Vector arrows can also be animated. For a complex vector field, the arrow directions\n", "rotate smoothly as the phase sweeps, visualizing the time-harmonic solution:\n", "\n", "$$\\vec{u}(x, t) = \\text{Re}\\bigl(\\vec{u}_{\\text{complex}}(x) \\cdot e^{i\\omega t}\\bigr)$$" ] }, { "cell_type": "code", "execution_count": null, "id": "c13", "metadata": {}, "outputs": [], "source": [ "from ngsolve import *\n", "from ngsolve_webgpu.mesh import MeshData\n", "from ngsolve_webgpu.cf import FunctionData\n", "from ngsolve_webgpu.vectors import SurfaceVectors\n", "import webgpu.jupyter as wj\n", "\n", "mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))\n", "fes = VectorH1(mesh, order=2, complex=True)\n", "gf = GridFunction(fes)\n", "gf.Set(CF((1j*z, -1j*z, x + 1j*y)))\n", "\n", "mesh_data = MeshData(mesh)\n", "function_data = FunctionData(mesh_data, gf, order=2)\n", "vectors = SurfaceVectors(function_data, grid_size=15)\n", "scene3 = wj.Draw([vectors], 600, 600)" ] }, { "cell_type": "code", "execution_count": null, "id": "c14", "metadata": {}, "outputs": [], "source": [ "# Start phase animation — arrows rotate in place\n", "vectors.animate_phase(scene3, speed=0.1)" ] }, { "cell_type": "code", "execution_count": null, "id": "c15", "metadata": {}, "outputs": [], "source": [ "vectors.stop_animation()" ] }, { "cell_type": "markdown", "id": "c16", "metadata": {}, "source": [ "## Scaling by Value\n", "\n", "With `scale_by_value=True`, arrow length is proportional to the instantaneous field magnitude.\n", "During phase animation, arrows grow and shrink as the field oscillates.\n", "Color always shows the current magnitude via the colormap.\n", "The scale is auto-calculated from the bounding box and maximum field value." ] }, { "cell_type": "code", "execution_count": null, "id": "c17", "metadata": {}, "outputs": [], "source": [ "from ngsolve import *\n", "from ngsolve_webgpu.mesh import MeshData\n", "from ngsolve_webgpu.cf import FunctionData\n", "from ngsolve_webgpu.vectors import SurfaceVectors\n", "import webgpu.jupyter as wj\n", "\n", "mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))\n", "fes = VectorH1(mesh, order=2, complex=True)\n", "gf = GridFunction(fes)\n", "gf.Set(CF((1j*z, -1j*z, x + 1j*y)))\n", "\n", "mesh_data = MeshData(mesh)\n", "function_data = FunctionData(mesh_data, gf, order=2)\n", "vectors = SurfaceVectors(function_data, grid_size=15, scale_by_value=True)\n", "scene4 = wj.Draw([vectors], 600, 600)" ] }, { "cell_type": "code", "execution_count": null, "id": "c18", "metadata": {}, "outputs": [], "source": [ "# Animate: arrows grow/shrink as the phase rotates\n", "vectors.animate_phase(scene4, speed=0.2)" ] }, { "cell_type": "code", "execution_count": null, "id": "c19", "metadata": {}, "outputs": [], "source": [ "vectors.stop_animation()" ] }, { "cell_type": "code", "execution_count": null, "id": "00a1b155-87f7-4b9d-a76e-f444d1ced774", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.5" } }, "nbformat": 4, "nbformat_minor": 5 }