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

痛点场景还原:一个具体的例子

先看一段“有毛病”的代码,直观感受一下问题:

from manim import * import numpy as np class BadRoseCurve(Scene): def construct(self): axes = Axes(x_range=[-3, 3], y_range=[-3, 3]) self.add(axes) # 用累积密度法生成非均匀 theta:尖端稀疏(快),中部密集(慢) n_points = 500 t = np.linspace(0, 2 * PI, n_points) # 密度函数:在 cos(5t)=0 处(花瓣中部)密度高,在 |cos(5t)|=1 处(尖端)密度低 density = 1 + 3 * np.sin(5 * t) ** 2 theta = np.cumsum(density) theta = theta / theta[-1] * PI # 归一化到 [0, π](k为奇数时只需π即可画完) r = np.cos(5 * theta) x = r * 2 * np.cos(theta) y = r * 2 * np.sin(theta) points = [axes.c2p(x[i], y[i]) for i in range(n_points)] curve = VMobject(color=PINK, stroke_width=3) curve.set_points_as_corners(points) # 用 Create 画曲线——速度明显不均匀! self.play(Create(curve), run_time=5, rate_func=linear) self.wait()

运行这个动画,你会看到:花瓣尖端几帧就画完了,而花瓣中部却画得很慢。

rate_func=linear控制的是动画进度的均匀,但曲线本身点的分布就是疏密不均的,所以视觉速度必然忽快忽慢。

2. SymPy 解决方案:弧长参数化三步走

要让绘制速度均匀,核心思路是:生成一组让相邻点之间实际距离相等的参数值。具体分三步:

  1. 用积分算出弧长函数 L(θ)——从起点到参数 θ 的弧长
  2. 算总弧长,等分出一组目标弧长值 s1,s2,...
  3. 对每个$ s_i ,反解方程 L(\theta)=s_i ,得到对应的 \theta_i $

这三步,SymPy都能帮上忙。

2.1 用 SymPy 推导弧长函数

import sympy as sp theta = sp.Symbol('theta', real=True) n = 5 # 五瓣玫瑰线 # 极坐标方程(注意这里放大了2倍,与痛点代码中的 r*2 保持一致) r = 2 * sp.cos(n * theta) # 极坐标 → 直角坐标(符号推导,零误差) x = r * sp.cos(theta) # x = r(θ)·cos(θ) y = r * sp.sin(theta) # y = r(θ)·sin(θ) # 弧长微元:ds/dθ = sqrt((dx/dθ)² + (dy/dθ)²) dx_dtheta = sp.diff(x, theta) dy_dtheta = sp.diff(y, theta) # 弧长微元表达式(注意:这里不算积分,只保留被积函数) ds_dtheta = sp.sqrt(dx_dtheta**2 + dy_dtheta**2) print("弧长微元 ds/dθ =", sp.simplify(ds_dtheta)) # 运行结果: ''' 弧长微元 ds/dθ = 2*sqrt((2*sin(4*theta) + 3*sin(6*theta))**2 + (2*cos(4*theta) - 3*cos(6*theta))**2) '''

SymPy会输出一个椭圆积分形式的表达式——这是正常的,很多曲线的弧长都没有初等表达式。

没关系,数值求解一样好用。

2.2 用 nsolve 反解等弧长参数点

import numpy as np from scipy.integrate import quad from scipy.optimize import bisect # 数值求根,稳定且快 # 把弧长微元转成可数值计算的函数 ds_func = sp.lambdify(theta, ds_dtheta, 'numpy') # 数值弧长函数:L(t) = ∫₀^t ds def arc_length(t): """计算从 0 到 t 的弧长""" result, _ = quad(ds_func, 0, t, limit=100) return result # 计算总弧长 total_length = arc_length(2 * np.pi) print(f"总弧长: {total_length:.4f}") # 等分弧长,用数值求根反解对应的 theta N = 500 s_values = np.linspace(0, total_length, N) theta_vals = [] # 辅助函数:f(t) = L(t) - s,我们要找 f(t)=0 的根 def f(t, s): return arc_length(t) - s for i, s in enumerate(s_values): if i == 0: theta_vals.append(0.0) # s=0 对应 theta=0 continue # 初始搜索区间:用上一个 theta 作为左边界 # 弧长是单调递增的,所以解一定在 [左边界, 右边界] 之间 left = theta_vals[-1] # 上一个已解出的 theta right = left + 0.5 # 向右扩展,足够覆盖下一个等分点 # 扩展右边界直到 f(right, s) > 0(确保根在区间内) while f(right, s) < 0: right += 0.5 # 二分法求根(稳定、快速) sol = bisect(f, left, right, args=(s,), xtol=1e-8) theta_vals.append(float(sol)) theta_vals = np.array(theta_vals)

