ellipse.py 49 KB

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