N-Body模拟程序
介绍
在本示例中,使用ArkTS编程语言开发了业界编程语言基准测试项目[Benchmarks Game]中的[N体问题模拟程序],实现类木星体轨道计算。
本示例用到了@ohos.taskpool和@ohos.worker 接口。示例中的程序可以用于AOT(Ahead Of Time)等性能测试。
效果预览
使用说明
1.点击按钮Calculate By TaskPool,会从任务池创建一个任务,进行N-Body计算。
2.点击按钮Calculate By Worker,会创建一个Worker,进行N-Body计算。
工程目录
├──entry/src/main/ets/
│ ├──entryability
│ │ └──EntryAbility.ets // 封装整个模块启用
│ ├──model
│ │ ├──CalculateUtil.ets // worker和taskpool都在这里调用
│ │ └──NBody_ETS_6.ts // 天体轨道计算
│ ├──pages
│ │ └──Index.ets // 首页
│ ├──utils
│ │ ├──Constants.ts // 封装只读常量
│ │ └──Logger.ts // 封装整个日志
│ └──worker
│ └──CalculateWorker.ts // worker线程
└──entry/src/main/resources // 应用静态资源目录
相关概念
AOT(Ahead Of Time),即预先编译,在应用程序运行前,将代码预先编译成高性能机器代码,避免在运行时的编译性能消耗和内存消耗,让程序在首次运行就能通过执行高性能机器码获得性能收益。
具体实现
- 使用TaskPool开启子线程运行,计算500万次时间推移天体运行轨道,源码参考[CalculateUtil.ets]
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { taskpool, worker, MessageEvents, util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { offsetMomentum, energy, advance } from './NBody_ETS_6';
import Logger from '../utils/Logger';
const TAG: string = 'CalculateUtil';
let calculateResult: string = "Total time costed = %s ms."
class WorkerMessage {
timeSteps: number;
constructor(timeSteps: number) {
this.timeSteps = timeSteps;
}
}
/**
* 运行天体轨道计算程序
* @param totalTimeSteps 时间推移量
* @returns 计算时间
*/
@Concurrent
export function computeTask(totalTimeSteps: number): number {
const tagInTask: string = 'computeTask';
const timeStep: number = 0.01; // 单位:hour
const fractionDigits: number = 9; // 机械能数值小数位
let start: number = new Date().getTime();
// 建立孤立系统的动量守恒
offsetMomentum();
Logger.info(tagInTask, energy().toFixed(fractionDigits));
// 更新天体在按指定的时间变化后的位置信息
for (let i: number = 0; i < totalTimeSteps; i++) {
advance(timeStep);
}
// 判断系统计算前后机械能守恒
Logger.info(tagInTask, energy().toFixed(fractionDigits));
let end: number = new Date().getTime();
return end - start;
}
/**
* 使用TaskPool开启子线程,执行轨道计算任务
* @param totalTimeSteps 时间推移量
*/
export function computeNBodyByTaskPool(totalTimeSteps: number): void {
Logger.info(TAG, "computeNBodyByTaskPool: start executing");
let task: taskpool.Task = new taskpool.Task(computeTask, totalTimeSteps);
try {
Logger.info(TAG, 'computeNBodyByTaskPool: start calculating...');
// 向taskpool线程池派发子线程任务
taskpool.execute(task, taskpool.Priority.HIGH).then((res) => {
Logger.info(TAG, 'computeNBodyByTaskPool: executed successfully, total time costed = ' + res + ' ms.');
AppStorage.set<String>('timeCost', util.format(calculateResult, res.toString()))
})
} catch (err) {
Logger.error(TAG, "computeNBodyByTaskPool: execute failed, " + (err as BusinessError).toString());
}
Logger.info(TAG, "computeNBodyByTaskPool: finish executing");
}
/**
* 使用Worker开启子线程,执行轨道计算任务
* @param totalTimeSteps 时间推移量
*/
export function computeNBodyByWorker(totalTimeSteps: number): void {
Logger.info(TAG, "computeNBodyByWorker: start executing");
let workerInstance = new worker.ThreadWorker("entry/ets/workers/CalculateWorker.ts");
// 设置如何处理,来自worker线程的消息
workerInstance.onmessage = (e: MessageEvents): void => {
let data: Record<string, Object> = e.data;
Logger.info(TAG, 'computeNBodyByWorker: executed successfully, total time costed = ' + data.result + ' ms.');
AppStorage.set<String>('timeCost', util.format(calculateResult, data.result));
}
// 设置由主线程向worker线程发送什么消息
workerInstance.postMessage(new WorkerMessage(totalTimeSteps));
}
-
通过调用computeNBodyByTaskPool()创建一个task任务,并向taskpool线程池派发子线程任务。
-
等待子线程执行任务computeTask(),计算完成后再把结果返回主线程。
-
使用Worker开启子线程运行,计算5000万次时间推计算移天体运行轨道,源码参考[CalculateUtil.ets]
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { taskpool, worker, MessageEvents, util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { offsetMomentum, energy, advance } from './NBody_ETS_6';
import Logger from '../utils/Logger';
const TAG: string = 'CalculateUtil';
let calculateResult: string = "Total time costed = %s ms."
class WorkerMessage {
timeSteps: number;
constructor(timeSteps: number) {
this.timeSteps = timeSteps;
}
}
/**
* 运行天体轨道计算程序
* @param totalTimeSteps 时间推移量
* @returns 计算时间
*/
@Concurrent
export function computeTask(totalTimeSteps: number): number {
const tagInTask: string = 'computeTask';
const timeStep: number = 0.01; // 单位:hour
const fractionDigits: number = 9; // 机械能数值小数位
let start: number = new Date().getTime();
// 建立孤立系统的动量守恒
offsetMomentum();
Logger.info(tagInTask, energy().toFixed(fractionDigits));
// 更新天体在按指定的时间变化后的位置信息
for (let i: number = 0; i < totalTimeSteps; i++) {
advance(timeStep);
}
// 判断系统计算前后机械能守恒
Logger.info(tagInTask, energy().toFixed(fractionDigits));
let end: number = new Date().getTime();
return end - start;
}
/**
* 使用TaskPool开启子线程,执行轨道计算任务
* @param totalTimeSteps 时间推移量
*/
export function computeNBodyByTaskPool(totalTimeSteps: number): void {
Logger.info(TAG, "computeNBodyByTaskPool: start executing");
let task: taskpool.Task = new taskpool.Task(computeTask, totalTimeSteps);
try {
Logger.info(TAG, 'computeNBodyByTaskPool: start calculating...');
// 向taskpool线程池派发子线程任务
taskpool.execute(task, taskpool.Priority.HIGH).then((res) => {
Logger.info(TAG, 'computeNBodyByTaskPool: executed successfully, total time costed = ' + res + ' ms.');
AppStorage.set<String>('timeCost', util.format(calculateResult, res.toString()))
})
} catch (err) {
Logger.error(TAG, "computeNBodyByTaskPool: execute failed, " + (err as BusinessError).toString());
}
Logger.info(TAG, "computeNBodyByTaskPool: finish executing");
}
/**
* 使用Worker开启子线程,执行轨道计算任务
* @param totalTimeSteps 时间推移量
*/
export function computeNBodyByWorker(totalTimeSteps: number): void {
Logger.info(TAG, "computeNBodyByWorker: start executing");
let workerInstance = new worker.ThreadWorker("entry/ets/workers/CalculateWorker.ts");
// 设置如何处理,来自worker线程的消息
workerInstance.onmessage = (e: MessageEvents): void => {
let data: Record<string, Object> = e.data;
Logger.info(TAG, 'computeNBodyByWorker: executed successfully, total time costed = ' + data.result + ' ms.');
AppStorage.set<String>('timeCost', util.format(calculateResult, data.result));
}
// 设置由主线程向worker线程发送什么消息
workerInstance.postMessage(new WorkerMessage(totalTimeSteps));
}
-
通过调用computeNBodyByWorker()创建一个worker线程,把要计算的时间推移量发送给worker线程。
-
等待worker线程调用computeTask()计算完成后再把结果返回主线程。
-
computeTask()完成具体计算任务,源码参考[NBody_ETS_6.ts]
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Constants from '../utils/Constants';
/**
* 天体类
*/
class Body {
x: number;
y: number;
z: number;
vx: number;
vy: number;
vz: number;
mass: number;
constructor(x: number, y: number, z: number, vx: number, vy: number, vz: number, mass: number) {
this.x = x;
this.y = y;
this.z = z;
this.vx = vx;
this.vy = vy;
this.vz = vz;
this.mass = mass;
}
}
const jupiter: Body = new Body(Constants.JUPITER_X, Constants.JUPITER_Y, Constants.JUPITER_Z, Constants.JUPITER_VX,
Constants.JUPITER_VY, Constants.JUPITER_VZ, Constants.JUPITER_MASS);
const saturn: Body = new Body(Constants.SATURN_X, Constants.SATURN_Y, Constants.SATURN_Z, Constants.SATURN_VX,
Constants.SATURN_VY, Constants.SATURN_VZ, Constants.SATURN_MASS);
const uranus: Body = new Body(Constants.URANUS_X, Constants.URANUS_Y, Constants.URANUS_Z, Constants.URANUS_VX,
Constants.URANUS_VY, Constants.URANUS_VZ, Constants.URANUS_MASS);
const neptune: Body = new Body(Constants.NEPTUNE_X, Constants.NEPTUNE_Y, Constants.NEPTUNE_Z, Constants.NEPTUNE_VX,
Constants.NEPTUNE_VY, Constants.NEPTUNE_VZ, Constants.NEPTUNE_MASS);
const sun: Body = new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Constants.SOLAR_MASS);
const bodies: Body[] = Array(sun, jupiter, saturn, uranus, neptune);
/**
* 调整太阳的速度,保证该孤立系统动量守恒
*/
export function offsetMomentum(): void {
// p为momentum的缩写,表示:动量, 等于质量乘以速度 p=mv
let px: number = 0;
let py: number = 0;
let pz: number = 0;
// 累加计算整个系统中,各个天体在三维各矢量方向的动量
for (let i: number = 0; i < bodies.length; i++) {
const body: Body = bodies[i];
const mass: number = body.mass;
px += body.vx * mass;
py += body.vy * mass;
pz += body.vz * mass;
}
// 太阳预设速度为0,通过动量守恒定律,反推太阳各矢量方向速度
const body: Body = bodies[0];
body.vx = -px / Constants.SOLAR_MASS;
body.vy = -py / Constants.SOLAR_MASS;
body.vz = -pz / Constants.SOLAR_MASS;
}
/**
* 更新天体在按指定的时间变化后的位置信息
* @param dt - delta time 时间变化
*/
export function advance(dt: number): void {
const size = bodies.length;
// 两两配对计算各天体瞬时速度
for (let i = 0; i < size; i++) {
const iBody = bodies[i];
let vxi: number = iBody.vx;
let vyi: number = iBody.vy;
let vzi: number = iBody.vz;
for (let j: number = i + 1; j < size; j++) {
const jBody: Body = bodies[j];
// 天体间距离差
const dx: number = iBody.x - jBody.x;
const dy: number = iBody.y - jBody.y;
const dz: number = iBody.z - jBody.z;
const d2: number = dx * dx + dy * dy + dz * dz;
const mag: number = dt / (d2 * Math.sqrt(d2));
// 由天体距离计算引力对速度的相互影响
const jMass: number = jBody.mass;
vxi -= dx * jMass * mag;
vyi -= dy * jMass * mag;
vzi -= dz * jMass * mag;
const iMass: number = iBody.mass;
jBody.vx += dx * iMass * mag;
jBody.vy += dy * iMass * mag;
jBody.vz += dz * iMass * mag;
}
iBody.vx = vxi;
iBody.vy = vyi;
iBody.vz = vzi;
}
// 更新天体的位置信息
for (let i: number = 0; i < size; i++) {
const body: Body = bodies[i];
body.x += dt * body.vx; // 位置 = 时间 * 速度
body.y += dt * body.vy;
body.z += dt * body.vz;
}
}
/**
* 在程序开始和结束后调用,通过计算机械能,判断机械能守恒与否,以检查程序的运行正确性
* @returns 返回系统机械能
*/
export function energy(): number {
let energy: number = 0.0;
const size: number = bodies.length;
// 计算各天体的机械能总和,机械能公式:机械能=动能+势能
for (let i: number = 0; i < size; i++) {
const iBody: Body = bodies[i];
// 对每个天体的动能进行加和,动能公式为:动能=1/2×物体质量×运动速度的平方
energy += 0.5 * iBody.mass * (iBody.vx * iBody.vx + iBody.vy * iBody.vy + iBody.vz * iBody.vz);
// 计算当前遍历到的天体和其他天体间的势能,势能公式为:引力势能=-G*物理A质量*物理B质量/距离
for (let j: number = i + 1; j < size; j++) {
const jBody: Body = bodies[j];
const dx: number = iBody.x - jBody.x;
const dy: number = iBody.y - jBody.y;
const dz: number = iBody.z - jBody.z;
const distance: number = Math.sqrt(dx * dx + dy * dy + dz * dz);
energy -= (iBody.mass * jBody.mass) / distance;
}
}
return energy;
}
- offsetMomentum() 建立孤立系统的动量守恒。
- advance() 更新天体在按指定的时间变化后的位置信息。
- energy() 判断系统计算前后机械能守恒。
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
鸿蒙【北向应用开发+南向系统层开发】文档
鸿蒙【基础+实战项目】视频
鸿蒙面经
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!