implicitregion.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. from sympy.core.numbers import Rational
  2. from sympy.core.singleton import S
  3. from sympy.core.symbol import symbols
  4. from sympy.functions.elementary.complexes import sign
  5. from sympy.functions.elementary.miscellaneous import sqrt
  6. from sympy.polys.polytools import gcd
  7. from sympy.sets.sets import Complement
  8. from sympy.core import Basic, Tuple, diff, expand, Eq, Integer
  9. from sympy.core.sorting import ordered
  10. from sympy.core.symbol import _symbol
  11. from sympy.solvers import solveset, nonlinsolve, diophantine
  12. from sympy.polys import total_degree
  13. from sympy.geometry import Point
  14. from sympy.ntheory.factor_ import core
  15. class ImplicitRegion(Basic):
  16. """
  17. Represents an implicit region in space.
  18. Examples
  19. ========
  20. >>> from sympy import Eq
  21. >>> from sympy.abc import x, y, z, t
  22. >>> from sympy.vector import ImplicitRegion
  23. >>> ImplicitRegion((x, y), x**2 + y**2 - 4)
  24. ImplicitRegion((x, y), x**2 + y**2 - 4)
  25. >>> ImplicitRegion((x, y), Eq(y*x, 1))
  26. ImplicitRegion((x, y), x*y - 1)
  27. >>> parabola = ImplicitRegion((x, y), y**2 - 4*x)
  28. >>> parabola.degree
  29. 2
  30. >>> parabola.equation
  31. -4*x + y**2
  32. >>> parabola.rational_parametrization(t)
  33. (4/t**2, 4/t)
  34. >>> r = ImplicitRegion((x, y, z), Eq(z, x**2 + y**2))
  35. >>> r.variables
  36. (x, y, z)
  37. >>> r.singular_points()
  38. {(0, 0, 0)}
  39. >>> r.regular_point()
  40. (-10, -10, 200)
  41. Parameters
  42. ==========
  43. variables : tuple to map variables in implicit equation to base scalars.
  44. equation : An expression or Eq denoting the implicit equation of the region.
  45. """
  46. def __new__(cls, variables, equation):
  47. if not isinstance(variables, Tuple):
  48. variables = Tuple(*variables)
  49. if isinstance(equation, Eq):
  50. equation = equation.lhs - equation.rhs
  51. return super().__new__(cls, variables, equation)
  52. @property
  53. def variables(self):
  54. return self.args[0]
  55. @property
  56. def equation(self):
  57. return self.args[1]
  58. @property
  59. def degree(self):
  60. return total_degree(self.equation)
  61. def regular_point(self):
  62. """
  63. Returns a point on the implicit region.
  64. Examples
  65. ========
  66. >>> from sympy.abc import x, y, z
  67. >>> from sympy.vector import ImplicitRegion
  68. >>> circle = ImplicitRegion((x, y), (x + 2)**2 + (y - 3)**2 - 16)
  69. >>> circle.regular_point()
  70. (-2, -1)
  71. >>> parabola = ImplicitRegion((x, y), x**2 - 4*y)
  72. >>> parabola.regular_point()
  73. (0, 0)
  74. >>> r = ImplicitRegion((x, y, z), (x + y + z)**4)
  75. >>> r.regular_point()
  76. (-10, -10, 20)
  77. References
  78. ==========
  79. - Erik Hillgarter, "Rational Points on Conics", Diploma Thesis, RISC-Linz,
  80. J. Kepler Universitat Linz, 1996. Availaible:
  81. https://www3.risc.jku.at/publications/download/risc_1355/Rational%20Points%20on%20Conics.pdf
  82. """
  83. equation = self.equation
  84. if len(self.variables) == 1:
  85. return (list(solveset(equation, self.variables[0], domain=S.Reals))[0],)
  86. elif len(self.variables) == 2:
  87. if self.degree == 2:
  88. coeffs = a, b, c, d, e, f = conic_coeff(self.variables, equation)
  89. if b**2 == 4*a*c:
  90. x_reg, y_reg = self._regular_point_parabola(*coeffs)
  91. else:
  92. x_reg, y_reg = self._regular_point_ellipse(*coeffs)
  93. return x_reg, y_reg
  94. if len(self.variables) == 3:
  95. x, y, z = self.variables
  96. for x_reg in range(-10, 10):
  97. for y_reg in range(-10, 10):
  98. if not solveset(equation.subs({x: x_reg, y: y_reg}), self.variables[2], domain=S.Reals).is_empty:
  99. return (x_reg, y_reg, list(solveset(equation.subs({x: x_reg, y: y_reg})))[0])
  100. if len(self.singular_points()) != 0:
  101. return list[self.singular_points()][0]
  102. raise NotImplementedError()
  103. def _regular_point_parabola(self, a, b, c, d, e, f):
  104. ok = (a, d) != (0, 0) and (c, e) != (0, 0) and b**2 == 4*a*c and (a, c) != (0, 0)
  105. if not ok:
  106. raise ValueError("Rational Point on the conic does not exist")
  107. if a != 0:
  108. d_dash, f_dash = (4*a*e - 2*b*d, 4*a*f - d**2)
  109. if d_dash != 0:
  110. y_reg = -f_dash/d_dash
  111. x_reg = -(d + b*y_reg)/(2*a)
  112. else:
  113. ok = False
  114. elif c != 0:
  115. d_dash, f_dash = (4*c*d - 2*b*e, 4*c*f - e**2)
  116. if d_dash != 0:
  117. x_reg = -f_dash/d_dash
  118. y_reg = -(e + b*x_reg)/(2*c)
  119. else:
  120. ok = False
  121. if ok:
  122. return x_reg, y_reg
  123. else:
  124. raise ValueError("Rational Point on the conic does not exist")
  125. def _regular_point_ellipse(self, a, b, c, d, e, f):
  126. D = 4*a*c - b**2
  127. ok = D
  128. if not ok:
  129. raise ValueError("Rational Point on the conic does not exist")
  130. if a == 0 and c == 0:
  131. K = -1
  132. L = 4*(d*e - b*f)
  133. elif c != 0:
  134. K = D
  135. L = 4*c**2*d**2 - 4*b*c*d*e + 4*a*c*e**2 + 4*b**2*c*f - 16*a*c**2*f
  136. else:
  137. K = D
  138. L = 4*a**2*e**2 - 4*b*a*d*e + 4*b**2*a*f
  139. ok = L != 0 and not(K > 0 and L < 0)
  140. if not ok:
  141. raise ValueError("Rational Point on the conic does not exist")
  142. K = Rational(K).limit_denominator(10**12)
  143. L = Rational(L).limit_denominator(10**12)
  144. k1, k2 = K.p, K.q
  145. l1, l2 = L.p, L.q
  146. g = gcd(k2, l2)
  147. a1 = (l2*k2)/g
  148. b1 = (k1*l2)/g
  149. c1 = -(l1*k2)/g
  150. a2 = sign(a1)*core(abs(a1), 2)
  151. r1 = sqrt(a1/a2)
  152. b2 = sign(b1)*core(abs(b1), 2)
  153. r2 = sqrt(b1/b2)
  154. c2 = sign(c1)*core(abs(c1), 2)
  155. r3 = sqrt(c1/c2)
  156. g = gcd(gcd(a2, b2), c2)
  157. a2 = a2/g
  158. b2 = b2/g
  159. c2 = c2/g
  160. g1 = gcd(a2, b2)
  161. a2 = a2/g1
  162. b2 = b2/g1
  163. c2 = c2*g1
  164. g2 = gcd(a2,c2)
  165. a2 = a2/g2
  166. b2 = b2*g2
  167. c2 = c2/g2
  168. g3 = gcd(b2, c2)
  169. a2 = a2*g3
  170. b2 = b2/g3
  171. c2 = c2/g3
  172. x, y, z = symbols("x y z")
  173. eq = a2*x**2 + b2*y**2 + c2*z**2
  174. solutions = diophantine(eq)
  175. if len(solutions) == 0:
  176. raise ValueError("Rational Point on the conic does not exist")
  177. flag = False
  178. for sol in solutions:
  179. syms = Tuple(*sol).free_symbols
  180. rep = {s: 3 for s in syms}
  181. sol_z = sol[2]
  182. if sol_z == 0:
  183. flag = True
  184. continue
  185. if not isinstance(sol_z, (int, Integer)):
  186. syms_z = sol_z.free_symbols
  187. if len(syms_z) == 1:
  188. p = next(iter(syms_z))
  189. p_values = Complement(S.Integers, solveset(Eq(sol_z, 0), p, S.Integers))
  190. rep[p] = next(iter(p_values))
  191. if len(syms_z) == 2:
  192. p, q = list(ordered(syms_z))
  193. for i in S.Integers:
  194. subs_sol_z = sol_z.subs(p, i)
  195. q_values = Complement(S.Integers, solveset(Eq(subs_sol_z, 0), q, S.Integers))
  196. if not q_values.is_empty:
  197. rep[p] = i
  198. rep[q] = next(iter(q_values))
  199. break
  200. if len(syms) != 0:
  201. x, y, z = tuple(s.subs(rep) for s in sol)
  202. else:
  203. x, y, z = sol
  204. flag = False
  205. break
  206. if flag:
  207. raise ValueError("Rational Point on the conic does not exist")
  208. x = (x*g3)/r1
  209. y = (y*g2)/r2
  210. z = (z*g1)/r3
  211. x = x/z
  212. y = y/z
  213. if a == 0 and c == 0:
  214. x_reg = (x + y - 2*e)/(2*b)
  215. y_reg = (x - y - 2*d)/(2*b)
  216. elif c != 0:
  217. x_reg = (x - 2*d*c + b*e)/K
  218. y_reg = (y - b*x_reg - e)/(2*c)
  219. else:
  220. y_reg = (x - 2*e*a + b*d)/K
  221. x_reg = (y - b*y_reg - d)/(2*a)
  222. return x_reg, y_reg
  223. def singular_points(self):
  224. """
  225. Returns a set of singular points of the region.
  226. The singular points are those points on the region
  227. where all partial derivatives vanish.
  228. Examples
  229. ========
  230. >>> from sympy.abc import x, y
  231. >>> from sympy.vector import ImplicitRegion
  232. >>> I = ImplicitRegion((x, y), (y-1)**2 -x**3 + 2*x**2 -x)
  233. >>> I.singular_points()
  234. {(1, 1)}
  235. """
  236. eq_list = [self.equation]
  237. for var in self.variables:
  238. eq_list += [diff(self.equation, var)]
  239. return nonlinsolve(eq_list, list(self.variables))
  240. def multiplicity(self, point):
  241. """
  242. Returns the multiplicity of a singular point on the region.
  243. A singular point (x,y) of region is said to be of multiplicity m
  244. if all the partial derivatives off to order m - 1 vanish there.
  245. Examples
  246. ========
  247. >>> from sympy.abc import x, y, z
  248. >>> from sympy.vector import ImplicitRegion
  249. >>> I = ImplicitRegion((x, y, z), x**2 + y**3 - z**4)
  250. >>> I.singular_points()
  251. {(0, 0, 0)}
  252. >>> I.multiplicity((0, 0, 0))
  253. 2
  254. """
  255. if isinstance(point, Point):
  256. point = point.args
  257. modified_eq = self.equation
  258. for i, var in enumerate(self.variables):
  259. modified_eq = modified_eq.subs(var, var + point[i])
  260. modified_eq = expand(modified_eq)
  261. if len(modified_eq.args) != 0:
  262. terms = modified_eq.args
  263. m = min([total_degree(term) for term in terms])
  264. else:
  265. terms = modified_eq
  266. m = total_degree(terms)
  267. return m
  268. def rational_parametrization(self, parameters=('t', 's'), reg_point=None):
  269. """
  270. Returns the rational parametrization of implict region.
  271. Examples
  272. ========
  273. >>> from sympy import Eq
  274. >>> from sympy.abc import x, y, z, s, t
  275. >>> from sympy.vector import ImplicitRegion
  276. >>> parabola = ImplicitRegion((x, y), y**2 - 4*x)
  277. >>> parabola.rational_parametrization()
  278. (4/t**2, 4/t)
  279. >>> circle = ImplicitRegion((x, y), Eq(x**2 + y**2, 4))
  280. >>> circle.rational_parametrization()
  281. (4*t/(t**2 + 1), 4*t**2/(t**2 + 1) - 2)
  282. >>> I = ImplicitRegion((x, y), x**3 + x**2 - y**2)
  283. >>> I.rational_parametrization()
  284. (t**2 - 1, t*(t**2 - 1))
  285. >>> cubic_curve = ImplicitRegion((x, y), x**3 + x**2 - y**2)
  286. >>> cubic_curve.rational_parametrization(parameters=(t))
  287. (t**2 - 1, t*(t**2 - 1))
  288. >>> sphere = ImplicitRegion((x, y, z), x**2 + y**2 + z**2 - 4)
  289. >>> sphere.rational_parametrization(parameters=(t, s))
  290. (-2 + 4/(s**2 + t**2 + 1), 4*s/(s**2 + t**2 + 1), 4*t/(s**2 + t**2 + 1))
  291. For some conics, regular_points() is unable to find a point on curve.
  292. To calulcate the parametric representation in such cases, user need
  293. to determine a point on the region and pass it using reg_point.
  294. >>> c = ImplicitRegion((x, y), (x - 1/2)**2 + (y)**2 - (1/4)**2)
  295. >>> c.rational_parametrization(reg_point=(3/4, 0))
  296. (0.75 - 0.5/(t**2 + 1), -0.5*t/(t**2 + 1))
  297. References
  298. ==========
  299. - Christoph M. Hoffmann, "Conversion Methods between Parametric and
  300. Implicit Curves and Surfaces", Purdue e-Pubs, 1990. Available:
  301. https://docs.lib.purdue.edu/cgi/viewcontent.cgi?article=1827&context=cstech
  302. """
  303. equation = self.equation
  304. degree = self.degree
  305. if degree == 1:
  306. if len(self.variables) == 1:
  307. return (equation,)
  308. elif len(self.variables) == 2:
  309. x, y = self.variables
  310. y_par = list(solveset(equation, y))[0]
  311. return x, y_par
  312. else:
  313. raise NotImplementedError()
  314. point = ()
  315. # Finding the (n - 1) fold point of the monoid of degree
  316. if degree == 2:
  317. # For degree 2 curves, either a regular point or a singular point can be used.
  318. if reg_point is not None:
  319. # Using point provided by the user as regular point
  320. point = reg_point
  321. else:
  322. if len(self.singular_points()) != 0:
  323. point = list(self.singular_points())[0]
  324. else:
  325. point = self.regular_point()
  326. if len(self.singular_points()) != 0:
  327. singular_points = self.singular_points()
  328. for spoint in singular_points:
  329. syms = Tuple(*spoint).free_symbols
  330. rep = {s: 2 for s in syms}
  331. if len(syms) != 0:
  332. spoint = tuple(s.subs(rep) for s in spoint)
  333. if self.multiplicity(spoint) == degree - 1:
  334. point = spoint
  335. break
  336. if len(point) == 0:
  337. # The region in not a monoid
  338. raise NotImplementedError()
  339. modified_eq = equation
  340. # Shifting the region such that fold point moves to origin
  341. for i, var in enumerate(self.variables):
  342. modified_eq = modified_eq.subs(var, var + point[i])
  343. modified_eq = expand(modified_eq)
  344. hn = hn_1 = 0
  345. for term in modified_eq.args:
  346. if total_degree(term) == degree:
  347. hn += term
  348. else:
  349. hn_1 += term
  350. hn_1 = -1*hn_1
  351. if not isinstance(parameters, tuple):
  352. parameters = (parameters,)
  353. if len(self.variables) == 2:
  354. parameter1 = parameters[0]
  355. if parameter1 == 's':
  356. # To avoid name conflict between parameters
  357. s = _symbol('s_', real=True)
  358. else:
  359. s = _symbol('s', real=True)
  360. t = _symbol(parameter1, real=True)
  361. hn = hn.subs({self.variables[0]: s, self.variables[1]: t})
  362. hn_1 = hn_1.subs({self.variables[0]: s, self.variables[1]: t})
  363. x_par = (s*(hn_1/hn)).subs(s, 1) + point[0]
  364. y_par = (t*(hn_1/hn)).subs(s, 1) + point[1]
  365. return x_par, y_par
  366. elif len(self.variables) == 3:
  367. parameter1, parameter2 = parameters
  368. if 'r' in parameters:
  369. # To avoid name conflict between parameters
  370. r = _symbol('r_', real=True)
  371. else:
  372. r = _symbol('r', real=True)
  373. s = _symbol(parameter2, real=True)
  374. t = _symbol(parameter1, real=True)
  375. hn = hn.subs({self.variables[0]: r, self.variables[1]: s, self.variables[2]: t})
  376. hn_1 = hn_1.subs({self.variables[0]: r, self.variables[1]: s, self.variables[2]: t})
  377. x_par = (r*(hn_1/hn)).subs(r, 1) + point[0]
  378. y_par = (s*(hn_1/hn)).subs(r, 1) + point[1]
  379. z_par = (t*(hn_1/hn)).subs(r, 1) + point[2]
  380. return x_par, y_par, z_par
  381. raise NotImplementedError()
  382. def conic_coeff(variables, equation):
  383. if total_degree(equation) != 2:
  384. raise ValueError()
  385. x = variables[0]
  386. y = variables[1]
  387. equation = expand(equation)
  388. a = equation.coeff(x**2)
  389. b = equation.coeff(x*y)
  390. c = equation.coeff(y**2)
  391. d = equation.coeff(x, 1).coeff(y, 0)
  392. e = equation.coeff(y, 1).coeff(x, 0)
  393. f = equation.coeff(x, 0).coeff(y, 0)
  394. return a, b, c, d, e, f