Skip to content

Quantum Canvas: An interactive 2D quantum simulator where you can "paint" potential barriers and watch wave packets evolve in real-time. Built with vanilla JavaScript and rendered with WebGL.

License

Notifications You must be signed in to change notification settings

alzweidi/quantum-canvas

Repository files navigation

Quantum Canvas: An Interactive 2D Quantum Physics Simulator

science is the grammar of nature; art is the accent we choose. the constants and equations would be discovered by someone, sooner or later; beethoven’s ninth would not. quantum canvas lives in that seam; taking the schrödinger equation off the chalkboard and onto a stage of light, where interference writes and phase becomes color. what begins as numerics resolves into an experience: repeatable like an experiment, singular like a performance.

The Eye

Features

Real-time Quantum Simulation

  • solves the 2D time-dependent schrödinger equation using the performant split-step fourier method

WebGL Rendering

A beautiful visualisation powered by a custom GLSL shader that includes:

  • perceptually uniform color mapping based on the wave function's phase and magnitude
  • multi-scale glow effect to give the wave packet a realistic, field-like appearance
  • phase contour lines to clearly visualise wave structure and interference patterns
  • integrated, real-time visualisation of user-drawn potential barriers

Interactive Physics Playground

  • draw/erase potential walls: left-click to draw potential barriers and right-click to erase them
  • drag the wave packet: physically move the wave packet's position in real-time
  • nudge the wave packet: apply a momentum "kick" by dragging and releasing, demonstrating a core quantum mechanical concept interactively
  • classic experiment presets: load famous quantum mechanics experiments with a single click, including the double slit experiment and quantum tunneling (Not perfect)
  • dynamic parameter control: fine-tune the simulation in real-time with a comprehensive UI panel to adjust everything from the time step (dt) to the initial momentum and width of the wave packet

Robust and Efficient

  • features a hand-coded, in-place Fast Fourier Transform (FFT) implementation
  • the animation loop pauses automatically when the tab is not visible to conserve resources
  • includes graceful error handling and recovery for both the computation and rendering engines
  • handles browser zoom and display resolution changes seamlessly via Device Pixel Ratio monitoring

How to Run This

  1. clone this repository to your local machine

  2. since this project uses ES Modules, it must be served by a local web server

  3. recommended: Use the Live Server extension in Visual Studio Code

    • right-click on index.html and select "Open with Live Server"
  4. alternative: Run a simple Python server from the project's root directory:

python -m http.server

then navigate to http://localhost:8000 in your browser.

Acknowledgements

  1. Once you take a capture, it may not look like the images you see throughout the repo since you will have to use something like Apple photo editer to enhance visiability since this is something i am still working on to have built in and specific to the engine

  2. The double slit experiment is not perfect since i am still working on it

  3. The absorber is not perfect since i am still working on it

Architecture

the simulator follows a clear, modular structure that separates concerns into distinct components:

  • main.js (Application Core): initialises all modules, runs the primary animation loop, and manages application-level state like pause/play and visibility

  • SimulationState.js (Physics State): manages the simulation's state, including the wave function (ψ), potential fields, boundary conditions, and all physical parameters

  • ComputationEngine.js (Physics Engine): executes the time evolution of the wave function using the split-step fourier method

  • fft.js (Mathematical Operations): a self-coded, in-place fast fourier transform and its inverse

  • constants.js: stores fundamental constants and initial simulation parameters

  • Renderer.js (visualisation): handles all WebGL rendering via regl, including the advanced shaders that visualise the quantum state

  • UIController.js (user interaction): manages all user input from the UI panel and the canvas, translating it into changes in the simulation state

  • presets.js: defines the configurations for the included quantum experiments

  • index.html & style.css: the application's structure and styling, featuring a modern, responsive UI panel

Technical

The Physics Engine

SimulationState.js is the data-heart of the simulation. it initialises the system with a Gaussian wave packet, a common and physically meaningful initial state, defined by the equation:

$$\psi(x,y) = A e^{-\frac{(x-x_0)^2 + (y-y_0)^2}{2\sigma^2}} e^{\frac{i}{\hbar}(p_x x + p_y y)}$$

it also manages boundary conditions, which can be reflective (high-potential walls) or absorbing (a damping field at the edges to simulate infinite space).

ComputationEngine.js evolves the wave function in time using the split-step fourier method. this numerical method is highly efficient and stable for solving the time-dependent schrödinger equation. it works by splitting the evolution operator into a potential energy step (in position space) and a kinetic energy step (in momentum space). the evolution for a single time step $\Delta t$ is approximated as:

