ellipse.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780
  1. """Elliptical geometrical entities.
  2. Contains
  3. * Ellipse
  4. * Circle
  5. """
  6. from sympy.core.expr import Expr
  7. from sympy.core.relational import Eq
  8. from sympy.core import S, pi, sympify
  9. from sympy.core.evalf import N
  10. from sympy.core.parameters import global_parameters
  11. from sympy.core.logic import fuzzy_bool
  12. from sympy.core.numbers import Rational, oo
  13. from sympy.core.sorting import ordered
  14. from sympy.core.symbol import Dummy, uniquely_named_symbol, _symbol
  15. from sympy.simplify import simplify, trigsimp
  16. from sympy.functions.elementary.miscellaneous import sqrt, Max
  17. from sympy.functions.elementary.trigonometric import cos, sin
  18. from sympy.functions.special.elliptic_integrals import elliptic_e
  19. from .entity import GeometryEntity, GeometrySet
  20. from .exceptions import GeometryError
  21. from .line import Line, Segment, Ray2D, Segment2D, Line2D, LinearEntity3D
  22. from .point import Point, Point2D, Point3D
  23. from .util import idiff, find
  24. from sympy.polys import DomainError, Poly, PolynomialError
  25. from sympy.polys.polyutils import _not_a_coeff, _nsort
  26. from sympy.solvers import solve
  27. from sympy.solvers.solveset import linear_coeffs
  28. from sympy.utilities.misc import filldedent, func_name
  29. from mpmath.libmp.libmpf import prec_to_dps
  30. import random
  31. x, y = [Dummy('ellipse_dummy', real=True) for i in range(2)]
  32. class Ellipse(GeometrySet):
  33. """An elliptical GeometryEntity.
  34. Parameters
  35. ==========
  36. center : Point, optional
  37. Default value is Point(0, 0)
  38. hradius : number or SymPy expression, optional
  39. vradius : number or SymPy expression, optional
  40. eccentricity : number or SymPy expression, optional
  41. Two of `hradius`, `vradius` and `eccentricity` must be supplied to
  42. create an Ellipse. The third is derived from the two supplied.
  43. Attributes
  44. ==========
  45. center
  46. hradius
  47. vradius
  48. area
  49. circumference
  50. eccentricity
  51. periapsis
  52. apoapsis
  53. focus_distance
  54. foci
  55. Raises
  56. ======
  57. GeometryError
  58. When `hradius`, `vradius` and `eccentricity` are incorrectly supplied
  59. as parameters.
  60. TypeError
  61. When `center` is not a Point.
  62. See Also
  63. ========
  64. Circle
  65. Notes
  66. -----
  67. Constructed from a center and two radii, the first being the horizontal
  68. radius (along the x-axis) and the second being the vertical radius (along
  69. the y-axis).
  70. When symbolic value for hradius and vradius are used, any calculation that
  71. refers to the foci or the major or minor axis will assume that the ellipse
  72. has its major radius on the x-axis. If this is not true then a manual
  73. rotation is necessary.
  74. Examples
  75. ========
  76. >>> from sympy import Ellipse, Point, Rational
  77. >>> e1 = Ellipse(Point(0, 0), 5, 1)
  78. >>> e1.hradius, e1.vradius
  79. (5, 1)
  80. >>> e2 = Ellipse(Point(3, 1), hradius=3, eccentricity=Rational(4, 5))
  81. >>> e2
  82. Ellipse(Point2D(3, 1), 3, 9/5)
  83. """
  84. def __contains__(self, o):
  85. if isinstance(o, Point):
  86. res = self.equation(x, y).subs({x: o.x, y: o.y})
  87. return trigsimp(simplify(res)) is S.Zero
  88. elif isinstance(o, Ellipse):
  89. return self == o
  90. return False
  91. def __eq__(self, o):
  92. """Is the other GeometryEntity the same as this ellipse?"""
  93. return isinstance(o, Ellipse) and (self.center == o.center and
  94. self.hradius == o.hradius and
  95. self.vradius == o.vradius)
  96. def __hash__(self):
  97. return super().__hash__()
  98. def __new__(
  99. cls, center=None, hradius=None, vradius=None, eccentricity=None, **kwargs):
  100. hradius = sympify(hradius)
  101. vradius = sympify(vradius)
  102. if center is None:
  103. center = Point(0, 0)
  104. else:
  105. if len(center) != 2:
  106. raise ValueError('The center of "{}" must be a two dimensional point'.format(cls))
  107. center = Point(center, dim=2)
  108. if len(list(filter(lambda x: x is not None, (hradius, vradius, eccentricity)))) != 2:
  109. raise ValueError(filldedent('''
  110. Exactly two arguments of "hradius", "vradius", and
  111. "eccentricity" must not be None.'''))
  112. if eccentricity is not None:
  113. eccentricity = sympify(eccentricity)
  114. if eccentricity.is_negative:
  115. raise GeometryError("Eccentricity of ellipse/circle should lie between [0, 1)")
  116. elif hradius is None:
  117. hradius = vradius / sqrt(1 - eccentricity**2)
  118. elif vradius is None:
  119. vradius = hradius * sqrt(1 - eccentricity**2)
  120. if hradius == vradius:
  121. return Circle(center, hradius, **kwargs)
  122. if S.Zero in (hradius, vradius):
  123. return Segment(Point(center[0] - hradius, center[1] - vradius), Point(center[0] + hradius, center[1] + vradius))
  124. if hradius.is_real is False or vradius.is_real is False:
  125. raise GeometryError("Invalid value encountered when computing hradius / vradius.")
  126. return GeometryEntity.__new__(cls, center, hradius, vradius, **kwargs)
  127. def _svg(self, scale_factor=1., fill_color="#66cc99"):
  128. """Returns SVG ellipse element for the Ellipse.
  129. Parameters
  130. ==========
  131. scale_factor : float
  132. Multiplication factor for the SVG stroke-width. Default is 1.
  133. fill_color : str, optional
  134. Hex string for fill color. Default is "#66cc99".
  135. """
  136. c = N(self.center)
  137. h, v = N(self.hradius), N(self.vradius)
  138. return (
  139. '<ellipse fill="{1}" stroke="#555555" '
  140. 'stroke-width="{0}" opacity="0.6" cx="{2}" cy="{3}" rx="{4}" ry="{5}"/>'
  141. ).format(2. * scale_factor, fill_color, c.x, c.y, h, v)
  142. @property
  143. def ambient_dimension(self):
  144. return 2
  145. @property
  146. def apoapsis(self):
  147. """The apoapsis of the ellipse.
  148. The greatest distance between the focus and the contour.
  149. Returns
  150. =======
  151. apoapsis : number
  152. See Also
  153. ========
  154. periapsis : Returns shortest distance between foci and contour
  155. Examples
  156. ========
  157. >>> from sympy import Point, Ellipse
  158. >>> p1 = Point(0, 0)
  159. >>> e1 = Ellipse(p1, 3, 1)
  160. >>> e1.apoapsis
  161. 2*sqrt(2) + 3
  162. """
  163. return self.major * (1 + self.eccentricity)
  164. def arbitrary_point(self, parameter='t'):
  165. """A parameterized point on the ellipse.
  166. Parameters
  167. ==========
  168. parameter : str, optional
  169. Default value is 't'.
  170. Returns
  171. =======
  172. arbitrary_point : Point
  173. Raises
  174. ======
  175. ValueError
  176. When `parameter` already appears in the functions.
  177. See Also
  178. ========
  179. sympy.geometry.point.Point
  180. Examples
  181. ========
  182. >>> from sympy import Point, Ellipse
  183. >>> e1 = Ellipse(Point(0, 0), 3, 2)
  184. >>> e1.arbitrary_point()
  185. Point2D(3*cos(t), 2*sin(t))
  186. """
  187. t = _symbol(parameter, real=True)
  188. if t.name in (f.name for f in self.free_symbols):
  189. raise ValueError(filldedent('Symbol %s already appears in object '
  190. 'and cannot be used as a parameter.' % t.name))
  191. return Point(self.center.x + self.hradius*cos(t),
  192. self.center.y + self.vradius*sin(t))
  193. @property
  194. def area(self):
  195. """The area of the ellipse.
  196. Returns
  197. =======
  198. area : number
  199. Examples
  200. ========
  201. >>> from sympy import Point, Ellipse
  202. >>> p1 = Point(0, 0)
  203. >>> e1 = Ellipse(p1, 3, 1)
  204. >>> e1.area
  205. 3*pi
  206. """
  207. return simplify(S.Pi * self.hradius * self.vradius)
  208. @property
  209. def bounds(self):
  210. """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
  211. rectangle for the geometric figure.
  212. """
  213. h, v = self.hradius, self.vradius
  214. return (self.center.x - h, self.center.y - v, self.center.x + h, self.center.y + v)
  215. @property
  216. def center(self):
  217. """The center of the ellipse.
  218. Returns
  219. =======
  220. center : number
  221. See Also
  222. ========
  223. sympy.geometry.point.Point
  224. Examples
  225. ========
  226. >>> from sympy import Point, Ellipse
  227. >>> p1 = Point(0, 0)
  228. >>> e1 = Ellipse(p1, 3, 1)
  229. >>> e1.center
  230. Point2D(0, 0)
  231. """
  232. return self.args[0]
  233. @property
  234. def circumference(self):
  235. """The circumference of the ellipse.
  236. Examples
  237. ========
  238. >>> from sympy import Point, Ellipse
  239. >>> p1 = Point(0, 0)
  240. >>> e1 = Ellipse(p1, 3, 1)
  241. >>> e1.circumference
  242. 12*elliptic_e(8/9)
  243. """
  244. if self.eccentricity == 1:
  245. # degenerate
  246. return 4*self.major
  247. elif self.eccentricity == 0:
  248. # circle
  249. return 2*pi*self.hradius
  250. else:
  251. return 4*self.major*elliptic_e(self.eccentricity**2)
  252. @property
  253. def eccentricity(self):
  254. """The eccentricity of the ellipse.
  255. Returns
  256. =======
  257. eccentricity : number
  258. Examples
  259. ========
  260. >>> from sympy import Point, Ellipse, sqrt
  261. >>> p1 = Point(0, 0)
  262. >>> e1 = Ellipse(p1, 3, sqrt(2))
  263. >>> e1.eccentricity
  264. sqrt(7)/3
  265. """
  266. return self.focus_distance / self.major
  267. def encloses_point(self, p):
  268. """
  269. Return True if p is enclosed by (is inside of) self.
  270. Notes
  271. -----
  272. Being on the border of self is considered False.
  273. Parameters
  274. ==========
  275. p : Point
  276. Returns
  277. =======
  278. encloses_point : True, False or None
  279. See Also
  280. ========
  281. sympy.geometry.point.Point
  282. Examples
  283. ========
  284. >>> from sympy import Ellipse, S
  285. >>> from sympy.abc import t
  286. >>> e = Ellipse((0, 0), 3, 2)
  287. >>> e.encloses_point((0, 0))
  288. True
  289. >>> e.encloses_point(e.arbitrary_point(t).subs(t, S.Half))
  290. False
  291. >>> e.encloses_point((4, 0))
  292. False
  293. """
  294. p = Point(p, dim=2)
  295. if p in self:
  296. return False
  297. if len(self.foci) == 2:
  298. # if the combined distance from the foci to p (h1 + h2) is less
  299. # than the combined distance from the foci to the minor axis
  300. # (which is the same as the major axis length) then p is inside
  301. # the ellipse
  302. h1, h2 = [f.distance(p) for f in self.foci]
  303. test = 2*self.major - (h1 + h2)
  304. else:
  305. test = self.radius - self.center.distance(p)
  306. return fuzzy_bool(test.is_positive)
  307. def equation(self, x='x', y='y', _slope=None):
  308. """
  309. Returns the equation of an ellipse aligned with the x and y axes;
  310. when slope is given, the equation returned corresponds to an ellipse
  311. with a major axis having that slope.
  312. Parameters
  313. ==========
  314. x : str, optional
  315. Label for the x-axis. Default value is 'x'.
  316. y : str, optional
  317. Label for the y-axis. Default value is 'y'.
  318. _slope : Expr, optional
  319. The slope of the major axis. Ignored when 'None'.
  320. Returns
  321. =======
  322. equation : SymPy expression
  323. See Also
  324. ========
  325. arbitrary_point : Returns parameterized point on ellipse
  326. Examples
  327. ========
  328. >>> from sympy import Point, Ellipse, pi
  329. >>> from sympy.abc import x, y
  330. >>> e1 = Ellipse(Point(1, 0), 3, 2)
  331. >>> eq1 = e1.equation(x, y); eq1
  332. y**2/4 + (x/3 - 1/3)**2 - 1
  333. >>> eq2 = e1.equation(x, y, _slope=1); eq2
  334. (-x + y + 1)**2/8 + (x + y - 1)**2/18 - 1
  335. A point on e1 satisfies eq1. Let's use one on the x-axis:
  336. >>> p1 = e1.center + Point(e1.major, 0)
  337. >>> assert eq1.subs(x, p1.x).subs(y, p1.y) == 0
  338. When rotated the same as the rotated ellipse, about the center
  339. point of the ellipse, it will satisfy the rotated ellipse's
  340. equation, too:
  341. >>> r1 = p1.rotate(pi/4, e1.center)
  342. >>> assert eq2.subs(x, r1.x).subs(y, r1.y) == 0
  343. References
  344. ==========
  345. .. [1] https://math.stackexchange.com/questions/108270/what-is-the-equation-of-an-ellipse-that-is-not-aligned-with-the-axis
  346. .. [2] https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse
  347. """
  348. x = _symbol(x, real=True)
  349. y = _symbol(y, real=True)
  350. dx = x - self.center.x
  351. dy = y - self.center.y
  352. if _slope is not None:
  353. L = (dy - _slope*dx)**2
  354. l = (_slope*dy + dx)**2
  355. h = 1 + _slope**2
  356. b = h*self.major**2
  357. a = h*self.minor**2
  358. return l/b + L/a - 1
  359. else:
  360. t1 = (dx/self.hradius)**2
  361. t2 = (dy/self.vradius)**2
  362. return t1 + t2 - 1
  363. def evolute(self, x='x', y='y'):
  364. """The equation of evolute of the ellipse.
  365. Parameters
  366. ==========
  367. x : str, optional
  368. Label for the x-axis. Default value is 'x'.
  369. y : str, optional
  370. Label for the y-axis. Default value is 'y'.
  371. Returns
  372. =======
  373. equation : SymPy expression
  374. Examples
  375. ========
  376. >>> from sympy import Point, Ellipse
  377. >>> e1 = Ellipse(Point(1, 0), 3, 2)
  378. >>> e1.evolute()
  379. 2**(2/3)*y**(2/3) + (3*x - 3)**(2/3) - 5**(2/3)
  380. """
  381. if len(self.args) != 3:
  382. raise NotImplementedError('Evolute of arbitrary Ellipse is not supported.')
  383. x = _symbol(x, real=True)
  384. y = _symbol(y, real=True)
  385. t1 = (self.hradius*(x - self.center.x))**Rational(2, 3)
  386. t2 = (self.vradius*(y - self.center.y))**Rational(2, 3)
  387. return t1 + t2 - (self.hradius**2 - self.vradius**2)**Rational(2, 3)
  388. @property
  389. def foci(self):
  390. """The foci of the ellipse.
  391. Notes
  392. -----
  393. The foci can only be calculated if the major/minor axes are known.
  394. Raises
  395. ======
  396. ValueError
  397. When the major and minor axis cannot be determined.
  398. See Also
  399. ========
  400. sympy.geometry.point.Point
  401. focus_distance : Returns the distance between focus and center
  402. Examples
  403. ========
  404. >>> from sympy import Point, Ellipse
  405. >>> p1 = Point(0, 0)
  406. >>> e1 = Ellipse(p1, 3, 1)
  407. >>> e1.foci
  408. (Point2D(-2*sqrt(2), 0), Point2D(2*sqrt(2), 0))
  409. """
  410. c = self.center
  411. hr, vr = self.hradius, self.vradius
  412. if hr == vr:
  413. return (c, c)
  414. # calculate focus distance manually, since focus_distance calls this
  415. # routine
  416. fd = sqrt(self.major**2 - self.minor**2)
  417. if hr == self.minor:
  418. # foci on the y-axis
  419. return (c + Point(0, -fd), c + Point(0, fd))
  420. elif hr == self.major:
  421. # foci on the x-axis
  422. return (c + Point(-fd, 0), c + Point(fd, 0))
  423. @property
  424. def focus_distance(self):
  425. """The focal distance of the ellipse.
  426. The distance between the center and one focus.
  427. Returns
  428. =======
  429. focus_distance : number
  430. See Also
  431. ========
  432. foci
  433. Examples
  434. ========
  435. >>> from sympy import Point, Ellipse
  436. >>> p1 = Point(0, 0)
  437. >>> e1 = Ellipse(p1, 3, 1)
  438. >>> e1.focus_distance
  439. 2*sqrt(2)
  440. """
  441. return Point.distance(self.center, self.foci[0])
  442. @property
  443. def hradius(self):
  444. """The horizontal radius of the ellipse.
  445. Returns
  446. =======
  447. hradius : number
  448. See Also
  449. ========
  450. vradius, major, minor
  451. Examples
  452. ========
  453. >>> from sympy import Point, Ellipse
  454. >>> p1 = Point(0, 0)
  455. >>> e1 = Ellipse(p1, 3, 1)
  456. >>> e1.hradius
  457. 3
  458. """
  459. return self.args[1]
  460. def intersection(self, o):
  461. """The intersection of this ellipse and another geometrical entity
  462. `o`.
  463. Parameters
  464. ==========
  465. o : GeometryEntity
  466. Returns
  467. =======
  468. intersection : list of GeometryEntity objects
  469. Notes
  470. -----
  471. Currently supports intersections with Point, Line, Segment, Ray,
  472. Circle and Ellipse types.
  473. See Also
  474. ========
  475. sympy.geometry.entity.GeometryEntity
  476. Examples
  477. ========
  478. >>> from sympy import Ellipse, Point, Line
  479. >>> e = Ellipse(Point(0, 0), 5, 7)
  480. >>> e.intersection(Point(0, 0))
  481. []
  482. >>> e.intersection(Point(5, 0))
  483. [Point2D(5, 0)]
  484. >>> e.intersection(Line(Point(0,0), Point(0, 1)))
  485. [Point2D(0, -7), Point2D(0, 7)]
  486. >>> e.intersection(Line(Point(5,0), Point(5, 1)))
  487. [Point2D(5, 0)]
  488. >>> e.intersection(Line(Point(6,0), Point(6, 1)))
  489. []
  490. >>> e = Ellipse(Point(-1, 0), 4, 3)
  491. >>> e.intersection(Ellipse(Point(1, 0), 4, 3))
  492. [Point2D(0, -3*sqrt(15)/4), Point2D(0, 3*sqrt(15)/4)]
  493. >>> e.intersection(Ellipse(Point(5, 0), 4, 3))
  494. [Point2D(2, -3*sqrt(7)/4), Point2D(2, 3*sqrt(7)/4)]
  495. >>> e.intersection(Ellipse(Point(100500, 0), 4, 3))
  496. []
  497. >>> e.intersection(Ellipse(Point(0, 0), 3, 4))
  498. [Point2D(3, 0), Point2D(-363/175, -48*sqrt(111)/175), Point2D(-363/175, 48*sqrt(111)/175)]
  499. >>> e.intersection(Ellipse(Point(-1, 0), 3, 4))
  500. [Point2D(-17/5, -12/5), Point2D(-17/5, 12/5), Point2D(7/5, -12/5), Point2D(7/5, 12/5)]
  501. """
  502. # TODO: Replace solve with nonlinsolve, when nonlinsolve will be able to solve in real domain
  503. if isinstance(o, Point):
  504. if o in self:
  505. return [o]
  506. else:
  507. return []
  508. elif isinstance(o, (Segment2D, Ray2D)):
  509. ellipse_equation = self.equation(x, y)
  510. result = solve([ellipse_equation, Line(
  511. o.points[0], o.points[1]).equation(x, y)], [x, y],
  512. set=True)[1]
  513. return list(ordered([Point(i) for i in result if i in o]))
  514. elif isinstance(o, Polygon):
  515. return o.intersection(self)
  516. elif isinstance(o, (Ellipse, Line2D)):
  517. if o == self:
  518. return self
  519. else:
  520. ellipse_equation = self.equation(x, y)
  521. return list(ordered([Point(i) for i in solve(
  522. [ellipse_equation, o.equation(x, y)], [x, y],
  523. set=True)[1]]))
  524. elif isinstance(o, LinearEntity3D):
  525. raise TypeError('Entity must be two dimensional, not three dimensional')
  526. else:
  527. raise TypeError('Intersection not handled for %s' % func_name(o))
  528. def is_tangent(self, o):
  529. """Is `o` tangent to the ellipse?
  530. Parameters
  531. ==========
  532. o : GeometryEntity
  533. An Ellipse, LinearEntity or Polygon
  534. Raises
  535. ======
  536. NotImplementedError
  537. When the wrong type of argument is supplied.
  538. Returns
  539. =======
  540. is_tangent: boolean
  541. True if o is tangent to the ellipse, False otherwise.
  542. See Also
  543. ========
  544. tangent_lines
  545. Examples
  546. ========
  547. >>> from sympy import Point, Ellipse, Line
  548. >>> p0, p1, p2 = Point(0, 0), Point(3, 0), Point(3, 3)
  549. >>> e1 = Ellipse(p0, 3, 2)
  550. >>> l1 = Line(p1, p2)
  551. >>> e1.is_tangent(l1)
  552. True
  553. """
  554. if isinstance(o, Point2D):
  555. return False
  556. elif isinstance(o, Ellipse):
  557. intersect = self.intersection(o)
  558. if isinstance(intersect, Ellipse):
  559. return True
  560. elif intersect:
  561. return all((self.tangent_lines(i)[0]).equals(o.tangent_lines(i)[0]) for i in intersect)
  562. else:
  563. return False
  564. elif isinstance(o, Line2D):
  565. hit = self.intersection(o)
  566. if not hit:
  567. return False
  568. if len(hit) == 1:
  569. return True
  570. # might return None if it can't decide
  571. return hit[0].equals(hit[1])
  572. elif isinstance(o, Ray2D):
  573. intersect = self.intersection(o)
  574. if len(intersect) == 1:
  575. return intersect[0] != o.source and not self.encloses_point(o.source)
  576. else:
  577. return False
  578. elif isinstance(o, (Segment2D, Polygon)):
  579. all_tangents = False
  580. segments = o.sides if isinstance(o, Polygon) else [o]
  581. for segment in segments:
  582. intersect = self.intersection(segment)
  583. if len(intersect) == 1:
  584. if not any(intersect[0] in i for i in segment.points) \
  585. and not any(self.encloses_point(i) for i in segment.points):
  586. all_tangents = True
  587. continue
  588. else:
  589. return False
  590. else:
  591. return all_tangents
  592. return all_tangents
  593. elif isinstance(o, (LinearEntity3D, Point3D)):
  594. raise TypeError('Entity must be two dimensional, not three dimensional')
  595. else:
  596. raise TypeError('Is_tangent not handled for %s' % func_name(o))
  597. @property
  598. def major(self):
  599. """Longer axis of the ellipse (if it can be determined) else hradius.
  600. Returns
  601. =======
  602. major : number or expression
  603. See Also
  604. ========
  605. hradius, vradius, minor
  606. Examples
  607. ========
  608. >>> from sympy import Point, Ellipse, Symbol
  609. >>> p1 = Point(0, 0)
  610. >>> e1 = Ellipse(p1, 3, 1)
  611. >>> e1.major
  612. 3
  613. >>> a = Symbol('a')
  614. >>> b = Symbol('b')
  615. >>> Ellipse(p1, a, b).major
  616. a
  617. >>> Ellipse(p1, b, a).major
  618. b
  619. >>> m = Symbol('m')
  620. >>> M = m + 1
  621. >>> Ellipse(p1, m, M).major
  622. m + 1
  623. """
  624. ab = self.args[1:3]
  625. if len(ab) == 1:
  626. return ab[0]
  627. a, b = ab
  628. o = b - a < 0
  629. if o == True:
  630. return a
  631. elif o == False:
  632. return b
  633. return self.hradius
  634. @property
  635. def minor(self):
  636. """Shorter axis of the ellipse (if it can be determined) else vradius.
  637. Returns
  638. =======
  639. minor : number or expression
  640. See Also
  641. ========
  642. hradius, vradius, major
  643. Examples
  644. ========
  645. >>> from sympy import Point, Ellipse, Symbol
  646. >>> p1 = Point(0, 0)
  647. >>> e1 = Ellipse(p1, 3, 1)
  648. >>> e1.minor
  649. 1
  650. >>> a = Symbol('a')
  651. >>> b = Symbol('b')
  652. >>> Ellipse(p1, a, b).minor
  653. b
  654. >>> Ellipse(p1, b, a).minor
  655. a
  656. >>> m = Symbol('m')
  657. >>> M = m + 1
  658. >>> Ellipse(p1, m, M).minor
  659. m
  660. """
  661. ab = self.args[1:3]
  662. if len(ab) == 1:
  663. return ab[0]
  664. a, b = ab
  665. o = a - b < 0
  666. if o == True:
  667. return a
  668. elif o == False:
  669. return b
  670. return self.vradius
  671. def normal_lines(self, p, prec=None):
  672. """Normal lines between `p` and the ellipse.
  673. Parameters
  674. ==========
  675. p : Point
  676. Returns
  677. =======
  678. normal_lines : list with 1, 2 or 4 Lines
  679. Examples
  680. ========
  681. >>> from sympy import Point, Ellipse
  682. >>> e = Ellipse((0, 0), 2, 3)
  683. >>> c = e.center
  684. >>> e.normal_lines(c + Point(1, 0))
  685. [Line2D(Point2D(0, 0), Point2D(1, 0))]
  686. >>> e.normal_lines(c)
  687. [Line2D(Point2D(0, 0), Point2D(0, 1)), Line2D(Point2D(0, 0), Point2D(1, 0))]
  688. Off-axis points require the solution of a quartic equation. This
  689. often leads to very large expressions that may be of little practical
  690. use. An approximate solution of `prec` digits can be obtained by
  691. passing in the desired value:
  692. >>> e.normal_lines((3, 3), prec=2)
  693. [Line2D(Point2D(-0.81, -2.7), Point2D(0.19, -1.2)),
  694. Line2D(Point2D(1.5, -2.0), Point2D(2.5, -2.7))]
  695. Whereas the above solution has an operation count of 12, the exact
  696. solution has an operation count of 2020.
  697. """
  698. p = Point(p, dim=2)
  699. # XXX change True to something like self.angle == 0 if the arbitrarily
  700. # rotated ellipse is introduced.
  701. # https://github.com/sympy/sympy/issues/2815)
  702. if True:
  703. rv = []
  704. if p.x == self.center.x:
  705. rv.append(Line(self.center, slope=oo))
  706. if p.y == self.center.y:
  707. rv.append(Line(self.center, slope=0))
  708. if rv:
  709. # at these special orientations of p either 1 or 2 normals
  710. # exist and we are done
  711. return rv
  712. # find the 4 normal points and construct lines through them with
  713. # the corresponding slope
  714. eq = self.equation(x, y)
  715. dydx = idiff(eq, y, x)
  716. norm = -1/dydx
  717. slope = Line(p, (x, y)).slope
  718. seq = slope - norm
  719. # TODO: Replace solve with solveset, when this line is tested
  720. yis = solve(seq, y)[0]
  721. xeq = eq.subs(y, yis).as_numer_denom()[0].expand()
  722. if len(xeq.free_symbols) == 1:
  723. try:
  724. # this is so much faster, it's worth a try
  725. xsol = Poly(xeq, x).real_roots()
  726. except (DomainError, PolynomialError, NotImplementedError):
  727. # TODO: Replace solve with solveset, when these lines are tested
  728. xsol = _nsort(solve(xeq, x), separated=True)[0]
  729. points = [Point(i, solve(eq.subs(x, i), y)[0]) for i in xsol]
  730. else:
  731. raise NotImplementedError(
  732. 'intersections for the general ellipse are not supported')
  733. slopes = [norm.subs(zip((x, y), pt.args)) for pt in points]
  734. if prec is not None:
  735. points = [pt.n(prec) for pt in points]
  736. slopes = [i if _not_a_coeff(i) else i.n(prec) for i in slopes]
  737. return [Line(pt, slope=s) for pt, s in zip(points, slopes)]
  738. @property
  739. def periapsis(self):
  740. """The periapsis of the ellipse.
  741. The shortest distance between the focus and the contour.
  742. Returns
  743. =======
  744. periapsis : number
  745. See Also
  746. ========
  747. apoapsis : Returns greatest distance between focus and contour
  748. Examples
  749. ========
  750. >>> from sympy import Point, Ellipse
  751. >>> p1 = Point(0, 0)
  752. >>> e1 = Ellipse(p1, 3, 1)
  753. >>> e1.periapsis
  754. 3 - 2*sqrt(2)
  755. """
  756. return self.major * (1 - self.eccentricity)
  757. @property
  758. def semilatus_rectum(self):
  759. """
  760. Calculates the semi-latus rectum of the Ellipse.
  761. Semi-latus rectum is defined as one half of the chord through a
  762. focus parallel to the conic section directrix of a conic section.
  763. Returns
  764. =======
  765. semilatus_rectum : number
  766. See Also
  767. ========
  768. apoapsis : Returns greatest distance between focus and contour
  769. periapsis : The shortest distance between the focus and the contour
  770. Examples
  771. ========
  772. >>> from sympy import Point, Ellipse
  773. >>> p1 = Point(0, 0)
  774. >>> e1 = Ellipse(p1, 3, 1)
  775. >>> e1.semilatus_rectum
  776. 1/3
  777. References
  778. ==========
  779. .. [1] https://mathworld.wolfram.com/SemilatusRectum.html
  780. .. [2] https://en.wikipedia.org/wiki/Ellipse#Semi-latus_rectum
  781. """
  782. return self.major * (1 - self.eccentricity ** 2)
  783. def auxiliary_circle(self):
  784. """Returns a Circle whose diameter is the major axis of the ellipse.
  785. Examples
  786. ========
  787. >>> from sympy import Ellipse, Point, symbols
  788. >>> c = Point(1, 2)
  789. >>> Ellipse(c, 8, 7).auxiliary_circle()
  790. Circle(Point2D(1, 2), 8)
  791. >>> a, b = symbols('a b')
  792. >>> Ellipse(c, a, b).auxiliary_circle()
  793. Circle(Point2D(1, 2), Max(a, b))
  794. """
  795. return Circle(self.center, Max(self.hradius, self.vradius))
  796. def director_circle(self):
  797. """
  798. Returns a Circle consisting of all points where two perpendicular
  799. tangent lines to the ellipse cross each other.
  800. Returns
  801. =======
  802. Circle
  803. A director circle returned as a geometric object.
  804. Examples
  805. ========
  806. >>> from sympy import Ellipse, Point, symbols
  807. >>> c = Point(3,8)
  808. >>> Ellipse(c, 7, 9).director_circle()
  809. Circle(Point2D(3, 8), sqrt(130))
  810. >>> a, b = symbols('a b')
  811. >>> Ellipse(c, a, b).director_circle()
  812. Circle(Point2D(3, 8), sqrt(a**2 + b**2))
  813. References
  814. ==========
  815. .. [1] https://en.wikipedia.org/wiki/Director_circle
  816. """
  817. return Circle(self.center, sqrt(self.hradius**2 + self.vradius**2))
  818. def plot_interval(self, parameter='t'):
  819. """The plot interval for the default geometric plot of the Ellipse.
  820. Parameters
  821. ==========
  822. parameter : str, optional
  823. Default value is 't'.
  824. Returns
  825. =======
  826. plot_interval : list
  827. [parameter, lower_bound, upper_bound]
  828. Examples
  829. ========
  830. >>> from sympy import Point, Ellipse
  831. >>> e1 = Ellipse(Point(0, 0), 3, 2)
  832. >>> e1.plot_interval()
  833. [t, -pi, pi]
  834. """
  835. t = _symbol(parameter, real=True)
  836. return [t, -S.Pi, S.Pi]
  837. def random_point(self, seed=None):
  838. """A random point on the ellipse.
  839. Returns
  840. =======
  841. point : Point
  842. Examples
  843. ========
  844. >>> from sympy import Point, Ellipse
  845. >>> e1 = Ellipse(Point(0, 0), 3, 2)
  846. >>> e1.random_point() # gives some random point
  847. Point2D(...)
  848. >>> p1 = e1.random_point(seed=0); p1.n(2)
  849. Point2D(2.1, 1.4)
  850. Notes
  851. =====
  852. When creating a random point, one may simply replace the
  853. parameter with a random number. When doing so, however, the
  854. random number should be made a Rational or else the point
  855. may not test as being in the ellipse:
  856. >>> from sympy.abc import t
  857. >>> from sympy import Rational
  858. >>> arb = e1.arbitrary_point(t); arb
  859. Point2D(3*cos(t), 2*sin(t))
  860. >>> arb.subs(t, .1) in e1
  861. False
  862. >>> arb.subs(t, Rational(.1)) in e1
  863. True
  864. >>> arb.subs(t, Rational('.1')) in e1
  865. True
  866. See Also
  867. ========
  868. sympy.geometry.point.Point
  869. arbitrary_point : Returns parameterized point on ellipse
  870. """
  871. t = _symbol('t', real=True)
  872. x, y = self.arbitrary_point(t).args
  873. # get a random value in [-1, 1) corresponding to cos(t)
  874. # and confirm that it will test as being in the ellipse
  875. if seed is not None:
  876. rng = random.Random(seed)
  877. else:
  878. rng = random
  879. # simplify this now or else the Float will turn s into a Float
  880. r = Rational(rng.random())
  881. c = 2*r - 1
  882. s = sqrt(1 - c**2)
  883. return Point(x.subs(cos(t), c), y.subs(sin(t), s))
  884. def reflect(self, line):
  885. """Override GeometryEntity.reflect since the radius
  886. is not a GeometryEntity.
  887. Examples
  888. ========
  889. >>> from sympy import Circle, Line
  890. >>> Circle((0, 1), 1).reflect(Line((0, 0), (1, 1)))
  891. Circle(Point2D(1, 0), -1)
  892. >>> from sympy import Ellipse, Line, Point
  893. >>> Ellipse(Point(3, 4), 1, 3).reflect(Line(Point(0, -4), Point(5, 0)))
  894. Traceback (most recent call last):
  895. ...
  896. NotImplementedError:
  897. General Ellipse is not supported but the equation of the reflected
  898. Ellipse is given by the zeros of: f(x, y) = (9*x/41 + 40*y/41 +
  899. 37/41)**2 + (40*x/123 - 3*y/41 - 364/123)**2 - 1
  900. Notes
  901. =====
  902. Until the general ellipse (with no axis parallel to the x-axis) is
  903. supported a NotImplemented error is raised and the equation whose
  904. zeros define the rotated ellipse is given.
  905. """
  906. if line.slope in (0, oo):
  907. c = self.center
  908. c = c.reflect(line)
  909. return self.func(c, -self.hradius, self.vradius)
  910. else:
  911. x, y = [uniquely_named_symbol(
  912. name, (self, line), modify=lambda s: '_' + s, real=True)
  913. for name in 'xy']
  914. expr = self.equation(x, y)
  915. p = Point(x, y).reflect(line)
  916. result = expr.subs(zip((x, y), p.args
  917. ), simultaneous=True)
  918. raise NotImplementedError(filldedent(
  919. 'General Ellipse is not supported but the equation '
  920. 'of the reflected Ellipse is given by the zeros of: ' +
  921. "f(%s, %s) = %s" % (str(x), str(y), str(result))))
  922. def rotate(self, angle=0, pt=None):
  923. """Rotate ``angle`` radians counterclockwise about Point ``pt``.
  924. Note: since the general ellipse is not supported, only rotations that
  925. are integer multiples of pi/2 are allowed.
  926. Examples
  927. ========
  928. >>> from sympy import Ellipse, pi
  929. >>> Ellipse((1, 0), 2, 1).rotate(pi/2)
  930. Ellipse(Point2D(0, 1), 1, 2)
  931. >>> Ellipse((1, 0), 2, 1).rotate(pi)
  932. Ellipse(Point2D(-1, 0), 2, 1)
  933. """
  934. if self.hradius == self.vradius:
  935. return self.func(self.center.rotate(angle, pt), self.hradius)
  936. if (angle/S.Pi).is_integer:
  937. return super().rotate(angle, pt)
  938. if (2*angle/S.Pi).is_integer:
  939. return self.func(self.center.rotate(angle, pt), self.vradius, self.hradius)
  940. # XXX see https://github.com/sympy/sympy/issues/2815 for general ellipes
  941. raise NotImplementedError('Only rotations of pi/2 are currently supported for Ellipse.')
  942. def scale(self, x=1, y=1, pt=None):
  943. """Override GeometryEntity.scale since it is the major and minor
  944. axes which must be scaled and they are not GeometryEntities.
  945. Examples
  946. ========
  947. >>> from sympy import Ellipse
  948. >>> Ellipse((0, 0), 2, 1).scale(2, 4)
  949. Circle(Point2D(0, 0), 4)
  950. >>> Ellipse((0, 0), 2, 1).scale(2)
  951. Ellipse(Point2D(0, 0), 4, 1)
  952. """
  953. c = self.center
  954. if pt:
  955. pt = Point(pt, dim=2)
  956. return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
  957. h = self.hradius
  958. v = self.vradius
  959. return self.func(c.scale(x, y), hradius=h*x, vradius=v*y)
  960. def tangent_lines(self, p):
  961. """Tangent lines between `p` and the ellipse.
  962. If `p` is on the ellipse, returns the tangent line through point `p`.
  963. Otherwise, returns the tangent line(s) from `p` to the ellipse, or
  964. None if no tangent line is possible (e.g., `p` inside ellipse).
  965. Parameters
  966. ==========
  967. p : Point
  968. Returns
  969. =======
  970. tangent_lines : list with 1 or 2 Lines
  971. Raises
  972. ======
  973. NotImplementedError
  974. Can only find tangent lines for a point, `p`, on the ellipse.
  975. See Also
  976. ========
  977. sympy.geometry.point.Point, sympy.geometry.line.Line
  978. Examples
  979. ========
  980. >>> from sympy import Point, Ellipse
  981. >>> e1 = Ellipse(Point(0, 0), 3, 2)
  982. >>> e1.tangent_lines(Point(3, 0))
  983. [Line2D(Point2D(3, 0), Point2D(3, -12))]
  984. """
  985. p = Point(p, dim=2)
  986. if self.encloses_point(p):
  987. return []
  988. if p in self:
  989. delta = self.center - p
  990. rise = (self.vradius**2)*delta.x
  991. run = -(self.hradius**2)*delta.y
  992. p2 = Point(simplify(p.x + run),
  993. simplify(p.y + rise))
  994. return [Line(p, p2)]
  995. else:
  996. if len(self.foci) == 2:
  997. f1, f2 = self.foci
  998. maj = self.hradius
  999. test = (2*maj -
  1000. Point.distance(f1, p) -
  1001. Point.distance(f2, p))
  1002. else:
  1003. test = self.radius - Point.distance(self.center, p)
  1004. if test.is_number and test.is_positive:
  1005. return []
  1006. # else p is outside the ellipse or we can't tell. In case of the
  1007. # latter, the solutions returned will only be valid if
  1008. # the point is not inside the ellipse; if it is, nan will result.
  1009. eq = self.equation(x, y)
  1010. dydx = idiff(eq, y, x)
  1011. slope = Line(p, Point(x, y)).slope
  1012. # TODO: Replace solve with solveset, when this line is tested
  1013. tangent_points = solve([slope - dydx, eq], [x, y])
  1014. # handle horizontal and vertical tangent lines
  1015. if len(tangent_points) == 1:
  1016. if tangent_points[0][
  1017. 0] == p.x or tangent_points[0][1] == p.y:
  1018. return [Line(p, p + Point(1, 0)), Line(p, p + Point(0, 1))]
  1019. else:
  1020. return [Line(p, p + Point(0, 1)), Line(p, tangent_points[0])]
  1021. # others
  1022. return [Line(p, tangent_points[0]), Line(p, tangent_points[1])]
  1023. @property
  1024. def vradius(self):
  1025. """The vertical radius of the ellipse.
  1026. Returns
  1027. =======
  1028. vradius : number
  1029. See Also
  1030. ========
  1031. hradius, major, minor
  1032. Examples
  1033. ========
  1034. >>> from sympy import Point, Ellipse
  1035. >>> p1 = Point(0, 0)
  1036. >>> e1 = Ellipse(p1, 3, 1)
  1037. >>> e1.vradius
  1038. 1
  1039. """
  1040. return self.args[2]
  1041. def second_moment_of_area(self, point=None):
  1042. """Returns the second moment and product moment area of an ellipse.
  1043. Parameters
  1044. ==========
  1045. point : Point, two-tuple of sympifiable objects, or None(default=None)
  1046. point is the point about which second moment of area is to be found.
  1047. If "point=None" it will be calculated about the axis passing through the
  1048. centroid of the ellipse.
  1049. Returns
  1050. =======
  1051. I_xx, I_yy, I_xy : number or SymPy expression
  1052. I_xx, I_yy are second moment of area of an ellise.
  1053. I_xy is product moment of area of an ellipse.
  1054. Examples
  1055. ========
  1056. >>> from sympy import Point, Ellipse
  1057. >>> p1 = Point(0, 0)
  1058. >>> e1 = Ellipse(p1, 3, 1)
  1059. >>> e1.second_moment_of_area()
  1060. (3*pi/4, 27*pi/4, 0)
  1061. References
  1062. ==========
  1063. .. [1] https://en.wikipedia.org/wiki/List_of_second_moments_of_area
  1064. """
  1065. I_xx = (S.Pi*(self.hradius)*(self.vradius**3))/4
  1066. I_yy = (S.Pi*(self.hradius**3)*(self.vradius))/4
  1067. I_xy = 0
  1068. if point is None:
  1069. return I_xx, I_yy, I_xy
  1070. # parallel axis theorem
  1071. I_xx = I_xx + self.area*((point[1] - self.center.y)**2)
  1072. I_yy = I_yy + self.area*((point[0] - self.center.x)**2)
  1073. I_xy = I_xy + self.area*(point[0] - self.center.x)*(point[1] - self.center.y)
  1074. return I_xx, I_yy, I_xy
  1075. def polar_second_moment_of_area(self):
  1076. """Returns the polar second moment of area of an Ellipse
  1077. It is a constituent of the second moment of area, linked through
  1078. the perpendicular axis theorem. While the planar second moment of
  1079. area describes an object's resistance to deflection (bending) when
  1080. subjected to a force applied to a plane parallel to the central
  1081. axis, the polar second moment of area describes an object's
  1082. resistance to deflection when subjected to a moment applied in a
  1083. plane perpendicular to the object's central axis (i.e. parallel to
  1084. the cross-section)
  1085. Examples
  1086. ========
  1087. >>> from sympy import symbols, Circle, Ellipse
  1088. >>> c = Circle((5, 5), 4)
  1089. >>> c.polar_second_moment_of_area()
  1090. 128*pi
  1091. >>> a, b = symbols('a, b')
  1092. >>> e = Ellipse((0, 0), a, b)
  1093. >>> e.polar_second_moment_of_area()
  1094. pi*a**3*b/4 + pi*a*b**3/4
  1095. References
  1096. ==========
  1097. .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia
  1098. """
  1099. second_moment = self.second_moment_of_area()
  1100. return second_moment[0] + second_moment[1]
  1101. def section_modulus(self, point=None):
  1102. """Returns a tuple with the section modulus of an ellipse
  1103. Section modulus is a geometric property of an ellipse defined as the
  1104. ratio of second moment of area to the distance of the extreme end of
  1105. the ellipse from the centroidal axis.
  1106. Parameters
  1107. ==========
  1108. point : Point, two-tuple of sympifyable objects, or None(default=None)
  1109. point is the point at which section modulus is to be found.
  1110. If "point=None" section modulus will be calculated for the
  1111. point farthest from the centroidal axis of the ellipse.
  1112. Returns
  1113. =======
  1114. S_x, S_y: numbers or SymPy expressions
  1115. S_x is the section modulus with respect to the x-axis
  1116. S_y is the section modulus with respect to the y-axis
  1117. A negative sign indicates that the section modulus is
  1118. determined for a point below the centroidal axis.
  1119. Examples
  1120. ========
  1121. >>> from sympy import Symbol, Ellipse, Circle, Point2D
  1122. >>> d = Symbol('d', positive=True)
  1123. >>> c = Circle((0, 0), d/2)
  1124. >>> c.section_modulus()
  1125. (pi*d**3/32, pi*d**3/32)
  1126. >>> e = Ellipse(Point2D(0, 0), 2, 4)
  1127. >>> e.section_modulus()
  1128. (8*pi, 4*pi)
  1129. >>> e.section_modulus((2, 2))
  1130. (16*pi, 4*pi)
  1131. References
  1132. ==========
  1133. .. [1] https://en.wikipedia.org/wiki/Section_modulus
  1134. """
  1135. x_c, y_c = self.center
  1136. if point is None:
  1137. # taking x and y as maximum distances from centroid
  1138. x_min, y_min, x_max, y_max = self.bounds
  1139. y = max(y_c - y_min, y_max - y_c)
  1140. x = max(x_c - x_min, x_max - x_c)
  1141. else:
  1142. # taking x and y as distances of the given point from the center
  1143. point = Point2D(point)
  1144. y = point.y - y_c
  1145. x = point.x - x_c
  1146. second_moment = self.second_moment_of_area()
  1147. S_x = second_moment[0]/y
  1148. S_y = second_moment[1]/x
  1149. return S_x, S_y
  1150. class Circle(Ellipse):
  1151. """A circle in space.
  1152. Constructed simply from a center and a radius, from three
  1153. non-collinear points, or the equation of a circle.
  1154. Parameters
  1155. ==========
  1156. center : Point
  1157. radius : number or SymPy expression
  1158. points : sequence of three Points
  1159. equation : equation of a circle
  1160. Attributes
  1161. ==========
  1162. radius (synonymous with hradius, vradius, major and minor)
  1163. circumference
  1164. equation
  1165. Raises
  1166. ======
  1167. GeometryError
  1168. When the given equation is not that of a circle.
  1169. When trying to construct circle from incorrect parameters.
  1170. See Also
  1171. ========
  1172. Ellipse, sympy.geometry.point.Point
  1173. Examples
  1174. ========
  1175. >>> from sympy import Point, Circle, Eq
  1176. >>> from sympy.abc import x, y, a, b
  1177. A circle constructed from a center and radius:
  1178. >>> c1 = Circle(Point(0, 0), 5)
  1179. >>> c1.hradius, c1.vradius, c1.radius
  1180. (5, 5, 5)
  1181. A circle constructed from three points:
  1182. >>> c2 = Circle(Point(0, 0), Point(1, 1), Point(1, 0))
  1183. >>> c2.hradius, c2.vradius, c2.radius, c2.center
  1184. (sqrt(2)/2, sqrt(2)/2, sqrt(2)/2, Point2D(1/2, 1/2))
  1185. A circle can be constructed from an equation in the form
  1186. `a*x**2 + by**2 + gx + hy + c = 0`, too:
  1187. >>> Circle(x**2 + y**2 - 25)
  1188. Circle(Point2D(0, 0), 5)
  1189. If the variables corresponding to x and y are named something
  1190. else, their name or symbol can be supplied:
  1191. >>> Circle(Eq(a**2 + b**2, 25), x='a', y=b)
  1192. Circle(Point2D(0, 0), 5)
  1193. """
  1194. def __new__(cls, *args, **kwargs):
  1195. evaluate = kwargs.get('evaluate', global_parameters.evaluate)
  1196. if len(args) == 1 and isinstance(args[0], (Expr, Eq)):
  1197. x = kwargs.get('x', 'x')
  1198. y = kwargs.get('y', 'y')
  1199. equation = args[0].expand()
  1200. if isinstance(equation, Eq):
  1201. equation = equation.lhs - equation.rhs
  1202. x = find(x, equation)
  1203. y = find(y, equation)
  1204. try:
  1205. a, b, c, d, e = linear_coeffs(equation, x**2, y**2, x, y)
  1206. except ValueError:
  1207. raise GeometryError("The given equation is not that of a circle.")
  1208. if S.Zero in (a, b) or a != b:
  1209. raise GeometryError("The given equation is not that of a circle.")
  1210. center_x = -c/a/2
  1211. center_y = -d/b/2
  1212. r2 = (center_x**2) + (center_y**2) - e/a
  1213. return Circle((center_x, center_y), sqrt(r2), evaluate=evaluate)
  1214. else:
  1215. c, r = None, None
  1216. if len(args) == 3:
  1217. args = [Point(a, dim=2, evaluate=evaluate) for a in args]
  1218. t = Triangle(*args)
  1219. if not isinstance(t, Triangle):
  1220. return t
  1221. c = t.circumcenter
  1222. r = t.circumradius
  1223. elif len(args) == 2:
  1224. # Assume (center, radius) pair
  1225. c = Point(args[0], dim=2, evaluate=evaluate)
  1226. r = args[1]
  1227. # this will prohibit imaginary radius
  1228. try:
  1229. r = Point(r, 0, evaluate=evaluate).x
  1230. except ValueError:
  1231. raise GeometryError("Circle with imaginary radius is not permitted")
  1232. if not (c is None or r is None):
  1233. if r == 0:
  1234. return c
  1235. return GeometryEntity.__new__(cls, c, r, **kwargs)
  1236. raise GeometryError("Circle.__new__ received unknown arguments")
  1237. def _eval_evalf(self, prec=15, **options):
  1238. pt, r = self.args
  1239. dps = prec_to_dps(prec)
  1240. pt = pt.evalf(n=dps, **options)
  1241. r = r.evalf(n=dps, **options)
  1242. return self.func(pt, r, evaluate=False)
  1243. @property
  1244. def circumference(self):
  1245. """The circumference of the circle.
  1246. Returns
  1247. =======
  1248. circumference : number or SymPy expression
  1249. Examples
  1250. ========
  1251. >>> from sympy import Point, Circle
  1252. >>> c1 = Circle(Point(3, 4), 6)
  1253. >>> c1.circumference
  1254. 12*pi
  1255. """
  1256. return 2 * S.Pi * self.radius
  1257. def equation(self, x='x', y='y'):
  1258. """The equation of the circle.
  1259. Parameters
  1260. ==========
  1261. x : str or Symbol, optional
  1262. Default value is 'x'.
  1263. y : str or Symbol, optional
  1264. Default value is 'y'.
  1265. Returns
  1266. =======
  1267. equation : SymPy expression
  1268. Examples
  1269. ========
  1270. >>> from sympy import Point, Circle
  1271. >>> c1 = Circle(Point(0, 0), 5)
  1272. >>> c1.equation()
  1273. x**2 + y**2 - 25
  1274. """
  1275. x = _symbol(x, real=True)
  1276. y = _symbol(y, real=True)
  1277. t1 = (x - self.center.x)**2
  1278. t2 = (y - self.center.y)**2
  1279. return t1 + t2 - self.major**2
  1280. def intersection(self, o):
  1281. """The intersection of this circle with another geometrical entity.
  1282. Parameters
  1283. ==========
  1284. o : GeometryEntity
  1285. Returns
  1286. =======
  1287. intersection : list of GeometryEntities
  1288. Examples
  1289. ========
  1290. >>> from sympy import Point, Circle, Line, Ray
  1291. >>> p1, p2, p3 = Point(0, 0), Point(5, 5), Point(6, 0)
  1292. >>> p4 = Point(5, 0)
  1293. >>> c1 = Circle(p1, 5)
  1294. >>> c1.intersection(p2)
  1295. []
  1296. >>> c1.intersection(p4)
  1297. [Point2D(5, 0)]
  1298. >>> c1.intersection(Ray(p1, p2))
  1299. [Point2D(5*sqrt(2)/2, 5*sqrt(2)/2)]
  1300. >>> c1.intersection(Line(p2, p3))
  1301. []
  1302. """
  1303. return Ellipse.intersection(self, o)
  1304. @property
  1305. def radius(self):
  1306. """The radius of the circle.
  1307. Returns
  1308. =======
  1309. radius : number or SymPy expression
  1310. See Also
  1311. ========
  1312. Ellipse.major, Ellipse.minor, Ellipse.hradius, Ellipse.vradius
  1313. Examples
  1314. ========
  1315. >>> from sympy import Point, Circle
  1316. >>> c1 = Circle(Point(3, 4), 6)
  1317. >>> c1.radius
  1318. 6
  1319. """
  1320. return self.args[1]
  1321. def reflect(self, line):
  1322. """Override GeometryEntity.reflect since the radius
  1323. is not a GeometryEntity.
  1324. Examples
  1325. ========
  1326. >>> from sympy import Circle, Line
  1327. >>> Circle((0, 1), 1).reflect(Line((0, 0), (1, 1)))
  1328. Circle(Point2D(1, 0), -1)
  1329. """
  1330. c = self.center
  1331. c = c.reflect(line)
  1332. return self.func(c, -self.radius)
  1333. def scale(self, x=1, y=1, pt=None):
  1334. """Override GeometryEntity.scale since the radius
  1335. is not a GeometryEntity.
  1336. Examples
  1337. ========
  1338. >>> from sympy import Circle
  1339. >>> Circle((0, 0), 1).scale(2, 2)
  1340. Circle(Point2D(0, 0), 2)
  1341. >>> Circle((0, 0), 1).scale(2, 4)
  1342. Ellipse(Point2D(0, 0), 2, 4)
  1343. """
  1344. c = self.center
  1345. if pt:
  1346. pt = Point(pt, dim=2)
  1347. return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
  1348. c = c.scale(x, y)
  1349. x, y = [abs(i) for i in (x, y)]
  1350. if x == y:
  1351. return self.func(c, x*self.radius)
  1352. h = v = self.radius
  1353. return Ellipse(c, hradius=h*x, vradius=v*y)
  1354. @property
  1355. def vradius(self):
  1356. """
  1357. This Ellipse property is an alias for the Circle's radius.
  1358. Whereas hradius, major and minor can use Ellipse's conventions,
  1359. the vradius does not exist for a circle. It is always a positive
  1360. value in order that the Circle, like Polygons, will have an
  1361. area that can be positive or negative as determined by the sign
  1362. of the hradius.
  1363. Examples
  1364. ========
  1365. >>> from sympy import Point, Circle
  1366. >>> c1 = Circle(Point(3, 4), 6)
  1367. >>> c1.vradius
  1368. 6
  1369. """
  1370. return abs(self.radius)
  1371. from .polygon import Polygon, Triangle