def evolute(self, x='x', y='y'): """The equation of evolute of the ellipse. Parameters ========== x : str, optional Label for the x-axis. Default value is 'x'. y : str, optional Label for the y-axis. Default value is 'y'. Returns ======= equation : sympy expression Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(1, 0), 3, 2) >>> e1.evolute() 2**(2/3)*y**(2/3) + (3*x - 3)**(2/3) - 5**(2/3) """ if len(self.args) != 3: raise NotImplementedError( 'Evolute of arbitrary Ellipse is not supported.') x = _symbol(x, real=True) y = _symbol(y, real=True) t1 = (self.hradius * (x - self.center.x))**Rational(2, 3) t2 = (self.vradius * (y - self.center.y))**Rational(2, 3) return t1 + t2 - (self.hradius**2 - self.vradius**2)**Rational(2, 3)
def equation(self, x='x', y='y'): """The equation of the ellipse. Parameters ========== x : str, optional Label for the x-axis. Default value is 'x'. y : str, optional Label for the y-axis. Default value is 'y'. Returns ======= equation : sympy expression See Also ======== arbitrary_point : Returns parameterized point on ellipse Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(1, 0), 3, 2) >>> e1.equation() y**2/4 + (x/3 - 1/3)**2 - 1 """ x = _symbol(x, real=True) y = _symbol(y, real=True) t1 = ((x - self.center.x) / self.hradius)**2 t2 = ((y - self.center.y) / self.vradius)**2 return t1 + t2 - 1
def equation(self, x='x', y='y'): """The equation of the circle. Parameters ========== x : str or Symbol, optional Default value is 'x'. y : str or Symbol, optional Default value is 'y'. Returns ======= equation : SymPy expression Examples ======== >>> from sympy import Point, Circle >>> c1 = Circle(Point(0, 0), 5) >>> c1.equation() x**2 + y**2 - 25 """ x = _symbol(x, real=True) y = _symbol(y, real=True) t1 = (x - self.center.x)**2 t2 = (y - self.center.y)**2 return t1 + t2 - self.major**2
def evolute(self, x='x', y='y'): """The equation of evolute of the ellipse. Parameters ========== x : str, optional Label for the x-axis. Default value is 'x'. y : str, optional Label for the y-axis. Default value is 'y'. Returns ======= equation : sympy expression Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(1, 0), 3, 2) >>> e1.evolute() 2**(2/3)*y**(2/3) + (3*x - 3)**(2/3) - 5**(2/3) """ if len(self.args) != 3: raise NotImplementedError('Evolute of arbitrary Ellipse is not supported.') x = _symbol(x, real=True) y = _symbol(y, real=True) t1 = (self.hradius*(x - self.center.x))**Rational(2, 3) t2 = (self.vradius*(y - self.center.y))**Rational(2, 3) return t1 + t2 - (self.hradius**2 - self.vradius**2)**Rational(2, 3)
def arbitrary_point(self, u=None, v=None): """ Returns an arbitrary point on the Plane. If given two parameters, the point ranges over the entire plane. If given 1 or no parameters, returns a point with one parameter which, when varying from 0 to 2*pi, moves the point in a circle of radius 1 about p1 of the Plane. Examples ======== >>> from sympy.geometry import Plane, Ray >>> from sympy.abc import u, v, t, r >>> p = Plane((1, 1, 1), normal_vector=(1, 0, 0)) >>> p.arbitrary_point(u, v) Point3D(1, u + 1, v + 1) >>> p.arbitrary_point(t) Point3D(1, cos(t) + 1, sin(t) + 1) While arbitrary values of u and v can move the point anywhere in the plane, the single-parameter point can be used to construct a ray whose arbitrary point can be located at angle t and radius r from p.p1: >>> Ray(p.p1, _).arbitrary_point(r) Point3D(1, r*cos(t) + 1, r*sin(t) + 1) Returns ======= Point3D """ circle = v is None if circle: u = _symbol(u or 't', real=True) else: u = _symbol(u or 'u', real=True) v = _symbol(v or 'v', real=True) x, y, z = self.normal_vector a, b, c = self.p1.args # x1, y1, z1 is a nonzero vector parallel to the plane if x.is_zero and y.is_zero: x1, y1, z1 = S.One, S.Zero, S.Zero else: x1, y1, z1 = -y, x, S.Zero # x2, y2, z2 is also parallel to the plane, and orthogonal to x1, y1, z1 x2, y2, z2 = tuple(Matrix((x, y, z)).cross(Matrix((x1, y1, z1)))) if circle: x1, y1, z1 = (w / sqrt(x1**2 + y1**2 + z1**2) for w in (x1, y1, z1)) x2, y2, z2 = (w / sqrt(x2**2 + y2**2 + z2**2) for w in (x2, y2, z2)) p = Point3D(a + x1*cos(u) + x2*sin(u), \ b + y1*cos(u) + y2*sin(u), \ c + z1*cos(u) + z2*sin(u)) else: p = Point3D(a + x1 * u + x2 * v, b + y1 * u + y2 * v, c + z1 * u + z2 * v) return p
def arbitrary_point(self, u=None, v=None): """ Returns an arbitrary point on the Plane. If given two parameters, the point ranges over the entire plane. If given 1 or no parameters, returns a point with one parameter which, when varying from 0 to 2*pi, moves the point in a circle of radius 1 about p1 of the Plane. Examples ======== >>> from sympy.geometry import Plane, Ray >>> from sympy.abc import u, v, t, r >>> p = Plane((1, 1, 1), normal_vector=(1, 0, 0)) >>> p.arbitrary_point(u, v) Point3D(1, u + 1, v + 1) >>> p.arbitrary_point(t) Point3D(1, cos(t) + 1, sin(t) + 1) While arbitrary values of u and v can move the point anywhere in the plane, the single-parameter point can be used to construct a ray whose arbitrary point can be located at angle t and radius r from p.p1: >>> Ray(p.p1, _).arbitrary_point(r) Point3D(1, r*cos(t) + 1, r*sin(t) + 1) Returns ======= Point3D """ circle = v is None if circle: u = _symbol(u or 't', real=True) else: u = _symbol(u or 'u', real=True) v = _symbol(v or 'v', real=True) x, y, z = self.normal_vector a, b, c = self.p1.args # x1, y1, z1 is a nonzero vector parallel to the plane if x.is_zero and y.is_zero: x1, y1, z1 = S.One, S.Zero, S.Zero else: x1, y1, z1 = -y, x, S.Zero # x2, y2, z2 is also parallel to the plane, and orthogonal to x1, y1, z1 x2, y2, z2 = tuple(Matrix((x, y, z)).cross(Matrix((x1, y1, z1)))) if circle: x1, y1, z1 = (w/sqrt(x1**2 + y1**2 + z1**2) for w in (x1, y1, z1)) x2, y2, z2 = (w/sqrt(x2**2 + y2**2 + z2**2) for w in (x2, y2, z2)) p = Point3D(a + x1*cos(u) + x2*sin(u), \ b + y1*cos(u) + y2*sin(u), \ c + z1*cos(u) + z2*sin(u)) else: p = Point3D(a + x1*u + x2*v, b + y1*u + y2*v, c + z1*u + z2*v) return p
def plot_interval(self, parameter='t'): """The plot interval for the default geometric plot of the Ellipse. Parameters ========== parameter : str, optional Default value is 't'. Returns ======= plot_interval : list [parameter, lower_bound, upper_bound] Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.plot_interval() [t, -pi, pi] """ t = _symbol(parameter, real=True) return [t, -S.Pi, S.Pi]
def plot_interval(self, parameter='t'): """The plot interval for the default geometric plot of the curve. Parameters ========== parameter : str or Symbol, optional Default value is 't'; otherwise the provided symbol is used. Returns ======= List : the plot interval as below: [parameter, lower_bound, upper_bound] Examples ======== >>> from sympy import Curve, sin >>> from sympy.abc import x, s >>> Curve((x, sin(x)), (x, 1, 2)).plot_interval() [t, 1, 2] >>> Curve((x, sin(x)), (x, 1, 2)).plot_interval(s) [s, 1, 2] See Also ======== limits : Returns limits of the parameter interval """ t = _symbol(parameter, self.parameter, real=True) return [t] + list(self.limits[1:])
def plot_interval(self, parameter='t'): """The plot interval for the default geometric plot of the curve. Parameters ========== parameter : str or Symbol, optional Default value is 't'; otherwise the provided symbol is used. Returns ======= plot_interval : list (plot interval) [parameter, lower_bound, upper_bound] See Also ======== limits : Returns limits of the parameter interval Examples ======== >>> from sympy import Curve, sin >>> from sympy.abc import x, t, s >>> Curve((x, sin(x)), (x, 1, 2)).plot_interval() [t, 1, 2] >>> Curve((x, sin(x)), (x, 1, 2)).plot_interval(s) [s, 1, 2] """ t = _symbol(parameter, self.parameter, real=True) return [t] + list(self.limits[1:])
def random_point(self, seed=None): """A random point on the ellipse. Returns ======= point : Point Examples ======== >>> from sympy import Point, Ellipse, Segment >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.random_point() # gives some random point Point2D(...) >>> p1 = e1.random_point(seed=0); p1.n(2) Point2D(2.1, 1.4) Notes ===== When creating a random point, one may simply replace the parameter with a random number. When doing so, however, the random number should be made a Rational or else the point may not test as being in the ellipse: >>> from sympy.abc import t >>> from sympy import Rational >>> arb = e1.arbitrary_point(t); arb Point2D(3*cos(t), 2*sin(t)) >>> arb.subs(t, .1) in e1 False >>> arb.subs(t, Rational(.1)) in e1 True >>> arb.subs(t, Rational('.1')) in e1 True See Also ======== sympy.geometry.point.Point arbitrary_point : Returns parameterized point on ellipse """ from sympy import sin, cos, Rational t = _symbol('t', real=True) x, y = self.arbitrary_point(t).args # get a random value in [-1, 1) corresponding to cos(t) # and confirm that it will test as being in the ellipse if seed is not None: rng = random.Random(seed) else: rng = random # simplify this now or else the Float will turn s into a Float r = Rational(rng.random()) c = 2 * r - 1 s = sqrt(1 - c**2) return Point(x.subs(cos(t), c), y.subs(sin(t), s))
def random_point(self, seed=None): """A random point on the ellipse. Returns ======= point : Point Examples ======== >>> from sympy import Point, Ellipse, Segment >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.random_point() # gives some random point Point2D(...) >>> p1 = e1.random_point(seed=0); p1.n(2) Point2D(2.1, 1.4) Notes ===== When creating a random point, one may simply replace the parameter with a random number. When doing so, however, the random number should be made a Rational or else the point may not test as being in the ellipse: >>> from sympy.abc import t >>> from sympy import Rational >>> arb = e1.arbitrary_point(t); arb Point2D(3*cos(t), 2*sin(t)) >>> arb.subs(t, .1) in e1 False >>> arb.subs(t, Rational(.1)) in e1 True >>> arb.subs(t, Rational('.1')) in e1 True See Also ======== sympy.geometry.point.Point arbitrary_point : Returns parameterized point on ellipse """ from sympy import sin, cos, Rational t = _symbol('t', real=True) x, y = self.arbitrary_point(t).args # get a random value in [-1, 1) corresponding to cos(t) # and confirm that it will test as being in the ellipse if seed is not None: rng = random.Random(seed) else: rng = random # simplify this now or else the Float will turn s into a Float r = Rational(rng.random()) c = 2*r - 1 s = sqrt(1 - c**2) return Point(x.subs(cos(t), c), y.subs(sin(t), s))
def equation(self, x='x', y='y'): """The equation of the parabola. Parameters ========== x : str, optional Label for the x-axis. Default value is 'x'. y : str, optional Label for the y-axis. Default value is 'y'. Returns ======= equation : SymPy expression Examples ======== >>> from sympy import Parabola, Point, Line >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) >>> p1.equation() -x**2 - 16*y + 64 >>> p1.equation('f') -f**2 - 16*y + 64 >>> p1.equation(y='z') -x**2 - 16*z + 64 """ x = _symbol(x, real=True) y = _symbol(y, real=True) m = self.directrix.slope if m is S.Infinity: t1 = 4 * (self.p_parameter) * (x - self.vertex.x) t2 = (y - self.vertex.y)**2 elif m == 0: t1 = 4 * (self.p_parameter) * (y - self.vertex.y) t2 = (x - self.vertex.x)**2 else: a, b = self.focus c, d = self.directrix.coefficients[:2] t1 = (x - a)**2 + (y - b)**2 t2 = self.directrix.equation(x, y)**2 / (c**2 + d**2) return t1 - t2
def arbitrary_point(self, parameter='t'): """ A parameterized point on the curve. Parameters ========== parameter : str or Symbol, optional Default value is 't'; the Curve's parameter is selected with None or self.parameter otherwise the provided symbol is used. Returns ======= arbitrary_point : Point Raises ====== ValueError When `parameter` already appears in the functions. See Also ======== sympy.geometry.point.Point Examples ======== >>> from sympy import Symbol >>> from sympy.abc import s >>> from sympy.geometry import Curve >>> C = Curve([2*s, s**2], (s, 0, 2)) >>> C.arbitrary_point() Point2D(2*t, t**2) >>> C.arbitrary_point(C.parameter) Point2D(2*s, s**2) >>> C.arbitrary_point(None) Point2D(2*s, s**2) >>> C.arbitrary_point(Symbol('a')) Point2D(2*a, a**2) """ if parameter is None: return Point(*self.functions) tnew = _symbol(parameter, self.parameter, real=True) t = self.parameter if (tnew.name != t.name and tnew.name in (f.name for f in self.free_symbols)): raise ValueError('Symbol %s already appears in object ' 'and cannot be used as a parameter.' % tnew.name) return Point(*[w.subs(t, tnew) for w in self.functions])
def _(obj, parameters=('t', 's')): definition = obj.rational_parametrization(parameters) bounds = [] for i in range(len(obj.variables) - 1): # Each parameter is replaced by its tangent to simplify intergation parameter = _symbol(parameters[i], real=True) definition = [trigsimp(elem.subs(parameter, tan(parameter/2))) for elem in definition] bounds.append((parameter, 0, 2*pi),) definition = Tuple(*definition) return [ParametricRegion(definition, *bounds)]
def equation(self, x='x', y='y'): """The equation of the parabola. Parameters ========== x : str, optional Label for the x-axis. Default value is 'x'. y : str, optional Label for the y-axis. Default value is 'y'. Returns ======= equation : sympy expression Examples ======== >>> from sympy import Parabola, Point, Line >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) >>> p1.equation() -x**2 - 16*y + 64 >>> p1.equation('f') -f**2 - 16*y + 64 >>> p1.equation(y='z') -x**2 - 16*z + 64 """ x = _symbol(x, real=True) y = _symbol(y, real=True) if (self.axis_of_symmetry.slope == 0): t1 = 4 * (self.p_parameter) * (x - self.vertex.x) t2 = (y - self.vertex.y)**2 else: t1 = 4 * (self.p_parameter) * (y - self.vertex.y) t2 = (x - self.vertex.x)**2 return t1 - t2
def _(obj, parameter='t'): t = _symbol(parameter, real=True) definition = obj.arbitrary_point(t).args for i in range(0, 3): lower_bound = solve(definition[i] - obj.points[0].args[i], t) upper_bound = solve(definition[i] - obj.points[1].args[i], t) if len(lower_bound) == 1 and len(upper_bound) == 1: bounds = t, lower_bound[0], upper_bound[0] break definition_tuple = obj.arbitrary_point(parameter).args return [ParametricRegion(definition_tuple, bounds)]
def test__uniquely_named_symbol_and__symbol(): F = _uniquely_named_symbol x = Symbol("x") assert F(x) == x assert F("x") == x assert str(F("x", x)) == "_x" assert str(F("x", (x + 1, 1 / x))) == "_x" _x = Symbol("x", real=True) assert F(("x", _x)) == _x assert F((x, _x)) == _x assert F("x", real=True).is_real y = Symbol("y") assert F(("x", y), real=True).is_real r = Symbol("x", real=True) assert F(("x", r)).is_real assert F(("x", r), real=False).is_real assert F("x1", Symbol("x1"), compare=lambda i: str(i).rstrip("1")).name == "x1" assert F("x1", Symbol("x1"), modify=lambda i: i + "_").name == "x1_" assert _symbol(x, _x) == x
def test__uniquely_named_symbol_and__symbol(): F = _uniquely_named_symbol x = Symbol('x') assert F(x) == x assert F('x') == x assert str(F('x', x)) == '_x' assert str(F('x', (x + 1, 1 / x))) == '_x' _x = Symbol('x', real=True) assert F(('x', _x)) == _x assert F((x, _x)) == _x assert F('x', real=True).is_real y = Symbol('y') assert F(('x', y), real=True).is_real r = Symbol('x', real=True) assert F(('x', r)).is_real assert F(('x', r), real=False).is_real assert F('x1', Symbol('x1'), compare=lambda i: str(i).rstrip('1')).name == 'x1' assert F('x1', Symbol('x1'), modify=lambda i: i + '_').name == 'x1_' assert _symbol(x, _x) == x
def arbitrary_point(self, parameter='t'): """A parameterized point on the ellipse. Parameters ========== parameter : str, optional Default value is 't'. Returns ======= arbitrary_point : Point Raises ====== ValueError When `parameter` already appears in the functions. See Also ======== sympy.geometry.point.Point Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.arbitrary_point() Point2D(3*cos(t), 2*sin(t)) """ t = _symbol(parameter, real=True) if t.name in (f.name for f in self.free_symbols): raise ValueError( filldedent('Symbol %s already appears in object ' 'and cannot be used as a parameter.' % t.name)) return Point(self.center.x + self.hradius * cos(t), self.center.y + self.vradius * sin(t))
def test__uniquely_named_symbol_and__symbol(): F = _uniquely_named_symbol x = Symbol('x') assert F(x) == x assert F('x') == x assert str(F('x', x)) == '_x' assert str(F('x', (x + 1, 1/x))) == '_x' _x = Symbol('x', real=True) assert F(('x', _x)) == _x assert F((x, _x)) == _x assert F('x', real=True).is_real y = Symbol('y') assert F(('x', y), real=True).is_real r = Symbol('x', real=True) assert F(('x', r)).is_real assert F(('x', r), real=False).is_real assert F('x1', Symbol('x1'), compare=lambda i: str(i).rstrip('1')).name == 'x1' assert F('x1', Symbol('x1'), modify=lambda i: i + '_').name == 'x1_' assert _symbol(x, _x) == x
def arbitrary_point(self, parameter='t'): """A parameterized point on the ellipse. Parameters ========== parameter : str, optional Default value is 't'. Returns ======= arbitrary_point : Point Raises ====== ValueError When `parameter` already appears in the functions. See Also ======== sympy.geometry.point.Point Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.arbitrary_point() Point2D(3*cos(t), 2*sin(t)) """ t = _symbol(parameter, real=True) if t.name in (f.name for f in self.free_symbols): raise ValueError(filldedent('Symbol %s already appears in object ' 'and cannot be used as a parameter.' % t.name)) return Point(self.center.x + self.hradius*cos(t), self.center.y + self.vradius*sin(t))
def _(obj, parameter='t'): definition = obj.arbitrary_point(parameter).args t = _symbol(parameter, real=True) bounds = (t, 0, 2 * pi) return [ParametricRegion(definition, bounds)]
def equation(self, x='x', y='y', _slope=None): """ Returns the equation of an ellipse aligned with the x and y axes; when slope is given, the equation returned corresponds to an ellipse with a major axis having that slope. Parameters ========== x : str, optional Label for the x-axis. Default value is 'x'. y : str, optional Label for the y-axis. Default value is 'y'. _slope : Expr, optional The slope of the major axis. Ignored when 'None'. Returns ======= equation : sympy expression See Also ======== arbitrary_point : Returns parameterized point on ellipse Examples ======== >>> from sympy import Point, Ellipse, pi >>> from sympy.abc import x, y >>> e1 = Ellipse(Point(1, 0), 3, 2) >>> eq1 = e1.equation(x, y); eq1 y**2/4 + (x/3 - 1/3)**2 - 1 >>> eq2 = e1.equation(x, y, _slope=1); eq2 (-x + y + 1)**2/8 + (x + y - 1)**2/18 - 1 A point on e1 satisfies eq1. Let's use one on the x-axis: >>> p1 = e1.center + Point(e1.major, 0) >>> assert eq1.subs(x, p1.x).subs(y, p1.y) == 0 When rotated the same as the rotated ellipse, about the center point of the ellipse, it will satisfy the rotated ellipse's equation, too: >>> r1 = p1.rotate(pi/4, e1.center) >>> assert eq2.subs(x, r1.x).subs(y, r1.y) == 0 References ========== .. [1] https://math.stackexchange.com/questions/108270/what-is-the-equation-of-an-ellipse-that-is-not-aligned-with-the-axis .. [2] https://en.wikipedia.org/wiki/Ellipse#Equation_of_a_shifted_ellipse """ x = _symbol(x, real=True) y = _symbol(y, real=True) dx = x - self.center.x dy = y - self.center.y if _slope is not None: L = (dy - _slope*dx)**2 l = (_slope*dy + dx)**2 h = 1 + _slope**2 b = h*self.major**2 a = h*self.minor**2 return l/b + L/a - 1 else: t1 = (dx/self.hradius)**2 t2 = (dy/self.vradius)**2 return t1 + t2 - 1
def random_point(self, seed=None): """A random point on the ellipse. Returns ======= point : Point See Also ======== sympy.geometry.point.Point arbitrary_point : Returns parameterized point on ellipse Notes ----- A random point may not appear to be on the ellipse, ie, `p in e` may return False. This is because the coordinates of the point will be floating point values, and when these values are substituted into the equation for the ellipse the result may not be zero because of floating point rounding error. Examples ======== >>> from sympy import Point, Ellipse, Segment >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.random_point() # gives some random point Point2D(...) >>> p1 = e1.random_point(seed=0); p1.n(2) Point2D(2.1, 1.4) The random_point method assures that the point will test as being in the ellipse: >>> p1 in e1 True Notes ===== An arbitrary_point with a random value of t substituted into it may not test as being on the ellipse because the expression tested that a point is on the ellipse doesn't simplify to zero and doesn't evaluate exactly to zero: >>> from sympy.abc import t >>> e1.arbitrary_point(t) Point2D(3*cos(t), 2*sin(t)) >>> p2 = _.subs(t, 0.1) >>> p2 in e1 False Note that arbitrary_point routine does not take this approach. A value for cos(t) and sin(t) (not t) is substituted into the arbitrary point. There is a small chance that this will give a point that will not test as being in the ellipse, so the process is repeated (up to 10 times) until a valid point is obtained. """ from sympy import sin, cos, Rational t = _symbol('t', real=True) x, y = self.arbitrary_point(t).args # get a random value in [-1, 1) corresponding to cos(t) # and confirm that it will test as being in the ellipse if seed is not None: rng = random.Random(seed) else: rng = random for i in range(10): # should be enough? # simplify this now or else the Float will turn s into a Float c = 2*Rational(rng.random()) - 1 s = sqrt(1 - c**2) p1 = Point(x.subs(cos(t), c), y.subs(sin(t), s)) if p1 in self: return p1 raise GeometryError( 'Having problems generating a point in the ellipse.')
def rational_parametrization(self, parameters=('t', 's'), reg_point=None): """ Returns the rational parametrization of implict region. Examples ======== >>> from sympy import Eq >>> from sympy.abc import x, y, z, s, t >>> from sympy.vector import ImplicitRegion >>> parabola = ImplicitRegion((x, y), y**2 - 4*x) >>> parabola.rational_parametrization() (4/t**2, 4/t) >>> circle = ImplicitRegion((x, y), Eq(x**2 + y**2, 4)) >>> circle.rational_parametrization() (4*t/(t**2 + 1), 4*t**2/(t**2 + 1) - 2) >>> I = ImplicitRegion((x, y), x**3 + x**2 - y**2) >>> I.rational_parametrization() (t**2 - 1, t*(t**2 - 1)) >>> cubic_curve = ImplicitRegion((x, y), x**3 + x**2 - y**2) >>> cubic_curve.rational_parametrization(parameters=(t)) (t**2 - 1, t*(t**2 - 1)) >>> sphere = ImplicitRegion((x, y, z), x**2 + y**2 + z**2 - 4) >>> sphere.rational_parametrization(parameters=(t, s)) (-2 + 4/(s**2 + t**2 + 1), 4*s/(s**2 + t**2 + 1), 4*t/(s**2 + t**2 + 1)) For some conics, regular_points() is unable to find a point on curve. To calulcate the parametric representation in such cases, user need to determine a point on the region and pass it using reg_point. >>> c = ImplicitRegion((x, y), (x - 1/2)**2 + (y)**2 - (1/4)**2) >>> c.rational_parametrization(reg_point=(3/4, 0)) (0.75 - 0.5/(t**2 + 1), -0.5*t/(t**2 + 1)) References ========== - Christoph M. Hoffmann, "Conversion Methods between Parametric and Implicit Curves and Surfaces", Purdue e-Pubs, 1990. Available: https://docs.lib.purdue.edu/cgi/viewcontent.cgi?article=1827&context=cstech """ equation = self.equation degree = self.degree if degree == 1: if len(self.variables) == 1: return (equation, ) elif len(self.variables) == 2: x, y = self.variables y_par = list(solveset(equation, y))[0] return x, y_par else: raise NotImplementedError() point = () # Finding the (n - 1) fold point of the monoid of degree if degree == 2: # For degree 2 curves, either a regular point or a singular point can be used. if reg_point is not None: # Using point provided by the user as regular point point = reg_point else: if len(self.singular_points()) != 0: point = list(self.singular_points())[0] else: point = self.regular_point() if len(self.singular_points()) != 0: singular_points = self.singular_points() for spoint in singular_points: syms = Tuple(*spoint).free_symbols rep = {s: 2 for s in syms} if len(syms) != 0: spoint = tuple(s.subs(rep) for s in spoint) if self.multiplicity(spoint) == degree - 1: point = spoint break if len(point) == 0: # The region in not a monoid raise NotImplementedError() modified_eq = equation # Shifting the region such that fold point moves to origin for i, var in enumerate(self.variables): modified_eq = modified_eq.subs(var, var + point[i]) modified_eq = expand(modified_eq) hn = hn_1 = 0 for term in modified_eq.args: if total_degree(term) == degree: hn += term else: hn_1 += term hn_1 = -1 * hn_1 if not isinstance(parameters, tuple): parameters = (parameters, ) if len(self.variables) == 2: parameter1 = parameters[0] if parameter1 == 's': # To avoid name conflict between parameters s = _symbol('s_', real=True) else: s = _symbol('s', real=True) t = _symbol(parameter1, real=True) hn = hn.subs({self.variables[0]: s, self.variables[1]: t}) hn_1 = hn_1.subs({self.variables[0]: s, self.variables[1]: t}) x_par = (s * (hn_1 / hn)).subs(s, 1) + point[0] y_par = (t * (hn_1 / hn)).subs(s, 1) + point[1] return x_par, y_par elif len(self.variables) == 3: parameter1, parameter2 = parameters if parameter1 == 'r' or parameter2 == 'r': # To avoid name conflict between parameters r = _symbol('r_', real=True) else: r = _symbol('r', real=True) s = _symbol(parameter2, real=True) t = _symbol(parameter1, real=True) hn = hn.subs({ self.variables[0]: r, self.variables[1]: s, self.variables[2]: t }) hn_1 = hn_1.subs({ self.variables[0]: r, self.variables[1]: s, self.variables[2]: t }) x_par = (r * (hn_1 / hn)).subs(r, 1) + point[0] y_par = (s * (hn_1 / hn)).subs(r, 1) + point[1] z_par = (t * (hn_1 / hn)).subs(r, 1) + point[2] return x_par, y_par, z_par raise NotImplementedError()