原标题:Python 求解自行车前后轮轨迹问题

转载自:python中文社区

作者:crazyhat,Python及科学计算爱好者。

数月前偶遇一道自行车相关的趣味数学题:根据下图[1]所示自行车前、后轮轨迹,判断自行车的前进方向,是从左往右还是从右往左?

自行车趣题

不曾想过,平常无奇的自行车的两轮之间还蕴藏着恒定的规律。在赞叹答案思路清奇之余,也萌生了基于前轮轨迹用Python画出相应后轮轨迹的想法;于是便有了本文。

数学模型

文章开头的问题最早出自《Which Way did the Bicycle Go? … and Other Intriguing Mathematical Mysteries》一书,封面用的即是这道看似无法求解的问题。福尔摩斯探案集系列中也设计了类似的案件,可惜给出的却不是正确的解释。在揭晓答案之前,不妨先花几分钟尝试一下能否得到思路。

封面

这实在是一道精彩的几何问题。考虑一下自行车的结构和特点:前轮可以自由控制行进方向,而后轮因为焊死在车架上而只能沿着车身方向行进。因此,后轮轨迹曲线上任一点的切线方向即为车身方向,并且该方向与前轮轨迹曲线的交点即为自行车前后轮轮毂中心的距离,而这保持一个定长(车身长度)。用数学语言描述为:

作后轮曲线上任一点P的切线,得到与前轮轨迹曲线的交点Q,线段PQ保持定长。

所以解题方法为,分别假设前后轮轨迹,然后按照上述结论进行验证即可。本题答案为自行车从左向右行驶,具体参考下图:

自行车趣题解法

自行车趣题解法

好了,数学趣题到此结束。而本文才刚刚开始——已知任意给定的前轮轨迹,如何求解相应的后轮轨迹?为方便起见,我们用参数方程来描述曲线上任一点的坐标(x,y),例如已知前轮轨迹x=α(t), y=β(t),求解后轮轨迹x=x(t), y=y(t)。

由于数学并不是本文的重点,略去具体的推导步骤(高中数学知识即可)得到下图中红色所示的x'和y'的方程。这两个方程便是冥冥之中的“规律”,默默地支配着我们习以为常的自行车后轮的运动轨迹。

数学模型

常微分方程数值解

与我们初等数学阶段学习的方程的不同之处在于,这是关于未知数(x, y)的一阶导数的方程。实际上,大自然正是以微分方程的形式控制着一切的运动和变化,无论是日月星辰的运行,还是手中一杯茶的冷却。所幸的是,本文要处理的问题相对来说非常简单,没有高阶导数、多自变量、非线性的耦合,而仅仅只是一阶常微分方程组。

然而,即便这么一个“简单”的问题,我们大多数情况下依然不能解析求解后轮曲线的代数方程;所以本文采用的是数值求解的方法。欧拉在求解形如y'(x)=f(x, y)的微分方程的数值解问题上给出了一些标准解法。本文待求解的方程组中α, β都是参数t的函数,因此可以归结为x'(t)=f(t, x, y)和y'(t)=g(t, x, y)。其中t为自变量,x和y是待求解的量。

为了套用标准的y'(x)=f(x, y),我们将上述方程组改写为向量形式Y'=F(t, Y)。其中,

Y=(x,y)表示待求解微分方程组的各个分量组成的向量,也就是后轮曲线上的x和y坐标;

F=(f,g)表示待求解微分方程组的各个控制方程。

作为练习,我们当然可以自己实现欧拉方法或者改进的算法(例如Runge-Kutta方法)来求解上述问题,但这不是本文的重点。所以我们直接采用Python科学计算库scipy中的odeint方法来求解常微分方程。经过漫长的铺垫,终于来到Python部分了!

odeint函数声明如下,除了最重要的前三个参数外,其余统一用**kw表示,具体参考官方文档[2]。

scipy.integrate.odeint(func, y0, t, **kw)

func 表示待求解的函数对象,默认参数列表callable(Y, t, …),即待求解变量在前,自变量t在后(可以设置tfirst=True改变为callable(t, Y, …)的形式);如果求解微分方程组,则函数返回值为各个微分方程返回值组成的向量。

y0 表示给定的初始条件。

t表示自变量的求解区间或者给定序列;注意t[0]必须正好对应初始解y0。

函数返回值为二维numpy数组,每一行对应给定的自变量序列t的一个值,每一列对应待求解变量y的一个分量。

Python求解方案

前两节分别建立了数学模型和给出了求解方法,至此理论上这个问题已经解决了,不过我们的Python实践才刚刚开始。考虑到微分方程的解依赖于初始条件,也就是说即便前轮走同样的轨迹,后轮轨迹也会因为刚启动自行车时车身姿态(后轮位置)的不同而不同。当初想到这个问题,也正是想探究以不同姿态启动时,后轮是如何追随前轮轨迹的。所以,下面以前轮轨迹参数方程为构造函数参数开始自行车轨迹问题的面向对象求解。

