Home EuCAP2023 - Min-Path-Tracing: A Diffraction Aware Alternative to Image Method in Ray Tracing
Post
Cancel

EuCAP2023 - Min-Path-Tracing: A Diffraction Aware Alternative to Image Method in Ray Tracing

EuCAP 2023 presentation slides and code.

On Monday, March 27, 2023, I presented my work at the 17th European Conference on Antennas and Propagation (EuCAP 2023). The work was pretty similar to the one presented at the COST meeting last year, in Lyon, and contains very minimal changes in the contents.

Slides, however, were modified to contain more details about the mathematics behind our methods, as well as an example application to meta-surfaces.

Slides

The following slides are made with RevealJS and are interactive!

Use basic keys like LEFT and RIGHT to navigate through slides, or F to go full screen.

The slides were generated using the Manim (The Manim Community Developers, 2022) animation engine and Manim Slides, one of my open source projects, to combine the animations into slides and later convert them in a PowerPoint .pptx file.

References

  1. The Manim Community Developers. (2022). Manim – Mathematical Animation Framework (Version v0.17.2). https://www.manim.community/

Source code

import numpy as np
import sympy as sy
from manim import *
from manim_slides import Slide
from shapely.geometry import LineString

"""
Some useful function required for the 'simple example'.
"""


def row(*args):
    """
    Create a symbol row (or col) vector from input arguments.
    """
    return sy.Matrix(args)


def generate_c(as_gamma=False):
    """
    Return C(x) and it's derivative.
    """
    # Unknowns
    s1, s2 = sy.symbols("s_1 s_2", real=True)  # s1, s2 are real values

    # Geometry

    # - Nodes
    BS = row(2, -1)
    UE = row(2, 4)

    # - Interaction points
    X1 = row(s1, s1)
    X2 = row(5, s2)

    # - Surface normals
    n1 = row(1, -1).normalized()
    n2 = row(-1, 0).normalized()

    # - Aliases
    V0 = X1 - BS
    V1 = X2 - X1
    V2 = UE - X2

    if as_gamma:
        g1 = sy.Function(r"\gamma_1", real=True)(s1, s2)
        g2 = sy.Function(r"\gamma_2", real=True)(s1, s2)
    else:
        g1 = V0.norm() / V1.norm()
        g2 = V1.norm() / V2.norm()

    # Write different equations
    eqs = [
        g1 * V1 - (V0 - 2 * V0.dot(n1) * n1),
        g2 * V2 - (V1 - 2 * V1.dot(n2) * n2),
    ]

    F = sy.Matrix.vstack(*eqs)
    f = F.norm() ** 2

    _df = sy.lambdify((s1, s2), row(f.diff(s1), f.diff(s2)))

    def df(x):
        return _df(*x).reshape(-1)

    return sy.lambdify((s1, s2), f), df


def generate_c_ris(phi=0):
    """
    Return C(x) and it's derivative for a RIS.
    """
    # Unknowns
    s1, s2 = sy.symbols("s_1 s_2", real=True)  # s1, s2 are real values

    # Geometry

    # - Nodes
    BS = row(2, -1)
    UE = row(2, 4 - 0.5)

    # - Interaction points
    X1 = row(s1, s1)
    X2 = row(5, s2)

    # - Surface normals
    n1 = row(1, -1).normalized()
    n2 = row(-1, 0).normalized()

    # Aliases
    V0 = X1 - BS
    V1 = X2 - X1
    V2 = UE - X2

    cross = n2[0] * V2[1] - n2[1] * V2[0]

    g1 = V0.norm() / V1.norm()

    # Write different equations
    eqs = [
        g1 * V1 - (V0 - 2 * V0.dot(n1) * n1),
        row(cross - sy.sin(phi) * V2.norm()),
    ]

    F = sy.Matrix.vstack(*eqs)
    f = F.norm() ** 2

    _df = sy.lambdify((s1, s2), row(f.diff(s1), f.diff(s2)))

    def df(x):
        return _df(*x).reshape(-1)

    return sy.lambdify((s1, s2), f), df


def gradient_descent(x0, df, tol=1e-12, max_it=100, return_steps=False):
    """
    Perform a gradient descent using optimal alpha for linear systems.
    """
    xa = x0
    dfxa = df(xa)
    xb = xa - 0.25 * dfxa  # First step, alpha = .5
    dfxb = df(xb)

    dx = xb - xa
    dfx = dfxb - dfxa

    n_it = 1

    steps = [dx]

    while np.linalg.norm(dx) > tol and n_it < max_it:
        alpha = np.dot(dx, dfx) / np.linalg.norm(dfx) ** 2
        xa, xb = xb, xb - alpha * dfxb
        dfxa, dfxb = dfxb, df(xb)
        dx = xb - xa
        dfx = dfxb - dfxa
        n_it += 1

        if return_steps:
            steps.append(dx)

    if return_steps:
        return steps
    return xb


