SymPy 几何模块线型实体完全指南:Line、Ray 与 Segment 的 API 与源码解析
2026/9/14 7:47:40 网站建设 项目流程

SymPy 几何模块线型实体完全指南:Line、Ray 与 Segment 的 API 与源码解析

【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy

本篇技术指南围绕 SymPy 几何模块中的线型(linear)实体展开,系统讲解 doc/src/modules/geometry/lines.rst 所声明的全部 11 个类:抽象基类LinearEntity、二维/三维的LineRaySegment及其 2D/3D 具体子类。读完本文,你将掌握如何用两点、点加斜率、方向向量或方程四种方式构造直线,如何计算夹角、交点、距离、投影与垂线,以及 SymPy 内部如何用仿射秩(affine rank)和线性代数方法统一处理平面内与空间中的相交判定——所有结论均有 line.py 源码与 test_line.py 测试用例可查证。

文档定位:从 autodoc 声明到真正的 API 来源

lines.rst本身是一个 Sphinx autodoc 声明文件,正文由.. autoclass::指令从源码 docstring 自动生成,因此理解本主题必须回到实现文件:

  • 全部线型实体的实现位于 sympy/geometry/line.py(共 2878 行);
  • 文档声明的 11 个类依次为:LinearEntityLineRaySegmentLinearEntity2DLine2DRay2DSegment2DLinearEntity3DLine3DRay3DSegment3D
  • 模块导出见 sympy/geometry/init.py,其中Line, Ray, Segment, Line2D, Segment2D, Ray2D, Line3D, Segment3D, Ray3D均从sympy.geometry.line导入并进入__all__,因此用户可直接from sympy import Line, Ray, Segment使用;
  • 几何模块的整体定位可参见 doc/src/modules/geometry/index.rst:支持创建二维几何实体并查询其性质,以数值实体为主要场景,同时支持符号表示。

所有类共享同一继承骨架(见 line.py):

GeometryEntity → GeometrySet → LinearEntity → {Line, Ray, Segment} → LinearEntity2D → {Line2D, Ray2D, Segment2D} → LinearEntity3D → {Line3D, Ray3D, Segment3D}

LinearEntityLinearEntity2D/3D均为抽象基类(docstring 明确 "not meant to be instantiated"),实际使用的是六个具体类。LineRaySegment__new__会依据输入点的维度自动返回对应的 2D 或 3D 子类实例。

LinearEntity:所有线型实体的公共能力

LinearEntity是 n 维欧氏空间中所有线型实体(Line、Ray、Segment)的基类,定义了方向、长度、包含性、相交、夹角、投影等核心语义。

公共属性

属性含义实现位置
p1/p2定义实体的两个点line.py,直接返回self.args[0]self.args[1]
points两个定义点组成的元组(p1, p2)line.py
direction方向向量,即p2 - p1line.py,可用.unit归一化
length实体长度:Line/RayooSegment为实际距离line.py
ambient_dimension所在空间维度(2 或 3)line.py,即len(self.p1)

需要注意:点的顺序决定direction的方向,进而影响angle_between等角度计算的符号语义——文档示例中Line((0,0),(1,0))Line((1,1),(0,0))对同一条线给出互为补角的答案。

角度计算:angle_between 与 smallest_angle_between

angle_between(l1, l2)返回两条线方向向量夹角的非钝角补全(取值范围[0, π]),由点积公式实现(line.py):

acos(v1.dot(v2)/(abs(v1)*abs(v2)))

smallest_angle_between(l1, l2)则返回两线在交点处形成的锐角(取值范围[0, π/2]),实现仅多了一层绝对值(line.py):

acos(abs(v1.dot(v2))/(abs(v1)*abs(v2)))

两方法都要求参数为LinearEntity,否则抛出TypeError('Must pass only LinearEntity objects')。源码 docstring 给出了直观示例:

>>> from sympy import Line >>> e = Line((0, 0), (1, 0)) >>> ne = Line((0, 0), (1, 1)) >>> sw = Line((1, 1), (0, 0)) >>> ne.angle_between(e) pi/4 >>> sw.angle_between(e) 3*pi/4 >>> sw.smallest_angle_between(e) pi/4

3D 下同样适用:

>>> from sympy import Point3D, Line3D >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0) >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3) >>> l1.angle_between(l2) acos(-sqrt(2)/3)

参数化点:arbitrary_point

arbitrary_point(parameter='t')返回p1 + (p2 - p1)*t,即线上参数化点,t=0对应p1t=1对应p2(line.py)。若参数名已出现在实体定义的自由符号中,会抛出ValueError;参数被强制为实数(_symbol(parameter, real=True)):

>>> from sympy import Point, Line >>> p1, p2 = Point(1, 0), Point(5, 3) >>> Line(p1, p2).arbitrary_point() Point2D(4*t + 1, 3*t)

该方法是内部许多算法的基础:random_point通过替换参数生成随机点;intersection在处理含浮点坐标的共面相交时,通过求解两个arbitrary_point的相等方程并检查参数范围来判定归属。

相交判定:intersection

intersection(other)是线型实体最常用的方法,接受Point或任意LinearEntity,返回几何实体列表(line.py)。其算法在源码中以仿射秩(Point.affine_rank)为分水岭:

  • rank == 1(共线):按双方类型组合调用三个内部辅助函数——intersect_parallel_raysintersect_parallel_ray_and_segmentintersect_parallel_segments,基于_span_test(判断点相对p1是否在方向向量的正半张成空间内,返回 -1/0/1)确定平行射线/线段的重叠部分;
  • rank == 2(共面不共线):先检查方向是否成标量倍数(平行则返回[]),否则构造矩阵方程t*d + p1 == s*d' + p1',用增广矩阵rref(simplify=True)求解交点(line.py)。若实体含浮点坐标导致精确包含判定失败,会退化到参数化求解并检查参数符号;
  • rank 为其他(异面 skew):直接返回[]
>>> from sympy import Point, Line, Segment >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7) >>> l1 = Line(p1, p2) >>> l1.intersection(p3) [Point2D(7, 7)] >>> p4, p5 = Point(5, 0), Point(0, 3) >>> l1.intersection(Line(p4, p5)) [Point2D(15/8, 15/8)] >>> l1.intersection(Segment(Point(0, 5), Point(2, 6))) []

平行、垂直与相似

  • is_parallel(l1, l2):判断l1.directionl2.direction是否互为标量倍数(line.py);
  • is_perpendicular(l1, l2):判断方向向量点积是否等于 0(line.py),用S.Zero.equals处理符号表达式求值;
  • is_similar(other):返回 True 当且仅当二者共线(line.py),实现为Line(self.p1, self.p2).contains(other)

平行线、垂线与投影

  • parallel_line(p):返回过点p且与自身平行的Line(p, p + self.direction)(line.py);
  • perpendicular_line(p):返回过点p的垂线(line.py)。2D 实现直接取self.direction.orthogonal_direction(任意两条平面直线必相交,更快);3D 实现则用self.projection(p)求投影点,docstring 明确:3D 中垂线的第一个点是通过点p,第二个点(任意地)落在原直线上;
  • perpendicular_segment(p):返回从p到直线的垂线段Segment(p, p2),其中p2是垂线与直线的唯一交点(line.py);若p本身在线上则直接返回点p
  • projection(other):将点、线、射线或线段投影到自身(line.py),返回类型与输入类型匹配。对点实现为Point.project(p - self.p1, self.direction) + self.p1,对线型实体则投影两个端点后用Intersection收缩到自身范围内,并保证投影方向与自身一致。
>>> from sympy import Point, Line >>> l1 = Line(Point(0, 0), Point(1, 1)) >>> l1.perpendicular_segment(Point(4, 0)) Segment2D(Point2D(4, 0), Point2D(2, 2)) >>> l1.projection(Point(1, 0)) # 等价于 (1,0) 投影到 y=x 上(符号坐标示例见源码) Point2D(1/2, 1/2)

共点性与角平分线

  • are_concurrent(*lines):静态方法,判断一组线型实体是否交于同一点(line.py),实现为Intersection(*lines)是含 1 个元素的有限集;
  • bisectors(other):返回过两线交点且共面的两条角平分线(line.py),以两条单位方向向量之和/差为方向构造Line
>>> from sympy import Point3D, Line3D >>> r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)) >>> r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0)) >>> r1.bisectors(r2) [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))]

Line:无限直线与四种构造方式

Line表示空间中的无限直线(line.py),根据p1的维度自动子类化为Line2DLine3D。其__new__支持四种构造方式:

  1. 两个不同点Line(Point(2,3), Point(3,5))
  2. 一个点 +slope关键字(仅 2D)Line(Point(0,0), slope=0),源码将斜率转为方向增量(1, slope);斜率为无穷时令dx=0, dy=1表示竖直直线(line.py);
  3. 一个点 +direction_ratio关键字(仅 3D)Line3D(Point3D(...), direction_ratio=[2,8,8]),方向比率长度必须为 3;
  4. 方程(仅 2D)Line(3*x + y + 18)Line(Eq(3*a + b, -18), x='a', y=b),内部用linear_coeffs提取系数a, b, c,当b≠0时转为Line((0, -c/b), slope=-a/b),当a≠0(竖直)时转为Line((-c/a, 0), slope=oo)(line.py)。

此外还可传入另一个线型实体,例如Line(s)其中sSegment

Line 专有方法

方法说明
contains(other)判定点或线型实体是否在线上,基于Point.is_collinear(line.py);注意l1 == l2l1 in l2语义不同,反向定义的线不相等但互相包含
distance(other)点到直线的最短距离(line.py),若点在线上返回 0,否则返回perpendicular_segment(other).length
equals(other)数学实体相等判定,即四点共线检查(line.py)
plot_interval()绘图参数区间[t, -5, 5](line.py)

Line2D额外提供:

  • slope:斜率,竖直直线返回oo(line.py);
  • coefficients(a, b, c)满足ax + by + c = 0(line.py);
  • equation(x='x', y='y'):返回形如-3*x + 4*y + 3的表达式(line.py),可自定义轴变量名。
>>> from sympy import Line, Point >>> L = Line(Point(2, 3), Point(3, 5)) >>> L.equation() -2*x + y + 1 >>> L.coefficients (-2, 1, 1)

Line3Dequation(x, y, z)返回定义空间直线的两个联立方程元组(line.py),内部通过引入参数k联立三个方向方程再消元得到。其distance(other)还支持Point3DLine3DPlane:平行直线取点到线距离,异面直线则用方向向量叉积构造法向量平面,再求点到平面距离(line.py):

>>> from sympy.geometry import Line3D >>> Line3D((0, 0, 0), (0, 0, 1)).distance(Line3D((0, 1, 0), (1, 1, 1))) 1

Ray:带源点的半直线

Ray表示带源点与方向的半直线(line.py),按维度自动子类化为Ray2D/Ray3D。构造方式除两点外,还支持角度Ray(Point(0, 0), angle=pi/4)(弧度,逆时针为正)。源码对角度做了象限分解:_pi_coeff识别 π 的有理数倍,对 0、π/2、π、3π/2 等特殊角直接给出单位向量,其余情况用Piecewisetan组合构造方向点(line.py)。

关键属性与方法:

  • source:源点,即p1(line.py);
  • xdirection/ydirection:射线在 x/y 方向上的符号(oo-oo或 0,垂直/水平时对应方向为 0)(line.py);
  • Ray3D额外提供zdirection(line.py);
  • contains(other):点在线方向上(共线且方向点积非负)才判定包含,同时支持Segment/Ray子集判定(line.py);
  • distance(other):射线到点的距离,先投影再判断投影点是否在射线上,否则取到源点的距离(line.py);
  • plot_interval()返回[t, 0, 10]
  • Ray2D.closing_angle(r1, r2):返回 r2 需旋转多少弧度才能与 r1 同向(逆时针为正),基于atan2计算两方向角之差并做符号归一(line.py),仅接受Ray2D参数:
>>> from sympy import Ray, pi >>> r1 = Ray((0, 0), (1, 0)) >>> r2 = r1.rotate(-pi/2) >>> r1.closing_angle(r2) pi/2

Segment:有限线段

Segment表示空间中的有限线段(line.py),自动子类化为Segment2D/Segment3D。构造要求两个点;当两点重合时,Segment2D.__new__/Segment3D.__new__直接返回该点(退化情形)。

关键属性与方法:

  • length:线段长度,即Point.distance(self.p1, self.p2)
  • midpoint:中点;
  • slope(2D):斜率;
  • contains(other):点判定使用"共线 + 三角不等式"技巧——判断|d1| + |d2| == |d|是否恒成立(line.py),并兼容Segment2D的包围盒快速路径;若无法判定则抛出Undecidable
  • distance(other):到点的最短距离,通过方向向量与两端点向量的点积符号分成三种情形(垂足落在段内则取点到直线距离,否则取到较近端点的距离,line.py);
  • perpendicular_bisector(p=None):返回线段的垂直平分线;若提供点pp在平分线上,则返回连接p与中点的Segment,否则返回Line(line.py);
  • plot_interval()返回[t, 0, 1]
>>> from sympy import Point, Segment >>> s = Segment(Point(4, 3), Point(1, 1)) >>> s.slope, s.length, s.midpoint (2/3, sqrt(13), Point2D(5/2, 2)) >>> s.perpendicular_bisector() Line2D(Point2D(5/2, 2), Point2D(5/2, -1)) # 输出细节以实际运行结果为准

LinearEntity2D / LinearEntity3D:维度专属能力

两个维度基类补充了各自的专属属性(均不直接实例化):

LinearEntity2D(line.py)

  • bounds:包围矩形(xmin, ymin, xmax, ymax)
  • slope:斜率(竖直为oo);
  • perpendicular_line(p):2D 快速实现,直接取正交方向。

LinearEntity3D(line.py)

  • direction_ratio:方向比率[dx, dy, dz](未归一化,由p1.direction_ratio(p2)计算);
  • direction_cosine:归一化方向余弦,平方和为 1,可用于验证。
>>> from sympy import Point3D, Line3D >>> l = Line3D(Point3D(0, 0, 0), Point3D(5, 3, 1)) >>> l.direction_ratio [5, 3, 1] >>> l.direction_cosine [sqrt(35)/7, 3*sqrt(35)/35, sqrt(35)/35]

边界行为与错误约定

源码中的参数校验与异常约定值得注意:

  • LinearEntity.__new__要求两个不同的点(ValueError),且两点维度必须一致;Segment2D/3D对退化(两点相同)则返回单个Point
  • Line方程构造中,若给x/y之外的关键字会抛ValueError;第二个参数既非有效Point又未用slope关键字时,Line2D会提示"如果它是斜率,请用关键字 slope 传入"(line.py);
  • angle_betweensmallest_angle_betweenis_parallelis_perpendicular对非LinearEntity参数统一抛TypeError
  • 当包含关系无法确定时,__contains__会抛出sympy.utilities.misc.Undecidable,测试 test_line.py 中的test_containstest_contains_nonreal_symbols专门覆盖了符号坐标下的不可判定场景;
  • 这些行为均有对应测试验证,例如test_validation_for_linear_entity_methods(参数类型校验)、test_intersection_2d/test_intersection_3d(相交算法)、test_distance_2d/test_distance_3d(距离)、test_ray_generation(角度构造射线)、test_bisectors(角平分线)等,共 40 余个测试函数,是深入理解各方法语义的最佳参考。

实战小结

把四类构造、两类角度 API 与一整套几何查询方法组合起来,可以完成常见计算任务:

from sympy import Line, Ray, Segment, Point, pi # 构造:两点 / 点+斜率 / 点+方向比率 / 方程 l = Line(Point(0, 0), Point(1, 1)) l2 = Line(Point(1, 0), slope=1) # 平行于 l r = Ray(Point(0, 0), angle=pi/4) # 与 l 同方向的射线 s = Segment(Point(0, 2), Point(2, 0)) # 查询 l.angle_between(l2) # 0 l.is_parallel(l2) # True l.intersection(s) # 与线段求交 l.perpendicular_segment(Point(2, 2)) # 垂线段

所有类与方法的权威说明(参数、返回值、示例)均随源码 docstring 自动汇入 lines.rst 对应的 API 章节;若要进一步深挖实现,直接阅读 sympy/geometry/line.py 及其测试 test_line.py 即可获得与文档一致的完整契约。

【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询