当前位置: 首页 > news >正文

用SymPy自动求解三角形构造与全等条件验证

痛点场景还原

假设我要做一个SSS全等判定的演示:给定三边长度AB=4, BC=3, AC=5,在坐标系中画出这个三角形,然后改变边长观察三角形是否唯一确定。

如果纯手动操作,我会这样写:

from manim import * import numpy as np class PainfulSSSDemo(Scene): def construct(self): # 手动指定顶点坐标 —— 怎么知道这三个坐标能满足边长条件? A = np.array([0, 0, 0]) # 固定 A 在原点 B = np.array([4, 0, 0]) # 固定 B 在 (4,0),这样 AB=4 # C 的位置?需要同时满足 AC=5, BC=3 # 只能手动解方程:x²+y²=25, (x-4)²+y²=9 # 手算得 x=4, y=3,于是 C=(4,3) —— 但这是直角三角形特例 # 换一组边长又要重新手算,而且每次都要判断镜像解 C = np.array([4, 3, 0]) triangle = Polygon(A, B, C) self.add(triangle)

核心痛点:

  • 给 A、B 定好位置后,C 的坐标必须同时满足 AC 和 BC 的距离约束,手算就是解二元二次方程组,换个边长就要重算一次。
  • SASASA更麻烦,涉及角度条件,需要把“∠A=60∘”转化为向量点积方程,手算极易出错。
  • 方程组通常有两组解(镜像三角形),需要判断哪个是合理的朝向。
  • 手动算出来的坐标是近似值,动画中顶点位置不够精准。

这些计算本质上就是几何约束求解,完全可以交给SymPy自动完成。

2. SymPy 解决方案介绍

SymPy可以将几何条件转化为代数方程,然后自动求解顶点坐标。

2.1 SSS 全等:已知三边求顶点

已知 A=(0,0), B=(c,0),求 C=(x,y) 满足 AC=b, BC=a。

import sympy as sp x, y = sp.symbols('x y', real=True) a, b, c = 3, 5, 4 # BC, AC, AB # 距离约束转化为方程 eq1 = (x - 0)**2 + (y - 0)**2 - b**2 # AC = 5 eq2 = (x - c)**2 + (y - 0)**2 - a**2 # BC = 3 solutions = sp.solve([eq1, eq2], (x, y)) # 输出两组解:[(4, -3), (4, 3)] —— 对应镜像三角形

对于SASASA,只需把角度条件用向量点积或余弦定理表示,同样构建方程组求解。

2.2 SAS 全等:已知两边和夹角求第三顶点

已知 AB=c, AC=b, $ \angle A=\theta , A 在原点, B 在 (c,0) , C 满足到 A 的距离为 b 、到 B $ 的距离用余弦定理求:

import sympy as sp x, y = sp.symbols('x y', real=True) b, c, theta = 4, 5, sp.rad(60) # AC=4, AB=5, ∠A=60° # C 到 A 的距离 eq1 = x**2 + y**2 - b**2 # 用余弦定理:BC² = AB² + AC² - 2·AB·AC·cosθ bc_sq = c**2 + b**2 - 2*c*b*sp.cos(theta) eq2 = (x - c)**2 + y**2 - bc_sq solutions = sp.solve([eq1, eq2], (x, y))

2.3 筛选合理解

SymPy会返回两组解(关于 AB 所在直线对称),通过指定 y 的正负号可以筛选:

# 筛选 y >= 0 的解(取上方三角形) valid_solution = [sol for sol in solutions if sol[1] >= 0][0]

这样就能得到唯一确定的三角形顶点坐标,完美支撑全等判定定理的可视化。

3. Manim 联动实战

下面是一个完整的动画场景,用ValueTracker控制边长,动态展示SSS全等下三角形的唯一确定性。

from manim import * import sympy as sp import numpy as np class SSSCongruenceDemo(Scene): def construct(self): # 固定两个顶点 A = np.array([-1, 0, 0]) B = np.array([1, 0, 0]) # 可调边长 a_tracker = ValueTracker(2) # BC b_tracker = ValueTracker(2) # AC # 用 always_redraw 动态更新三角形(两个镜像三角形) triangles = always_redraw( lambda: self.get_triangles( A, B, a_tracker.get_value(), b_tracker.get_value() ) ) self.add(triangles) # 顶点标签 labels = always_redraw( lambda: self.get_labels(A, B, a_tracker.get_value(), b_tracker.get_value()) ) self.add(labels) # 边长标注 side_labels = always_redraw( lambda: self.get_side_labels( A, B, a_tracker.get_value(), b_tracker.get_value() ) ) self.add(side_labels) # 动画:改变 BC 和 AC 的长度 self.play(a_tracker.animate.set_value(3), run_time=2) self.play(b_tracker.animate.set_value(1), run_time=2) self.play( a_tracker.animate.set_value(2), b_tracker.animate.set_value(2), run_time=2 ) self.wait() def solve_vertex_C(self, A, B, a, b): """用 SymPy 求解顶点 C,返回两个镜像点的 np.array 坐标列表""" x, y = sp.symbols("x y", real=True) # AB 的长度 c = np.linalg.norm(B - A) # 距离约束方程 eq1 = (x - 0) ** 2 + (y - 0) ** 2 - b**2 # 以 A 为原点 eq2 = (x - c) ** 2 + (y - 0) ** 2 - a**2 # 以 B 为原点 solutions = sp.solve([eq1, eq2], (x, y), dict=True) if not solutions: return [] # 转换到实际坐标系 AB_vec = B - A x_axis = AB_vec / c y_axis = np.array([-x_axis[1], x_axis[0], 0]) C_points = [] for sol in solutions: sol_x = float(sp.N(sol[x])) sol_y = float(sp.N(sol[y])) # 局部坐标转全局坐标 C = A + sol_x * x_axis + sol_y * y_axis C_points.append(C) return C_points def get_triangles(self, A, B, a, b): C_points = self.solve_vertex_C(A, B, a, b) if not C_points: return VGroup() # 无法构成三角形时返回空 triangles = VGroup() colors = [BLUE, GREEN] for i, C in enumerate(C_points): triangle = Polygon( A, B, C, color=colors[i % 2], fill_opacity=0.3, stroke_width=2 ) triangles.add(triangle) return triangles def get_labels(self, A, B, a, b): C_points = self.solve_vertex_C(A, B, a, b) if not C_points: return VGroup() label_A = MathTex("A", color=WHITE, font_size=28).next_to(A, DL, buff=0.15) label_B = MathTex("B", color=WHITE, font_size=28).next_to(B, DR, buff=0.15) labels = VGroup(label_A, label_B) for i, C in enumerate(C_points): direction = UP if C[1] >= A[1] else DOWN label_C = MathTex(f"C_{i+1}", color=WHITE, font_size=28).next_to( C, direction, buff=0.15 ) labels.add(label_C) return labels def get_side_labels(self, A, B, a, b): C_points = self.solve_vertex_C(A, B, a, b) if not C_points: return VGroup() c = np.linalg.norm(B - A) labels = VGroup() # AB 边长标注(共用) label_AB = MathTex(f"{c:.1f}", font_size=24, color=YELLOW).move_to( (A + B) / 2 + DOWN * 0.3 ) labels.add(label_AB) # 每个三角形的 AC 和 BC 边长标注 for i, C in enumerate(C_points): direction = LEFT if C[0] < (A[0] + B[0]) / 2 else RIGHT label_AC = MathTex(f"{b:.1f}", font_size=24, color=RED).move_to( (A + C) / 2 + direction * 0.3 ) label_BC = MathTex(f"{a:.1f}", font_size=24, color=RED).move_to( (B + C) / 2 + (-direction) * 0.3 ) labels.add(label_AC, label_BC) return labels

http://www.jsqmd.com/news/1086985/

相关文章:

  • 如何用PiliPlus打造你的专属B站体验?
  • 终极字体库指南:15款专业字体一键获取与完整使用教程
  • 同样是库文件,嵌入式静态库和动态库差异到底在哪?
  • YimMenu终极指南:安全增强你的GTA5游戏体验
  • 从酷狗音乐到MoeKoe Music:一个二次元音乐爱好者的技术突围之路
  • 量子计算在分子模拟中的应用与VQE算法实践
  • Untrunc视频修复工具终极指南:免费恢复损坏的MP4视频文件的完整教程
  • BetterNCM插件管理器:Rust技术栈打造的高效网易云音乐扩展方案
  • 文件上传漏洞代码审计:从原理到实战的攻防博弈
  • 流式输出(Streaming)原理与踩坑经验
  • VSCODE下verilog-format插件配置全攻略:从零到优雅排版
  • 5个实用技巧让EhViewer漫画阅读体验全面升级
  • macOS NVIDIA显卡驱动终极指南:一键安装与智能管理全解析
  • 世界杯一粒进球被吹掉,背后可能有多少 AI?
  • 如何解决AMD Ryzen硬件调试中的5大难题:高级工具实战指南
  • Translumo:Windows平台终极实时屏幕翻译神器,3分钟开启无障碍游戏体验
  • 分布式系统故障排查自动化实践与DrP平台解析
  • Radeon Software Slimmer:重构AMD显卡驱动的智能精简革新
  • Keccak哈希引擎的轻量级统一架构与容错设计
  • 终极PT站一键转载神器:告别繁琐操作,3分钟快速上手
  • 如何用项目经验打动Java面试官
  • 2026年揭秘!市面上热门的伺服电力测功机工厂口碑究竟如何?
  • 离线漫画收藏的艺术:picacomic-downloader如何重新定义你的数字阅读体验
  • Appium Android自动化测试环境搭建:从原理到实战的完整指南
  • 3个方法有效解决Windows窗口尺寸锁定问题:WindowResizer让你重新掌控屏幕布局
  • RH850/U2C评估板原理图深度解析:从电源设计到调试实战
  • 3分钟颠覆你的聊天记忆管理:让微信对话成为永久数字资产
  • 如何高效使用ACOLITE大气校正工具:完整实战指南
  • 5分钟免费绕过iPhone激活锁:applera1n实用指南
  • WebAssembly AI 推理插件——让浏览器跑起轻量模型的工程方案