Skip to content

Support for Removable Discontinuities in 3D Surface Plotting #4290

@nicominguez

Description

@nicominguez

When graphing with a function of one variable with a removable discontinuity (function is not defined at a point but otherwise continuous), we can pass in a discontinuities parameter into Axes.plot() such that we "skip" over that point and treat the discontinuous function as a piecewise continuous function such that it can be graphed successfully.

For example, for a function $f$ given by

$$ f(x) = \frac{x^2 - 9}{x-3} $$

with an evitable discontinuity at $x = 3$, the following code works perfectly:

from manim import *

class OneVariable(Scene):
    def construct(self):
        ax = Axes(
            x_range=[0, 10], 
            y_range=[0, 10], 
            x_length=12,
            y_length=6
        ).scale(0.8)
        x_label, y_label = ax.get_axis_labels("x", "f(x)")
        
        def func(x):
            return (x**2 - 9) / (x - 3) - 2
        
        self.add(ax.plot(func, discontinuities=[3]))

This creates the following image:
Image

Description of Proposed Feature

Essentially the same feature for a function of two variables—whether using Surface, or plot_surface directly—having a parameter for the coordinates at which that function has a removable discontinuity, such that the graph "skips" graphing that point for $f$.

This would expand the range of functions that can be graphed in 3D.

Why the Workaround Fails

Say you want to create a 3D surface representing the function:

$$ f(x, y) = \frac{x^2y}{x^2 + y^2} $$

At the point $(0, 0)$, this function has a denominator of $0$, and thus is not defined. It's impossible to graph it. Hence we attempt to "skip" graphing that point by returning None for the undefined point:

from manim import *

class SurfaceTest(ThreeDScene):
    def construct(self):
        ax = ThreeDAxes()
        self.add(ax)

        epsilon = 0.001

        def func(x, y):  # removable discontinuity at (0, 0)
            if x == 0 and y == 0:
                return None
            else:
                return ((x**2)*y) / (x**2 + y**2)

        surface = Surface(
            lambda u, v: ax.c2p(u, v, func(u, v)),
            u_range=[-5, 5],
            v_range=[-5, 5],
            resolution=(16, 16),
            fill_color=ORANGE,
            fill_opacity=0.8,
            checkerboard_colors=None
        )
        self.play(FadeIn(surface))
        self.wait()
        self.play(FadeOut(surface))

But this does not work, for the same reason that it wouldn't work in a function of one variable. The lambda function in Surface does not know how to handle None, so it's actually impossible to skip graphing a point. The analogous solution to the one-variable scenario would be to split up the surface into different pieces, which would themselves be surfaces or curves. The issue with this is that if we have multiple discontinuities, this becomes a very cumbersome process.

Additional Comments

I'm basing my knowledge of handling discontinuities on the following source:
https://github.com/ManimCommunity/manim/blob/main/manim/mobject/graphing/functions.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    new featureEnhancement specifically adding a new feature (feature request should be used for issues instead)

    Type

    No type

    Projects

    Status

    🆕 New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions