Source code for manim_physics.wave

"""3D and 2D Waves module."""

from __future__ import annotations
from typing import Iterable, Optional

from manim import *

__all__ = [
    "LinearWave",
    "RadialWave",
    "StandingWave",
]


try:
    # For manim < 0.15.0
    from manim.mobject.opengl_compatibility import ConvertToOpenGL
except ModuleNotFoundError:
    # For manim >= 0.15.0
    from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL


[docs]class RadialWave(Surface, metaclass=ConvertToOpenGL): def __init__( self, *sources: Optional[np.ndarray], wavelength: float = 1, period: float = 1, amplitude: float = 0.1, x_range: Iterable[float] = [-5, 5], y_range: Iterable[float] = [-5, 5], **kwargs, ) -> None: """A 3D Surface with waves moving radially. Parameters ---------- sources The sources of disturbance. wavelength The wavelength of the wave. period The period of the wave. amplitude The amplitude of the wave. x_range The range of the wave in the x direction. y_range The range of the wave in the y direction. kwargs Additional parameters to be passed to :class:`~Surface`. Examples -------- .. manim:: RadialWaveExampleScene class RadialWaveExampleScene(ThreeDScene): def construct(self): self.set_camera_orientation(60 * DEGREES, -45 * DEGREES) wave = RadialWave( LEFT * 2 + DOWN * 5, # Two source of waves RIGHT * 2 + DOWN * 5, checkerboard_colors=[BLUE_D], stroke_width=0, ) self.add(wave) wave.start_wave() self.wait() wave.stop_wave() """ self.wavelength = wavelength self.period = period self.amplitude = amplitude self.time = 0 self.kwargs = kwargs self.sources = sources super().__init__( lambda u, v: np.array([u, v, self._wave_z(u, v, sources)]), u_range=x_range, v_range=y_range, **kwargs, ) def _wave_z(self, u: float, v: float, sources: Iterable[np.ndarray]) -> float: z = 0 for source in sources: x0, y0, _ = source z += self.amplitude * np.sin( (2 * PI / self.wavelength) * ((u - x0) ** 2 + (v - y0) ** 2) ** 0.5 - 2 * PI * self.time / self.period ) return z def _update_wave(self, mob: Mobject, dt: float) -> None: self.time += dt mob.match_points( Surface( lambda u, v: np.array([u, v, self._wave_z(u, v, self.sources)]), u_range=self.u_range, v_range=self.v_range, **self.kwargs, ) )
[docs] def start_wave(self): """Animate the wave propagation.""" self.add_updater(self._update_wave)
[docs] def stop_wave(self): """Stop animating the wave propagation.""" self.remove_updater(self._update_wave)
[docs]class LinearWave(RadialWave): def __init__( self, wavelength: float = 1, period: float = 1, amplitude: float = 0.1, x_range: Iterable[float] = [-5, 5], y_range: Iterable[float] = [-5, 5], **kwargs, ) -> None: """A 3D Surface with waves in one direction. Parameters ---------- wavelength The wavelength of the wave. period The period of the wave. amplitude The amplitude of the wave. x_range The range of the wave in the x direction. y_range The range of the wave in the y direction. kwargs Additional parameters to be passed to :class:`~Surface`. Examples -------- .. manim:: LinearWaveExampleScene class LinearWaveExampleScene(ThreeDScene): def construct(self): self.set_camera_orientation(60 * DEGREES, -45 * DEGREES) wave = LinearWave() self.add(wave) wave.start_wave() self.wait() wave.stop_wave() """ super().__init__( ORIGIN, wavelength=wavelength, period=period, amplitude=amplitude, x_range=x_range, y_range=y_range, **kwargs, ) def _wave_z(self, u: float, v: float, sources: Iterable[np.ndarray]) -> float: return self.amplitude * np.sin( (2 * PI / self.wavelength) * u - 2 * PI * self.time / self.period )
[docs]class StandingWave(ParametricFunction): def __init__( self, n: int = 2, length: float = 4, period: float = 1, amplitude: float = 1, **kwargs, ) -> None: """A 2D standing wave. Parameters ---------- n Harmonic number. length The length of the wave. period The time taken for one full oscillation. amplitude The maximum height of the wave. kwargs Additional parameters to be passed to :class:`~ParametricFunction`. Examples -------- .. manim:: StandingWaveExampleScene from manim_physics import * class StandingWaveExampleScene(Scene): def construct(self): wave1 = StandingWave(1) wave2 = StandingWave(2) wave3 = StandingWave(3) wave4 = StandingWave(4) waves = VGroup(wave1, wave2, wave3, wave4) waves.arrange(DOWN).move_to(ORIGIN) self.add(waves) for wave in waves: wave.start_wave() self.wait() """ self.n = n self.length = length self.period = period self.amplitude = amplitude self.time = 0 self.kwargs = {**kwargs} super().__init__( lambda t: np.array([t, amplitude * np.sin(n * PI * t / length), 0]), t_range=[0, length], **kwargs, ) self.shift([-self.length / 2, 0, 0]) def _update_wave(self, mob: Mobject, dt: float) -> None: self.time += dt mob.become( ParametricFunction( lambda t: np.array( [ t, self.amplitude * np.sin(self.n * PI * t / self.length) * np.cos(2 * PI * self.time / self.period), 0, ] ), t_range=[0, self.length], **self.kwargs, ).shift(self.wave_center + [-self.length / 2, 0, 0]) ) def start_wave(self): self.wave_center = self.get_center() self.add_updater(self._update_wave) def stop_wave(self): self.remove_updater(self._update_wave)