importautograd.numpy asnp

fromautograd importgrad

fromscipy.integrate importodeint

classBicycleTrack:

'''

Solving and plot back wheel track according to front wheel track and

initial position of the bicycle.

The track of front wheel is given by:

x = fx(t)

y = fy(t)

Arguments:

fx : function object of front wheel track x component

fy : function object of front wheel track y component

'''

def__init__(self, fx, fy):

# front wheel track

self.front_track_x = fx

self.front_track_y = fy

# first derivative of front wheel track on parameter t

self.dfx = grad(fx)

self.dfy = grad(fy)

# solved back track represented with t, x, y

self.t, self.X, self.Y = None, None, None# back track

self.FX, self.FY = None, None# front track

从这个问题的控制方程可知,我们需要求解前轮轨迹的一阶导数,这里借助第三方库autograd来实现[3]。例如上述代码中self.dfx = grad(fx)即为得到前轮横坐标参数方程的函数对象fx的一阶导函数对象dfx。此外,为了避免使用该库过程中出现未知的问题,平时导入numpy库的代码改为import autograd.numpy as np。

接下来定义待求解的微分方程组:

defgoverning_equation(self, t, Y):

''' ODEs of Bicycle Track Problem '''

x, y = Y

k1 = np.array([self.dfx(t), self.dfy(t)])

k2 = np.array([x-self.front_track_x(t), y-self.front_track_y(t)])

returnnp.sum(k1*k2) * k2 / self.L** 2

本文采用了callable(t, Y, …)形式的参数列表(仅仅因为个人习惯),因此后面被odeint调用时需要指明tfirst=True;

self.L为自行车车身长度(前后轮毂中心距离),它在给定求解区间和初始条件时被确定。

最后,调用odeint求解数值解:

defsolve(self, span, P0, num=100):

'''

span: solving range of parameter t

P0 : initial position of back wheel (x, y)

'''

# initial point of back wheel

P0 = np.array(P0)

# initial point of front wheel is defined by parametric equations

t0, t1 = span

Q0 = np.array([self.front_track_x(t0), self.front_track_y(t0)])

# frame length is defined by P0 and Q0

self.L = np.sum((P0-Q0)** 2)** 0.5

# solving

self.t = np.linspace(t0, t1, num)

res = odeint(self.governing_equation, P0, self.t, tfirst= True)

# solved back track

self.X, self.Y = res[:, 0], res[:, 1]

# front wheel track

self.FX, self.FY = self.front_track_x(self.t), self.front_track_y(self.t)

self.FX、 self.FY、 self.X和 self.Y分别保存为求解后的前后轮横纵坐标值,可以直接用于后续的可视化。

验证与结果展示

上文提到仅有少数简单的情形才能得到后轮轨迹的解析表达式,我们以特殊情形之一—— 曳物线[4]为例进行验证。曳物线可以理解为:自行车初始沿着 y轴停放且保持前轮正好在原点,然后前轮沿着 x轴正方向行进,此时后轮的轨迹即为曳物线。

前轮轨迹非常简单:x=t, y=0

根据曳物线的解析表达式得到后轮的解析解为:x=L(t-tanht), y=Lsecht(L为车长)。

importnumpy

asnp

importmatplotlib.pyplot asplt

frombicycle_track importBicycleTrack

L = 1# length

# numeric solution

BT = BicycleTrack( lambdat: t, lambdat: 0*t)

BT.solve([ 0, 5], np.array([ 0, L]), 50)

# analytical solution

t = BT.t

x = L*(t-np.sinh(t)/np.cosh(t))

y = L/np.cosh(t)

# plot

plt.plot(BT.X, BT.Y, 'ro', label= 'numeric solution')

plt.plot(x, y, 'deepskyblue', label= 'analytical solution')

plt.legend

plt.show

验证曳物线

最后,求解一个相对复杂的运动并以动画形式展示结果。限于篇幅,matplotlib动画过程具体参考文末汇总代码。

轨迹动画

BicycleTrack类及主要测试代码汇总如下:https://github.com/dothinking/blog/blob/master/src/bicycle_track/bicycle_track.py

