ellipse.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782
  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. class Ellipse(GeometrySet):
  32. """An elliptical GeometryEntity.
  33. Parameters
  34. ==========
  35. center : Point, optional
  36. Default value is Point(0, 0)
  37. hradius : number or SymPy expression, optional
  38. vradius : number or SymPy expression, optional
  39. eccentricity : number or SymPy expression, optional
  40. Two of `hradius`, `vradius` and `eccentricity` must be supplied to
  41. create an Ellipse. The third is derived from the two supplied.
  42. Attributes
  43. ==========
  44. center
  45. hradius
  46. vradius
  47. area
  48. circumference
  49. eccentricity
  50. periapsis
  51. apoapsis
  52. focus_distance
  53. foci
  54. Raises
  55. ======
  56. GeometryError
  57. When `hradius`, `vradius` and `eccentricity` are incorrectly supplied
  58. as parameters.
  59. TypeError
  60. When `center` is not a Point.
  61. See Also
  62. ========
  63. Circle
  64. Notes
  65. -----
  66. Constructed from a center and two radii, the first being the horizontal
  67. radius (along the x-axis) and the second being the vertical radius (along
  68. the y-axis).
  69. When symbolic value for hradius and vradius are used, any calculation that
  70. refers to the foci or the major or minor axis will assume that the ellipse
  71. has its major radius on the x-axis. If this is not true then a manual
  72. rotation is necessary.
  73. Examples
  74. ========
  75. >>> from sympy import Ellipse, Point, Rational
  76. >>> e1 = Ellipse(Point(0, 0), 5, 1)
  77. >>> e1.hradius, e1.vradius
  78. (5, 1)
  79. >>> e2 = Ellipse(Point(3, 1), hradius=3, eccentricity=Rational(4, 5))
  80. >>> e2
  81. Ellipse(Point2D(3, 1), 3, 9/5)
  82. """
  83. def __contains__(self, o):
  84. if isinstance(o, Point):
  85. x = Dummy('x', real=True)
  86. y = Dummy('y', real=True)
  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. eccentricity = sympify(eccentricity)
  104. if center is None:
  105. center = Point(0, 0)
  106. else:
  107. center = Point(center, dim=2)
  108. if len(center) != 2:
  109. raise ValueError('The center of "{}" must be a two dimensional point'.format(cls))
  110. if len(list(filter(lambda x: x is not None, (hradius, vradius, eccentricity)))) != 2:
  111. raise ValueError(filldedent('''
  112. Exactly two arguments of "hradius", "vradius", and
  113. "eccentricity" must not be None.'''))
  114. if eccentricity is not None:
  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#Equation_of_a_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. x = Dummy('x', real=True)
  505. y = Dummy('y', real=True)
  506. if isinstance(o, Point):
  507. if o in self:
  508. return [o]
  509. else:
  510. return []
  511. elif isinstance(o, (Segment2D, Ray2D)):
  512. ellipse_equation = self.equation(x, y)
  513. result = solve([ellipse_equation, Line(o.points[0], o.points[1]).equation(x, y)], [x, y])
  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([ellipse_equation, o.equation(x, y)], [x, y])]))
  523. elif isinstance(o, LinearEntity3D):
  524. raise TypeError('Entity must be two dimensional, not three dimensional')
  525. else:
  526. raise TypeError('Intersection not handled for %s' % func_name(o))
  527. def is_tangent(self, o):
  528. """Is `o` tangent to the ellipse?
  529. Parameters
  530. ==========
  531. o : GeometryEntity
  532. An Ellipse, LinearEntity or Polygon
  533. Raises
  534. ======
  535. NotImplementedError
  536. When the wrong type of argument is supplied.
  537. Returns
  538. =======
  539. is_tangent: boolean
  540. True if o is tangent to the ellipse, False otherwise.
  541. See Also
  542. ========
  543. tangent_lines
  544. Examples
  545. ========
  546. >>> from sympy import Point, Ellipse, Line
  547. >>> p0, p1, p2 = Point(0, 0), Point(3, 0), Point(3, 3)
  548. >>> e1 = Ellipse(p0, 3, 2)
  549. >>> l1 = Line(p1, p2)
  550. >>> e1.is_tangent(l1)
  551. True
  552. """
  553. if isinstance(o, Point2D):
  554. return False
  555. elif isinstance(o, Ellipse):
  556. intersect = self.intersection(o)
  557. if isinstance(intersect, Ellipse):
  558. return True
  559. elif intersect:
  560. return all((self.tangent_lines(i)[0]).equals(o.tangent_lines(i)[0]) for i in intersect)
  561. else:
  562. return False
  563. elif isinstance(o, Line2D):
  564. hit = self.intersection(o)
  565. if not hit:
  566. return False
  567. if len(hit) == 1:
  568. return True
  569. # might return None if it can't decide
  570. return hit[0].equals(hit[1])
  571. elif isinstance(o, Ray2D):
  572. intersect = self.intersection(o)
  573. if len(intersect) == 1:
  574. return intersect[0] != o.source and not self.encloses_point(o.source)
  575. else:
  576. return False
  577. elif isinstance(o, (Segment2D, Polygon)):
  578. all_tangents = False
  579. segments = o.sides if isinstance(o, Polygon) else [o]
  580. for segment in segments:
  581. intersect = self.intersection(segment)
  582. if len(intersect) == 1:
  583. if not any(intersect[0] in i for i in segment.points) \
  584. and not any(self.encloses_point(i) for i in segment.points):
  585. all_tangents = True
  586. continue
  587. else:
  588. return False
  589. else:
  590. return all_tangents
  591. return all_tangents
  592. elif isinstance(o, (LinearEntity3D, Point3D)):
  593. raise TypeError('Entity must be two dimensional, not three dimensional')
  594. else:
  595. raise TypeError('Is_tangent not handled for %s' % func_name(o))
  596. @property
  597. def major(self):
  598. """Longer axis of the ellipse (if it can be determined) else hradius.
  599. Returns
  600. =======
  601. major : number or expression
  602. See Also
  603. ========
  604. hradius, vradius, minor
  605. Examples
  606. ========
  607. >>> from sympy import Point, Ellipse, Symbol
  608. >>> p1 = Point(0, 0)
  609. >>> e1 = Ellipse(p1, 3, 1)
  610. >>> e1.major
  611. 3
  612. >>> a = Symbol('a')
  613. >>> b = Symbol('b')
  614. >>> Ellipse(p1, a, b).major
  615. a
  616. >>> Ellipse(p1, b, a).major
  617. b
  618. >>> m = Symbol('m')
  619. >>> M = m + 1
  620. >>> Ellipse(p1, m, M).major
  621. m + 1
  622. """
  623. ab = self.args[1:3]
  624. if len(ab) == 1:
  625. return ab[0]
  626. a, b = ab
  627. o = b - a < 0
  628. if o == True:
  629. return a
  630. elif o == False:
  631. return b
  632. return self.hradius
  633. @property
  634. def minor(self):
  635. """Shorter axis of the ellipse (if it can be determined) else vradius.
  636. Returns
  637. =======
  638. minor : number or expression
  639. See Also
  640. ========
  641. hradius, vradius, major
  642. Examples
  643. ========
  644. >>> from sympy import Point, Ellipse, Symbol
  645. >>> p1 = Point(0, 0)
  646. >>> e1 = Ellipse(p1, 3, 1)
  647. >>> e1.minor
  648. 1
  649. >>> a = Symbol('a')
  650. >>> b = Symbol('b')
  651. >>> Ellipse(p1, a, b).minor
  652. b
  653. >>> Ellipse(p1, b, a).minor
  654. a
  655. >>> m = Symbol('m')
  656. >>> M = m + 1
  657. >>> Ellipse(p1, m, M).minor
  658. m
  659. """
  660. ab = self.args[1:3]
  661. if len(ab) == 1:
  662. return ab[0]
  663. a, b = ab
  664. o = a - b < 0
  665. if o == True:
  666. return a
  667. elif o == False:
  668. return b
  669. return self.vradius
  670. def normal_lines(self, p, prec=None):
  671. """Normal lines between `p` and the ellipse.
  672. Parameters
  673. ==========
  674. p : Point
  675. Returns
  676. =======
  677. normal_lines : list with 1, 2 or 4 Lines
  678. Examples
  679. ========
  680. >>> from sympy import Point, Ellipse
  681. >>> e = Ellipse((0, 0), 2, 3)
  682. >>> c = e.center
  683. >>> e.normal_lines(c + Point(1, 0))
  684. [Line2D(Point2D(0, 0), Point2D(1, 0))]
  685. >>> e.normal_lines(c)
  686. [Line2D(Point2D(0, 0), Point2D(0, 1)), Line2D(Point2D(0, 0), Point2D(1, 0))]
  687. Off-axis points require the solution of a quartic equation. This
  688. often leads to very large expressions that may be of little practical
  689. use. An approximate solution of `prec` digits can be obtained by
  690. passing in the desired value:
  691. >>> e.normal_lines((3, 3), prec=2)
  692. [Line2D(Point2D(-0.81, -2.7), Point2D(0.19, -1.2)),
  693. Line2D(Point2D(1.5, -2.0), Point2D(2.5, -2.7))]
  694. Whereas the above solution has an operation count of 12, the exact
  695. solution has an operation count of 2020.
  696. """
  697. p = Point(p, dim=2)
  698. # XXX change True to something like self.angle == 0 if the arbitrarily
  699. # rotated ellipse is introduced.
  700. # https://github.com/sympy/sympy/issues/2815)
  701. if True:
  702. rv = []
  703. if p.x == self.center.x:
  704. rv.append(Line(self.center, slope=oo))
  705. if p.y == self.center.y:
  706. rv.append(Line(self.center, slope=0))
  707. if rv:
  708. # at these special orientations of p either 1 or 2 normals
  709. # exist and we are done
  710. return rv
  711. # find the 4 normal points and construct lines through them with
  712. # the corresponding slope
  713. x, y = Dummy('x', real=True), Dummy('y', real=True)
  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] http://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. x, y = Dummy('x'), Dummy('y')
  1010. eq = self.equation(x, y)
  1011. dydx = idiff(eq, y, x)
  1012. slope = Line(p, Point(x, y)).slope
  1013. # TODO: Replace solve with solveset, when this line is tested
  1014. tangent_points = solve([slope - dydx, eq], [x, y])
  1015. # handle horizontal and vertical tangent lines
  1016. if len(tangent_points) == 1:
  1017. if tangent_points[0][
  1018. 0] == p.x or tangent_points[0][1] == p.y:
  1019. return [Line(p, p + Point(1, 0)), Line(p, p + Point(0, 1))]
  1020. else:
  1021. return [Line(p, p + Point(0, 1)), Line(p, tangent_points[0])]
  1022. # others
  1023. return [Line(p, tangent_points[0]), Line(p, tangent_points[1])]
  1024. @property
  1025. def vradius(self):
  1026. """The vertical radius of the ellipse.
  1027. Returns
  1028. =======
  1029. vradius : number
  1030. See Also
  1031. ========
  1032. hradius, major, minor
  1033. Examples
  1034. ========
  1035. >>> from sympy import Point, Ellipse
  1036. >>> p1 = Point(0, 0)
  1037. >>> e1 = Ellipse(p1, 3, 1)
  1038. >>> e1.vradius
  1039. 1
  1040. """
  1041. return self.args[2]
  1042. def second_moment_of_area(self, point=None):
  1043. """Returns the second moment and product moment area of an ellipse.
  1044. Parameters
  1045. ==========
  1046. point : Point, two-tuple of sympifiable objects, or None(default=None)
  1047. point is the point about which second moment of area is to be found.
  1048. If "point=None" it will be calculated about the axis passing through the
  1049. centroid of the ellipse.
  1050. Returns
  1051. =======
  1052. I_xx, I_yy, I_xy : number or SymPy expression
  1053. I_xx, I_yy are second moment of area of an ellise.
  1054. I_xy is product moment of area of an ellipse.
  1055. Examples
  1056. ========
  1057. >>> from sympy import Point, Ellipse
  1058. >>> p1 = Point(0, 0)
  1059. >>> e1 = Ellipse(p1, 3, 1)
  1060. >>> e1.second_moment_of_area()
  1061. (3*pi/4, 27*pi/4, 0)
  1062. References
  1063. ==========
  1064. .. [1] https://en.wikipedia.org/wiki/List_of_second_moments_of_area
  1065. """
  1066. I_xx = (S.Pi*(self.hradius)*(self.vradius**3))/4
  1067. I_yy = (S.Pi*(self.hradius**3)*(self.vradius))/4
  1068. I_xy = 0
  1069. if point is None:
  1070. return I_xx, I_yy, I_xy
  1071. # parallel axis theorem
  1072. I_xx = I_xx + self.area*((point[1] - self.center.y)**2)
  1073. I_yy = I_yy + self.area*((point[0] - self.center.x)**2)
  1074. I_xy = I_xy + self.area*(point[0] - self.center.x)*(point[1] - self.center.y)
  1075. return I_xx, I_yy, I_xy
  1076. def polar_second_moment_of_area(self):
  1077. """Returns the polar second moment of area of an Ellipse
  1078. It is a constituent of the second moment of area, linked through
  1079. the perpendicular axis theorem. While the planar second moment of
  1080. area describes an object's resistance to deflection (bending) when
  1081. subjected to a force applied to a plane parallel to the central
  1082. axis, the polar second moment of area describes an object's
  1083. resistance to deflection when subjected to a moment applied in a
  1084. plane perpendicular to the object's central axis (i.e. parallel to
  1085. the cross-section)
  1086. Examples
  1087. ========
  1088. >>> from sympy import symbols, Circle, Ellipse
  1089. >>> c = Circle((5, 5), 4)
  1090. >>> c.polar_second_moment_of_area()
  1091. 128*pi
  1092. >>> a, b = symbols('a, b')
  1093. >>> e = Ellipse((0, 0), a, b)
  1094. >>> e.polar_second_moment_of_area()
  1095. pi*a**3*b/4 + pi*a*b**3/4
  1096. References
  1097. ==========
  1098. .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia
  1099. """
  1100. second_moment = self.second_moment_of_area()
  1101. return second_moment[0] + second_moment[1]
  1102. def section_modulus(self, point=None):
  1103. """Returns a tuple with the section modulus of an ellipse
  1104. Section modulus is a geometric property of an ellipse defined as the
  1105. ratio of second moment of area to the distance of the extreme end of
  1106. the ellipse from the centroidal axis.
  1107. Parameters
  1108. ==========
  1109. point : Point, two-tuple of sympifyable objects, or None(default=None)
  1110. point is the point at which section modulus is to be found.
  1111. If "point=None" section modulus will be calculated for the
  1112. point farthest from the centroidal axis of the ellipse.
  1113. Returns
  1114. =======
  1115. S_x, S_y: numbers or SymPy expressions
  1116. S_x is the section modulus with respect to the x-axis
  1117. S_y is the section modulus with respect to the y-axis
  1118. A negative sign indicates that the section modulus is
  1119. determined for a point below the centroidal axis.
  1120. Examples
  1121. ========
  1122. >>> from sympy import Symbol, Ellipse, Circle, Point2D
  1123. >>> d = Symbol('d', positive=True)
  1124. >>> c = Circle((0, 0), d/2)
  1125. >>> c.section_modulus()
  1126. (pi*d**3/32, pi*d**3/32)
  1127. >>> e = Ellipse(Point2D(0, 0), 2, 4)
  1128. >>> e.section_modulus()
  1129. (8*pi, 4*pi)
  1130. >>> e.section_modulus((2, 2))
  1131. (16*pi, 4*pi)
  1132. References
  1133. ==========
  1134. .. [1] https://en.wikipedia.org/wiki/Section_modulus
  1135. """
  1136. x_c, y_c = self.center
  1137. if point is None:
  1138. # taking x and y as maximum distances from centroid
  1139. x_min, y_min, x_max, y_max = self.bounds
  1140. y = max(y_c - y_min, y_max - y_c)
  1141. x = max(x_c - x_min, x_max - x_c)
  1142. else:
  1143. # taking x and y as distances of the given point from the center
  1144. point = Point2D(point)
  1145. y = point.y - y_c
  1146. x = point.x - x_c
  1147. second_moment = self.second_moment_of_area()
  1148. S_x = second_moment[0]/y
  1149. S_y = second_moment[1]/x
  1150. return S_x, S_y
  1151. class Circle(Ellipse):
  1152. """A circle in space.
  1153. Constructed simply from a center and a radius, from three
  1154. non-collinear points, or the equation of a circle.
  1155. Parameters
  1156. ==========
  1157. center : Point
  1158. radius : number or SymPy expression
  1159. points : sequence of three Points
  1160. equation : equation of a circle
  1161. Attributes
  1162. ==========
  1163. radius (synonymous with hradius, vradius, major and minor)
  1164. circumference
  1165. equation
  1166. Raises
  1167. ======
  1168. GeometryError
  1169. When the given equation is not that of a circle.
  1170. When trying to construct circle from incorrect parameters.
  1171. See Also
  1172. ========
  1173. Ellipse, sympy.geometry.point.Point
  1174. Examples
  1175. ========
  1176. >>> from sympy import Point, Circle, Eq
  1177. >>> from sympy.abc import x, y, a, b
  1178. A circle constructed from a center and radius:
  1179. >>> c1 = Circle(Point(0, 0), 5)
  1180. >>> c1.hradius, c1.vradius, c1.radius
  1181. (5, 5, 5)
  1182. A circle constructed from three points:
  1183. >>> c2 = Circle(Point(0, 0), Point(1, 1), Point(1, 0))
  1184. >>> c2.hradius, c2.vradius, c2.radius, c2.center
  1185. (sqrt(2)/2, sqrt(2)/2, sqrt(2)/2, Point2D(1/2, 1/2))
  1186. A circle can be constructed from an equation in the form
  1187. `a*x**2 + by**2 + gx + hy + c = 0`, too:
  1188. >>> Circle(x**2 + y**2 - 25)
  1189. Circle(Point2D(0, 0), 5)
  1190. If the variables corresponding to x and y are named something
  1191. else, their name or symbol can be supplied:
  1192. >>> Circle(Eq(a**2 + b**2, 25), x='a', y=b)
  1193. Circle(Point2D(0, 0), 5)
  1194. """
  1195. def __new__(cls, *args, **kwargs):
  1196. evaluate = kwargs.get('evaluate', global_parameters.evaluate)
  1197. if len(args) == 1 and isinstance(args[0], (Expr, Eq)):
  1198. x = kwargs.get('x', 'x')
  1199. y = kwargs.get('y', 'y')
  1200. equation = args[0]
  1201. if isinstance(equation, Eq):
  1202. equation = equation.lhs - equation.rhs
  1203. x = find(x, equation)
  1204. y = find(y, equation)
  1205. try:
  1206. a, b, c, d, e = linear_coeffs(equation, x**2, y**2, x, y)
  1207. except ValueError:
  1208. raise GeometryError("The given equation is not that of a circle.")
  1209. if S.Zero in (a, b) or a != b:
  1210. raise GeometryError("The given equation is not that of a circle.")
  1211. center_x = -c/a/2
  1212. center_y = -d/b/2
  1213. r2 = (center_x**2) + (center_y**2) - e
  1214. return Circle((center_x, center_y), sqrt(r2), evaluate=evaluate)
  1215. else:
  1216. c, r = None, None
  1217. if len(args) == 3:
  1218. args = [Point(a, dim=2, evaluate=evaluate) for a in args]
  1219. t = Triangle(*args)
  1220. if not isinstance(t, Triangle):
  1221. return t
  1222. c = t.circumcenter
  1223. r = t.circumradius
  1224. elif len(args) == 2:
  1225. # Assume (center, radius) pair
  1226. c = Point(args[0], dim=2, evaluate=evaluate)
  1227. r = args[1]
  1228. # this will prohibit imaginary radius
  1229. try:
  1230. r = Point(r, 0, evaluate=evaluate).x
  1231. except ValueError:
  1232. raise GeometryError("Circle with imaginary radius is not permitted")
  1233. if not (c is None or r is None):
  1234. if r == 0:
  1235. return c
  1236. return GeometryEntity.__new__(cls, c, r, **kwargs)
  1237. raise GeometryError("Circle.__new__ received unknown arguments")
  1238. def _eval_evalf(self, prec=15, **options):
  1239. pt, r = self.args
  1240. dps = prec_to_dps(prec)
  1241. pt = pt.evalf(n=dps, **options)
  1242. r = r.evalf(n=dps, **options)
  1243. return self.func(pt, r, evaluate=False)
  1244. @property
  1245. def circumference(self):
  1246. """The circumference of the circle.
  1247. Returns
  1248. =======
  1249. circumference : number or SymPy expression
  1250. Examples
  1251. ========
  1252. >>> from sympy import Point, Circle
  1253. >>> c1 = Circle(Point(3, 4), 6)
  1254. >>> c1.circumference
  1255. 12*pi
  1256. """
  1257. return 2 * S.Pi * self.radius
  1258. def equation(self, x='x', y='y'):
  1259. """The equation of the circle.
  1260. Parameters
  1261. ==========
  1262. x : str or Symbol, optional
  1263. Default value is 'x'.
  1264. y : str or Symbol, optional
  1265. Default value is 'y'.
  1266. Returns
  1267. =======
  1268. equation : SymPy expression
  1269. Examples
  1270. ========
  1271. >>> from sympy import Point, Circle
  1272. >>> c1 = Circle(Point(0, 0), 5)
  1273. >>> c1.equation()
  1274. x**2 + y**2 - 25
  1275. """
  1276. x = _symbol(x, real=True)
  1277. y = _symbol(y, real=True)
  1278. t1 = (x - self.center.x)**2
  1279. t2 = (y - self.center.y)**2
  1280. return t1 + t2 - self.major**2
  1281. def intersection(self, o):
  1282. """The intersection of this circle with another geometrical entity.
  1283. Parameters
  1284. ==========
  1285. o : GeometryEntity
  1286. Returns
  1287. =======
  1288. intersection : list of GeometryEntities
  1289. Examples
  1290. ========
  1291. >>> from sympy import Point, Circle, Line, Ray
  1292. >>> p1, p2, p3 = Point(0, 0), Point(5, 5), Point(6, 0)
  1293. >>> p4 = Point(5, 0)
  1294. >>> c1 = Circle(p1, 5)
  1295. >>> c1.intersection(p2)
  1296. []
  1297. >>> c1.intersection(p4)
  1298. [Point2D(5, 0)]
  1299. >>> c1.intersection(Ray(p1, p2))
  1300. [Point2D(5*sqrt(2)/2, 5*sqrt(2)/2)]
  1301. >>> c1.intersection(Line(p2, p3))
  1302. []
  1303. """
  1304. return Ellipse.intersection(self, o)
  1305. @property
  1306. def radius(self):
  1307. """The radius of the circle.
  1308. Returns
  1309. =======
  1310. radius : number or SymPy expression
  1311. See Also
  1312. ========
  1313. Ellipse.major, Ellipse.minor, Ellipse.hradius, Ellipse.vradius
  1314. Examples
  1315. ========
  1316. >>> from sympy import Point, Circle
  1317. >>> c1 = Circle(Point(3, 4), 6)
  1318. >>> c1.radius
  1319. 6
  1320. """
  1321. return self.args[1]
  1322. def reflect(self, line):
  1323. """Override GeometryEntity.reflect since the radius
  1324. is not a GeometryEntity.
  1325. Examples
  1326. ========
  1327. >>> from sympy import Circle, Line
  1328. >>> Circle((0, 1), 1).reflect(Line((0, 0), (1, 1)))
  1329. Circle(Point2D(1, 0), -1)
  1330. """
  1331. c = self.center
  1332. c = c.reflect(line)
  1333. return self.func(c, -self.radius)
  1334. def scale(self, x=1, y=1, pt=None):
  1335. """Override GeometryEntity.scale since the radius
  1336. is not a GeometryEntity.
  1337. Examples
  1338. ========
  1339. >>> from sympy import Circle
  1340. >>> Circle((0, 0), 1).scale(2, 2)
  1341. Circle(Point2D(0, 0), 2)
  1342. >>> Circle((0, 0), 1).scale(2, 4)
  1343. Ellipse(Point2D(0, 0), 2, 4)
  1344. """
  1345. c = self.center
  1346. if pt:
  1347. pt = Point(pt, dim=2)
  1348. return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
  1349. c = c.scale(x, y)
  1350. x, y = [abs(i) for i in (x, y)]
  1351. if x == y:
  1352. return self.func(c, x*self.radius)
  1353. h = v = self.radius
  1354. return Ellipse(c, hradius=h*x, vradius=v*y)
  1355. @property
  1356. def vradius(self):
  1357. """
  1358. This Ellipse property is an alias for the Circle's radius.
  1359. Whereas hradius, major and minor can use Ellipse's conventions,
  1360. the vradius does not exist for a circle. It is always a positive
  1361. value in order that the Circle, like Polygons, will have an
  1362. area that can be positive or negative as determined by the sign
  1363. of the hradius.
  1364. Examples
  1365. ========
  1366. >>> from sympy import Point, Circle
  1367. >>> c1 = Circle(Point(3, 4), 6)
  1368. >>> c1.vradius
  1369. 6
  1370. """
  1371. return abs(self.radius)
  1372. from .polygon import Polygon, Triangle