123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883 |
- from sympy.core import Expr, S, oo, pi, sympify
- from sympy.core.evalf import N
- from sympy.core.sorting import default_sort_key, ordered
- from sympy.core.symbol import _symbol, Dummy, Symbol
- from sympy.functions.elementary.complexes import sign
- from sympy.functions.elementary.piecewise import Piecewise
- from sympy.functions.elementary.trigonometric import cos, sin, tan
- from .ellipse import Circle
- from .entity import GeometryEntity, GeometrySet
- from .exceptions import GeometryError
- from .line import Line, Segment, Ray
- from .point import Point
- from sympy.logic import And
- from sympy.matrices import Matrix
- from sympy.simplify.simplify import simplify
- from sympy.solvers.solvers import solve
- from sympy.utilities.iterables import has_dups, has_variety, uniq, rotate_left, least_rotation
- from sympy.utilities.misc import as_int, func_name
- from mpmath.libmp.libmpf import prec_to_dps
- import warnings
- x, y, T = [Dummy('polygon_dummy', real=True) for i in range(3)]
- class Polygon(GeometrySet):
- """A two-dimensional polygon.
- A simple polygon in space. Can be constructed from a sequence of points
- or from a center, radius, number of sides and rotation angle.
- Parameters
- ==========
- vertices
- A sequence of points.
- n : int, optional
- If $> 0$, an n-sided RegularPolygon is created.
- Default value is $0$.
- Attributes
- ==========
- area
- angles
- perimeter
- vertices
- centroid
- sides
- Raises
- ======
- GeometryError
- If all parameters are not Points.
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.line.Segment, Triangle
- Notes
- =====
- Polygons are treated as closed paths rather than 2D areas so
- some calculations can be be negative or positive (e.g., area)
- based on the orientation of the points.
- Any consecutive identical points are reduced to a single point
- and any points collinear and between two points will be removed
- unless they are needed to define an explicit intersection (see examples).
- A Triangle, Segment or Point will be returned when there are 3 or
- fewer points provided.
- Examples
- ========
- >>> from sympy import Polygon, pi
- >>> p1, p2, p3, p4, p5 = [(0, 0), (1, 0), (5, 1), (0, 1), (3, 0)]
- >>> Polygon(p1, p2, p3, p4)
- Polygon(Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1))
- >>> Polygon(p1, p2)
- Segment2D(Point2D(0, 0), Point2D(1, 0))
- >>> Polygon(p1, p2, p5)
- Segment2D(Point2D(0, 0), Point2D(3, 0))
- The area of a polygon is calculated as positive when vertices are
- traversed in a ccw direction. When the sides of a polygon cross the
- area will have positive and negative contributions. The following
- defines a Z shape where the bottom right connects back to the top
- left.
- >>> Polygon((0, 2), (2, 2), (0, 0), (2, 0)).area
- 0
- When the keyword `n` is used to define the number of sides of the
- Polygon then a RegularPolygon is created and the other arguments are
- interpreted as center, radius and rotation. The unrotated RegularPolygon
- will always have a vertex at Point(r, 0) where `r` is the radius of the
- circle that circumscribes the RegularPolygon. Its method `spin` can be
- used to increment that angle.
- >>> p = Polygon((0,0), 1, n=3)
- >>> p
- RegularPolygon(Point2D(0, 0), 1, 3, 0)
- >>> p.vertices[0]
- Point2D(1, 0)
- >>> p.args[0]
- Point2D(0, 0)
- >>> p.spin(pi/2)
- >>> p.vertices[0]
- Point2D(0, 1)
- """
- __slots__ = ()
- def __new__(cls, *args, n = 0, **kwargs):
- if n:
- args = list(args)
- # return a virtual polygon with n sides
- if len(args) == 2: # center, radius
- args.append(n)
- elif len(args) == 3: # center, radius, rotation
- args.insert(2, n)
- return RegularPolygon(*args, **kwargs)
- vertices = [Point(a, dim=2, **kwargs) for a in args]
- # remove consecutive duplicates
- nodup = []
- for p in vertices:
- if nodup and p == nodup[-1]:
- continue
- nodup.append(p)
- if len(nodup) > 1 and nodup[-1] == nodup[0]:
- nodup.pop() # last point was same as first
- # remove collinear points
- i = -3
- while i < len(nodup) - 3 and len(nodup) > 2:
- a, b, c = nodup[i], nodup[i + 1], nodup[i + 2]
- if Point.is_collinear(a, b, c):
- nodup.pop(i + 1)
- if a == c:
- nodup.pop(i)
- else:
- i += 1
- vertices = list(nodup)
- if len(vertices) > 3:
- return GeometryEntity.__new__(cls, *vertices, **kwargs)
- elif len(vertices) == 3:
- return Triangle(*vertices, **kwargs)
- elif len(vertices) == 2:
- return Segment(*vertices, **kwargs)
- else:
- return Point(*vertices, **kwargs)
- @property
- def area(self):
- """
- The area of the polygon.
- Notes
- =====
- The area calculation can be positive or negative based on the
- orientation of the points. If any side of the polygon crosses
- any other side, there will be areas having opposite signs.
- See Also
- ========
- sympy.geometry.ellipse.Ellipse.area
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.area
- 3
- In the Z shaped polygon (with the lower right connecting back
- to the upper left) the areas cancel out:
- >>> Z = Polygon((0, 1), (1, 1), (0, 0), (1, 0))
- >>> Z.area
- 0
- In the M shaped polygon, areas do not cancel because no side
- crosses any other (though there is a point of contact).
- >>> M = Polygon((0, 0), (0, 1), (2, 0), (3, 1), (3, 0))
- >>> M.area
- -3/2
- """
- area = 0
- args = self.args
- for i in range(len(args)):
- x1, y1 = args[i - 1].args
- x2, y2 = args[i].args
- area += x1*y2 - x2*y1
- return simplify(area) / 2
- @staticmethod
- def _isright(a, b, c):
- """Return True/False for cw/ccw orientation.
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> a, b, c = [Point(i) for i in [(0, 0), (1, 1), (1, 0)]]
- >>> Polygon._isright(a, b, c)
- True
- >>> Polygon._isright(a, c, b)
- False
- """
- ba = b - a
- ca = c - a
- t_area = simplify(ba.x*ca.y - ca.x*ba.y)
- res = t_area.is_nonpositive
- if res is None:
- raise ValueError("Can't determine orientation")
- return res
- @property
- def angles(self):
- """The internal angle at each vertex.
- Returns
- =======
- angles : dict
- A dictionary where each key is a vertex and each value is the
- internal angle at that vertex. The vertices are represented as
- Points.
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.line.LinearEntity.angle_between
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.angles[p1]
- pi/2
- >>> poly.angles[p2]
- acos(-4*sqrt(17)/17)
- """
- # Determine orientation of points
- args = self.vertices
- cw = self._isright(args[-1], args[0], args[1])
- ret = {}
- for i in range(len(args)):
- a, b, c = args[i - 2], args[i - 1], args[i]
- ang = Ray(b, a).angle_between(Ray(b, c))
- if cw ^ self._isright(a, b, c):
- ret[b] = 2*S.Pi - ang
- else:
- ret[b] = ang
- return ret
- @property
- def ambient_dimension(self):
- return self.vertices[0].ambient_dimension
- @property
- def perimeter(self):
- """The perimeter of the polygon.
- Returns
- =======
- perimeter : number or Basic instance
- See Also
- ========
- sympy.geometry.line.Segment.length
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.perimeter
- sqrt(17) + 7
- """
- p = 0
- args = self.vertices
- for i in range(len(args)):
- p += args[i - 1].distance(args[i])
- return simplify(p)
- @property
- def vertices(self):
- """The vertices of the polygon.
- Returns
- =======
- vertices : list of Points
- Notes
- =====
- When iterating over the vertices, it is more efficient to index self
- rather than to request the vertices and index them. Only use the
- vertices when you want to process all of them at once. This is even
- more important with RegularPolygons that calculate each vertex.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.vertices
- [Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)]
- >>> poly.vertices[0]
- Point2D(0, 0)
- """
- return list(self.args)
- @property
- def centroid(self):
- """The centroid of the polygon.
- Returns
- =======
- centroid : Point
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.util.centroid
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.centroid
- Point2D(31/18, 11/18)
- """
- A = 1/(6*self.area)
- cx, cy = 0, 0
- args = self.args
- for i in range(len(args)):
- x1, y1 = args[i - 1].args
- x2, y2 = args[i].args
- v = x1*y2 - x2*y1
- cx += v*(x1 + x2)
- cy += v*(y1 + y2)
- return Point(simplify(A*cx), simplify(A*cy))
- def second_moment_of_area(self, point=None):
- """Returns the second moment and product moment of area of a two dimensional polygon.
- Parameters
- ==========
- point : Point, two-tuple of sympifyable objects, or None(default=None)
- point is the point about which second moment of area is to be found.
- If "point=None" it will be calculated about the axis passing through the
- centroid of the polygon.
- Returns
- =======
- I_xx, I_yy, I_xy : number or SymPy expression
- I_xx, I_yy are second moment of area of a two dimensional polygon.
- I_xy is product moment of area of a two dimensional polygon.
- Examples
- ========
- >>> from sympy import Polygon, symbols
- >>> a, b = symbols('a, b')
- >>> p1, p2, p3, p4, p5 = [(0, 0), (a, 0), (a, b), (0, b), (a/3, b/3)]
- >>> rectangle = Polygon(p1, p2, p3, p4)
- >>> rectangle.second_moment_of_area()
- (a*b**3/12, a**3*b/12, 0)
- >>> rectangle.second_moment_of_area(p5)
- (a*b**3/9, a**3*b/9, a**2*b**2/36)
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Second_moment_of_area
- """
- I_xx, I_yy, I_xy = 0, 0, 0
- args = self.vertices
- for i in range(len(args)):
- x1, y1 = args[i-1].args
- x2, y2 = args[i].args
- v = x1*y2 - x2*y1
- I_xx += (y1**2 + y1*y2 + y2**2)*v
- I_yy += (x1**2 + x1*x2 + x2**2)*v
- I_xy += (x1*y2 + 2*x1*y1 + 2*x2*y2 + x2*y1)*v
- A = self.area
- c_x = self.centroid[0]
- c_y = self.centroid[1]
- # parallel axis theorem
- I_xx_c = (I_xx/12) - (A*(c_y**2))
- I_yy_c = (I_yy/12) - (A*(c_x**2))
- I_xy_c = (I_xy/24) - (A*(c_x*c_y))
- if point is None:
- return I_xx_c, I_yy_c, I_xy_c
- I_xx = (I_xx_c + A*((point[1]-c_y)**2))
- I_yy = (I_yy_c + A*((point[0]-c_x)**2))
- I_xy = (I_xy_c + A*((point[0]-c_x)*(point[1]-c_y)))
- return I_xx, I_yy, I_xy
- def first_moment_of_area(self, point=None):
- """
- Returns the first moment of area of a two-dimensional polygon with
- respect to a certain point of interest.
- First moment of area is a measure of the distribution of the area
- of a polygon in relation to an axis. The first moment of area of
- the entire polygon about its own centroid is always zero. Therefore,
- here it is calculated for an area, above or below a certain point
- of interest, that makes up a smaller portion of the polygon. This
- area is bounded by the point of interest and the extreme end
- (top or bottom) of the polygon. The first moment for this area is
- is then determined about the centroidal axis of the initial polygon.
- References
- ==========
- .. [1] https://skyciv.com/docs/tutorials/section-tutorials/calculating-the-statical-or-first-moment-of-area-of-beam-sections/?cc=BMD
- .. [2] https://mechanicalc.com/reference/cross-sections
- Parameters
- ==========
- point: Point, two-tuple of sympifyable objects, or None (default=None)
- point is the point above or below which the area of interest lies
- If ``point=None`` then the centroid acts as the point of interest.
- Returns
- =======
- Q_x, Q_y: number or SymPy expressions
- Q_x is the first moment of area about the x-axis
- Q_y is the first moment of area about the y-axis
- A negative sign indicates that the section modulus is
- determined for a section below (or left of) the centroidal axis
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> a, b = 50, 10
- >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)]
- >>> p = Polygon(p1, p2, p3, p4)
- >>> p.first_moment_of_area()
- (625, 3125)
- >>> p.first_moment_of_area(point=Point(30, 7))
- (525, 3000)
- """
- if point:
- xc, yc = self.centroid
- else:
- point = self.centroid
- xc, yc = point
- h_line = Line(point, slope=0)
- v_line = Line(point, slope=S.Infinity)
- h_poly = self.cut_section(h_line)
- v_poly = self.cut_section(v_line)
- poly_1 = h_poly[0] if h_poly[0].area <= h_poly[1].area else h_poly[1]
- poly_2 = v_poly[0] if v_poly[0].area <= v_poly[1].area else v_poly[1]
- Q_x = (poly_1.centroid.y - yc)*poly_1.area
- Q_y = (poly_2.centroid.x - xc)*poly_2.area
- return Q_x, Q_y
- def polar_second_moment_of_area(self):
- """Returns the polar modulus of a two-dimensional polygon
- It is a constituent of the second moment of area, linked through
- the perpendicular axis theorem. While the planar second moment of
- area describes an object's resistance to deflection (bending) when
- subjected to a force applied to a plane parallel to the central
- axis, the polar second moment of area describes an object's
- resistance to deflection when subjected to a moment applied in a
- plane perpendicular to the object's central axis (i.e. parallel to
- the cross-section)
- Examples
- ========
- >>> from sympy import Polygon, symbols
- >>> a, b = symbols('a, b')
- >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b))
- >>> rectangle.polar_second_moment_of_area()
- a**3*b/12 + a*b**3/12
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia
- """
- second_moment = self.second_moment_of_area()
- return second_moment[0] + second_moment[1]
- def section_modulus(self, point=None):
- """Returns a tuple with the section modulus of a two-dimensional
- polygon.
- Section modulus is a geometric property of a polygon defined as the
- ratio of second moment of area to the distance of the extreme end of
- the polygon from the centroidal axis.
- Parameters
- ==========
- point : Point, two-tuple of sympifyable objects, or None(default=None)
- point is the point at which section modulus is to be found.
- If "point=None" it will be calculated for the point farthest from the
- centroidal axis of the polygon.
- Returns
- =======
- S_x, S_y: numbers or SymPy expressions
- S_x is the section modulus with respect to the x-axis
- S_y is the section modulus with respect to the y-axis
- A negative sign indicates that the section modulus is
- determined for a point below the centroidal axis
- Examples
- ========
- >>> from sympy import symbols, Polygon, Point
- >>> a, b = symbols('a, b', positive=True)
- >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b))
- >>> rectangle.section_modulus()
- (a*b**2/6, a**2*b/6)
- >>> rectangle.section_modulus(Point(a/4, b/4))
- (-a*b**2/3, -a**2*b/3)
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Section_modulus
- """
- x_c, y_c = self.centroid
- if point is None:
- # taking x and y as maximum distances from centroid
- x_min, y_min, x_max, y_max = self.bounds
- y = max(y_c - y_min, y_max - y_c)
- x = max(x_c - x_min, x_max - x_c)
- else:
- # taking x and y as distances of the given point from the centroid
- y = point.y - y_c
- x = point.x - x_c
- second_moment= self.second_moment_of_area()
- S_x = second_moment[0]/y
- S_y = second_moment[1]/x
- return S_x, S_y
- @property
- def sides(self):
- """The directed line segments that form the sides of the polygon.
- Returns
- =======
- sides : list of sides
- Each side is a directed Segment.
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.line.Segment
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.sides
- [Segment2D(Point2D(0, 0), Point2D(1, 0)),
- Segment2D(Point2D(1, 0), Point2D(5, 1)),
- Segment2D(Point2D(5, 1), Point2D(0, 1)), Segment2D(Point2D(0, 1), Point2D(0, 0))]
- """
- res = []
- args = self.vertices
- for i in range(-len(args), 0):
- res.append(Segment(args[i], args[i + 1]))
- return res
- @property
- def bounds(self):
- """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding
- rectangle for the geometric figure.
- """
- verts = self.vertices
- xs = [p.x for p in verts]
- ys = [p.y for p in verts]
- return (min(xs), min(ys), max(xs), max(ys))
- def is_convex(self):
- """Is the polygon convex?
- A polygon is convex if all its interior angles are less than 180
- degrees and there are no intersections between sides.
- Returns
- =======
- is_convex : boolean
- True if this polygon is convex, False otherwise.
- See Also
- ========
- sympy.geometry.util.convex_hull
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly = Polygon(p1, p2, p3, p4)
- >>> poly.is_convex()
- True
- """
- # Determine orientation of points
- args = self.vertices
- cw = self._isright(args[-2], args[-1], args[0])
- for i in range(1, len(args)):
- if cw ^ self._isright(args[i - 2], args[i - 1], args[i]):
- return False
- # check for intersecting sides
- sides = self.sides
- for i, si in enumerate(sides):
- pts = si.args
- # exclude the sides connected to si
- for j in range(1 if i == len(sides) - 1 else 0, i - 1):
- sj = sides[j]
- if sj.p1 not in pts and sj.p2 not in pts:
- hit = si.intersection(sj)
- if hit:
- return False
- return True
- def encloses_point(self, p):
- """
- Return True if p is enclosed by (is inside of) self.
- Notes
- =====
- Being on the border of self is considered False.
- Parameters
- ==========
- p : Point
- Returns
- =======
- encloses_point : True, False or None
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.encloses_point
- Examples
- ========
- >>> from sympy import Polygon, Point
- >>> p = Polygon((0, 0), (4, 0), (4, 4))
- >>> p.encloses_point(Point(2, 1))
- True
- >>> p.encloses_point(Point(2, 2))
- False
- >>> p.encloses_point(Point(5, 5))
- False
- References
- ==========
- .. [1] http://paulbourke.net/geometry/polygonmesh/#insidepoly
- """
- p = Point(p, dim=2)
- if p in self.vertices or any(p in s for s in self.sides):
- return False
- # move to p, checking that the result is numeric
- lit = []
- for v in self.vertices:
- lit.append(v - p) # the difference is simplified
- if lit[-1].free_symbols:
- return None
- poly = Polygon(*lit)
- # polygon closure is assumed in the following test but Polygon removes duplicate pts so
- # the last point has to be added so all sides are computed. Using Polygon.sides is
- # not good since Segments are unordered.
- args = poly.args
- indices = list(range(-len(args), 1))
- if poly.is_convex():
- orientation = None
- for i in indices:
- a = args[i]
- b = args[i + 1]
- test = ((-a.y)*(b.x - a.x) - (-a.x)*(b.y - a.y)).is_negative
- if orientation is None:
- orientation = test
- elif test is not orientation:
- return False
- return True
- hit_odd = False
- p1x, p1y = args[0].args
- for i in indices[1:]:
- p2x, p2y = args[i].args
- if 0 > min(p1y, p2y):
- if 0 <= max(p1y, p2y):
- if 0 <= max(p1x, p2x):
- if p1y != p2y:
- xinters = (-p1y)*(p2x - p1x)/(p2y - p1y) + p1x
- if p1x == p2x or 0 <= xinters:
- hit_odd = not hit_odd
- p1x, p1y = p2x, p2y
- return hit_odd
- def arbitrary_point(self, parameter='t'):
- """A parameterized point on the polygon.
- The parameter, varying from 0 to 1, assigns points to the position on
- the perimeter that is that fraction of the total perimeter. So the
- point evaluated at t=1/2 would return the point from the first vertex
- that is 1/2 way around the polygon.
- Parameters
- ==========
- parameter : str, optional
- Default value is 't'.
- Returns
- =======
- arbitrary_point : Point
- Raises
- ======
- ValueError
- When `parameter` already appears in the Polygon's definition.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Polygon, Symbol
- >>> t = Symbol('t', real=True)
- >>> tri = Polygon((0, 0), (1, 0), (1, 1))
- >>> p = tri.arbitrary_point('t')
- >>> perimeter = tri.perimeter
- >>> s1, s2 = [s.length for s in tri.sides[:2]]
- >>> p.subs(t, (s1 + s2/2)/perimeter)
- Point2D(1, 1/2)
- """
- t = _symbol(parameter, real=True)
- if t.name in (f.name for f in self.free_symbols):
- raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name)
- sides = []
- perimeter = self.perimeter
- perim_fraction_start = 0
- for s in self.sides:
- side_perim_fraction = s.length/perimeter
- perim_fraction_end = perim_fraction_start + side_perim_fraction
- pt = s.arbitrary_point(parameter).subs(
- t, (t - perim_fraction_start)/side_perim_fraction)
- sides.append(
- (pt, (And(perim_fraction_start <= t, t < perim_fraction_end))))
- perim_fraction_start = perim_fraction_end
- return Piecewise(*sides)
- def parameter_value(self, other, t):
- if not isinstance(other,GeometryEntity):
- other = Point(other, dim=self.ambient_dimension)
- if not isinstance(other,Point):
- raise ValueError("other must be a point")
- if other.free_symbols:
- raise NotImplementedError('non-numeric coordinates')
- unknown = False
- p = self.arbitrary_point(T)
- for pt, cond in p.args:
- sol = solve(pt - other, T, dict=True)
- if not sol:
- continue
- value = sol[0][T]
- if simplify(cond.subs(T, value)) == True:
- return {t: value}
- unknown = True
- if unknown:
- raise ValueError("Given point may not be on %s" % func_name(self))
- raise ValueError("Given point is not on %s" % func_name(self))
- def plot_interval(self, parameter='t'):
- """The plot interval for the default geometric plot of the polygon.
- Parameters
- ==========
- parameter : str, optional
- Default value is 't'.
- Returns
- =======
- plot_interval : list (plot interval)
- [parameter, lower_bound, upper_bound]
- Examples
- ========
- >>> from sympy import Polygon
- >>> p = Polygon((0, 0), (1, 0), (1, 1))
- >>> p.plot_interval()
- [t, 0, 1]
- """
- t = Symbol(parameter, real=True)
- return [t, 0, 1]
- def intersection(self, o):
- """The intersection of polygon and geometry entity.
- The intersection may be empty and can contain individual Points and
- complete Line Segments.
- Parameters
- ==========
- other: GeometryEntity
- Returns
- =======
- intersection : list
- The list of Segments and Points
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.line.Segment
- Examples
- ========
- >>> from sympy import Point, Polygon, Line
- >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
- >>> poly1 = Polygon(p1, p2, p3, p4)
- >>> p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)])
- >>> poly2 = Polygon(p5, p6, p7)
- >>> poly1.intersection(poly2)
- [Point2D(1/3, 1), Point2D(2/3, 0), Point2D(9/5, 1/5), Point2D(7/3, 1)]
- >>> poly1.intersection(Line(p1, p2))
- [Segment2D(Point2D(0, 0), Point2D(1, 0))]
- >>> poly1.intersection(p1)
- [Point2D(0, 0)]
- """
- intersection_result = []
- k = o.sides if isinstance(o, Polygon) else [o]
- for side in self.sides:
- for side1 in k:
- intersection_result.extend(side.intersection(side1))
- intersection_result = list(uniq(intersection_result))
- points = [entity for entity in intersection_result if isinstance(entity, Point)]
- segments = [entity for entity in intersection_result if isinstance(entity, Segment)]
- if points and segments:
- points_in_segments = list(uniq([point for point in points for segment in segments if point in segment]))
- if points_in_segments:
- for i in points_in_segments:
- points.remove(i)
- return list(ordered(segments + points))
- else:
- return list(ordered(intersection_result))
- def cut_section(self, line):
- """
- Returns a tuple of two polygon segments that lie above and below
- the intersecting line respectively.
- Parameters
- ==========
- line: Line object of geometry module
- line which cuts the Polygon. The part of the Polygon that lies
- above and below this line is returned.
- Returns
- =======
- upper_polygon, lower_polygon: Polygon objects or None
- upper_polygon is the polygon that lies above the given line.
- lower_polygon is the polygon that lies below the given line.
- upper_polygon and lower polygon are ``None`` when no polygon
- exists above the line or below the line.
- Raises
- ======
- ValueError: When the line does not intersect the polygon
- Examples
- ========
- >>> from sympy import Polygon, Line
- >>> a, b = 20, 10
- >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)]
- >>> rectangle = Polygon(p1, p2, p3, p4)
- >>> t = rectangle.cut_section(Line((0, 5), slope=0))
- >>> t
- (Polygon(Point2D(0, 10), Point2D(0, 5), Point2D(20, 5), Point2D(20, 10)),
- Polygon(Point2D(0, 5), Point2D(0, 0), Point2D(20, 0), Point2D(20, 5)))
- >>> upper_segment, lower_segment = t
- >>> upper_segment.area
- 100
- >>> upper_segment.centroid
- Point2D(10, 15/2)
- >>> lower_segment.centroid
- Point2D(10, 5/2)
- References
- ==========
- .. [1] https://github.com/sympy/sympy/wiki/A-method-to-return-a-cut-section-of-any-polygon-geometry
- """
- intersection_points = self.intersection(line)
- if not intersection_points:
- raise ValueError("This line does not intersect the polygon")
- points = list(self.vertices)
- points.append(points[0])
- eq = line.equation(x, y)
- # considering equation of line to be `ax +by + c`
- a = eq.coeff(x)
- b = eq.coeff(y)
- upper_vertices = []
- lower_vertices = []
- # prev is true when previous point is above the line
- prev = True
- prev_point = None
- for point in points:
- # when coefficient of y is 0, right side of the line is
- # considered
- compare = eq.subs({x: point.x, y: point.y})/b if b \
- else eq.subs(x, point.x)/a
- # if point lies above line
- if compare > 0:
- if not prev:
- # if previous point lies below the line, the intersection
- # point of the polygon edge and the line has to be included
- edge = Line(point, prev_point)
- new_point = edge.intersection(line)
- upper_vertices.append(new_point[0])
- lower_vertices.append(new_point[0])
- upper_vertices.append(point)
- prev = True
- else:
- if prev and prev_point:
- edge = Line(point, prev_point)
- new_point = edge.intersection(line)
- upper_vertices.append(new_point[0])
- lower_vertices.append(new_point[0])
- lower_vertices.append(point)
- prev = False
- prev_point = point
- upper_polygon, lower_polygon = None, None
- if upper_vertices and isinstance(Polygon(*upper_vertices), Polygon):
- upper_polygon = Polygon(*upper_vertices)
- if lower_vertices and isinstance(Polygon(*lower_vertices), Polygon):
- lower_polygon = Polygon(*lower_vertices)
- return upper_polygon, lower_polygon
- def distance(self, o):
- """
- Returns the shortest distance between self and o.
- If o is a point, then self does not need to be convex.
- If o is another polygon self and o must be convex.
- Examples
- ========
- >>> from sympy import Point, Polygon, RegularPolygon
- >>> p1, p2 = map(Point, [(0, 0), (7, 5)])
- >>> poly = Polygon(*RegularPolygon(p1, 1, 3).vertices)
- >>> poly.distance(p2)
- sqrt(61)
- """
- if isinstance(o, Point):
- dist = oo
- for side in self.sides:
- current = side.distance(o)
- if current == 0:
- return S.Zero
- elif current < dist:
- dist = current
- return dist
- elif isinstance(o, Polygon) and self.is_convex() and o.is_convex():
- return self._do_poly_distance(o)
- raise NotImplementedError()
- def _do_poly_distance(self, e2):
- """
- Calculates the least distance between the exteriors of two
- convex polygons e1 and e2. Does not check for the convexity
- of the polygons as this is checked by Polygon.distance.
- Notes
- =====
- - Prints a warning if the two polygons possibly intersect as the return
- value will not be valid in such a case. For a more through test of
- intersection use intersection().
- See Also
- ========
- sympy.geometry.point.Point.distance
- Examples
- ========
- >>> from sympy import Point, Polygon
- >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
- >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1))
- >>> square._do_poly_distance(triangle)
- sqrt(2)/2
- Description of method used
- ==========================
- Method:
- [1] https://web.archive.org/web/20150509035744/http://cgm.cs.mcgill.ca/~orm/mind2p.html
- Uses rotating calipers:
- [2] https://en.wikipedia.org/wiki/Rotating_calipers
- and antipodal points:
- [3] https://en.wikipedia.org/wiki/Antipodal_point
- """
- e1 = self
- '''Tests for a possible intersection between the polygons and outputs a warning'''
- e1_center = e1.centroid
- e2_center = e2.centroid
- e1_max_radius = S.Zero
- e2_max_radius = S.Zero
- for vertex in e1.vertices:
- r = Point.distance(e1_center, vertex)
- if e1_max_radius < r:
- e1_max_radius = r
- for vertex in e2.vertices:
- r = Point.distance(e2_center, vertex)
- if e2_max_radius < r:
- e2_max_radius = r
- center_dist = Point.distance(e1_center, e2_center)
- if center_dist <= e1_max_radius + e2_max_radius:
- warnings.warn("Polygons may intersect producing erroneous output",
- stacklevel=3)
- '''
- Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2
- '''
- e1_ymax = Point(0, -oo)
- e2_ymin = Point(0, oo)
- for vertex in e1.vertices:
- if vertex.y > e1_ymax.y or (vertex.y == e1_ymax.y and vertex.x > e1_ymax.x):
- e1_ymax = vertex
- for vertex in e2.vertices:
- if vertex.y < e2_ymin.y or (vertex.y == e2_ymin.y and vertex.x < e2_ymin.x):
- e2_ymin = vertex
- min_dist = Point.distance(e1_ymax, e2_ymin)
- '''
- Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points
- to which the vertex is connected as its value. The same is then done for e2.
- '''
- e1_connections = {}
- e2_connections = {}
- for side in e1.sides:
- if side.p1 in e1_connections:
- e1_connections[side.p1].append(side.p2)
- else:
- e1_connections[side.p1] = [side.p2]
- if side.p2 in e1_connections:
- e1_connections[side.p2].append(side.p1)
- else:
- e1_connections[side.p2] = [side.p1]
- for side in e2.sides:
- if side.p1 in e2_connections:
- e2_connections[side.p1].append(side.p2)
- else:
- e2_connections[side.p1] = [side.p2]
- if side.p2 in e2_connections:
- e2_connections[side.p2].append(side.p1)
- else:
- e2_connections[side.p2] = [side.p1]
- e1_current = e1_ymax
- e2_current = e2_ymin
- support_line = Line(Point(S.Zero, S.Zero), Point(S.One, S.Zero))
- '''
- Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax,
- this information combined with the above produced dictionaries determines the
- path that will be taken around the polygons
- '''
- point1 = e1_connections[e1_ymax][0]
- point2 = e1_connections[e1_ymax][1]
- angle1 = support_line.angle_between(Line(e1_ymax, point1))
- angle2 = support_line.angle_between(Line(e1_ymax, point2))
- if angle1 < angle2:
- e1_next = point1
- elif angle2 < angle1:
- e1_next = point2
- elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2):
- e1_next = point2
- else:
- e1_next = point1
- point1 = e2_connections[e2_ymin][0]
- point2 = e2_connections[e2_ymin][1]
- angle1 = support_line.angle_between(Line(e2_ymin, point1))
- angle2 = support_line.angle_between(Line(e2_ymin, point2))
- if angle1 > angle2:
- e2_next = point1
- elif angle2 > angle1:
- e2_next = point2
- elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2):
- e2_next = point2
- else:
- e2_next = point1
- '''
- Loop which determines the distance between anti-podal pairs and updates the
- minimum distance accordingly. It repeats until it reaches the starting position.
- '''
- while True:
- e1_angle = support_line.angle_between(Line(e1_current, e1_next))
- e2_angle = pi - support_line.angle_between(Line(
- e2_current, e2_next))
- if (e1_angle < e2_angle) is True:
- support_line = Line(e1_current, e1_next)
- e1_segment = Segment(e1_current, e1_next)
- min_dist_current = e1_segment.distance(e2_current)
- if min_dist_current.evalf() < min_dist.evalf():
- min_dist = min_dist_current
- if e1_connections[e1_next][0] != e1_current:
- e1_current = e1_next
- e1_next = e1_connections[e1_next][0]
- else:
- e1_current = e1_next
- e1_next = e1_connections[e1_next][1]
- elif (e1_angle > e2_angle) is True:
- support_line = Line(e2_next, e2_current)
- e2_segment = Segment(e2_current, e2_next)
- min_dist_current = e2_segment.distance(e1_current)
- if min_dist_current.evalf() < min_dist.evalf():
- min_dist = min_dist_current
- if e2_connections[e2_next][0] != e2_current:
- e2_current = e2_next
- e2_next = e2_connections[e2_next][0]
- else:
- e2_current = e2_next
- e2_next = e2_connections[e2_next][1]
- else:
- support_line = Line(e1_current, e1_next)
- e1_segment = Segment(e1_current, e1_next)
- e2_segment = Segment(e2_current, e2_next)
- min1 = e1_segment.distance(e2_next)
- min2 = e2_segment.distance(e1_next)
- min_dist_current = min(min1, min2)
- if min_dist_current.evalf() < min_dist.evalf():
- min_dist = min_dist_current
- if e1_connections[e1_next][0] != e1_current:
- e1_current = e1_next
- e1_next = e1_connections[e1_next][0]
- else:
- e1_current = e1_next
- e1_next = e1_connections[e1_next][1]
- if e2_connections[e2_next][0] != e2_current:
- e2_current = e2_next
- e2_next = e2_connections[e2_next][0]
- else:
- e2_current = e2_next
- e2_next = e2_connections[e2_next][1]
- if e1_current == e1_ymax and e2_current == e2_ymin:
- break
- return min_dist
- def _svg(self, scale_factor=1., fill_color="#66cc99"):
- """Returns SVG path element for the Polygon.
- Parameters
- ==========
- scale_factor : float
- Multiplication factor for the SVG stroke-width. Default is 1.
- fill_color : str, optional
- Hex string for fill color. Default is "#66cc99".
- """
- verts = map(N, self.vertices)
- coords = ["{},{}".format(p.x, p.y) for p in verts]
- path = "M {} L {} z".format(coords[0], " L ".join(coords[1:]))
- return (
- '<path fill-rule="evenodd" fill="{2}" stroke="#555555" '
- 'stroke-width="{0}" opacity="0.6" d="{1}" />'
- ).format(2. * scale_factor, path, fill_color)
- def _hashable_content(self):
- D = {}
- def ref_list(point_list):
- kee = {}
- for i, p in enumerate(ordered(set(point_list))):
- kee[p] = i
- D[i] = p
- return [kee[p] for p in point_list]
- S1 = ref_list(self.args)
- r_nor = rotate_left(S1, least_rotation(S1))
- S2 = ref_list(list(reversed(self.args)))
- r_rev = rotate_left(S2, least_rotation(S2))
- if r_nor < r_rev:
- r = r_nor
- else:
- r = r_rev
- canonical_args = [ D[order] for order in r ]
- return tuple(canonical_args)
- def __contains__(self, o):
- """
- Return True if o is contained within the boundary lines of self.altitudes
- Parameters
- ==========
- other : GeometryEntity
- Returns
- =======
- contained in : bool
- The points (and sides, if applicable) are contained in self.
- See Also
- ========
- sympy.geometry.entity.GeometryEntity.encloses
- Examples
- ========
- >>> from sympy import Line, Segment, Point
- >>> p = Point(0, 0)
- >>> q = Point(1, 1)
- >>> s = Segment(p, q*2)
- >>> l = Line(p, q)
- >>> p in q
- False
- >>> p in s
- True
- >>> q*3 in s
- False
- >>> s in l
- True
- """
- if isinstance(o, Polygon):
- return self == o
- elif isinstance(o, Segment):
- return any(o in s for s in self.sides)
- elif isinstance(o, Point):
- if o in self.vertices:
- return True
- for side in self.sides:
- if o in side:
- return True
- return False
- def bisectors(p, prec=None):
- """Returns angle bisectors of a polygon. If prec is given
- then approximate the point defining the ray to that precision.
- The distance between the points defining the bisector ray is 1.
- Examples
- ========
- >>> from sympy import Polygon, Point
- >>> p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3))
- >>> p.bisectors(2)
- {Point2D(0, 0): Ray2D(Point2D(0, 0), Point2D(0.71, 0.71)),
- Point2D(0, 3): Ray2D(Point2D(0, 3), Point2D(0.23, 2.0)),
- Point2D(1, 1): Ray2D(Point2D(1, 1), Point2D(0.19, 0.42)),
- Point2D(2, 0): Ray2D(Point2D(2, 0), Point2D(1.1, 0.38))}
- """
- b = {}
- pts = list(p.args)
- pts.append(pts[0]) # close it
- cw = Polygon._isright(*pts[:3])
- if cw:
- pts = list(reversed(pts))
- for v, a in p.angles.items():
- i = pts.index(v)
- p1, p2 = Point._normalize_dimension(pts[i], pts[i + 1])
- ray = Ray(p1, p2).rotate(a/2, v)
- dir = ray.direction
- ray = Ray(ray.p1, ray.p1 + dir/dir.distance((0, 0)))
- if prec is not None:
- ray = Ray(ray.p1, ray.p2.n(prec))
- b[v] = ray
- return b
- class RegularPolygon(Polygon):
- """
- A regular polygon.
- Such a polygon has all internal angles equal and all sides the same length.
- Parameters
- ==========
- center : Point
- radius : number or Basic instance
- The distance from the center to a vertex
- n : int
- The number of sides
- Attributes
- ==========
- vertices
- center
- radius
- rotation
- apothem
- interior_angle
- exterior_angle
- circumcircle
- incircle
- angles
- Raises
- ======
- GeometryError
- If the `center` is not a Point, or the `radius` is not a number or Basic
- instance, or the number of sides, `n`, is less than three.
- Notes
- =====
- A RegularPolygon can be instantiated with Polygon with the kwarg n.
- Regular polygons are instantiated with a center, radius, number of sides
- and a rotation angle. Whereas the arguments of a Polygon are vertices, the
- vertices of the RegularPolygon must be obtained with the vertices method.
- See Also
- ========
- sympy.geometry.point.Point, Polygon
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> r = RegularPolygon(Point(0, 0), 5, 3)
- >>> r
- RegularPolygon(Point2D(0, 0), 5, 3, 0)
- >>> r.vertices[0]
- Point2D(5, 0)
- """
- __slots__ = ('_n', '_center', '_radius', '_rot')
- def __new__(self, c, r, n, rot=0, **kwargs):
- r, n, rot = map(sympify, (r, n, rot))
- c = Point(c, dim=2, **kwargs)
- if not isinstance(r, Expr):
- raise GeometryError("r must be an Expr object, not %s" % r)
- if n.is_Number:
- as_int(n) # let an error raise if necessary
- if n < 3:
- raise GeometryError("n must be a >= 3, not %s" % n)
- obj = GeometryEntity.__new__(self, c, r, n, **kwargs)
- obj._n = n
- obj._center = c
- obj._radius = r
- obj._rot = rot % (2*S.Pi/n) if rot.is_number else rot
- return obj
- def _eval_evalf(self, prec=15, **options):
- c, r, n, a = self.args
- dps = prec_to_dps(prec)
- c, r, a = [i.evalf(n=dps, **options) for i in (c, r, a)]
- return self.func(c, r, n, a)
- @property
- def args(self):
- """
- Returns the center point, the radius,
- the number of sides, and the orientation angle.
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> r = RegularPolygon(Point(0, 0), 5, 3)
- >>> r.args
- (Point2D(0, 0), 5, 3, 0)
- """
- return self._center, self._radius, self._n, self._rot
- def __str__(self):
- return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args)
- def __repr__(self):
- return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args)
- @property
- def area(self):
- """Returns the area.
- Examples
- ========
- >>> from sympy import RegularPolygon
- >>> square = RegularPolygon((0, 0), 1, 4)
- >>> square.area
- 2
- >>> _ == square.length**2
- True
- """
- c, r, n, rot = self.args
- return sign(r)*n*self.length**2/(4*tan(pi/n))
- @property
- def length(self):
- """Returns the length of the sides.
- The half-length of the side and the apothem form two legs
- of a right triangle whose hypotenuse is the radius of the
- regular polygon.
- Examples
- ========
- >>> from sympy import RegularPolygon
- >>> from sympy import sqrt
- >>> s = square_in_unit_circle = RegularPolygon((0, 0), 1, 4)
- >>> s.length
- sqrt(2)
- >>> sqrt((_/2)**2 + s.apothem**2) == s.radius
- True
- """
- return self.radius*2*sin(pi/self._n)
- @property
- def center(self):
- """The center of the RegularPolygon
- This is also the center of the circumscribing circle.
- Returns
- =======
- center : Point
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.center
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 5, 4)
- >>> rp.center
- Point2D(0, 0)
- """
- return self._center
- centroid = center
- @property
- def circumcenter(self):
- """
- Alias for center.
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 5, 4)
- >>> rp.circumcenter
- Point2D(0, 0)
- """
- return self.center
- @property
- def radius(self):
- """Radius of the RegularPolygon
- This is also the radius of the circumscribing circle.
- Returns
- =======
- radius : number or instance of Basic
- See Also
- ========
- sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy import RegularPolygon, Point
- >>> radius = Symbol('r')
- >>> rp = RegularPolygon(Point(0, 0), radius, 4)
- >>> rp.radius
- r
- """
- return self._radius
- @property
- def circumradius(self):
- """
- Alias for radius.
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy import RegularPolygon, Point
- >>> radius = Symbol('r')
- >>> rp = RegularPolygon(Point(0, 0), radius, 4)
- >>> rp.circumradius
- r
- """
- return self.radius
- @property
- def rotation(self):
- """CCW angle by which the RegularPolygon is rotated
- Returns
- =======
- rotation : number or instance of Basic
- Examples
- ========
- >>> from sympy import pi
- >>> from sympy.abc import a
- >>> from sympy import RegularPolygon, Point
- >>> RegularPolygon(Point(0, 0), 3, 4, pi/4).rotation
- pi/4
- Numerical rotation angles are made canonical:
- >>> RegularPolygon(Point(0, 0), 3, 4, a).rotation
- a
- >>> RegularPolygon(Point(0, 0), 3, 4, pi).rotation
- 0
- """
- return self._rot
- @property
- def apothem(self):
- """The inradius of the RegularPolygon.
- The apothem/inradius is the radius of the inscribed circle.
- Returns
- =======
- apothem : number or instance of Basic
- See Also
- ========
- sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy import RegularPolygon, Point
- >>> radius = Symbol('r')
- >>> rp = RegularPolygon(Point(0, 0), radius, 4)
- >>> rp.apothem
- sqrt(2)*r/2
- """
- return self.radius * cos(S.Pi/self._n)
- @property
- def inradius(self):
- """
- Alias for apothem.
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy import RegularPolygon, Point
- >>> radius = Symbol('r')
- >>> rp = RegularPolygon(Point(0, 0), radius, 4)
- >>> rp.inradius
- sqrt(2)*r/2
- """
- return self.apothem
- @property
- def interior_angle(self):
- """Measure of the interior angles.
- Returns
- =======
- interior_angle : number
- See Also
- ========
- sympy.geometry.line.LinearEntity.angle_between
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 4, 8)
- >>> rp.interior_angle
- 3*pi/4
- """
- return (self._n - 2)*S.Pi/self._n
- @property
- def exterior_angle(self):
- """Measure of the exterior angles.
- Returns
- =======
- exterior_angle : number
- See Also
- ========
- sympy.geometry.line.LinearEntity.angle_between
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 4, 8)
- >>> rp.exterior_angle
- pi/4
- """
- return 2*S.Pi/self._n
- @property
- def circumcircle(self):
- """The circumcircle of the RegularPolygon.
- Returns
- =======
- circumcircle : Circle
- See Also
- ========
- circumcenter, sympy.geometry.ellipse.Circle
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 4, 8)
- >>> rp.circumcircle
- Circle(Point2D(0, 0), 4)
- """
- return Circle(self.center, self.radius)
- @property
- def incircle(self):
- """The incircle of the RegularPolygon.
- Returns
- =======
- incircle : Circle
- See Also
- ========
- inradius, sympy.geometry.ellipse.Circle
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 4, 7)
- >>> rp.incircle
- Circle(Point2D(0, 0), 4*cos(pi/7))
- """
- return Circle(self.center, self.apothem)
- @property
- def angles(self):
- """
- Returns a dictionary with keys, the vertices of the Polygon,
- and values, the interior angle at each vertex.
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> r = RegularPolygon(Point(0, 0), 5, 3)
- >>> r.angles
- {Point2D(-5/2, -5*sqrt(3)/2): pi/3,
- Point2D(-5/2, 5*sqrt(3)/2): pi/3,
- Point2D(5, 0): pi/3}
- """
- ret = {}
- ang = self.interior_angle
- for v in self.vertices:
- ret[v] = ang
- return ret
- def encloses_point(self, p):
- """
- Return True if p is enclosed by (is inside of) self.
- Notes
- =====
- Being on the border of self is considered False.
- The general Polygon.encloses_point method is called only if
- a point is not within or beyond the incircle or circumcircle,
- respectively.
- Parameters
- ==========
- p : Point
- Returns
- =======
- encloses_point : True, False or None
- See Also
- ========
- sympy.geometry.ellipse.Ellipse.encloses_point
- Examples
- ========
- >>> from sympy import RegularPolygon, S, Point, Symbol
- >>> p = RegularPolygon((0, 0), 3, 4)
- >>> p.encloses_point(Point(0, 0))
- True
- >>> r, R = p.inradius, p.circumradius
- >>> p.encloses_point(Point((r + R)/2, 0))
- True
- >>> p.encloses_point(Point(R/2, R/2 + (R - r)/10))
- False
- >>> t = Symbol('t', real=True)
- >>> p.encloses_point(p.arbitrary_point().subs(t, S.Half))
- False
- >>> p.encloses_point(Point(5, 5))
- False
- """
- c = self.center
- d = Segment(c, p).length
- if d >= self.radius:
- return False
- elif d < self.inradius:
- return True
- else:
- # now enumerate the RegularPolygon like a general polygon.
- return Polygon.encloses_point(self, p)
- def spin(self, angle):
- """Increment *in place* the virtual Polygon's rotation by ccw angle.
- See also: rotate method which moves the center.
- >>> from sympy import Polygon, Point, pi
- >>> r = Polygon(Point(0,0), 1, n=3)
- >>> r.vertices[0]
- Point2D(1, 0)
- >>> r.spin(pi/6)
- >>> r.vertices[0]
- Point2D(sqrt(3)/2, 1/2)
- See Also
- ========
- rotation
- rotate : Creates a copy of the RegularPolygon rotated about a Point
- """
- self._rot += angle
- def rotate(self, angle, pt=None):
- """Override GeometryEntity.rotate to first rotate the RegularPolygon
- about its center.
- >>> from sympy import Point, RegularPolygon, pi
- >>> t = RegularPolygon(Point(1, 0), 1, 3)
- >>> t.vertices[0] # vertex on x-axis
- Point2D(2, 0)
- >>> t.rotate(pi/2).vertices[0] # vertex on y axis now
- Point2D(0, 2)
- See Also
- ========
- rotation
- spin : Rotates a RegularPolygon in place
- """
- r = type(self)(*self.args) # need a copy or else changes are in-place
- r._rot += angle
- return GeometryEntity.rotate(r, angle, pt)
- def scale(self, x=1, y=1, pt=None):
- """Override GeometryEntity.scale since it is the radius that must be
- scaled (if x == y) or else a new Polygon must be returned.
- >>> from sympy import RegularPolygon
- Symmetric scaling returns a RegularPolygon:
- >>> RegularPolygon((0, 0), 1, 4).scale(2, 2)
- RegularPolygon(Point2D(0, 0), 2, 4, 0)
- Asymmetric scaling returns a kite as a Polygon:
- >>> RegularPolygon((0, 0), 1, 4).scale(2, 1)
- Polygon(Point2D(2, 0), Point2D(0, 1), Point2D(-2, 0), Point2D(0, -1))
- """
- if pt:
- pt = Point(pt, dim=2)
- return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
- if x != y:
- return Polygon(*self.vertices).scale(x, y)
- c, r, n, rot = self.args
- r *= x
- return self.func(c, r, n, rot)
- def reflect(self, line):
- """Override GeometryEntity.reflect since this is not made of only
- points.
- Examples
- ========
- >>> from sympy import RegularPolygon, Line
- >>> RegularPolygon((0, 0), 1, 4).reflect(Line((0, 1), slope=-2))
- RegularPolygon(Point2D(4/5, 2/5), -1, 4, atan(4/3))
- """
- c, r, n, rot = self.args
- v = self.vertices[0]
- d = v - c
- cc = c.reflect(line)
- vv = v.reflect(line)
- dd = vv - cc
- # calculate rotation about the new center
- # which will align the vertices
- l1 = Ray((0, 0), dd)
- l2 = Ray((0, 0), d)
- ang = l1.closing_angle(l2)
- rot += ang
- # change sign of radius as point traversal is reversed
- return self.func(cc, -r, n, rot)
- @property
- def vertices(self):
- """The vertices of the RegularPolygon.
- Returns
- =======
- vertices : list
- Each vertex is a Point.
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import RegularPolygon, Point
- >>> rp = RegularPolygon(Point(0, 0), 5, 4)
- >>> rp.vertices
- [Point2D(5, 0), Point2D(0, 5), Point2D(-5, 0), Point2D(0, -5)]
- """
- c = self._center
- r = abs(self._radius)
- rot = self._rot
- v = 2*S.Pi/self._n
- return [Point(c.x + r*cos(k*v + rot), c.y + r*sin(k*v + rot))
- for k in range(self._n)]
- def __eq__(self, o):
- if not isinstance(o, Polygon):
- return False
- elif not isinstance(o, RegularPolygon):
- return Polygon.__eq__(o, self)
- return self.args == o.args
- def __hash__(self):
- return super().__hash__()
- class Triangle(Polygon):
- """
- A polygon with three vertices and three sides.
- Parameters
- ==========
- points : sequence of Points
- keyword: asa, sas, or sss to specify sides/angles of the triangle
- Attributes
- ==========
- vertices
- altitudes
- orthocenter
- circumcenter
- circumradius
- circumcircle
- inradius
- incircle
- exradii
- medians
- medial
- nine_point_circle
- Raises
- ======
- GeometryError
- If the number of vertices is not equal to three, or one of the vertices
- is not a Point, or a valid keyword is not given.
- See Also
- ========
- sympy.geometry.point.Point, Polygon
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
- Triangle(Point2D(0, 0), Point2D(4, 0), Point2D(4, 3))
- Keywords sss, sas, or asa can be used to give the desired
- side lengths (in order) and interior angles (in degrees) that
- define the triangle:
- >>> Triangle(sss=(3, 4, 5))
- Triangle(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4))
- >>> Triangle(asa=(30, 1, 30))
- Triangle(Point2D(0, 0), Point2D(1, 0), Point2D(1/2, sqrt(3)/6))
- >>> Triangle(sas=(1, 45, 2))
- Triangle(Point2D(0, 0), Point2D(2, 0), Point2D(sqrt(2)/2, sqrt(2)/2))
- """
- def __new__(cls, *args, **kwargs):
- if len(args) != 3:
- if 'sss' in kwargs:
- return _sss(*[simplify(a) for a in kwargs['sss']])
- if 'asa' in kwargs:
- return _asa(*[simplify(a) for a in kwargs['asa']])
- if 'sas' in kwargs:
- return _sas(*[simplify(a) for a in kwargs['sas']])
- msg = "Triangle instantiates with three points or a valid keyword."
- raise GeometryError(msg)
- vertices = [Point(a, dim=2, **kwargs) for a in args]
- # remove consecutive duplicates
- nodup = []
- for p in vertices:
- if nodup and p == nodup[-1]:
- continue
- nodup.append(p)
- if len(nodup) > 1 and nodup[-1] == nodup[0]:
- nodup.pop() # last point was same as first
- # remove collinear points
- i = -3
- while i < len(nodup) - 3 and len(nodup) > 2:
- a, b, c = sorted(
- [nodup[i], nodup[i + 1], nodup[i + 2]], key=default_sort_key)
- if Point.is_collinear(a, b, c):
- nodup[i] = a
- nodup[i + 1] = None
- nodup.pop(i + 1)
- i += 1
- vertices = list(filter(lambda x: x is not None, nodup))
- if len(vertices) == 3:
- return GeometryEntity.__new__(cls, *vertices, **kwargs)
- elif len(vertices) == 2:
- return Segment(*vertices, **kwargs)
- else:
- return Point(*vertices, **kwargs)
- @property
- def vertices(self):
- """The triangle's vertices
- Returns
- =======
- vertices : tuple
- Each element in the tuple is a Point
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> t = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
- >>> t.vertices
- (Point2D(0, 0), Point2D(4, 0), Point2D(4, 3))
- """
- return self.args
- def is_similar(t1, t2):
- """Is another triangle similar to this one.
- Two triangles are similar if one can be uniformly scaled to the other.
- Parameters
- ==========
- other: Triangle
- Returns
- =======
- is_similar : boolean
- See Also
- ========
- sympy.geometry.entity.GeometryEntity.is_similar
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
- >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -3))
- >>> t1.is_similar(t2)
- True
- >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -4))
- >>> t1.is_similar(t2)
- False
- """
- if not isinstance(t2, Polygon):
- return False
- s1_1, s1_2, s1_3 = [side.length for side in t1.sides]
- s2 = [side.length for side in t2.sides]
- def _are_similar(u1, u2, u3, v1, v2, v3):
- e1 = simplify(u1/v1)
- e2 = simplify(u2/v2)
- e3 = simplify(u3/v3)
- return bool(e1 == e2) and bool(e2 == e3)
- # There's only 6 permutations, so write them out
- return _are_similar(s1_1, s1_2, s1_3, *s2) or \
- _are_similar(s1_1, s1_3, s1_2, *s2) or \
- _are_similar(s1_2, s1_1, s1_3, *s2) or \
- _are_similar(s1_2, s1_3, s1_1, *s2) or \
- _are_similar(s1_3, s1_1, s1_2, *s2) or \
- _are_similar(s1_3, s1_2, s1_1, *s2)
- def is_equilateral(self):
- """Are all the sides the same length?
- Returns
- =======
- is_equilateral : boolean
- See Also
- ========
- sympy.geometry.entity.GeometryEntity.is_similar, RegularPolygon
- is_isosceles, is_right, is_scalene
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
- >>> t1.is_equilateral()
- False
- >>> from sympy import sqrt
- >>> t2 = Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3)))
- >>> t2.is_equilateral()
- True
- """
- return not has_variety(s.length for s in self.sides)
- def is_isosceles(self):
- """Are two or more of the sides the same length?
- Returns
- =======
- is_isosceles : boolean
- See Also
- ========
- is_equilateral, is_right, is_scalene
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(2, 4))
- >>> t1.is_isosceles()
- True
- """
- return has_dups(s.length for s in self.sides)
- def is_scalene(self):
- """Are all the sides of the triangle of different lengths?
- Returns
- =======
- is_scalene : boolean
- See Also
- ========
- is_equilateral, is_isosceles, is_right
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(1, 4))
- >>> t1.is_scalene()
- True
- """
- return not has_dups(s.length for s in self.sides)
- def is_right(self):
- """Is the triangle right-angled.
- Returns
- =======
- is_right : boolean
- See Also
- ========
- sympy.geometry.line.LinearEntity.is_perpendicular
- is_equilateral, is_isosceles, is_scalene
- Examples
- ========
- >>> from sympy import Triangle, Point
- >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
- >>> t1.is_right()
- True
- """
- s = self.sides
- return Segment.is_perpendicular(s[0], s[1]) or \
- Segment.is_perpendicular(s[1], s[2]) or \
- Segment.is_perpendicular(s[0], s[2])
- @property
- def altitudes(self):
- """The altitudes of the triangle.
- An altitude of a triangle is a segment through a vertex,
- perpendicular to the opposite side, with length being the
- height of the vertex measured from the line containing the side.
- Returns
- =======
- altitudes : dict
- The dictionary consists of keys which are vertices and values
- which are Segments.
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.line.Segment.length
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.altitudes[p1]
- Segment2D(Point2D(0, 0), Point2D(1/2, 1/2))
- """
- s = self.sides
- v = self.vertices
- return {v[0]: s[1].perpendicular_segment(v[0]),
- v[1]: s[2].perpendicular_segment(v[1]),
- v[2]: s[0].perpendicular_segment(v[2])}
- @property
- def orthocenter(self):
- """The orthocenter of the triangle.
- The orthocenter is the intersection of the altitudes of a triangle.
- It may lie inside, outside or on the triangle.
- Returns
- =======
- orthocenter : Point
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.orthocenter
- Point2D(0, 0)
- """
- a = self.altitudes
- v = self.vertices
- return Line(a[v[0]]).intersection(Line(a[v[1]]))[0]
- @property
- def circumcenter(self):
- """The circumcenter of the triangle
- The circumcenter is the center of the circumcircle.
- Returns
- =======
- circumcenter : Point
- See Also
- ========
- sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.circumcenter
- Point2D(1/2, 1/2)
- """
- a, b, c = [x.perpendicular_bisector() for x in self.sides]
- return a.intersection(b)[0]
- @property
- def circumradius(self):
- """The radius of the circumcircle of the triangle.
- Returns
- =======
- circumradius : number of Basic instance
- See Also
- ========
- sympy.geometry.ellipse.Circle.radius
- Examples
- ========
- >>> from sympy import Symbol
- >>> from sympy import Point, Triangle
- >>> a = Symbol('a')
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, a)
- >>> t = Triangle(p1, p2, p3)
- >>> t.circumradius
- sqrt(a**2/4 + 1/4)
- """
- return Point.distance(self.circumcenter, self.vertices[0])
- @property
- def circumcircle(self):
- """The circle which passes through the three vertices of the triangle.
- Returns
- =======
- circumcircle : Circle
- See Also
- ========
- sympy.geometry.ellipse.Circle
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.circumcircle
- Circle(Point2D(1/2, 1/2), sqrt(2)/2)
- """
- return Circle(self.circumcenter, self.circumradius)
- def bisectors(self):
- """The angle bisectors of the triangle.
- An angle bisector of a triangle is a straight line through a vertex
- which cuts the corresponding angle in half.
- Returns
- =======
- bisectors : dict
- Each key is a vertex (Point) and each value is the corresponding
- bisector (Segment).
- See Also
- ========
- sympy.geometry.point.Point, sympy.geometry.line.Segment
- Examples
- ========
- >>> from sympy import Point, Triangle, Segment
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> from sympy import sqrt
- >>> t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1))
- True
- """
- # use lines containing sides so containment check during
- # intersection calculation can be avoided, thus reducing
- # the processing time for calculating the bisectors
- s = [Line(l) for l in self.sides]
- v = self.vertices
- c = self.incenter
- l1 = Segment(v[0], Line(v[0], c).intersection(s[1])[0])
- l2 = Segment(v[1], Line(v[1], c).intersection(s[2])[0])
- l3 = Segment(v[2], Line(v[2], c).intersection(s[0])[0])
- return {v[0]: l1, v[1]: l2, v[2]: l3}
- @property
- def incenter(self):
- """The center of the incircle.
- The incircle is the circle which lies inside the triangle and touches
- all three sides.
- Returns
- =======
- incenter : Point
- See Also
- ========
- incircle, sympy.geometry.point.Point
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.incenter
- Point2D(1 - sqrt(2)/2, 1 - sqrt(2)/2)
- """
- s = self.sides
- l = Matrix([s[i].length for i in [1, 2, 0]])
- p = sum(l)
- v = self.vertices
- x = simplify(l.dot(Matrix([vi.x for vi in v]))/p)
- y = simplify(l.dot(Matrix([vi.y for vi in v]))/p)
- return Point(x, y)
- @property
- def inradius(self):
- """The radius of the incircle.
- Returns
- =======
- inradius : number of Basic instance
- See Also
- ========
- incircle, sympy.geometry.ellipse.Circle.radius
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(4, 0), Point(0, 3)
- >>> t = Triangle(p1, p2, p3)
- >>> t.inradius
- 1
- """
- return simplify(2 * self.area / self.perimeter)
- @property
- def incircle(self):
- """The incircle of the triangle.
- The incircle is the circle which lies inside the triangle and touches
- all three sides.
- Returns
- =======
- incircle : Circle
- See Also
- ========
- sympy.geometry.ellipse.Circle
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(2, 0), Point(0, 2)
- >>> t = Triangle(p1, p2, p3)
- >>> t.incircle
- Circle(Point2D(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2))
- """
- return Circle(self.incenter, self.inradius)
- @property
- def exradii(self):
- """The radius of excircles of a triangle.
- An excircle of the triangle is a circle lying outside the triangle,
- tangent to one of its sides and tangent to the extensions of the
- other two.
- Returns
- =======
- exradii : dict
- See Also
- ========
- sympy.geometry.polygon.Triangle.inradius
- Examples
- ========
- The exradius touches the side of the triangle to which it is keyed, e.g.
- the exradius touching side 2 is:
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2)
- >>> t = Triangle(p1, p2, p3)
- >>> t.exradii[t.sides[2]]
- -2 + sqrt(10)
- References
- ==========
- .. [1] https://mathworld.wolfram.com/Exradius.html
- .. [2] https://mathworld.wolfram.com/Excircles.html
- """
- side = self.sides
- a = side[0].length
- b = side[1].length
- c = side[2].length
- s = (a+b+c)/2
- area = self.area
- exradii = {self.sides[0]: simplify(area/(s-a)),
- self.sides[1]: simplify(area/(s-b)),
- self.sides[2]: simplify(area/(s-c))}
- return exradii
- @property
- def excenters(self):
- """Excenters of the triangle.
- An excenter is the center of a circle that is tangent to a side of the
- triangle and the extensions of the other two sides.
- Returns
- =======
- excenters : dict
- Examples
- ========
- The excenters are keyed to the side of the triangle to which their corresponding
- excircle is tangent: The center is keyed, e.g. the excenter of a circle touching
- side 0 is:
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2)
- >>> t = Triangle(p1, p2, p3)
- >>> t.excenters[t.sides[0]]
- Point2D(12*sqrt(10), 2/3 + sqrt(10)/3)
- See Also
- ========
- sympy.geometry.polygon.Triangle.exradii
- References
- ==========
- .. [1] https://mathworld.wolfram.com/Excircles.html
- """
- s = self.sides
- v = self.vertices
- a = s[0].length
- b = s[1].length
- c = s[2].length
- x = [v[0].x, v[1].x, v[2].x]
- y = [v[0].y, v[1].y, v[2].y]
- exc_coords = {
- "x1": simplify(-a*x[0]+b*x[1]+c*x[2]/(-a+b+c)),
- "x2": simplify(a*x[0]-b*x[1]+c*x[2]/(a-b+c)),
- "x3": simplify(a*x[0]+b*x[1]-c*x[2]/(a+b-c)),
- "y1": simplify(-a*y[0]+b*y[1]+c*y[2]/(-a+b+c)),
- "y2": simplify(a*y[0]-b*y[1]+c*y[2]/(a-b+c)),
- "y3": simplify(a*y[0]+b*y[1]-c*y[2]/(a+b-c))
- }
- excenters = {
- s[0]: Point(exc_coords["x1"], exc_coords["y1"]),
- s[1]: Point(exc_coords["x2"], exc_coords["y2"]),
- s[2]: Point(exc_coords["x3"], exc_coords["y3"])
- }
- return excenters
- @property
- def medians(self):
- """The medians of the triangle.
- A median of a triangle is a straight line through a vertex and the
- midpoint of the opposite side, and divides the triangle into two
- equal areas.
- Returns
- =======
- medians : dict
- Each key is a vertex (Point) and each value is the median (Segment)
- at that point.
- See Also
- ========
- sympy.geometry.point.Point.midpoint, sympy.geometry.line.Segment.midpoint
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.medians[p1]
- Segment2D(Point2D(0, 0), Point2D(1/2, 1/2))
- """
- s = self.sides
- v = self.vertices
- return {v[0]: Segment(v[0], s[1].midpoint),
- v[1]: Segment(v[1], s[2].midpoint),
- v[2]: Segment(v[2], s[0].midpoint)}
- @property
- def medial(self):
- """The medial triangle of the triangle.
- The triangle which is formed from the midpoints of the three sides.
- Returns
- =======
- medial : Triangle
- See Also
- ========
- sympy.geometry.line.Segment.midpoint
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.medial
- Triangle(Point2D(1/2, 0), Point2D(1/2, 1/2), Point2D(0, 1/2))
- """
- s = self.sides
- return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint)
- @property
- def nine_point_circle(self):
- """The nine-point circle of the triangle.
- Nine-point circle is the circumcircle of the medial triangle, which
- passes through the feet of altitudes and the middle points of segments
- connecting the vertices and the orthocenter.
- Returns
- =======
- nine_point_circle : Circle
- See also
- ========
- sympy.geometry.line.Segment.midpoint
- sympy.geometry.polygon.Triangle.medial
- sympy.geometry.polygon.Triangle.orthocenter
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.nine_point_circle
- Circle(Point2D(1/4, 1/4), sqrt(2)/4)
- """
- return Circle(*self.medial.vertices)
- @property
- def eulerline(self):
- """The Euler line of the triangle.
- The line which passes through circumcenter, centroid and orthocenter.
- Returns
- =======
- eulerline : Line (or Point for equilateral triangles in which case all
- centers coincide)
- Examples
- ========
- >>> from sympy import Point, Triangle
- >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
- >>> t = Triangle(p1, p2, p3)
- >>> t.eulerline
- Line2D(Point2D(0, 0), Point2D(1/2, 1/2))
- """
- if self.is_equilateral():
- return self.orthocenter
- return Line(self.orthocenter, self.circumcenter)
- def rad(d):
- """Return the radian value for the given degrees (pi = 180 degrees)."""
- return d*pi/180
- def deg(r):
- """Return the degree value for the given radians (pi = 180 degrees)."""
- return r/pi*180
- def _slope(d):
- rv = tan(rad(d))
- return rv
- def _asa(d1, l, d2):
- """Return triangle having side with length l on the x-axis."""
- xy = Line((0, 0), slope=_slope(d1)).intersection(
- Line((l, 0), slope=_slope(180 - d2)))[0]
- return Triangle((0, 0), (l, 0), xy)
- def _sss(l1, l2, l3):
- """Return triangle having side of length l1 on the x-axis."""
- c1 = Circle((0, 0), l3)
- c2 = Circle((l1, 0), l2)
- inter = [a for a in c1.intersection(c2) if a.y.is_nonnegative]
- if not inter:
- return None
- pt = inter[0]
- return Triangle((0, 0), (l1, 0), pt)
- def _sas(l1, d, l2):
- """Return triangle having side with length l2 on the x-axis."""
- p1 = Point(0, 0)
- p2 = Point(l2, 0)
- p3 = Point(cos(rad(d))*l1, sin(rad(d))*l1)
- return Triangle(p1, p2, p3)
|