Source code for manim_physics.electromagnetism.electrostatics

"""Electrostatics module"""

from __future__ import annotations
from typing import Iterable

from manim import normalize
from manim.constants import ORIGIN, TAU
from manim.mobject.geometry.arc import Arc, Dot
from manim.mobject.geometry.polygram import Rectangle
from manim.mobject.types.vectorized_mobject import VGroup
from manim.mobject.vector_field import ArrowVectorField
from manim.utils.color import BLUE, RED, RED_A, RED_D, color_gradient
import numpy as np


__all__ = [
    "Charge",
    "ElectricField",
]


[docs]class Charge(VGroup): def __init__( self, magnitude: float = 1, point: np.ndarray = ORIGIN, add_glow: bool = True, **kwargs, ) -> None: """An electrostatic charge object to produce an :class:`~ElectricField`. Parameters ---------- magnitude The strength of the electrostatic charge. point The position of the charge. add_glow Whether to add a glowing effect. Adds rings of varying opacities to simulate glowing effect. kwargs Additional parameters to be passed to ``VGroup``. """ VGroup.__init__(self, **kwargs) self.magnitude = magnitude self.point = point self.radius = (abs(magnitude) * 0.4 if abs(magnitude) < 2 else 0.8) * 0.3 if magnitude > 0: label = VGroup( Rectangle(width=0.32 * 1.1, height=0.006 * 1.1).set_z_index(1), Rectangle(width=0.006 * 1.1, height=0.32 * 1.1).set_z_index(1), ) color = RED layer_colors = [RED_D, RED_A] layer_radius = 4 else: label = Rectangle(width=0.27, height=0.003) color = BLUE layer_colors = ["#3399FF", "#66B2FF"] layer_radius = 2 if add_glow: # use many arcs to simulate glowing layer_num = 80 color_list = color_gradient(layer_colors, layer_num) opacity_func = lambda t: 1500 * (1 - abs(t - 0.009) ** 0.0001) rate_func = lambda t: t**2 for i in range(layer_num): self.add( Arc( radius=layer_radius * rate_func((0.5 + i) / layer_num), angle=TAU, color=color_list[i], stroke_width=101 * (rate_func((i + 1) / layer_num) - rate_func(i / layer_num)) * layer_radius, stroke_opacity=opacity_func(rate_func(i / layer_num)), ).shift(point) ) self.add(Dot(point=self.point, radius=self.radius, color=color)) self.add(label.scale(self.radius / 0.3).shift(point)) for mob in self: mob.set_z_index(1)
[docs]class ElectricField(ArrowVectorField): def __init__(self, *charges: Charge, **kwargs) -> None: """An electric field. Parameters ---------- charges The charges affecting the electric field. kwargs Additional parameters to be passed to ``ArrowVectorField``. Examples -------- .. manim:: ElectricFieldExampleScene :save_last_frame: from manim_physics import * class ElectricFieldExampleScene(Scene): def construct(self): charge1 = Charge(-1, LEFT + DOWN) charge2 = Charge(2, RIGHT + DOWN) charge3 = Charge(-1, UP) field = ElectricField(charge1, charge2, charge3) self.add(charge1, charge2, charge3) self.add(field) """ self.charges = charges positions = [] magnitudes = [] for charge in charges: positions.append(charge.get_center()) magnitudes.append(charge.magnitude) super().__init__(lambda p: self._field_func(p, positions, magnitudes), **kwargs) def _field_func( self, p: np.ndarray, positions: Iterable[np.ndarray], magnitudes: Iterable[float], ) -> np.ndarray: field_vect = np.zeros(3) for p0, mag in zip(positions, magnitudes): r = p - p0 dist = np.linalg.norm(r) if dist < 0.1: return np.zeros(3) field_vect += mag / dist**2 * normalize(r) return field_vect