[1] [1.9 Which Way Did the Bicycle Go? – and other bicycle ponderings](https://gdaymath.com/lessons/gmp/1-9-which-way-did-the-bicycle-go-and-other-bicycle-ponderings/)

[2][scipy.integrate.odeint](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html)

[3] [HIPS/autograd](https://github.com/HIPS/autograd)

[4] [曳物线](https://www.lfhacks.com/t/tractrix)返回搜狐,查看更多

责任编辑:

python 文件名随自变量变化_Python 求解自行车前后轮轨迹问题相关推荐

  1. Python 求解自行车前后轮轨迹问题

    ♚ 作者:crazyhat,Python及科学计算爱好者. 数月前偶遇一道自行车相关的趣味数学题:根据下图[1]所示自行车前.后轮轨迹,判断自行车的前进方向,是从左往右还是从右往左? 自行车趣题 不曾 ...

  2. python求平方根的代码_Python求解平方根的方法

    本文实例讲述了Python求解平方根的方法.分享给大家供大家参考.具体如下: 主要通过SICP的内容改写而来.基于newton method求解平方根.代码如下: #!/usr/bin/python ...

  3. python象棋棋盘麦粒问题_Python求解“棋盘米粒倍增”问题

    牟晓东 印度有个古老传说:舍罕王打算奖赏国际象棋的发明人--西萨宰相,在被问及想要得到的赏赐时,西萨回答说:"在棋盘的第1格放1粒大米,第2格放2粒,第3格放4粒,之后的每一格中的米粒数目都 ...

  4. python汉诺塔游戏_python求解汉诺塔游戏

    本文实例为大家分享了python求解汉诺塔游戏的具体代码,供大家参考,具体内容如下 一.问题定义 百度百科定义:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.据说大梵天创造世界的时候做了三 ...

  5. python文件名带日期变量_Python实现文件按照日期命名的方法

    {"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],&q ...

  6. python监控文件内容变化_Python监控文件内容变化

    {"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],&q ...

  7. python中socket怎么用_Python 之socket的应用

    本节主要讲解socket编程的有关知识点,顺便也会讲解一些其它的关联性知识: 一.概述(socket.socketserver): python对于socket编程,提供了两个模块,分别是socket ...

  8. python scikit learn 关闭开源_Python机器学习工具:Scikit-Learn介绍与实践

    Scikit-learn 简介 官方的解释很简单: Machine Learning in Python, 用python来玩机器学习. 什么是机器学习 机器学习关注的是: 计算机程序如何随着经验积累 ...

  9. c语言实现爬虫功能,用C/C 扩展Python语言_python 调用c语言 python实现简单爬虫功能_python实现简单爬虫...

    用C/C 扩展Python语言 Python是一门功能强大的脚本语言,它的强大不仅表现在功能上,还表现在其扩展性上.她提供大量的API以方便程序员利用C/C++对Python进行扩展.因为执行速度慢几 ...

最新文章

  1. 创龙28377d历程_C28x系列的28069、28377D的PWM使用经验
  2. hql 查询关联对象_在spring data jpa中如何做报表统计查询?
  3. linux mysql查看数据库编码_MySQL查看和修改字符编码的实现方法
  4. keyboard键盘demo
  5. mysql安装包设置本地yum源安装包_mysql 5.7.29 在centos7.6下超简单的本地yum源安装与配置...
  6. 【疑难杂症】Excel数值自定义显示万,并保留两位小数
  7. 《岛上书店》一本被高估的书
  8. 清理谷歌浏览器注册表_将谷歌浏览器的注册表彻底删除的方法
  9. 建行u盾弹不出来_安装建行的网银盾驱动的时候系统检测不出怎么办
  10. 华为鸿蒙未来生态,华为王成录:鸿蒙生态构建成功后,未来移动产业20年将属于中国...
  11. Mac电脑怎样关闭sip,苹果电脑关闭系统完整性保护SIP的方法
  12. fatal: You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists). Please, commit your change
  13. Python制作自己的第一人称射击游戏
  14. 震惊!为了家人请不要这样对待自己的身体!
  15. MRI预处理-去颅骨
  16. 计算机网络中的utp指的是,西安交通大学17年5月课程考试《计算机及网络应用基础》作业考核试题...
  17. 《国内十大中文博客托管网站排行榜》
  18. 通过OCR识别技术 识别视频和图片的文字信息怎样得到结果
  19. 现在转行做程序员还有必要吗,培训班有必要去吗?
  20. 百度网盘提取码_PPT+UI作品集+vi模板(百度网盘链接+取货码)送爱奇YI,优库vip...

热门文章

  1. Js 通过点击改变css样式
  2. freemaker转word xml注意事项
  3. 移动互联消亡者及原因分析
  4. 关于Int自增字段和GUID字段的性能测试。只有测试,没有分析,呵呵
  5. Python学习 Day 040 - css选择器
  6. 一个销售精英拜访客户的6大绝招,胜过10次培训,实用!
  7. arguments.callee弃用与webuploader
  8. 安卓虚拟机启动后报错: 类似 SDK Manager] Error: Error parsing .....devices.xml 解决方案...
  9. ERDAS遥感图像配准、及其它一些基本处理
  10. FlashCache初探(一)