UavMissionPlanning 培训报告
1. 报告目标
本文档面向后续接手 UavMissionPlanning 的开发者,目标是帮助读者在较短时间内同时掌握以下 5 件事:
- 看懂整个解决方案的分层架构与项目边界。
- 理解
MissionFlightModel及周边核心组件的设计意图。 - 快速完成 Gurobi 的安装、许可证配置、Visual Studio 接入和运行时排障。
- 能够独立编译并运行
UavExperiments中的示例与 RCP 实验。 - 建立清晰的代码阅读顺序,把论文模型与代码实现对应起来。
如果时间非常紧张,建议优先阅读:
README.mddocs/paper-formulation.mdUavCore/include/core/instance_data.hUavCore/src/parameter_builder.cppUavModel/include/model/mission_flight_model.hUavModel/src/mission_flight_model.cppUavExperiments/src/main.cpp
2. 项目整体架构分析
2.1 解决方案组成
整个解决方案由 4 个 Visual Studio 项目组成:
UavCoreUavModelUavExperimentsUavTests
它们的依赖关系如下:
如果从职责角度理解,可以把系统看成 4 层:
UavCore:问题定义层。负责描述无人机、航点、禁飞区、风区、时间网格和预处理参数。UavModel:优化建模层。负责把UavCore的输入转化为 Gurobi 的变量、约束和目标函数。UavExperiments:执行与实验层。负责构造实例、配置选项、启动求解并打印结果。UavTests:轻量验证层。负责验证UavCore中的基础逻辑是否正常,不直接覆盖求解器行为。
2.2 各子项目功能与相互关系
UavCore
UavCore 是基础数据层,也是其余项目共享的“语义字典”。
主要功能:
- 定义问题输入结构
InstanceData - 定义粗时间层/细时间层映射
TimeGrid - 定义预处理输出
DerivedParameters - 提供实例工厂
makePaperDemoInstance()和makeRcp2dInstance()
关键文件:
UavCore/include/core/instance_data.hUavCore/include/core/time_grid.hUavCore/include/core/parameter_builder.hUavCore/src/parameter_builder.cpp
设计意图:
- 把“问题是什么”与“如何建模求解”分离开来。
- 让
UavModel只关注优化模型,而不关心输入数据是如何组织和预处理的。
UavModel
UavModel 是求解器建模层,也是整个项目最核心的逻辑所在。
主要功能:
- 持有
GRBEnv和GRBModel - 根据输入数据创建变量
- 分模块添加约束
- 设置目标函数
- 调用 Gurobi 求解
关键文件:
UavModel/include/model/mission_flight_model.hUavModel/include/model/model_context.hUavModel/include/model/variable_store.hUavModel/include/model/norm_linearizer.hUavModel/src/mission_flight_model.cppUavModel/src/constraints/base_model.cppUavModel/src/constraints/flight_dynamics.cppUavModel/src/constraints/fuel_range_environment.cpp
设计意图:
- 用
MissionFlightModel作为统一建模门面。 - 用
VariableStore统一管理变量与索引。 - 用
ModelContext把共享上下文集中传递。 - 用
NormLinearizer封装范数近似逻辑,避免在多个约束文件里重复写辅助变量和包络约束。
UavExperiments
UavExperiments 是当前默认的主程序入口。
主要功能:
- 创建
GRBEnv - 生成示例问题
- 构造
MissionFlightModel - 选择要启用的扩展项
- 调用
build()和optimize() - 输出求解状态和目标值
关键文件:
UavExperiments/src/main.cpp
设计意图:
- 让运行逻辑保持很薄。
- 把大部分业务复杂度留在
UavCore和UavModel中。
UavTests
UavTests 是一个基于 assert 的轻量测试程序。
主要功能:
- 校验
TimeGrid的索引逻辑 - 校验
ParameterBuilder的数值辅助函数 - 校验 demo 实例是否能生成派生参数
关键文件:
UavTests/src/main.cpp
设计意图:
- 在不依赖 Gurobi 的前提下,尽快发现底层数据和预处理问题。
- 保持测试可在“未配置求解器”的环境下快速运行。
注意:
- 它不验证
MissionFlightModel的约束正确性。 - 它不覆盖 Gurobi 链接、许可证、求解结果等集成问题。
2.3 主要类和模块的设计意图及交互方式
系统主线可以概括为:
交互顺序如下:
UavCore中的实例工厂先生成InstanceDataParameterBuilder::build()生成DerivedParametersUavExperiments创建MissionFlightModelMissionFlightModel::build()依次创建变量、基础约束、扩展约束和目标函数MissionFlightModel::optimize()调用 GurobiUavExperiments输出状态与目标值
2.4 代码结构层次与文件组织方式
整个工程的文件结构具有比较典型的 C++ 静态库组织特点:
include/:放对外暴露的头文件,定义类型和接口src/:放实现文件src/constraints/:进一步按约束主题拆分实现build/:放编译配置文件,如gurobi.propsdocs/:放项目说明与论文映射文档
当前目录组织方式有以下优点:
- 接口和实现分离,便于阅读边界。
UavModel的约束按主题拆分,而不是全部堆在一个文件中。- Gurobi 配置集中在
build/gurobi.props,避免多个项目文件重复维护。
可以把当前目录结构进一步理解为下面这张图:
建议把这一层次理解为:
*.h负责告诉你“有什么”*.cpp负责告诉你“怎么做”
3. 核心代码结构关系图解
3.1 MissionFlightModel 与其他组件的关系
MissionFlightModel 是模型层的总入口,它不只是一个“容器”,而是整个模型装配过程的编排者。
它持有如下关键成员:
GRBEnv& env_GRBModel model_const InstanceData& instance_const DerivedParameters& derived_ModelOptions options_ModelContext context_NormLinearizer linearizer_VariableStore vars_
关系图如下:
从设计上看:
MissionFlightModel负责“调度”VariableStore负责“存变量”ModelContext负责“传上下文”NormLinearizer负责“复用范数近似逻辑”base_model.cpp、flight_dynamics.cpp、fuel_range_environment.cpp负责“落具体约束”
3.2 MissionFlightModel::build() 的装配顺序
MissionFlightModel::build() 是理解全系统协作关系的最佳入口。它的逻辑可以概括为:
configureModel()buildVariables()- 根据开关预处理部分风区变量
addBaseConstraints()- 条件性调用扩展约束模块
setObjective()
这意味着阅读模型代码时,应该始终沿着这个顺序理解,而不是按文件名随机跳读。
对应的装配流程如下:
3.3 VariableStore 变量存储系统的设计原理
VariableStore 的核心思想是:
- 以逻辑分组存储变量
- 以一维
std::vector<GRBVar>存储具体对象 - 通过索引函数把多维问题映射为线性数组下标
它存储的变量大体分为几类:
- 细时间层连续变量:
rx、ry、rz、vx、vy、speed、ax、ay、accel、vzPlus、vzMinus、fuel - 粗时间层状态变量:
airborne、started、active、minSpeedActive - 任务与模式变量:
visit、band - 环境和冲突变量:
collisionDirPos、collisionDirNeg、restrictedOutside、windFaceInside、windActive
设计原因主要有 4 点:
- Gurobi 变量数量很大,用统一容器便于批量管理。
- 建模代码里需要频繁按
u、tf、tc、w、band等维度索引变量。 - 如果直接在多个文件里散落索引公式,后续维护成本会很高。
- 一维数组更容易和固定维度公式配合,也便于按规模预分配。
例如,以下函数体现了这一点:
fineIndex(int u, int tf)coarseIndex(int u, int tc)visitIndex(int u, int w, int tc)bandIndex(int u, int altBand, int throttleBand, int tc)collisionIndex(int pairIndex, int axis, int tc)
这套设计的优点:
- 变量访问统一
- 下标含义清晰
- 减少重复公式和越界风险
这套设计的代价:
- 阅读者需要先理解索引映射规则
- 调试时看到的一维下标不如多维结构直观
可以把 VariableStore 理解成下面这种“按时间层和语义分区”的结构:
索引函数与变量分区之间的关系也可以简化为:
3.4 ModelContext 的作用
ModelContext 是一个轻量的上下文容器,里面打包了:
GRBEnv* envGRBModel* modelconst InstanceData* instanceconst DerivedParameters* derivedModelOptions options
它的设计意图是降低函数间传参复杂度。
如果没有它,辅助组件和约束模块可能需要反复接收:
- 求解器环境
- 模型对象
- 原始输入
- 派生参数
- 选项开关
在当前工程里,ModelContext 的作用更接近“依赖注入包”,方便 NormLinearizer 和各约束实现共享同一套状态。
3.5 NormLinearizer 的作用
NormLinearizer 负责处理范数相关的 MILP 线性近似。
当前提供两个接口:
addAbsEnvelope2d()addAbsEnvelope3d()
它的设计意义在于:
- 将“绝对值辅助变量 + 包络约束”的重复模板抽离出来
- 避免在多个约束场景下复制粘贴相似代码
- 让
base_model.cpp中的核心约束表达更集中于业务含义,而不是包络细节
当前主要使用场景包括:
- 地面控制站航程约束中的平面距离近似
- 速度范数近似
- 加速度范数近似
- 航点访问时的三维距离近似
它在模型中的位置可以用下面的图快速理解:
需要注意的是:
ModelOptions::normDirectionCount2d已经定义,但当前实现还没有用它控制离散方向数。- 现阶段
NormLinearizer使用的是固定包络形式,而不是可配置精度的多方向线性化。
4. Gurobi 优化器详细配置指南
4.1 从零开始的安装与环境准备
建议按以下顺序完成 Gurobi 准备工作:
- 从 Gurobi 官网下载 Windows 版本安装包。
- 安装到本机目录,例如:
C:\gurobi1300\win64 - 确认以下子目录存在:
includelibbin
- 准备许可证文件
gurobi.lic - 打开
UavMissionPlanning.sln - 编译并运行
UavExperiments
本工程当前验证通过的安装位置为:
C:\gurobi1300\win64
关键目录说明:
include:C/C++ 头文件目录,包含gurobi_c++.hlib:链接阶段所需.lib文件bin:运行阶段所需.dll与命令行工具
4.2 本工程中 Gurobi 的接入方式
本项目没有把 Gurobi 路径直接写死在每个 .vcxproj 内,而是通过统一属性表管理:
build/gurobi.props
导入方式:
UavModel.vcxproj导入该属性表UavExperiments.vcxproj导入该属性表UavTests.vcxproj没有导入,因为它不依赖求解器
这种集中式做法的优点:
- 路径只维护一次
- 易于升级 Gurobi 版本
- 便于多项目共享
- 运行时依赖和调试环境也可以集中配置
接入链路可以概括成下面这张图:
4.3 gurobi.props 参数逐项解释
当前文件内容可以概括为 4 组配置:路径、编译链接、调试环境、运行时 DLL 复制。
GUROBI_HOME
<GUROBI_HOME Condition="'$(GUROBI_HOME)' == ''">C:\gurobi1300\win64</GUROBI_HOME>
含义:
- 定义 Gurobi 安装根目录
- 只有当外部环境变量没有显式设置时,才使用这里的默认值
好处:
- 如果团队成员机器路径不同,可以优先通过系统环境变量覆盖
- 如果没有设置环境变量,项目本身仍有默认兜底
GRB_LICENSE_FILE
<GRB_LICENSE_FILE Condition="'$(GRB_LICENSE_FILE)' == '' and Exists('$(GUROBI_HOME)\bin\gurobi.lic')">$(GUROBI_HOME)\bin\gurobi.lic</GRB_LICENSE_FILE>
含义:
- 指定 Gurobi 许可证文件位置
- 如果系统没有设置该环境变量,并且默认路径下确实存在许可证文件,则自动采用该路径
好处:
- 降低“本地调试时找不到许可证”的概率
AdditionalIncludeDirectories
<AdditionalIncludeDirectories>$(GUROBI_HOME)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
含义:
- 把 Gurobi 头文件目录加入编译器搜索路径
作用:
- 让
#include "gurobi_c++.h"能被编译器找到
AdditionalLibraryDirectories
<AdditionalLibraryDirectories>$(GUROBI_HOME)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
含义:
- 把 Gurobi 的
.lib所在目录加入链接器搜索路径
作用:
- 让链接器能找到
gurobi130.lib和gurobi_c++*.lib
AdditionalDependencies
Debug 配置:
<AdditionalDependencies>gurobi_c++mdd2017.lib;gurobi130.lib;%(AdditionalDependencies)</AdditionalDependencies>
Release 配置:
<AdditionalDependencies>gurobi_c++md2017.lib;gurobi130.lib;%(AdditionalDependencies)</AdditionalDependencies>
含义:
- 明确告诉链接器本项目需要哪些 Gurobi 库
当前库说明:
gurobi130.lib:Gurobi 13.0 主库gurobi_c++mdd2017.lib:Debug C++ API 封装库gurobi_c++md2017.lib:Release C++ API 封装库
注意:
- 库名并不总是随 Visual Studio 版本简单变化,必须以本机
lib目录中的实际文件名为准。 - Debug/Release 混用错误库是最常见的链接问题之一。
LocalDebuggerEnvironment
<LocalDebuggerEnvironment>PATH=$(GUROBI_HOME)\bin;%PATH%
GRB_LICENSE_FILE=$(GRB_LICENSE_FILE)</LocalDebuggerEnvironment>
含义:
- 给 Visual Studio 本地调试器注入额外环境变量
具体效果:
- 运行调试时,
PATH会临时包含$(GUROBI_HOME)\bin GRB_LICENSE_FILE会传递给调试进程
好处:
- 即使系统级环境变量没有完全配置好,VS 内启动程序仍有很大概率直接运行成功
CopyGurobiRuntime
<Target Name="CopyGurobiRuntime" AfterTargets="Build" Condition="'$(ConfigurationType)' == 'Application'">
<Copy SourceFiles="$(GUROBI_HOME)\bin\gurobi130.dll" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
</Target>
含义:
- 在应用程序项目编译结束后,把
gurobi130.dll复制到输出目录
作用:
- 避免运行时由于 DLL 不在可执行文件附近而报错
注意:
- 这里只对
Application类型项目生效,不会对静态库项目执行复制
4.4 Visual Studio 2022 中的正确配置方式
当前工程已经通过 gurobi.props 自动完成主要配置,因此最推荐的方式不是手工给每个项目加路径,而是直接使用现有属性表。
建议步骤:
- 打开
UavMissionPlanning.sln - 选择
Debug|x64或Release|x64 - 确保
UavModel和UavExperiments均导入..\build\gurobi.props - 将启动项目设为
UavExperiments - 执行“生成解决方案”
- 启动调试或直接运行
如果你需要手工核对 VS 中的设置,可以检查:
C/C++ -> 常规 -> 附加包含目录链接器 -> 常规 -> 附加库目录链接器 -> 输入 -> 附加依赖项调试 -> 环境
这些值最终都应能映射到 gurobi.props 中的内容。
4.5 许可证配置和运行时依赖管理
本工程当前推荐的许可证路径为:
C:\gurobi1300\win64\bin\gurobi.lic
推荐的许可证配置方式有两层:
- 系统层
- 设置用户环境变量
GRB_LICENSE_FILE
- 设置用户环境变量
- 工程层
- 使用
gurobi.props中的默认兜底逻辑
- 使用
运行时依赖主要是:
gurobi130.dll
当前工程的处理方式:
- 在 Visual Studio 调试环境中把
$(GUROBI_HOME)\bin加入PATH - 构建后复制
gurobi130.dll到输出目录
这两层同时存在,能显著降低运行时报 DLL 缺失的概率。
许可证与运行时依赖关系可以简化成:
5. 项目运行和调试指导
5.1 如何编译和运行 UavExperiments
在 Visual Studio 中运行
推荐步骤:
- 打开
UavMissionPlanning.sln - 选择
Debug|x64 - 右键
UavExperiments,设置为启动项目 - 生成解决方案
- 启动调试或执行不调试运行
执行路径如下:
命令行编译
如果系统中没有把 msbuild 加入 PATH,可以直接调用 Visual Studio 自带的 MSBuild.exe:
& "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" `
"d:\ATKdevelop\三维飞机复现\UavMissionPlanning\UavMissionPlanning.sln" `
/p:Configuration=Debug /p:Platform=x64
命令行运行
$env:GRB_LICENSE_FILE = "C:\gurobi1300\win64\bin\gurobi.lic"
& "d:\ATKdevelop\三维飞机复现\UavMissionPlanning\x64\Debug\UavExperiments.exe"
5.2 Demo 实例的执行方法
默认运行 UavExperiments 时,会执行 runDemo():
- 创建
GRBEnv - 调用
makePaperDemoInstance() - 调用
ParameterBuilder::build() - 启用大多数扩展项
- 构造
MissionFlightModel - 调用
build()和optimize() - 输出
Status与Objective
这个模式适合:
- 验证 Gurobi 是否正确接入
- 观察完整模型装配路径
- 做最基本的功能烟雾测试
runDemo() 的流程图如下:
5.3 RCP 研究的执行方法
命令行带上 rcp 参数时,会执行 runRcp():
& "d:\ATKdevelop\三维飞机复现\UavMissionPlanning\x64\Debug\UavExperiments.exe" rcp
也可以带参数:
& "d:\ATKdevelop\三维飞机复现\UavMissionPlanning\x64\Debug\UavExperiments.exe" rcp 4 20
含义:
- 第一个数字:无人机数量
- 第二个数字:最大离散时间段数
执行逻辑:
- 从较小
intervals开始循环 - 每次调用
makeRcp2dInstance(uavCount, intervals) - 关闭部分与 RCP 研究无关的扩展,如航点、禁飞区、风区和范围扩展
- 保留燃油与避碰逻辑
- 找到第一个可接受状态时返回结果
这个模式适合:
- 观察模型在不同时间离散粒度下的行为
- 快速做序贯规模实验
runRcp() 的流程图如下:
5.4 调试建议
如果要调试建模逻辑,推荐按下面顺序断点:
UavExperiments/src/main.cppMissionFlightModel构造函数MissionFlightModel::build()MissionFlightModel::buildVariables()addBaseConstraints()- 对应扩展模块函数
setObjective()optimize()
这样可以清楚看到:
- 实例内容是否正确
- 变量规模是否符合预期
- 某个开关是否真的生效
- 哪类约束导致求解失败或变慢
推荐的调试断点路线如下:
6. 常见错误排查和解决方案
6.1 No Gurobi license found
原因:
- 程序找不到许可证
优先检查:
gurobi.lic是否存在GRB_LICENSE_FILE是否正确gurobi.props中的默认许可证路径是否有效
推荐路径:
C:\gurobi1300\win64\bin\gurobi.lic
6.2 cannot open include file: gurobi_c++.h
原因:
- 编译器没有找到 Gurobi 头文件目录
优先检查:
build/gurobi.props是否被正确导入GUROBI_HOME是否正确$(GUROBI_HOME)\include是否存在
6.3 unresolved external symbol
原因:
- 链接阶段缺少
.lib或 Debug/Release 配置不匹配
优先检查:
- Debug 是否使用了
gurobi_c++mdd2017.lib - Release 是否使用了
gurobi_c++md2017.lib gurobi130.lib是否存在于lib目录
6.4 程序运行时报缺少 DLL
原因:
gurobi130.dll不在运行时可搜索路径中
优先检查:
CopyGurobiRuntime是否执行成功- 输出目录是否存在
gurobi130.dll LocalDebuggerEnvironment是否把bin目录加入了PATH
6.5 程序能运行但选项效果不明显
原因可能包括:
- 部分开关是“脚手架式”或边界控制,而不是完全独立的功能模块
当前需要特别说明的点:
addRangeExtensions()当前基本是空实现,范围约束已在基础模型中处理normDirectionCount2d已定义但尚未真正驱动NormLinearizerrestrictedOutside和windFaceInside虽在VariableStore中声明,但当前相关约束实现仍是现场创建局部二进制变量
这类现象不是编译错误,但会影响后续开发者对代码完备度的判断。
可以按下面的排障路径快速定位问题:
7. 代码阅读路径建议
7.1 面向新开发者的推荐学习顺序
建议按下面顺序阅读:
README.md
先理解工程目标、工具链和文档入口。docs/paper-formulation.md
建立论文公式与代码模块的对应关系。UavCore/include/core/instance_data.h
先弄清楚问题输入由哪些结构组成。UavCore/include/core/time_grid.h
理解粗时间层和细时间层的离散映射。UavCore/src/parameter_builder.cpp
理解预处理逻辑和示例实例来源。UavModel/include/model/mission_flight_model.h
建立模型门面的整体认识。UavModel/src/mission_flight_model.cpp
理解模型装配顺序和变量创建方式。UavModel/src/constraints/base_model.cpp
先看骨架约束和目标函数。UavModel/src/constraints/flight_dynamics.cpp
再看飞行动力学扩展。UavModel/src/constraints/fuel_range_environment.cpp
最后理解燃油、环境和避碰扩展。UavExperiments/src/main.cpp
回到主入口,形成完整闭环。
7.2 面向模型开发者的阅读顺序
如果你接手后的工作重点是“改模型、加约束、改变量”,推荐顺序略有不同:
mission_flight_model.hmission_flight_model.cppvariable_store.hmodel_context.hnorm_linearizer.hbase_model.cppflight_dynamics.cppfuel_range_environment.cpp
这种顺序更适合快速理解:
- 变量规模
- 约束入口
- 模块边界
- 哪些位置适合扩展
7.3 关键算法与约束实现的解读
基础模型
base_model.cpp 负责基础骨架,主要包括:
- 起点和终点位置约束
- 基于细时间层的运动学更新
- 航程近似与通信范围限制
- 高度、速度、加速度边界
- 起飞/在航状态逻辑
- 速度带和高度带选择
- 航点访问逻辑
- 目标函数
这是最应该先吃透的部分,因为大多数扩展约束都建立在它之上。
飞行动力学扩展
flight_dynamics.cpp 增加的是“基础模型之外的更细致动态约束”,包括:
- 过渡窗口内的最小速度激活逻辑
- 不同带下的最大速度、最大下降率、最大上升率
- 结合状态的最小高度和加速度约束
燃油、环境与避碰扩展
fuel_range_environment.cpp 将多个主题放在一起:
- 燃油递推
- 燃油对高度、速度、加速度上界的影响
- 禁飞区外部性约束
- 风区激活逻辑
- 多机避碰逻辑
阅读这一部分时,建议先按函数拆分理解,而不是把它看成一整块。
7.4 论文模型到代码实现的映射关系
当前工程已通过 docs/paper-formulation.md 给出论文到代码的映射思路。
建议这样使用这份映射文档:
- 先看论文中某一组公式属于哪个主题
- 再定位到对应的约束实现文件
- 最后结合
MissionFlightModel::build()中的调用顺序理解它在整体模型中的位置
一个实用的映射规则是:
- 论文中的基础模型约束,优先看
base_model.cpp - 论文中的飞行动力学修正,优先看
flight_dynamics.cpp - 论文中的燃油、环境和碰撞约束,优先看
fuel_range_environment.cpp
如果想把学习路径也可视化,可以按下面的顺序推进:
7.5 论文公式与实际源码片段对照
这一节专门解决“论文里的公式到底落在代码哪里”这个问题。下面按 docs/paper-formulation.md 中的公式分组顺序,给出对应的实际源码片段。阅读方式建议如下:
- 先定位论文公式编号
- 再看本节对应的源码片段
- 最后回到对应的
.cpp文件阅读全文
时间层映射 tau(tf) = floor(tf / (nf + 1))
对应文件:
UavCore/include/core/time_grid.h
这部分代码实现了论文中粗时间层 T 和细时间层 Tf 的索引映射关系,尤其是 coarseOfFine() 和 fineOfCoarse()。
[[nodiscard]] int coarseOfFine(int fineStep) const {
if (fineStep < 0 || fineStep > fineIntervals()) {
throw std::out_of_range("fineStep out of range");
}
return fineStep / refinementFactor();
}
[[nodiscard]] int fineOfCoarse(int coarseStep) const {
if (coarseStep < 0 || coarseStep > coarseIntervals_) {
throw std::out_of_range("coarseStep out of range");
}
return coarseStep * refinementFactor();
}
公式组 (2.1) - (2.2):起点与终点状态约束
对应文件:
UavModel/src/constraints/base_model.cpp
这段代码把每架无人机在首末细时间点的位置固定到起点和终点,对应论文中的初始状态和终止状态约束。
model_.addConstr(vars_.rx[vars_.fineIndex(u, 0)] == vehicle.start.x, "start_x_" + std::to_string(u));
model_.addConstr(vars_.ry[vars_.fineIndex(u, 0)] == vehicle.start.y, "start_y_" + std::to_string(u));
model_.addConstr(vars_.rz[vars_.fineIndex(u, 0)] == vehicle.start.z, "start_z_" + std::to_string(u));
const int finalTf = Tf - 1;
model_.addConstr(vars_.rx[vars_.fineIndex(u, finalTf)] == vehicle.finish.x, "end_x_" + std::to_string(u));
model_.addConstr(vars_.ry[vars_.fineIndex(u, finalTf)] == vehicle.finish.y, "end_y_" + std::to_string(u));
model_.addConstr(vars_.rz[vars_.fineIndex(u, finalTf)] == vehicle.finish.z, "end_z_" + std::to_string(u));
公式组 (2.3) - (2.9):运动学、航程、高度、速度、加速度
对应文件:
UavModel/src/constraints/base_model.cpp
这一组公式在代码中主要分成两部分:一部分是几何/上界约束,另一部分是状态递推方程。
- 航程、高度、速度、加速度上界:
const GRBLinExpr rangeBound =
derived_.byUav[static_cast<std::size_t>(u)].rangeApproximation.slope * vars_.rz[vars_.fineIndex(u, tf)] +
derived_.byUav[static_cast<std::size_t>(u)].rangeApproximation.intercept;
GRBVar rangeNorm =
model_.addVar(0.0, GRB_INFINITY, 0.0, GRB_CONTINUOUS, "rangeNorm_" + std::to_string(u) + "_" + std::to_string(tf));
GRBVar dx =
model_.addVar(-GRB_INFINITY, GRB_INFINITY, 0.0, GRB_CONTINUOUS, "rangeDx_" + std::to_string(u) + "_" + std::to_string(tf));
GRBVar dy =
model_.addVar(-GRB_INFINITY, GRB_INFINITY, 0.0, GRB_CONTINUOUS, "rangeDy_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(dx == vars_.rx[vars_.fineIndex(u, tf)] - vehicle.groundControl.x);
model_.addConstr(dy == vars_.ry[vars_.fineIndex(u, tf)] - vehicle.groundControl.y);
linearizer_.addAbsEnvelope2d(dx, dy, rangeNorm, "range_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(rangeNorm <= rangeBound, "range_cap_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf)] >= type.minAltitude * vars_.airborne[vars_.coarseIndex(u, tc)]);
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf)] <= type.maxAltitude * vars_.airborne[vars_.coarseIndex(u, tc)]);
linearizer_.addAbsEnvelope2d(vars_.vx[vars_.fineIndex(u, tf)],
vars_.vy[vars_.fineIndex(u, tf)],
vars_.speed[vars_.fineIndex(u, tf)],
"speed_" + std::to_string(u) + "_" + std::to_string(tf));
linearizer_.addAbsEnvelope2d(vars_.ax[vars_.fineIndex(u, tf)],
vars_.ay[vars_.fineIndex(u, tf)],
vars_.accel[vars_.fineIndex(u, tf)],
"accel_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.speed[vars_.fineIndex(u, tf)] >= type.minVelocity * vars_.airborne[vars_.coarseIndex(u, tc)]);
model_.addConstr(vars_.speed[vars_.fineIndex(u, tf)] <= type.maxVelocity * vars_.airborne[vars_.coarseIndex(u, tc)]);
model_.addConstr(vars_.accel[vars_.fineIndex(u, tf)] <= type.maxAcceleration * vars_.airborne[vars_.coarseIndex(u, tc)]);
- 运动学状态递推:
model_.addConstr(vars_.rx[vars_.fineIndex(u, tf + 1)] ==
vars_.rx[vars_.fineIndex(u, tf)] +
dtFine * vars_.vx[vars_.fineIndex(u, tf)] +
dtFine * windContribution(instance_, vars_, u, tc, 0) +
0.5 * dtFine * dtFine * vars_.ax[vars_.fineIndex(u, tf)]);
model_.addConstr(vars_.ry[vars_.fineIndex(u, tf + 1)] ==
vars_.ry[vars_.fineIndex(u, tf)] +
dtFine * vars_.vy[vars_.fineIndex(u, tf)] +
dtFine * windContribution(instance_, vars_, u, tc, 1) +
0.5 * dtFine * dtFine * vars_.ay[vars_.fineIndex(u, tf)]);
model_.addConstr(vars_.vx[vars_.fineIndex(u, tf + 1)] ==
vars_.vx[vars_.fineIndex(u, tf)] + dtFine * vars_.ax[vars_.fineIndex(u, tf)]);
model_.addConstr(vars_.vy[vars_.fineIndex(u, tf + 1)] ==
vars_.vy[vars_.fineIndex(u, tf)] + dtFine * vars_.ay[vars_.fineIndex(u, tf)]);
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf + 1)] ==
vars_.rz[vars_.fineIndex(u, tf)] +
dtFine * (vars_.vzPlus[vars_.fineIndex(u, tf)] - vars_.vzMinus[vars_.fineIndex(u, tf)]));
公式组 (2.10) - (2.12):起飞/在航/降落状态逻辑
对应文件:
UavModel/src/constraints/base_model.cpp
这组公式通过 started、active、airborne 三类粗时间层二进制变量建立起飞和结束逻辑。
for (int tc = 0; tc < Tc; ++tc) {
model_.addConstr(vars_.airborne[vars_.coarseIndex(u, tc)] ==
vars_.started[vars_.coarseIndex(u, tc)] + vars_.active[vars_.coarseIndex(u, tc)] - 1.0);
}
for (int tc = 0; tc < Tc - 1; ++tc) {
model_.addConstr(vars_.active[vars_.coarseIndex(u, tc + 1)] <= vars_.active[vars_.coarseIndex(u, tc)]);
model_.addConstr(vars_.started[vars_.coarseIndex(u, tc)] <= vars_.started[vars_.coarseIndex(u, tc + 1)]);
}
公式组 (2.13) - (2.18):航点访问、禁飞区、碰撞避免
对应文件:
UavModel/src/constraints/base_model.cppUavModel/src/constraints/fuel_range_environment.cpp
这一组公式在工程里不是集中写在同一个函数中,而是按主题拆成多个实现。
- 航点访问约束:
const GRBVar& visit = vars_.visit[vars_.visitIndex(u, w, tc)];
assigned += visit;
if (tc < waypoint.window.coarseStart || tc > waypoint.window.coarseEnd) {
model_.addConstr(visit == 0.0, "visit_tw_" + std::to_string(u) + "_" + std::to_string(w) + "_" + std::to_string(tc));
continue;
}
const int tf = grid.fineOfCoarse(tc);
GRBVar distNorm =
model_.addVar(0.0, GRB_INFINITY, 0.0, GRB_CONTINUOUS, "visitDist_" + std::to_string(u) + "_" + std::to_string(w) + "_" + std::to_string(tc));
// ...
linearizer_.addAbsEnvelope3d(dx, dy, dz, distNorm,
"visit_norm_" + std::to_string(u) + "_" + std::to_string(w) + "_" + std::to_string(tc));
model_.addConstr(distNorm <= vehicle.waypointOperationalRange +
instance_.bigM.dist * (1.0 - visit));
// ...
model_.addConstr(assigned <= 1.0, "assign_once_" + std::to_string(w));
- 禁飞区约束:
GRBLinExpr outsideCount = 0.0;
for (int face = 0; face < static_cast<int>(zone.faces.size()); ++face) {
const auto& halfspace = zone.faces[static_cast<std::size_t>(face)];
GRBVar selector = context_.model->addVar(
0.0, 1.0, 0.0, GRB_BINARY,
"restricted_" + std::to_string(u) + "_" + std::to_string(q) + "_" + std::to_string(face) + "_" + std::to_string(tc));
outsideCount += selector;
context_.model->addConstr(
halfspace.normal.x * vars_.rx[vars_.fineIndex(u, tf)] +
halfspace.normal.y * vars_.ry[vars_.fineIndex(u, tf)] +
halfspace.normal.z * vars_.rz[vars_.fineIndex(u, tf)] >=
halfspace.rhs - instance_.bigM.dist * (1.0 - selector));
}
context_.model->addConstr(outsideCount >= 1.0,
"restricted_outside_" + std::to_string(u) + "_" + std::to_string(q) + "_" + std::to_string(tc));
- 碰撞避免约束:
context_.model->addConstr(vars_.rx[vars_.fineIndex(u1, tf)] - vars_.rx[vars_.fineIndex(u2, tf)] >=
instance_.safetyDistance.x - instance_.bigM.dist * (1.0 - exPos));
context_.model->addConstr(vars_.ry[vars_.fineIndex(u1, tf)] - vars_.ry[vars_.fineIndex(u2, tf)] >=
instance_.safetyDistance.y - instance_.bigM.dist * (1.0 - eyPos));
context_.model->addConstr(vars_.rz[vars_.fineIndex(u1, tf)] - vars_.rz[vars_.fineIndex(u2, tf)] >=
instance_.safetyDistance.z - instance_.bigM.dist * (1.0 - ezPos));
// ...
context_.model->addConstr(exPos + eyPos + ezPos + exNeg + eyNeg + ezNeg >=
vars_.airborne[vars_.coarseIndex(u1, tc)] + vars_.airborne[vars_.coarseIndex(u2, tc)] - 1.0,
"collision_logic_" + std::to_string(u1) + "_" + std::to_string(u2) + "_" + std::to_string(tc));
公式组 (2.19) - (2.23):燃油初值、带选择、速度带与高度带
对应文件:
UavModel/src/constraints/base_model.cpp
这组公式主要通过 band 变量以及速度/高度联动约束实现。
if (!options_.enableFuelDependentExtensions) {
model_.addConstr(vars_.fuel[vars_.fineIndex(u, 0)] == type.maxFuel, "fuel_init_" + std::to_string(u));
}
for (int tc = 0; tc < Tc; ++tc) {
GRBLinExpr bandSum = 0.0;
for (int i = 0; i < static_cast<int>(type.altitudeBands.size()); ++i) {
const auto& altitudeBand = type.altitudeBands[static_cast<std::size_t>(i)];
for (int j = 0; j < static_cast<int>(altitudeBand.throttleBands.size()); ++j) {
bandSum += vars_.band[vars_.bandIndex(u, i, j, tc)];
}
}
model_.addConstr(bandSum == vars_.airborne[vars_.coarseIndex(u, tc)], "band_sum_" + std::to_string(u) + "_" + std::to_string(tc));
}
GRBLinExpr speedBandExpr = 0.0;
GRBLinExpr altLower = 0.0;
GRBLinExpr altUpper = 0.0;
for (int i = 0; i < static_cast<int>(type.altitudeBands.size()); ++i) {
const auto& altitudeBand = type.altitudeBands[static_cast<std::size_t>(i)];
for (int j = 0; j < static_cast<int>(altitudeBand.throttleBands.size()); ++j) {
const GRBVar& selector = vars_.band[vars_.bandIndex(u, i, j, tc)];
speedBandExpr += altitudeBand.throttleBands[static_cast<std::size_t>(j)].speed * selector;
altLower += altitudeBand.lower * selector;
altUpper += altitudeBand.upper * selector;
}
}
model_.addConstr(vars_.speed[vars_.fineIndex(u, tf)] == speedBandExpr, "speed_band_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf)] >= altLower, "alt_low_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf)] <= altUpper + type.maxAltitude * (1.0 - vars_.airborne[vars_.coarseIndex(u, tc)]),
"alt_high_" + std::to_string(u) + "_" + std::to_string(tf));
公式 (3):基础目标函数
对应文件:
UavModel/src/constraints/base_model.cpp
这段代码实现了基础目标函数,包括航点收益、在航惩罚、燃油/高度/速度项。
for (int w = 0; w < vars_.waypointCount(); ++w) {
const double score = instance_.waypoints[static_cast<std::size_t>(w)].score;
for (int tc = 0; tc < vars_.coarsePointCount(); ++tc) {
objective += score * vars_.visit[vars_.visitIndex(u, w, tc)];
}
}
for (int tc = 0; tc < vars_.coarsePointCount(); ++tc) {
objective -= (static_cast<double>(tc) / static_cast<double>(std::max(1, grid.coarseIntervals()))) *
instance_.weights.air * vars_.airborne[vars_.coarseIndex(u, tc)];
}
for (int tf = 0; tf < vars_.finePointCount(); ++tf) {
objective += (1.0 / instance_.weights.fuel) * vars_.fuel[vars_.fineIndex(u, tf)];
objective += (1.0 / instance_.weights.altitude) * vars_.rz[vars_.fineIndex(u, tf)];
objective -= (1.0 / instance_.weights.velocity) * vars_.speed[vars_.fineIndex(u, tf)];
}
model_.setObjective(objective, GRB_MAXIMIZE);
公式 (2.24):最小速度激活窗口
对应文件:
UavModel/src/constraints/flight_dynamics.cpp
这段代码实现了过渡窗口上的最小速度激活条件,是飞行动力学扩展的入口。
const int transitionSteps =
static_cast<int>(std::ceil(type.minVelocity / std::max(1.0, type.maxAcceleration * instance_.coarseDeltaT)));
for (int tc = transitionSteps; tc < Tc - transitionSteps; ++tc) {
GRBLinExpr window = 0.0;
for (int k = -transitionSteps; k <= transitionSteps; ++k) {
window += vars_.airborne[vars_.coarseIndex(u, tc + k)];
}
model_.addConstr(vars_.minSpeedActive[vars_.coarseIndex(u, tc)] >= window - 2.0 * transitionSteps,
"prop1_" + std::to_string(u) + "_" + std::to_string(tc));
}
公式组 (2.8')、(2.4')、(2.8'')、(2.22')、(2.25)、(2.26)、(2.9')
对应文件:
UavModel/src/constraints/flight_dynamics.cpp
这些修正式都集中体现在飞行动力学扩展中:速度上界、最低高度、爬升/下降率和加速度上界都会根据速度带和状态切换而调整。
GRBLinExpr maxSpeedExpr = 0.0;
GRBLinExpr maxDescentExpr = 0.0;
GRBLinExpr maxClimbExpr = type.takeoffClimbRate;
for (int i = 0; i < static_cast<int>(type.altitudeBands.size()); ++i) {
const auto& altitudeBand = type.altitudeBands[static_cast<std::size_t>(i)];
const double altitudeBandMaxSpeed = altitudeBand.throttleBands.empty()
? type.maxVelocity
: altitudeBand.throttleBands.back().speed;
for (int j = 0; j < static_cast<int>(altitudeBand.throttleBands.size()); ++j) {
const auto& throttleBand = altitudeBand.throttleBands[static_cast<std::size_t>(j)];
const GRBVar& selector = vars_.band[vars_.bandIndex(u, i, j, tc)];
maxSpeedExpr += altitudeBandMaxSpeed * selector;
maxDescentExpr += throttleBand.descentRate * selector;
maxClimbExpr += -(type.takeoffClimbRate - throttleBand.climbRate) * selector;
}
}
model_.addConstr(vars_.speed[vars_.fineIndex(u, tf)] >=
type.minVelocity * vars_.minSpeedActive[vars_.coarseIndex(u, tc)],
"min_speed_ext_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.speed[vars_.fineIndex(u, tf)] <= maxSpeedExpr +
type.maxVelocity * (1.0 - vars_.airborne[vars_.coarseIndex(u, tc)]),
"max_speed_ext_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf)] >=
type.minAltitude * vars_.minSpeedActive[vars_.coarseIndex(u, tc)],
"min_alt_ext_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.vzMinus[vars_.fineIndex(u, tf)] <= maxDescentExpr,
"descent_band_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.vzPlus[vars_.fineIndex(u, tf)] <= maxClimbExpr,
"climb_band_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.accel[vars_.fineIndex(u, tf)] <=
type.maxAcceleration *
(vars_.airborne[vars_.coarseIndex(u, tc)] -
0.05 * vars_.minSpeedActive[vars_.coarseIndex(u, tc)]),
"accel_takeoff_cruise_" + std::to_string(u) + "_" + std::to_string(tf));
公式组 (2.20i) - (2.20iv):按在航状态分裂的燃油递推
对应文件:
UavModel/src/constraints/fuel_range_environment.cpp
这一组四条不等式共同实现论文中分段/Big-M 风格的燃油递推。
const GRBLinExpr burn =
dtFine * (type.climbFuelSurplus * vars_.vzPlus[vars_.fineIndex(u, tf)] + bandFuel +
type.phiFuel * vars_.fuel[vars_.fineIndex(u, tf)]);
model_.addConstr(vars_.fuel[vars_.fineIndex(u, tf + 1)] <=
type.maxFuel * (1.0 - vars_.airborne[vars_.coarseIndex(u, tc)]) +
vars_.fuel[vars_.fineIndex(u, tf)] - burn,
"fuel_dyn_ub1_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.fuel[vars_.fineIndex(u, tf + 1)] >=
-type.maxFuel * (1.0 - vars_.airborne[vars_.coarseIndex(u, tc)]) +
vars_.fuel[vars_.fineIndex(u, tf)] - burn,
"fuel_dyn_lb1_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.fuel[vars_.fineIndex(u, tf + 1)] <=
vars_.fuel[vars_.fineIndex(u, tf)] + type.maxFuel * vars_.airborne[vars_.coarseIndex(u, tc)],
"fuel_dyn_ub2_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.fuel[vars_.fineIndex(u, tf + 1)] >=
vars_.fuel[vars_.fineIndex(u, tf)] - type.maxFuel * vars_.airborne[vars_.coarseIndex(u, tc)],
"fuel_dyn_lb2_" + std::to_string(u) + "_" + std::to_string(tf));
公式 (2.27):燃油相关的可达高度上界
对应文件:
UavModel/src/constraints/fuel_range_environment.cpp
const double altitudeDenom = type.maxFuel * std::max(1e-6, 1.0 - type.phiAlt1);
GRBLinExpr maxReachableAltitude =
type.maxAltitude *
(1.0 + ((1.0 - type.phiAlt2) / altitudeDenom) *
(type.phiAlt1 * type.maxFuel - vars_.fuel[vars_.fineIndex(u, tf)]));
model_.addConstr(vars_.rz[vars_.fineIndex(u, tf)] <= maxReachableAltitude,
"fuel_altitude_" + std::to_string(u) + "_" + std::to_string(tf));
公式组 (2.28) - (2.29):燃油相关的速度/加速度上界
对应文件:
UavModel/src/constraints/fuel_range_environment.cpp
GRBLinExpr massFactor =
1.0 + (phiAcc / (type.maxFuel + type.emptyWeight)) * vars_.fuel[vars_.fineIndex(u, tf)];
model_.addConstr(vars_.speed[vars_.fineIndex(u, tf)] <= type.maxVelocity * massFactor +
type.maxVelocity * (1.0 - vars_.airborne[vars_.coarseIndex(u, tc)]),
"fuel_speed_" + std::to_string(u) + "_" + std::to_string(tf));
model_.addConstr(vars_.accel[vars_.fineIndex(u, tf)] <= type.maxAcceleration * massFactor,
"fuel_accel_" + std::to_string(u) + "_" + std::to_string(tf));
公式 (3'):带可变初始燃油的扩展目标函数
对应文件:
UavModel/src/constraints/base_model.cpp
当启用燃油相关扩展时,目标函数会额外惩罚初始燃油量,这一项就是论文中扩展目标函数的代码体现。
if (options_.enableFuelDependentExtensions) {
const auto& type = typeOf(u);
objective -= (1.0 / (instance_.weights.fuel * std::max(1.0, type.maxFuel))) *
vars_.fuel[vars_.fineIndex(u, 0)];
}
几何处理:范数线性化、禁飞区、风区
对应文件:
UavModel/src/mission_flight_model.cppUavModel/src/constraints/fuel_range_environment.cpp
docs/paper-formulation.md 中还特别强调了几何处理方式。对应实现主要有三块。
NormLinearizer的二维/三维绝对值包络:
void NormLinearizer::addAbsEnvelope2d(GRBVar x, GRBVar y, GRBVar norm, const std::string& tag) const {
GRBModel& model = *context_.model;
GRBVar ax = model.addVar(0.0, GRB_INFINITY, 0.0, GRB_CONTINUOUS, tag + "_ax");
GRBVar ay = model.addVar(0.0, GRB_INFINITY, 0.0, GRB_CONTINUOUS, tag + "_ay");
model.addConstr(ax >= x, tag + "_ax_pos");
model.addConstr(ax >= -x, tag + "_ax_neg");
model.addConstr(ay >= y, tag + "_ay_pos");
model.addConstr(ay >= -y, tag + "_ay_neg");
model.addConstr(norm >= ax, tag + "_norm_ge_ax");
model.addConstr(norm >= ay, tag + "_norm_ge_ay");
model.addConstr(norm >= (ax + ay) / std::sqrt(2.0), tag + "_norm_ge_diag");
model.addConstr(norm <= ax + ay, tag + "_norm_le_l1");
}
- 风区激活逻辑:
GRBLinExpr insideCount = 0.0;
for (int face = 0; face < static_cast<int>(zone.faces.size()); ++face) {
const auto& halfspace = zone.faces[static_cast<std::size_t>(face)];
GRBVar inside = context_.model->addVar(
0.0, 1.0, 0.0, GRB_BINARY,
"windFace_" + std::to_string(u) + "_" + std::to_string(p) + "_" + std::to_string(face) + "_" + std::to_string(tc));
insideCount += inside;
context_.model->addConstr(
halfspace.normal.x * vars_.rx[vars_.fineIndex(u, tf)] +
halfspace.normal.y * vars_.ry[vars_.fineIndex(u, tf)] +
halfspace.normal.z * vars_.rz[vars_.fineIndex(u, tf)] <=
halfspace.rhs + instance_.bigM.dist * (1.0 - inside));
}
context_.model->addConstr(insideCount >= static_cast<double>(zone.faces.size()) * active,
"wind_inside_lb_" + std::to_string(u) + "_" + std::to_string(p) + "_" + std::to_string(tc));
context_.model->addConstr(insideCount <= static_cast<double>(zone.faces.size()) + active - 1.0,
"wind_inside_ub_" + std::to_string(u) + "_" + std::to_string(p) + "_" + std::to_string(tc));
通过这几个片段,读者可以直接把论文中的公式编号映射到具体源码位置,也能更快地验证某条理论约束在代码中是否真的被实现。
8. 已知边界与维护建议
为了避免后续读者误判当前实现的完备度,下面几点需要明确写出:
UavTests只覆盖UavCore的轻量逻辑,不覆盖求解器集成测试。- 当前实例数据主要来自代码内置工厂,而不是外部文件加载器。
addRangeExtensions()目前基本为空,范围限制已由基础模型处理。normDirectionCount2d目前尚未在NormLinearizer中真正生效。VariableStore中部分环境相关变量具有脚手架性质,当前实现仍有局部变量现场创建的情况。
后续维护建议:
- 如果增加新的数据来源,优先在
UavCore中转换为InstanceData - 如果新增约束,尽量继续按主题拆分到
src/constraints/ - 如果升级 Gurobi,优先修改
build/gurobi.props - 如果加强测试体系,建议新增真正覆盖
UavModel和求解流程的集成测试
9. 总结
UavMissionPlanning 的整体思路可以概括为:
UavCore定义问题与预处理UavModel负责 Gurobi 建模UavExperiments负责组织实验与执行求解UavTests负责轻量验证build/gurobi.props负责统一管理 Gurobi 接入
对于后续开发者来说,只要先看懂“输入数据如何变成模型变量与约束”,再掌握 gurobi.props 的集中配置逻辑,就能比较快地进入项目维护和模型扩展阶段。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)