代码要点

  • sp.lambdify把符号表达式编译成 NumPy 函数,求值快
  • sp.nsolve数值解方程,给定一个好猜测值能显著加速
  • 猜测值用“弧长占比 × 总参数范围”做线性估计,足够接近真实解

3. Manim 联动实战

把上面的计算和 Manim 动画串起来,就是一份完整可运行的代码:

from manim import * import numpy as np import sympy as sp from scipy.integrate import quad from scipy.optimize import bisect class UniformRoseCurve(Scene): def construct(self): # ===== SymPy + 数值积分计算等弧长参数点 ===== theta = sp.Symbol("theta", real=True) n = 5 # 极坐标方程 r = 2*cos(5θ) r = 2 * sp.cos(n * theta) # 极坐标转直角坐标 x_expr = r * sp.cos(theta) y_expr = r * sp.sin(theta) # 弧长微元 dx = sp.diff(x_expr, theta) dy = sp.diff(y_expr, theta) ds_dtheta = sp.sqrt(dx**2 + dy**2) # 数值弧长函数 ds_func = sp.lambdify(theta, ds_dtheta, "numpy") def arc_length(t): val, _ = quad(ds_func, 0, t, limit=100) return val # 总弧长(k为奇数时只需π即可画完) total_len = arc_length(np.pi) print(f"总弧长: {total_len:.4f}") # 用二分法反解等弧长 theta(速度快) N = 500 s_vals = np.linspace(0, total_len, N) theta_vals = [0.0] def f(t, s): return arc_length(t) - s for i in range(1, N): s = s_vals[i] left = theta_vals[-1] right = left + 0.5 # 扩展右边界直到 f(right) > 0 while f(right, s) < 0: right += 0.5 sol = bisect(f, left, right, args=(s,), xtol=1e-8) theta_vals.append(float(sol)) theta_vals = np.array(theta_vals) # 计算直角坐标点 x_func = sp.lambdify(theta, x_expr, "numpy")
http://www.jsqmd.com/news/1094247/

相关文章:

  • DeepPCB:1500对图像数据集,开启PCB缺陷检测的AI时代
  • 嵌入式事件驱动架构:硬件自动化如何解放CPU并提升实时性
  • 【计算机毕业设计】Harcend学习网站的设计与实现
  • 计算机毕业设计之东北特产网上商城的设计与实现
  • 把 Agent 效果从 “感觉” 变成 “可验证”
  • GPT-4稀疏激活原理:MoE架构与动态路由技术解析
  • 告别低效手工:Nimble Document如何激活企业文档数据价值
  • Redis Key 空间事件通知机制
  • 计算机毕业设计之基于SSM框架的运动康复医疗管理系统
  • 怎样永久激活IDM下载工具:3步实用教程告别试用限制
  • 攻克eNSP AR1启动难题:从错误代码40到兼容性版本精准匹配
  • Agent 核心原理:用小项目验证核心能力
  • 为什么方向看准了,还是拿不住单子
  • AES加密在图片处理中的实战应用:原理、实现与安全考量
  • Win11Debloat终极指南:3分钟彻底优化你的Windows 11系统
  • 从 ReAct 到 Planning:从走一步看一步到先拆解再推进
  • 【交流纪实】现在的PCIe 6.0协议分析仪和训练器都进化到什么程度了?
  • Java集成MQTT协议对接第三方设备实战————从参数配置到业务落地的避坑指南
  • 【独家首发】ChatGPT Plus额度重置周期漏洞利用指南(非越狱,纯合规,已通过2024.06灰度测试)
  • 2026生成式引擎优化(GEO)行业观察:合肥本地AI搜索优化现状与落地逻辑
  • 告别传统:2026智能试剂柜行业智能化、物联化发展新趋势!
  • 2026顶流!5款AI论文工具实测,治愈文献焦虑,初稿撰写快人一步
  • ProperTree跨平台plist编辑器终极指南:如何高效管理macOS配置文件
  • 阿里云PolarDB(兼容Oracle)从入门到精通:部署、连接与SQL语法全解
  • 软件空对象管理化的空值默认处理
  • 如何使用 Python 设置 Excel 单元格数字格式
  • 基于双阀值区间扰动观察法与带预测模型模糊PID控制法的光伏MPPT控制仿真模型研究(Simulink仿真实现)
  • NHS-PEG-Silane 综合功能特性解析 —— 低吸附、高偶联、强锚固三大核心优势
  • 中小律所案件管理系统怎么选?案件云、Alpha、iCourt 适合谁
  • TAS5711数字功放芯片全解析:从D类放大原理到2.1声道实战设计