"""
Here, because I switched the background from black to white,
so I have to make default color for most things to be black (instead of white).
"""


def black(func):
    """
    Sets default color to black
    """

    def wrapper(*args, color=BLACK, **kwargs):
        return func(*args, color=color, **kwargs)

    return wrapper


Tex = black(Tex)
Text = black(Text)
MathTex = black(MathTex)
Line = black(Line)
Dot = black(Dot)
Brace = black(Brace)
Arrow = black(Arrow)
Angle = black(Angle)


"""
Slides generation
"""


class Main(Slide):
    def __init__(self, *args, **kwargs):
        super(Main, self).__init__(*args, **kwargs)
        self.slide_no = None
        self.slide_text = None

    def write_slide_number(self, inital=1, text=Tex, animation=Write, position=ORIGIN):
        self.slide_no = inital
        self.slide_text = text(str(inital)).shift(position)
        return animation(self.slide_text)

    def update_slide_number(self, text=Tex, animation=Transform):
        self.slide_no += 1
        new_text = text(str(self.slide_no)).move_to(self.slide_text)
        return animation(self.slide_text, new_text)

    def construct(self):
        self.camera.background_color = WHITE
        WALL_COLOR = ORANGE
        BS_COLOR = BLUE
        UE_COLOR = MAROON_D
        GOOD_COLOR = "#28C137"
        BAD_COLOR = "#FF0000"
        IMAGE_COLOR = "#636463"
        X_COLOR = DARK_BROWN

        NW = Dot().to_corner(UL)
        NE = Dot().to_corner(UR)
        SW = Dot().to_corner(DL)
        SE = Dot().to_corner(DR)
        NL = Line(NW.get_center(), NE.get_center()).set_color(WALL_COLOR)
        SL = Line(SW.get_center(), SE.get_center()).set_color(WALL_COLOR)
        WL = Line(NW.get_center(), SW.get_center()).set_color(WALL_COLOR)
        EL = Line(NE.get_center(), SE.get_center()).set_color(WALL_COLOR)

        slide_no_pos = SE.shift(0.15 * RIGHT + 0.2 * DOWN).get_center()

        # TeX Preamble
        tex_template = TexTemplate()
        tex_template.add_to_preamble(
            r"""
\usepackage{fontawesome5}
\usepackage{siunitx}
\DeclareSIQualifier\wattref{W}
\DeclareSIUnit\dbw{\decibel\wattref}
\usepackage{amsmath,amssymb,amsfonts,mathtools}
\newcommand{\bs}{\boldsymbol}
\newcommand{\scp}[3][]{#1\langle #2, #3 #1\rangle}
\newcommand{\bb}{\mathbb}
\newcommand{\cl}{\mathcal}
"""
        )

        # Slide: Title
        title = VGroup(
            Tex(
                r"\textbf{Min-Path-Tracing}:\\A Diffraction Aware Alternative to \\Image Method in ",
                r"Ray Tracing",
                font_size=60,
            ),
            Tex("Jérome Eertmans"),
        ).arrange(DOWN, buff=1)

        self.play(FadeIn(title), self.write_slide_number(position=slide_no_pos))
        self.next_slide()

        # Slide: room

        self.play(FadeOut(title), self.update_slide_number())

        BS = Tex(r"\faWifi", tex_template=tex_template, color=BS_COLOR).shift(4 * LEFT)
        UE = Tex(r"\faPhone", tex_template=tex_template, color=UE_COLOR).shift(
            3 * RIGHT
        )

        self.play(
            FadeIn(BS), FadeIn(UE), Create(NL), Create(SL), Create(WL), Create(EL)
        )
        self.next_slide()

        A = BS.copy().shift(0.5 * RIGHT)
        B = UE.copy().shift(0.5 * LEFT)

        LOS = Arrow(
            A.get_center(),
            B.get_center(),
            stroke_width=6,
            buff=0.0,
        )

        self.play(Write(LOS))

        self.next_slide()

        # Slide: multiple paths in indoor environment

        paths = VGroup()

        x = LOS.get_center()[0]
        for wall in [NL, SL]:
            y = wall.get_center()[1]
            middle = [x, y, 0]
            path = VGroup(
                Line(A.get_center(), middle, stroke_width=6),
                Arrow(
                    middle,
                    Dot(UE.get_center()).shift(UP * 0.5 * np.sign(y) + 0.25 * LEFT),
                    stroke_width=6,
                    buff=0.0,
                ),
            )
            path.z_index = -1
            paths.add(path)
            for p in path:
                self.play(Write(p))

            self.wait(0.1)
            self.next_slide()

        channel = MathTex(r"P, \tau, \phi...")
        channel.next_to(UE, UP + RIGHT)

        self.play(Write(channel))
        self.next_slide()

        self.play(FadeOut(paths), FadeOut(channel))

        self.next_slide()

        # Slide: challenge

        self.play(FadeOut(LOS))

        how_to = Tex("How to find all paths?")
        ray_tracing = Tex("Multiple methods exist!")

        group = VGroup(how_to, ray_tracing).arrange(DOWN)

        self.play(FadeIn(how_to))
        self.next_slide()

        self.play(FadeIn(ray_tracing, shift=UP))
        self.next_slide()

        # Slide: outline

        _, sec1, sec2, sec3 = outline = VGroup(
            Tex(r"\textbf{Outline:}"),
            Tex("1. Image-based method"),
            Tex("2. Our method"),
            Tex(r"3. Future \& Applications"),
        ).arrange(DOWN)

        for t in outline[2:]:
            t.align_to(outline[1], LEFT)

        self.play(FadeOut(group), self.update_slide_number())
        self.play(FadeIn(outline[0]))
        self.next_slide()

        for t in outline[1:]:
            self.play(FadeIn(t, shift=UP))
            self.next_slide()

        # Sec. 1

        # Slide: simple example

        outline -= sec1
        self.play(FadeOut(outline), self.update_slide_number())

        BS_dot, I1, I2, UE_dot, W1, W2, X1, X2 = locs = VGroup(
            Dot([2, -1, 0], color=BS_COLOR),
            Dot([-1, 2, 0], color=IMAGE_COLOR),
            Dot([11, 2, 0], color=IMAGE_COLOR),
            Dot([2, 4, 0], color=UE_COLOR),
            Line([3.3, 3.3, 0], [0, 0, 0], color=WALL_COLOR),
            Line([5, 4, 0], [5, 0.5, 0], color=WALL_COLOR),
            Dot([20 / 7, 20 / 7, 0], color=X_COLOR, stroke_width=2, fill_color=WHITE),
            Dot([5, 10 / 3, 0], color=X_COLOR, stroke_width=2, fill_color=WHITE),
        )

        locs.move_to(ORIGIN)

        X_OFFSET, Y_OFFSET, _ = np.array([2, -1, 0]) - BS_dot.get_center()

        self.play(
            sec1.animate.to_corner(UL),
            BS.animate.move_to(locs[0]),
            UE.animate.move_to(locs[3]),
            Transform(WL, W1),
            Transform(EL, W2),
            FadeOut(NL, shift=UP),
            FadeOut(SL, shift=DOWN),
        )

        self.next_slide()

        self.play(
            Transform(BS, BS_dot),
            Transform(UE, UE_dot),
        )

        self.next_slide()

        LOS = Arrow(BS, UE)

        self.play(Create(LOS))

        self.next_slide()

        self.play(LOS.animate.set_color(BAD_COLOR))

        self.next_slide()

        self.play(FadeOut(LOS))

        self.next_slide()

        arrow_1 = Arrow(BS, I1)
        arrow_2 = Arrow(I1, I2)
        right_angle_1 = RightAngle(arrow_1, W1, color=RED)
        right_angle_2 = RightAngle(arrow_2, W2, color=RED)

        self.play(Create(arrow_1), Create(right_angle_1))
        self.play(FadeIn(I1))

        self.next_slide()

        self.play(FadeOut(arrow_1), FadeOut(right_angle_1))
        self.play(Create(arrow_2), Create(right_angle_2))
        self.play(FadeIn(I2))
        self.play(FadeOut(arrow_2), FadeOut(right_angle_2))

        self.next_slide()

        line1 = Line(UE, I2)
        line2 = Line(X2, I1)

        self.play(Create(line1))

        self.next_slide()

        self.play(FadeIn(X2))

        self.next_slide()

        self.play(FadeOut(line1))

        self.next_slide()

        self.play(Create(line2))
        self.play(FadeIn(X1))
        self.play(FadeOut(line2))

        self.next_slide()

        path = VGroup(
            Line(BS, X1),
            Line(X1, X2),
            Line(X2, UE),
        )

        for p in path:
            self.play(Create(p))

        self.play(path.animate.set_color(GOOD_COLOR))

        self.next_slide()

        # Slide: summary of image RT

        old_objects = [
            mob for mob in self.mobjects if mob not in [self.slide_text, sec1]
        ]

        self.play(self.update_slide_number(), *[FadeOut(mob) for mob in old_objects])
        path.set_color(BLACK)

        pros = VGroup(
            Tex(r"\textbf{Pros}"),
            Tex(r"- Simple"),
            Tex(r"- Fast - $\cl O (n)$", tex_template=tex_template),
        ).arrange(DOWN)

        for pro in pros[1:]:
            pro.align_to(pros[0], LEFT)

        cons = VGroup(
            Tex(r"\textbf{Cons}"),
            Tex(r"- Limited to planar surfaces"),
            Tex(r"- Specular reflection only"),
        ).arrange(DOWN)

        for con in cons[1:]:
            con.align_to(cons[0], LEFT)

        summary = VGroup(
            Tex("Summary:", font_size=60),
            VGroup(pros, cons).arrange(RIGHT, buff=4),
        ).arrange(DOWN, buff=1)

        self.play(FadeIn(summary[0]))

        self.next_slide()

        self.play(FadeIn(summary[1][0]))

        self.next_slide()

        self.play(FadeIn(summary[1][1]))

        # Sec. 2

        # Slide: MPT

        self.next_slide()

        sec2.to_corner(UL)
        self.play(self.update_slide_number(), FadeOut(summary), Transform(sec1, sec2))

        BS_ = BS.copy().move_to(ORIGIN)
        UE_ = UE.copy().move_to(ORIGIN)
        W1_ = Line([-1.5, 0, 0], [1.5, 0, 0], color=WALL_COLOR)
        VGroup(VGroup(BS_, UE_).arrange(RIGHT, buff=5), W1_).arrange(DOWN, buff=3)

        X1_ = X1.copy().move_to(W1_.get_center())

        # Normal vector
        NV_ = always_redraw(lambda: Line(X1_, X1_.get_center() + 3 * UP).add_tip())
        VIN_ = always_redraw(lambda: Line(BS_, X1_))
        VOUT_ = always_redraw(lambda: Line(X1_, UE_))
        AIN_ = Angle(NV_, VIN_.copy().scale(-1), radius=1.01)
        AIN_ = always_redraw(
            lambda: Angle(NV_, VIN_.copy().scale(-1), radius=1.01, color=BS_COLOR)
        )
        AOUT_ = always_redraw(lambda: Angle(VOUT_, NV_, radius=1.01, color=UE_COLOR))
        ain_ = DecimalNumber(AIN_.get_value(degrees=True), unit=r"^{\circ}")
        ain_.next_to(AIN_, 2 * LEFT)
        aout_ = DecimalNumber(AOUT_.get_value(degrees=True), unit=r"^{\circ}")
        aout_.next_to(AOUT_, 2 * RIGHT)

        angle_in_ = VGroup(AIN_, ain_)
        angle_in_.set_color(BS_COLOR)
        ain_.add_updater(
            lambda m: m.set_value(
                Angle(NV_, VIN_.copy().scale(-1)).get_value(degrees=True)
            )
        )
        always(ain_.next_to, AIN_, 2 * LEFT)

        angle_out_ = VGroup(AOUT_, aout_)
        angle_out_.set_color(UE_COLOR)
        aout_.add_updater(
            lambda m: m.set_value(Angle(VOUT_, NV_).get_value(degrees=True))
        )
        always(aout_.next_to, AOUT_, 2 * RIGHT)

        scene_ = VGroup(BS_, UE_, W1_, X1_, NV_, VIN_, VOUT_)
        angles_ = VGroup(angle_in_, angle_out_)

        self.play(FadeIn(scene_))
        self.next_slide()

        self.add(angles_)
        self.wait(0.1)
        self.next_slide()

        def I_(BS, X1, UE):
            vin = X1.get_center() - BS.get_center()
            vout = UE.get_center() - X1.get_center()
            n = np.array([0, 1, 0])
            vin /= np.linalg.norm(vin)
            vout /= np.linalg.norm(vout)
            error = vout - (vin - 2 * np.dot(vin, n) * n)

            return np.linalg.norm(error) ** 2

        def C_(X1):
            line_y = W1_.get_center()[1]
            y = X1.get_center()[1]

            return (y - line_y) ** 2

        self.play(X1_.animate.move_to(W1_.get_start()))
        self.play(X1_.animate.move_to(W1_.get_end()))
        self.play(X1_.animate.move_to(W1_.get_center()))
        self.wait(0.1)
        self.next_slide()

        cost, i_number, plus, c_number = cost_label = (
            VGroup(
                MathTex(r"\mathcal{C} =", tex_template=tex_template),
                DecimalNumber(I_(BS_, X1_, UE_)),
                MathTex("+"),
                DecimalNumber(C_(X1_)),
            )
            .arrange(RIGHT)
            .next_to(W1_, 2 * DOWN)
            .set_color(BLUE)
        )

        def label_constructor(*args, **kwargs):
            return MathTex(*args, tex_template=tex_template, **kwargs)

        i_brace, c_brace = braces = VGroup(
            BraceLabel(i_number, r"\cl I", label_constructor=label_constructor),
            BraceLabel(c_number, r"\cl F", label_constructor=label_constructor),
        ).set_color(BLUE)

        i_number.add_updater(lambda m: m.set_value(I_(BS_, X1_, UE_)))
        c_number.add_updater(lambda m: m.set_value(C_(X1_)))

        self.play(FadeIn(cost), FadeIn(i_number), FadeIn(i_brace))
        self.next_slide()

        self.play(X1_.animate.move_to(W1_.get_start()))
        self.play(X1_.animate.move_to(W1_.get_end()))
        self.play(X1_.animate.move_to(W1_.get_center()))
        self.wait(0.1)
        self.next_slide()

        self.play(X1_.animate.shift(UP))
        self.wait(0.1)
        self.next_slide()

        self.play(FadeIn(plus, c_number, c_brace))
        self.next_slide()

        self.play(X1_.animate.move_to(W1_.get_center()))
        self.wait(0.1)

        # Slide: any reflection
        self.next_slide()

        arc_ = Arc(
            radius=1.5,
            arc_center=X1_.copy().shift(1.5 * DOWN).get_center(),
            color=WALL_COLOR,
            start_angle=PI,
            angle=-PI,
        )

        interaction = Tex("Reflection")
        interaction.next_to(NV_, UP)

        interaction_eq = MathTex(
            r"\cl I \sim \hat{\bs r} = \hat{\bs \imath} - 2 \scp{\hat{\bs \imath}}{\hat{\bs n}}\hat{\bs n}",
            tex_template=tex_template,
        )
        interaction_eq.to_corner(UR)

        self.play(
            FadeOut(cost_label),
            FadeOut(braces),
            FadeIn(interaction),
            FadeIn(interaction_eq),
        )
        self.next_slide()

        # Diffraction (setup)

        DIFF_W1_A = Polygon(
            W1_.get_start(),
            W1_.get_end(),
            W1_.get_end() + DOWN + 0.25 * LEFT,
            W1_.get_start() + DOWN + 0.25 * LEFT,
            stroke_opacity=0,
            fill_color=WALL_COLOR,
            fill_opacity=0.7,
        )

        DIFF_W1_B = Polygon(
            W1_.get_start(),
            W1_.get_end(),
            W1_.get_end() + 0.8 * DOWN + 0.25 * RIGHT,
            W1_.get_start() + 0.8 * DOWN + 0.25 * RIGHT,
            stroke_opacity=0,
            fill_color=WALL_COLOR,
            fill_opacity=0.5,
        )

        D_NV_ = Line(X1_, X1_.get_center() + RIGHT * 3).add_tip()
        D_AIN_ = Angle(
            D_NV_.copy().scale(-1),
            VIN_.copy().scale(-1),
            radius=1.01,
            other_angle=True,
            color=BS_COLOR,
        )
        D_AOUT_ = Angle(VOUT_, D_NV_, radius=1.01, other_angle=True, color=UE_COLOR)
        D_ain_ = DecimalNumber(
            D_AIN_.get_value(degrees=True), unit=r"^{\circ}", color=BS_COLOR
        )
        D_ain_.next_to(D_AIN_, 2 * LEFT)
        D_aout_ = DecimalNumber(
            D_AOUT_.get_value(degrees=True), unit=r"^{\circ}", color=UE_COLOR
        )
        D_aout_.next_to(D_AOUT_, 2 * RIGHT)

        # Slide: reflection on sphere

        W1_.save_state()
        self.play(Transform(W1_, arc_))
        self.next_slide()

        # Slide: reflection on metasurface

        UE_.save_state()

        phi = MathTex(r"\phi", color=UE_COLOR).move_to(aout_.get_center())

        self.play(
            Restore(W1_),
            UE_.animate.shift(RIGHT),
            FadeTransform(aout_, phi),
            Transform(
                interaction, Tex("Reflection on metasurfaces").move_to(interaction)
            ),
            Transform(
                interaction_eq,
                MathTex(
                    r"\cl I \sim \bs r = f(\hat{\bs n}, \phi)",
                    tex_template=tex_template,
                ).to_corner(UR),
            ),
        )

        self.next_slide()

        # Slide: diffraction

        refl_config = VGroup(NV_, AIN_, AOUT_, ain_, aout_)
        diff_config = VGroup(D_NV_, D_AIN_, D_AOUT_, D_ain_, D_aout_)
        refl_config.save_state()

        self.play(
            *[
                Transform(refl, diff)
                if not isinstance(refl, DecimalNumber)
                else FadeTransform(refl, diff)
                for refl, diff in zip(refl_config, diff_config)
            ],
            Restore(W1_),
            Restore(UE_),
            FadeOut(phi),
            FadeIn(DIFF_W1_B),
            FadeIn(DIFF_W1_A),
            Transform(interaction, Tex("Diffraction").move_to(interaction)),
            Transform(
                interaction_eq,
                MathTex(
                    r"\cl I \sim \frac{\scp{\bs i}{\hat{\bs e}}}{\| \bs i \|} =  \frac{\scp{\bs d}{\hat{\bs e}}}{\|\bs d\|}",
                    tex_template=tex_template,
                ).to_corner(UR),
            ),
        )
        self.remove(*refl_config)
        self.add(*diff_config)
        self.next_slide()

        # Slide: refraction

        UE_.shift(DOWN * 4),

        R_NV_ = Line(X1_, X1_.get_center() + UP * 3).add_tip()
        R_AIN_ = Angle(
            R_NV_,
            VIN_.copy().scale(-1),
            radius=1.01,
            color=BS_COLOR,
        )
        R_AOUT_ = Angle(
            R_NV_.copy().scale(-1), Line(X1_, UE_), radius=1.01, color=UE_COLOR
        )
        R_ain_ = DecimalNumber(
            R_AIN_.get_value(degrees=True), unit=r"^{\circ}", color=BS_COLOR
        )
        R_ain_.next_to(R_AIN_, 2 * LEFT)
        R_aout_ = DecimalNumber(
            R_AOUT_.get_value(degrees=True), unit=r"^{\circ}", color=UE_COLOR
        )
        R_aout_.next_to(R_AOUT_, DR + RIGHT)

        refr_config = VGroup(R_NV_, R_AIN_, R_AOUT_, R_ain_, R_aout_)

        dashed = DashedLine(X1_, X1_.get_center() + 2 * DOWN, color=GRAY)

        self.play(
            Write(dashed),
            FadeOut(DIFF_W1_A),
            FadeOut(DIFF_W1_B),
            *[
                Transform(refl, diff)
                if not isinstance(refl, DecimalNumber)
                else FadeTransform(refl, diff)
                for refl, diff in zip(diff_config, refr_config)
            ],
            Transform(interaction, Tex("Refraction").move_to(interaction)),
            Transform(
                interaction_eq,
                MathTex(
                    r"\cl I \sim v_1 \sin(\theta_2) = v_2 \sin(\theta_1)",
                    tex_template=tex_template,
                ).to_corner(UR),
            ),
        )

        self.remove(*diff_config)
        self.add(*refr_config)

        self.next_slide()

        self.play(
            FadeOut(dashed),
            FadeOut(refr_config),
            FadeOut(scene_),
            FadeOut(interaction),
            FadeOut(interaction_eq),
            self.update_slide_number(),
        )
        self.next_slide()

        minimize_eq = Tex(
            r"\[\underset{\bs{\cl X} \in \bb R^{n_t}}{\text{minimize}}\ \cl C(\bs{\cl X}) := \|\cl I(\bs{\cl X})\|^2 + \|\cl F(\bs{\cl X})\|^2\]",
            tex_template=tex_template,
        )
        nt_eq = Tex("where $n_t$ is the total number of unknowns").shift(DOWN)
        constraint_eq = MathTex(
            r"\cl C(\bs{\cl X})", r"= 0", tex_template=tex_template
        ).shift(2 * DOWN)
        constraint_eq_relaxed = MathTex(
            r"\cl C(\bs{\cl X})", r"\le \epsilon", tex_template=tex_template
        ).shift(2 * DOWN)

        self.play(FadeIn(minimize_eq))

        self.next_slide()

        self.play(FadeIn(nt_eq, shift=DOWN))

        self.next_slide()

        self.play(FadeIn(constraint_eq))

        self.next_slide()

        self.play(Transform(constraint_eq, constraint_eq_relaxed))

        self.next_slide()

        if_we_know = Tex(
            r"If we know a mapping s.t. $(x_k, y_k) \leftrightarrow t_k$"
        ).shift(UP)

        self.play(
            FadeIn(if_we_know),
        )

        self.next_slide()

        self.play(
            Transform(
                minimize_eq,
                Tex(
                    r"\[\underset{\bs{\cl T} \in \bb R^{n_r}}{\text{minimize}}\ \cl C(\bs{\cl X}(\bs{\cl T})) := \|\cl I(\bs{\cl X }(\bs{\cl T}))\|^2\]",
                    tex_template=tex_template,
                ).move_to(minimize_eq),
            ),
            Transform(
                nt_eq,
                Tex("where $n_r$ is the total number of (2d) reflections").move_to(
                    nt_eq
                ),
            ),
            Transform(
                constraint_eq,
                MathTex(
                    r"\cl C(\bs{\cl X(\cl T)})",
                    r"\le \epsilon",
                    tex_template=tex_template,
                ).move_to(constraint_eq),
            ),
        )

        self.next_slide()

        # Slide: gradient descent on simple example using MPT method

        self.play(
            FadeOut(if_we_know),
            FadeOut(minimize_eq),
            FadeOut(nt_eq),
            FadeOut(constraint_eq),
            self.update_slide_number(),
        )

        X1.move_to(W1.get_center())
        X2.move_to(W2.get_center())

        def intersects(l1, l2):
            l1 = LineString([l1.get_start()[:-1], l1.get_end()[:-1]])
            l2 = LineString([l2.get_start()[:-1], l2.get_end()[:-1]])
            return l1.intersects(l2)

        old_objects.remove(I1)
        old_objects.remove(I2)
        path.remove(*path)
        path.add(
            always_redraw(lambda: Line(BS, X1)),
            always_redraw(lambda: Line(X1, X2)),
            always_redraw(
                lambda: Line(
                    X2, UE, color=BAD_COLOR if intersects(Line(X2, UE), W1) else BLACK
                )
            ),
        )
        self.play(*[FadeIn(mob) for mob in old_objects])

        self.next_slide()

        # Slide: animate actual gradient descent

        f, df = generate_c()

        def remap(X1, X2):
            s1 = X1.get_center()[0]
            s2 = X2.get_center()[1]
            return s1 + X_OFFSET, s2 + Y_OFFSET

        cost_label, f_number = f_label = VGroup(
            MathTex(r"\mathcal{C} = ", tex_template=tex_template),
            DecimalNumber(
                f(*remap(X1, X2)),  # f(s1, s2)
                num_decimal_places=2,
                include_sign=False,
            ),
        )

        f_label.set_color(BLUE)
        f_label.arrange(RIGHT)
        f_label.next_to(W2, RIGHT)
        always(f_label.next_to, W2, RIGHT)
        f_always(f_number.set_value, lambda: f(*remap(X1, X2)))

        cost_eq = MathTex(
            r"\|[\cl I_1(t_1, t_2); \cl I_2(t_1,t_2)] \|^2",
            tex_template=tex_template,
            color=BLUE,
            font_size=40,
        ).next_to(cost_label, RIGHT)

        cost_eq_full = MathTex(
            r"&\left(- t_1 + t_2 + \frac{\left(t_2 - 4\right) \sqrt{\left(t_1 - 5\right)^{2} + \left(t_1 - t_2\right)^{2}}}{\sqrt{\left(t_2 - 4\right)^{2} + 9}}\right)^{2} \\+& \left(t_1 + \frac{3 \sqrt{\left(t_1 - 5\right)^{2} + \left(t_1 - t_2\right)^{2}}}{\sqrt{\left(t_2 - 4\right)^{2} + 9}} - 5\right)^{2} \\+& \Bigg|{t_1 + \frac{\left(t_1 - 5\right) \sqrt{\left(t_1 - 2\right)^{2} + \left(t_1 + 1\right)^{2}}}{\sqrt{\left(t_1 - 5\right)^{2} + \left(t_1 - t_2\right)^{2}}} - \frac{\sqrt{2} \left(\sqrt{2} \left(t_1 - 2\right) - \sqrt{2} \left(t_1 + 1\right)\right)}{2} - 2}\Bigg|^{2} \\+& \left|{t_1 + \frac{\left(t_1 - t_2\right) \sqrt{\left(t_1 - 2\right)^{2} + \left(t_1 + 1\right)^{2}}}{\sqrt{\left(t_1 - 5\right)^{2} + \left(t_1 - t_2\right)^{2}}} + \frac{\sqrt{2} \left(\sqrt{2} \left(t_1 - 2\right) - \sqrt{2} \left(t_1 + 1\right)\right)}{2} + 1}\right|^{2}",
            color=BLUE,
            font_size=16,
        ).next_to(cost_label, RIGHT)

        self.play(FadeIn(cost_label), FadeIn(cost_eq))

        self.next_slide()

        self.play(Transform(cost_eq, cost_eq_full))

        self.next_slide()

        self.play(FadeTransform(cost_eq, f_number))
        self.next_slide()

        x0 = remap(X1, X2)

        for ds1, ds2 in gradient_descent(x0, df, return_steps=True):
            self.play(
                X1.animate.shift([ds1, ds1, 0]),
                X2.animate.shift([0, ds2, 0]),
                run_time=1.3,
            )

        self.wait(0.1)

        self.next_slide()

        # Slide: what if METASURFACE?

        what_if_ms_text = Tex("What if we had a metasurface?").to_corner(UR)
        ms_text = Tex(r"$\phi=0$", color=GREEN).next_to(W2).shift(DOWN)
        self.play(FadeIn(what_if_ms_text))
        self.next_slide()

        self.play(UE.animate.shift(np.array([0, -0.5, 0])))
        self.play(EL.animate.set_color(GREEN))  # EL is W2
        self.play(FadeIn(ms_text))

        f, df = generate_c_ris()

        _, f_number = f_label = VGroup(
            MathTex(r"\mathcal{C} = ", tex_template=tex_template),
            DecimalNumber(
                f(*remap(X1, X2)),  # f(s1, s2)
                num_decimal_places=2,
                include_sign=False,
            ),
        )

        f_label.set_color(BLUE)
        f_label.arrange(RIGHT)
        f_label.next_to(W2, RIGHT)
        always(f_label.next_to, W2, RIGHT)
        f_always(f_number.set_value, lambda: f(*remap(X1, X2)))

        self.play(FadeIn(f_label, shift=UP))
        self.next_slide()

        x0 = remap(X1, X2)

        for ds1, ds2 in gradient_descent(x0, df, return_steps=True):
            self.play(
                X1.animate.shift([ds1, ds1, 0]),
                X2.animate.shift([0, ds2, 0]),
                run_time=0.6,
            )

        self.next_slide()

        # Sec. 3

        sec3.to_corner(UL)
        self.play(*[FadeOut(mob) for mob in self.mobjects])
        self.play(self.update_slide_number(), Transform(sec1, sec3))
        self.next_slide()

        geom = SVGMobject("geometry.svg").scale(5)
        tabl = Tex(
            r"""
\begin{tabular}{l|r|r|r|r|r|r|r|r|r|r|r}
        Number of interactions & \multicolumn{1}{r}{1}  & \multicolumn{3}{r}{2} & \multicolumn{7}{r}{3} \\
        \hline\\
        Interactions list & D & RD & DR & DD & RRD & RDR & RDD & DRR & DRD & DDR & DDD \\
        $E/E_\text{LOS}$ (\si{\decibel}) & \textbf{-32} & -236 & -242 & \textbf{-44} & -231 & -246 & \textbf{-69} & -212 & \textbf{-72} & -81 & \textbf{-60} \\
\end{tabular}
""",
            tex_template=tex_template,
        )
        results = VGroup(geom, tabl).arrange(DOWN, buff=2).scale(0.4)

        self.play(FadeIn(geom))
        self.next_slide()

        self.play(FadeIn(tabl))
        self.next_slide()

        # Slide: summary of MPT method
        self.play(FadeOut(results), self.update_slide_number())

        pros = (
            VGroup(
                Tex(r"\textbf{Pros}"),
                Tex(r"- Any geometry (but requires more info.)"),
                Tex(r"- Any \# of reflect., diff., and refract."),
                Tex(r"- Allows for multiple solutions"),
                Tex(r"- Optimizer can be chosen"),
            )
            .scale(0.5)
            .arrange(DOWN)
        )

        for pro in pros[1:]:
            pro.align_to(pros[0], LEFT)

        cons = (
            VGroup(
                Tex(r"\textbf{Cons}"),
                Tex(r"- In general, problem is not convex"),
                Tex(r"- Slower - $\cl O (k \cdot n)$", tex_template=tex_template),
            )
            .scale(0.5)
            .arrange(DOWN)
        )

        for con in cons[1:]:
            con.align_to(cons[0], LEFT)

        summary = VGroup(
            Tex("Summary:", font_size=60),
            VGroup(pros, cons).arrange(RIGHT, buff=2),
        ).arrange(DOWN, buff=1)

        cons.align_to(pros, UP)

        self.play(FadeIn(summary[0]))

        self.next_slide()

        self.play(FadeIn(summary[1][0]))

        self.next_slide()

        self.play(FadeIn(summary[1][1]))

        future = VGroup(
            Tex(r"\textbf{Future work:}"),
            Tex(r"- Compare with Ray Launching"),
            Tex(r"- Discuss different solvers / minimizers"),
        ).arrange(DOWN)

        for t in future[2:]:
            t.align_to(future[1], LEFT)

        self.next_slide()
        self.play(FadeOut(summary), self.update_slide_number())
        self.play(FadeIn(future))
        self.next_slide()

        # Slide: fade out everything and thanks

        self.play(*[FadeOut(mob) for mob in self.mobjects])

        thanks = Tex("Thanks for listening!").scale(2)

        self.play(FadeIn(thanks))

        self.wait()
        self.next_slide()
This post is licensed under CC BY 4.0 by the author.

Embedding Plotly in a Jekyll Post

Creating a GitHub action that runs your Rust application