Example #1
0
    def normal_lines(self, p, prec=None):
        """Normal lines between `p` and the ellipse.

        Parameters
        ==========

        p : Point

        Returns
        =======

        normal_lines : list with 1, 2 or 4 Lines

        Examples
        ========

        >>> from sympy import Line, Point, Ellipse
        >>> e = Ellipse((0, 0), 2, 3)
        >>> c = e.center
        >>> e.normal_lines(c + Point(1, 0))
        [Line(Point(0, 0), Point(1, 0))]
        >>> e.normal_lines(c)
        [Line(Point(0, 0), Point(0, 1)), Line(Point(0, 0), Point(1, 0))]

        Off-axis points require the solution of a quartic equation. This
        often leads to very large expressions that may be of little practical
        use. An approximate solution of `prec` digits can be obtained by
        passing in the desired value:

        >>> e.normal_lines((3, 3), prec=2)
        [Line(Point(-38/47, -85/31), Point(9/47, -21/17)),
        Line(Point(19/13, -43/21), Point(32/13, -8/3))]

        Whereas the above solution has an operation count of 12, the exact
        solution has an operation count of 2020.
        """
        p = Point(p)

        # XXX change True to something like self.angle == 0 if the arbitrarily
        # rotated ellipse is introduced.
        # https://github.com/sympy/sympy/issues/2815)
        if True:
            rv = []
            if p.x == self.center.x:
                rv.append(Line(self.center, slope=oo))
            if p.y == self.center.y:
                rv.append(Line(self.center, slope=0))
            if rv:
                # at these special orientations of p either 1 or 2 normals
                # exist and we are done
                return rv

        # find the 4 normal points and construct lines through them with
        # the corresponding slope
        x, y = Dummy('x', real=True), Dummy('y', real=True)
        eq = self.equation(x, y)
        dydx = idiff(eq, y, x)
        norm = -1 / dydx
        slope = Line(p, (x, y)).slope
        seq = slope - norm
        points = []
        if prec is not None:
            yis = solve(seq, y)[0]
            xeq = eq.subs(y, yis).as_numer_denom()[0].expand()
            try:
                iv = list(zip(*Poly(xeq).intervals()))[0]
                # bisection is safest here since other methods may miss root
                xsol = [
                    S(nroot(lambdify(x, xeq), i, solver="anderson"))
                    for i in iv
                ]
                points = [
                    Point(i,
                          solve(eq.subs(x, i), y)[0]).n(prec) for i in xsol
                ]
            except PolynomialError:
                pass
        if not points:
            points = solve((seq, eq), (x, y))
            # complicated expressions may not be decidably real so evaluate to
            # check whether they are real or not
            points = [
                Point(i).n(prec) if prec is not None else Point(i)
                for i in points if all(j.n(2).is_real for j in i)
            ]
        slopes = [norm.subs(zip((x, y), pt.args)) for pt in points]
        if prec is not None:
            slopes = [
                i.n(prec) if i not in (-oo, oo, zoo) else i for i in slopes
            ]
        return [Line(pt, slope=s) for pt, s in zip(points, slopes)]
    def normal_lines(self, p, prec=None):
        """Normal lines between `p` and the ellipse.

        Parameters
        ==========

        p : Point

        Returns
        =======

        normal_lines : list with 1, 2 or 4 Lines

        Examples
        ========

        >>> from sympy import Line, Point, Ellipse
        >>> e = Ellipse((0, 0), 2, 3)
        >>> c = e.center
        >>> e.normal_lines(c + Point(1, 0))
        [Line(Point(0, 0), Point(1, 0))]
        >>> e.normal_lines(c)
        [Line(Point(0, 0), Point(0, 1)), Line(Point(0, 0), Point(1, 0))]

        Off-axis points require the solution of a quartic equation. This
        often leads to very large expressions that may be of little practical
        use. An approximate solution of `prec` digits can be obtained by
        passing in the desired value:

        >>> e.normal_lines((3, 3), prec=2)
        [Line(Point(-38/47, -85/31), Point(9/47, -21/17)),
        Line(Point(19/13, -43/21), Point(32/13, -8/3))]

        Whereas the above solution has an operation count of 12, the exact
        solution has an operation count of 2020.
        """
        p = Point(p)

        # XXX change True to something like self.angle == 0 if the arbitrarily
        # rotated ellipse is introduced.
        # https://github.com/sympy/sympy/issues/2815)
        if True:
            rv = []
            if p.x == self.center.x:
                rv.append(Line(self.center, slope=oo))
            if p.y == self.center.y:
                rv.append(Line(self.center, slope=0))
            if rv:
                # at these special orientations of p either 1 or 2 normals
                # exist and we are done
                return rv

        # find the 4 normal points and construct lines through them with
        # the corresponding slope
        x, y = Dummy('x', real=True), Dummy('y', real=True)
        eq = self.equation(x, y)
        dydx = idiff(eq, y, x)
        norm = -1/dydx
        slope = Line(p, (x, y)).slope
        seq = slope - norm
        points = []
        if prec is not None:
            yis = solve(seq, y)[0]
            xeq = eq.subs(y, yis).as_numer_denom()[0].expand()
            try:
                iv = list(zip(*Poly(xeq).intervals()))[0]
                # bisection is safest here since other methods may miss root
                xsol = [S(nroot(lambdify(x, xeq), i, solver="anderson"))
                    for i in iv]
                points = [Point(i, solve(eq.subs(x, i), y)[0]).n(prec)
                    for i in xsol]
            except PolynomialError:
                pass
        if not points:
            points = [Point(*s) for s in solve((seq, eq), (x, y))]
            # complicated expressions may not be decidably real so evaluate to
            # check whether they are real or not
            points = [i.n(prec) if prec is not None else i
                for i in points if all(j.n(2).is_real for j in i.args)]
        slopes = [norm.subs(zip((x, y), pt.args)) for pt in points]
        if prec is not None:
            slopes = [i.n(prec) if i not in (-oo, oo, zoo) else i
                for i in slopes]
        return [Line(pt, slope=s) for pt,s in zip(points, slopes)]