$$\psi(t+\Delta t) \approx e^{-i\frac{V\Delta t}{2\hbar}} \cdot e^{-i\frac{T\Delta t}{\hbar}} \cdot e^{-i\frac{V\Delta t}{2\hbar}} \psi(t)$$

this symmetric splitting (also known as a strang splitting) makes the method accurate to the second order in $\Delta t$.

visualisation and rendering

Renderer.js uses regl, a functional WebGL library, to render the quantum state. the visualisation is not just a simple plot; it's a shader pipeline designed to be both beautiful and physically informative. the core of this is the fragment shader, which executes the following steps for every pixel:

// GLSL (Simplified for clarity)
void main() {
    // 1. read and decode the complex wave function from a texture
    vec2 psi = (texture2D(psiTexture, uv).rg * 2.0) - 1.0;
    float magnitude = length(psi);
    float phase = atan(psi.y, psi.x);

    // 2. read potential barrier data from another texture
    float potential = texture2D(potentialTexture, uv).r * 100.0;

    // 3. enhance small magnitudes to make faint parts of the wave more visible
    float enhancedMagnitude = enhanceMagnitude(magnitude);

    // 4. apply the visualisation pipeline
    vec3 baseColor = quantumColorMapping(enhancedMagnitude, phase); // map phase/mag to HSL color
    vec3 glowColor = applyGlow(baseColor, enhancedMagnitude, uv);   // add a soft glow
    vec3 contourColor = applyPhaseContours(glowColor, phase, enhancedMagnitude); // draw phase lines

    // 5. filter out low-magnitude noise and apply user-controlled brightness
    vec3 quantumColor = (magnitude < 0.01) ? vec3(0.0) : contourColor * u_brightness;

    // 6. overlay the potential barriers in a distinct color
    vec3 finalColor = applyPotentialBarriers(quantumColor, potential);
    gl_FragColor = vec4(finalColor, 1.0);
}

user interaction

UIController.js is the bridge between the user and the simulation. a particularly interesting feature is the "nudge" mode. when you drag and release the mouse, it applies a momentum kick to the wave packet. this is achieved by multiplying the wave function by a complex phase factor, which is the quantum mechanical operator for a momentum translation:

$$\psi_{\text{new}}(\mathbf{r}) = \psi_{\text{old}}(\mathbf{r}) \cdot e^{i(\Delta\mathbf{p} \cdot \mathbf{r})/\hbar}$$

How to Contribute

contributions are very welcome! any help to make it better is greatly appreciated. areas of particular interest are:

  • UI/UX improvements: enhancements to the layout, styling (style.css), or interaction flow (UIController.js) would be fantastic since i suck at UI/UX design lol

  • new presets: add more classic quantum experiments (e.g., quantum harmonic oscillator, Aharonov–Bohm effect)

  • optimisations: can you make it even faster? improvements to the rendering logic (Renderer.js) or physics computations (ComputationEngine.js) are always welcome

  • bug reports & ideas: found a bug or have a cool idea for a new feature? please open an issue!


Built with determination by Alzweidi

Genesis Art Pieces

  • Pieces from Genesis are not lossless simulations of the quantum state, yet they look interesting and are relatively easy with paint with due to it's pixels, i have saved it on a seperate branch whilst the photos taken below Genesis are lossless however visiability effects were enhanced outside of the engine as i stated above in the repo.

Genesis I Genesis II Genesis III

Genesis I    Genesis II    Genesis III

Alone Coloured Black & White

Alone    Coloured    Black & White

Recorded Test: Directed Packet with Absorbing Boundaries

  • initial state: a stationary gaussian wave packet with zero mean momentum.
  • impulse: a momentum kick Δp is applied toward the bottom-right, imparting group velocity v_g = Δp/m (ħ = 1, m = 1 in code units).
  • evolution: the packet propagates diagonally as intended; dispersion broadens its width while phase winds set the hue.
  • absorbing boundaries: as the leading edge enters the bottom-right absorbing layer, the outgoing amplitude is smoothly damped with minimal reflection, preventing unphysical wrap-around.
  • observation: after the kick, the remaining interior amplitude stays coherent; with peripheral probability density removed by the absorber, the visible core appears to re-localise transiently before continued dispersive spreading.

Direct link: captures-enhanced/testA1.mov

About

Quantum Canvas: An interactive 2D quantum simulator where you can "paint" potential barriers and watch wave packets evolve in real-time. Built with vanilla JavaScript and rendered with WebGL.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •