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、二维/三维的Line、Ray、Segment及其 2D/3D 具体子类。读完本文,你将掌握如何用两点、点加斜率、方向向量或方程四种方式构造直线,如何计算夹角、交点、距离、投影与垂线,以及 SymPy 内部如何用仿射秩(affine rank)和线性代数方法统一处理平面内与空间中的相交判定——所有结论均有 line.py 源码与 test_line.py 测试用例可查证。
文档定位:从 autodoc 声明到真正的 API 来源
lines.rst本身是一个 Sphinx autodoc 声明文件,正文由.. autoclass::指令从源码 docstring 自动生成,因此理解本主题必须回到实现文件:
- 全部线型实体的实现位于 sympy/geometry/line.py(共 2878 行);
- 文档声明的 11 个类依次为:
LinearEntity、Line、Ray、Segment、LinearEntity2D、Line2D、Ray2D、Segment2D、LinearEntity3D、Line3D、Ray3D、Segment3D; - 模块导出见 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}LinearEntity与LinearEntity2D/3D均为抽象基类(docstring 明确 "not meant to be instantiated"),实际使用的是六个具体类。Line、Ray、Segment的__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 - p1 | line.py,可用.unit归一化 |
length | 实体长度:Line/Ray为oo,Segment为实际距离 | 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/43D 下同样适用:
>>> 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对应p1,t=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_rays、intersect_parallel_ray_and_segment、intersect_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.direction与l2.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的维度自动子类化为Line2D或Line3D。其__new__支持四种构造方式:
- 两个不同点:
Line(Point(2,3), Point(3,5)); - 一个点 +
slope关键字(仅 2D):Line(Point(0,0), slope=0),源码将斜率转为方向增量(1, slope);斜率为无穷时令dx=0, dy=1表示竖直直线(line.py); - 一个点 +
direction_ratio关键字(仅 3D):Line3D(Point3D(...), direction_ratio=[2,8,8]),方向比率长度必须为 3; - 方程(仅 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)其中s是Segment。
Line 专有方法
| 方法 | 说明 |
|---|---|
contains(other) | 判定点或线型实体是否在线上,基于Point.is_collinear(line.py);注意l1 == l2与l1 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)Line3D的equation(x, y, z)返回定义空间直线的两个联立方程元组(line.py),内部通过引入参数k联立三个方向方程再消元得到。其distance(other)还支持Point3D、Line3D与Plane:平行直线取点到线距离,异面直线则用方向向量叉积构造法向量平面,再求点到平面距离(line.py):
>>> from sympy.geometry import Line3D >>> Line3D((0, 0, 0), (0, 0, 1)).distance(Line3D((0, 1, 0), (1, 1, 1))) 1Ray:带源点的半直线
Ray表示带源点与方向的半直线(line.py),按维度自动子类化为Ray2D/Ray3D。构造方式除两点外,还支持角度:Ray(Point(0, 0), angle=pi/4)(弧度,逆时针为正)。源码对角度做了象限分解:_pi_coeff识别 π 的有理数倍,对 0、π/2、π、3π/2 等特殊角直接给出单位向量,其余情况用Piecewise与tan组合构造方向点(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/2Segment:有限线段
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):返回线段的垂直平分线;若提供点p且p在平分线上,则返回连接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_between、smallest_angle_between、is_parallel、is_perpendicular对非LinearEntity参数统一抛TypeError;- 当包含关系无法确定时,
__contains__会抛出sympy.utilities.misc.Undecidable,测试 test_line.py 中的test_contains与test_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),仅供参考