F#奇妙游(17):F#与真空一维平面大地抛石飞行力学

news2024/12/28 16:21:18

F#还能干点啥

距离上一次更新已经过去了很久(40分钟之久!),这段时间我在学习F#,并且在工作(划掉,躺肥并没有工作要做)中使用F#。

那干点啥呢?还是老本行吧,搞点飞行力学。

有一个球(质点),在一维平面大地、真空两个假设条件下,以一定的初始速度和初始角度抛出,求其运动轨迹。什么?这也是飞行力学?当然是啊,飞行力学不就是研究物体在空间中的运动吗?我假设真空怎么了?我假设一维平面大地怎么了?我假设球是质点怎么了?还不是飞行力学啊!

扔石头就是最早的飞行力学应用!……好吧,我承认是我说的。但是,不能因为是我说的就说不对啊!

在这里插入图片描述

问题超级简单,球的位置可以用一个二维向量表示,速度也可以用一个二维向量表示,那么问题就是求解一个二阶微分方程组:

d 2 r ⃗ d t 2 = a ⃗ \frac{d^2\vec{r}}{dt^2} = \vec{a} \\ dt2d2r =a

拆成一阶微分方程组就是:
{ d x d t = v x d v x d t = 0 d y d t = v y d v y d t = − g \begin{cases} \frac{dx}{dt} = v_x \\ \frac{dv_x}{dt} = 0 \\ \frac{dy}{dt} = v_y \\ \frac{dv_y}{dt} = -g \end{cases} dtdx=vxdtdvx=0dtdy=vydtdvy=g

其中 g g g是重力加速度, g = 9.8 m / s 2 g=9.8m/s^2 g=9.8m/s2。在时刻 t = 0 t=0 t=0有,球的位置和速度分别为: r ⃗ 0 = [ x 0 , y 0 ] T \vec{r}_0=[x_0, y_0]^T r 0=[x0,y0]T v ⃗ 0 = [ v x 0 , v y 0 ] T \vec{v}_0=[v_{x0}, v_{y0}]^T v 0=[vx0,vy0]T。问题的解析解是:

{ x = v x 0 t + x 0 v x = v x 0 y = − 1 2 g t 2 + v y 0 t + y 0 v y = − g t + v y 0 \begin{cases} x = v_{x0}t + x_0 \\ v_x = v_{x0} \\ y = -\frac{1}{2}gt^2 + v_{y0}t + y_0 \\ v_y = -gt + v_{y0} \end{cases} x=vx0t+x0vx=vx0y=21gt2+vy0t+y0vy=gt+vy0

好无聊啊……这么简单的结果我不能接受!我要用更加复杂的办法,并且用F#来实现!

飞行力学的数学模型

这一类问题,在数学上都可以归结为Ordinary Differential Equation,ODE,常微分方程。

d y ⃗ d t = f ⃗ ( t , y ⃗ ) \frac{d\vec{y}}{dt} = \vec{f}(t, \vec{y}) dtdy =f (t,y )

或者写为:

y ⃗ ˙ = f ⃗ ( t , y ⃗ ) \dot{\vec{y}} = \vec{f}(t, \vec{y}) y ˙=f (t,y )

求解ODE的解,就是在 y ⃗ ( 0 ) \vec{y}(0) y (0)已知的情况下,求解 y ⃗ ( t ) , t ∈ [ 0 , t f ] \vec{y}(t), t\in [0, t_f] y (t),t[0,tf]

常微分方程的求解方法有很多,比如解析解、数值解、级数解等等。解析解就是上面那个解,数值解就是用数值方法求解,级数解就是用级数展开求解。

这里我使用四阶龙格库塔法来求解这个问题。

龙格库塔法

龙格库塔法是一种数值解常微分方程的方法,它的思想是,用一系列的中间变量来逼近微分方程的解。

{ k ⃗ 1 = f ⃗ ( t n , y ⃗ n ) k ⃗ 2 = f ⃗ ( t n + h 2 , y ⃗ n + h 2 k ⃗ 1 ) k ⃗ 3 = f ⃗ ( t n + h 2 , y ⃗ n + h 2 k ⃗ 2 ) k ⃗ 4 = f ⃗ ( t n + h , y ⃗ n + h k ⃗ 3 ) y ⃗ n + 1 = y ⃗ n + h 6 ( k ⃗ 1 + 2 k ⃗ 2 + 2 k ⃗ 3 + k ⃗ 4 ) \begin{cases} \vec{k}_1 = \vec{f}(t_n, \vec{y}_n) \\ \vec{k}_2 = \vec{f}(t_n + \frac{h}{2}, \vec{y}_n + \frac{h}{2}\vec{k}_1) \\ \vec{k}_3 = \vec{f}(t_n + \frac{h}{2}, \vec{y}_n + \frac{h}{2}\vec{k}_2) \\ \vec{k}_4 = \vec{f}(t_n + h, \vec{y}_n + h\vec{k}_3) \\ \vec{y}_{n+1} = \vec{y}_n + \frac{h}{6}(\vec{k}_1 + 2\vec{k}_2 + 2\vec{k}_3 + \vec{k}_4) \end{cases} k 1=f (tn,y n)k 2=f (tn+2h,y n+2hk 1)k 3=f (tn+2h,y n+2hk 2)k 4=f (tn+h,y n+hk 3)y n+1=y n+6h(k 1+2k 2+2k 3+k 4)

F#实现

运算符重载

可以看到上面的算法里面最突出的特征就是有很多向量的计算。那么首先我们先定义浮点数向量之间的算术计算。F#有一套很优雅的算子符号重载机制,我们可以用这个机制来实现向量的算术计算。

以加法为例,需要处理浮点数与向量的加法,向量与浮点数的加法,以及向量与向量的加法。

module arithmetic =
    let inline (.+) (a: obj) (b: obj) =
        match a, b with
        | :? float as op1, (:? array<float> as op2) -> Array.map (fun x -> op1 + x) op2
        | :? array<float> as op1, (:? float as op2) -> Array.map (fun x -> x + op2) op1
        | :? array<float> as op1, (:? array<float> as op2) -> Array.map2 (+) op1 op2
        | _ -> failwith "Type mismatch"

这个代码里使用了F#的类型判断match语法。唯一需要注意的就是第二个操作数的类型判断前后的括号,如果这里没有这个括号,op2就会映射到元组上,而不是float或者array上。

数组的计算,F#已经通过Array.map和Array.map2提供了支持,我们只需要在这个基础上进行封装即可。

通过这样的方法,就可以很容易把其他运算定义出来。

module arithmetic =
    let inline (.+) (a: obj) (b: obj) =
        match a, b with
        | :? float as op1, (:? array<float> as op2) -> Array.map (fun x -> op1 + x) op2
        | :? array<float> as op1, (:? float as op2) -> Array.map (fun x -> x + op2) op1
        | :? array<float> as op1, (:? array<float> as op2) -> Array.map2 (+) op1 op2
        | _ -> failwith "Type mismatch"

    let inline (.-) (a: obj) (b: obj) =
        match a, b with
        | :? float as op1, (:? array<float> as op2) -> Array.map (fun x -> op1 - x) op2
        | :? array<float> as op1, (:? float as op2) -> Array.map (fun x -> x - op2) op1
        | :? array<float> as op1, (:? array<float> as op2) -> Array.map2 (-) op1 op2
        | _ -> failwith "Type mismatch"

    let inline (.*) (a: obj) (b: obj) =
        match a, b with
        | :? float as op1, (:? (array<float>) as op2) -> Array.map (fun x -> op1 * x) op2
        | :? array<float> as op1, (:? float as op2) -> Array.map (fun x -> x * op2) op1
        | :? array<float> as op1, (:? array<float> as op2) -> Array.map2 (*) op1 op2
        | _ -> failwith "Type mismatch"

    let inline (./) (a: obj) (b: obj) =
        match a, b with
        | :? float as op1, (:? (array<float>) as op2) -> Array.map (fun x -> op1 / x) op2
        | :? array<float> as op1, (:? float as op2) -> Array.map (fun x -> x / op2) op1
        | :? (array<float>) as op1, (:? (array<float>) as op2) -> Array.map2 (/) op1 op2
        | _ -> failwith "Type mismatch"

    let inline (.^) (a: obj) (b: obj) =
        match a, b with
        | :? float as op1, (:? (array<float>) as op2) -> Array.map (fun x -> op1 ** x) op2
        | :? array<float> as op1, (:? float as op2) -> Array.map (fun x -> x ** op2) op1
        | :? array<float> as op1, (:? array<float> as op2) -> Array.map2 (fun x y -> x ** y) op1 op2
        | _ -> failwith "Type mismatch"

    let inline dotSin (a: array<float>) = Array.map sin

    let inline dotCos (a: array<float>) = Array.map cos

    let inline dotTan (a: array<float>) = Array.map tan

    let inline dotAsin (a: array<float>) = Array.map asin

    let inline dotAcos (a: array<float>) = Array.map acos

    let inline dotAtan (a: array<float>) = Array.map atan

    let inline dotSinh (a: array<float>) = Array.map sinh

    let inline dotCosh (a: array<float>) = Array.map cosh

    let inline dotTanh (a: array<float>) = Array.map tanh

    let inline dotLn (a: array<float>) = Array.map log

    let inline dotLog10 (a: array<float>) = Array.map log10

    let inline dotExp (a: array<float>) = Array.map exp

    let inline dotSqrt (a: array<float>) = Array.map sqrt

    let inline dotAbs (a: array<float>) = Array.map abs

    let inline dotSign (a: array<float>) = Array.map sign

    let inline dotCeil (a: array<float>) = Array.map ceil

    let inline dotFloor (a: array<float>) = Array.map floor

    let inline dotRound (a: array<float>) = Array.map round

    let inline dotTruncate (a: array<float>) = Array.map truncate

龙格库塔法

利用上面的算子符号重载,我们可以很容易地实现龙格库塔法的单步推进。

    open arithmetic


    let rk4Step
        (t0: float)
        (dt: float)
        (x0: array<float>)
        (para: obj)
        (f: obj -> float -> array<float> -> array<float>)
        =
        let k1 = f para t0 x0
        let k2 = f para (t0 + dt / 2.0) (x0 .+ dt / 2.0 .* k1)
        let k3 = f para (t0 + dt / 2.0) (x0 .+ dt / 2.0 .* k2)
        let k4 = f para (t0 + dt) (x0 .+ dt .* k3)

        x0
        .+ dt / 6.0 .* (k1 .+ 2.0 .* k2 .+ 2.0 .* k3 .+ k4)

可以看到,这就是数学公式的直接翻译,非常简单。

单步推进的基础上,就可以实现前面提到的ODE边值问题的解法。

    let rk4
        (t0: float)
        (dt: float)
        (x0: array<float>)
        (para: obj)
        (f: obj -> float -> array<float> -> array<float>)
        (t: float)
        =
        let n = int (t / dt)
        let rec loop (x: array<float>) (t: float) (n: int) =
            if n = 0 then
                x
            else
                let x1 = rk4Step t dt x para f
                loop x1 (t + dt) (n - 1)

        loop x0 t0 n

尾迭代虽然很酷炫,但是对于我们只能理解扔石头飞行力学的头脑而言,还是太难了……那么我们就是用F#不太纯一面来实现。

    let rk4
        (tspan: float * float)
        (dt: float)
        (x0: array<float>)
        (para: obj)
        (f: obj -> float -> array<float> -> array<float>)
        =
        let t0, tf = tspan
        let t = [| t0..dt..tf |]

        let t =
            if t[t.Length - 1] < tf then
                Array.append t [| tf |]
            else
                t

        let n = t.Length

        let x = Array.zeroCreate n

        x[0] <- x0

        for i = 0 to n - 2 do
            x[i + 1] <- rk4Step t[i] (t[i + 1] - t[i]) x.[i] para f

        t, x

这个代码就是把前面的rk4Step循环调用,但是对于时间不长和时间计算点进行了一点点处理,确保积分达到 t f t_f tf,实现了对ODE的求解。

问题的解

定高释放石头飞行力学

对于上面的飞行力学问题, y ⃗ = [ x , v x , y , v y ] T \vec{y}=[x, v_x, y, v_y]^T y =[x,vx,y,vy]T f ⃗ ( t , y ⃗ ) = [ v x , 0 , v y , − g ] T \vec{f}(t, \vec{y})=[v_x, 0, v_y, -g]^T f (t,y )=[vx,0,vy,g]T。我们首先来做一个更加简化的问题,假设没有初始速度,就在 x 0 , y 0 = 10 x_0, y_0=10 x0,y0=10的地方释放这个石头(这也是飞行力学!)。问题就变为:

{ d y d t = v y d v y d t = − g \begin{cases} \frac{dy}{dt} = v_y \\ \frac{dv_y}{dt} = -g \end{cases} {dtdy=vydtdvy=g

y 0 = 10 , v y 0 = 0 y_0 = 10, v_{y0} = 0 y0=10,vy0=0,那么解析解就是:

{ y = − 1 2 g t 2 + 10 v y = − g t \begin{cases} y = -\frac{1}{2}gt^2 + 10 \\ v_y = -gt \end{cases} {y=21gt2+10vy=gt

求解代码

求解的代码很简单。

let func (para: obj) (t: float) (y: float []) =
    let g = para :?> float
    [| y[1]; g |]

let y0 = [| 10.0; 0.0 |]
let t0 = 0.0
let dt = 0.0001

let t1 = 1.0000001

首先定义了被积函数,然后定义了初始条件,最后定义了积分区间。然后是调用rk4函数进行求解。注意,这里把重力加速度作为参数传递给了被积函数。

let t, yy = rk4 (t0, t1) dt y0 -9.8 func

最后是数据处理,绘制图形,绘图用的ScottPlot,前面已经介绍过。

                     
let n = yy.Length - 1


let get_y (y: float array array) i = (array2D [for j in 0 .. y.Length-1 -> y.[j]]).[*, i]

let yTherory t =
    0.5 * -9.8 .* t .* t .+ 10.0

let vTherory t =
    -9.8 .* t
    

let plt = Plot(600, 400)
plt.AddScatter(t, yTherory t, label = "Theoretical result")
plt.AddScatter(t, get_y yy 0, label = "Numerical result")
plt.XAxis.Label("t (s)")
plt.YAxis.Label("y (m)")
plt.Legend()
plt.Title("Position")
plt.SaveFig("fall_y.png", scale = 2.0)

plt.Clear()
plt.AddScatter(t, vTherory t, label = "Theoretical result")
plt.AddScatter(t, get_y yy 1, label = "Numerical result")
plt.XAxis.Label("t (s)")
plt.YAxis.Label("v (m/s)")
plt.Title("Velocity")
plt.SaveFig("fall_v.png", scale = 2.0)

结果对比

我们来看看数值解和解析解的对比:

在这里插入图片描述
在这里插入图片描述

一致性还是很强的,当然这里步长取得非常小,所以误差也很小。当我们把步长放到0.2的时候,还是能得到很好的结果。

在这里插入图片描述
在这里插入图片描述

总结

  1. 完美解决了真空一维平面大地的定高释放石头飞行力学问题;
  2. 学习了F#的运算符重载机制;
  3. 学习了F#复杂的类型测试match语法,注意括号的使用;
  4. F#进行数值计算果然还算是好用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/784059.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ELK 使用kibana查询和分析nginx日志

背景&#xff1a;使用kibana查询和分析nginx请求日志&#xff0c;方便开发人员查询系统日志和分析系统问题。 setp 1、定义Index patterns 2、定义Discover(Search 查询数据) 3、定义Visualizations 3.1 定义Vertical Bar 3.2 、Choose a source 3.3、定义图表 4、定义…

spring boot中常用的安全框架 Security框架 利用Security框架实现用户登录验证token和用户授权(接口权限控制)

spring boot中常用的安全框架 Security 和 Shiro 框架 Security 两大核心功能 认证 和 授权 重量级 Shiro 轻量级框架 不限于web 开发 在不使用安全框架的时候 一般我们利用过滤器和 aop自己实现 权限验证 用户登录 Security 实现逻辑 输入用户名和密码 提交把提交用户名和…

mysql的存储引擎以及适用场景

目录 mysql的体系结构 存储引擎简介 三种存储引擎的区别 如何选择使用哪种的存储引擎&#xff1f; mysql的体系结构 连接层 最上层是一些客户端的链接服务&#xff0c;主要完成一些类似于连接处理&#xff0c;授权认证&#xff0c;以相关的安全方案。服务器也会为安全接入每…

位运算修行手册

*明明自觉学会了不少知识&#xff0c;可真正开始做题时&#xff0c;却还是出现了“一支笔&#xff0c;一双手&#xff0c;一道力扣&#xff08;Leetcode&#xff09;做一宿”的窘境&#xff1f;你是否也有过这样的经历&#xff0c;题型不算很难&#xff0c;看题解也能弄明白&am…

Spring中事务失效的8中场景

1. 数据库引擎不支持事务 这里以 MySQL为例&#xff0c;MyISAM引擎是不支持事务操作的&#xff0c;一般要支持事务都会使用InnoDB引擎&#xff0c;根据MySQL 的官方文档说明&#xff0c;从MySQL 5.5.5 开始的默认存储引擎是 InnoDB&#xff0c;之前默认的都是 MyISAM&#xff…

【node】使用express+gitee搭建图床,并解决防盗链问题

首先创建一个gitee的项目&#xff0c;详细步骤我就不一一说明 注解&#xff1a;大家记得将这个项目开源&#xff0c;还有记得获取自己的私钥&#xff0c;私钥操作如下&#xff1a; node依赖下载&#xff1a; "axios": "cors": "express"…

FPGA设计时序分析一、时序路径

目录 一、前言 二、时序路径 2.1 时序路径构成 2.2 时序路径分类 2.3 数据捕获 2.4 Fast corner/Slow corner 2.5 Vivado时序报告 三、参考资料 一、前言 时序路径字面容易简单地理解为时钟路径&#xff0c;事实时钟存在的意义是为了数据的处理、传输&#xff0c;因此严…

记一次简单的MySql注入试验

试验环境&#xff1a; 1.已经搭建好的php服务器&#xff0c;并可以通过访问到localhost/index.php&#xff1b; 2.已经安装好数据库&#xff0c;并创建表test&#xff0c;表内有name、age等字段&#xff0c;并随便创建几个假数据用于测试&#xff1b;如图&#xff1a; 开始测…

docker 禅道 远程链接 MySQL

主要的坑在下边 红色字体&#xff1a;认真看 第一种方法 搜索镜像 docker search zentao 拉取镜像 docker pull easysoft/zentao:latest 启动容器 –name [容器名] 设置容器名称 -p [主机端口]:80 绑定端口 -v /home/zentao/zentaopms:/www/zentaopms 挂载数据目录 /h…

idea中Easy Code模版配置

首先找到模版位置 找到使用的模版&#xff0c;我用的是MybatisPlus-H,这是我新建的一个模版 controller.java.vm模版 ##导入宏定义 $!{define.vm}##设置表后缀&#xff08;宏定义&#xff09; #setTableSuffix("Controller")##保存文件&#xff08;宏定义&#xff…

PHP8知识详解:PHP8开发工具VS Code的安装

作为PHP8的开发工具有很多&#xff0c;具有IDE功能的有phpstorm、Visual Studio Code、Sublime Text、NetBeans、Eclipse、Codelobster、PHP Designer等&#xff0c;当然还有很多轻量的工具&#xff0c;比如Notepad、Editplus等。本文给你介绍的是万能编辑器Visual Studio Code…

Python基于PyTorch实现循环神经网络回归模型(LSTM回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 LSTM网络是目前更加通用的循环神经网络结构&#xff0c;全称为Long Short-Term Memory&#xff0c;翻…

计算机视觉(二)图像特征提取

文章目录 颜色特征量化颜色直方图适用颜色空间&#xff1a;RGB、HSV等颜色空间操作 几何特征边缘 Edge边缘定义边缘提取 基于关键点的特征描述子引入几何特征&#xff1a;关键点几何特征&#xff1a;Harris角点FAST角点检测几何特征&#xff1a;斑点局部特征&#xff1a;SIFT预…

GPT-4 模型详细教程

GPT-4&#xff08;Generative Pretrained Transformer 4&#xff09;是 OpenAI 的最新语言生成模型&#xff0c;其在各类文本生成任务中表现优秀&#xff0c;深受开发者和研究者喜爱。这篇教程将帮助你理解 GPT-4 的基本概念&#xff0c;并向你展示如何使用它来生成文本。 什么…

前端vue入门(纯代码)35_导航守卫

星光不问赶路人&#xff0c;时光不负有心人 【33.Vue Router--导航守卫】 导航守卫 正如其名&#xff0c;vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中&#xff1a;全局的, 单个路由独享的, 或者组件级的。 记住参数或查…

uniapp JS文件里面调用自定义组件(不用每个页面在template中加组件标签)

前言 工具&#xff1a;uniapp 开发端&#xff1a;微信小程序 其他&#xff1a;uview 2.0 场景&#xff1a;路由器里面&#xff0c;统一验证是否已登录&#xff0c;如果没登录&#xff0c;则直接弹出登录弹窗出来&#xff0c;不管哪个页面都如此。 效果如下&#xff1a; 直接上…

【笔试强训选择题】Day29.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…

rsync—远程同步

目录 一&#xff1a;rsync概述 1.1rsync简介 1.2rsync同步方式 二&#xff1a;rsync特性 三&#xff1a;rsync同步源 四&#xff1a;rsync与cp、scp对比 五&#xff1a;常用rsync命令 六&#xff1a;rsync本地复制实例 七&#xff1a;配置源的俩种表示方法 八&#x…

[NLP]Huggingface模型/数据文件下载方法

问题描述 作为一名自然语言处理算法人员&#xff0c;hugging face开源的transformers包在日常的使用十分频繁。在使用过程中&#xff0c;每次使用新模型的时候都需要进行下载。如果训练用的服务器有网&#xff0c;那么可以通过调用from_pretrained方法直接下载模型。但是就本人…