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

Apollo 9.0 速度动态规划决策算法 – path time heuristic optimizer

文章目录

  • 1. 动态规划
  • 2. 采样
  • 3. 代价函数
    • 3.1 障碍物代价
    • 3.2 距离终点代价
    • 3.3 速度代价
    • 3.4 加速度代价
    • 3.5 jerk代价
  • 4. 回溯


这一章将来讲解速度决策算法,也就是SPEED_HEURISTIC_OPTIMIZER task里面的内容。Apollo 9.0使用动态规划算法进行速度决策,从类名来看,PathTimeHeuristicOptimizer 是路径时间启发式优化器,顾名思义,这里的算法在非凸的ST空间进行了纵向超让的决策,同时也为后续速度规划算法提供了一个启发式的粗解。

1. 动态规划

动态规划,英文:Dynamic Programming,简称DP。

关于动态规划,网上其实有很多讲解,都很详细,这里不再赘述。其实如果你刷过动态规划的算法题,相信你对dp问题一定有很深的理解。如果没有,也没有关系。动态规划一句话概括就是:就是把大问题拆成小问题,记住小问题的答案,避免重复计算,最后拼出大问题的答案。

PathTimeHeuristicOptimizer算法,实际上是将ST空间进行离散,在这离散的图上寻找“最短”路径的问题,所以可以使用动态规划分而治之的思想进行求解。注意这里的“最短”,并不是路程上的最短,而是指包含障碍物代价,加速度代价等一系列代价最小的一条路径。

动态规划Task的入口在PathTimeHeuristicOptimizer类里面,下面是整个算法的求解步骤:

  • 变道和非变道加载不同的dp参数
  • 构建ST栅格图
  • ST栅格图搜素

path_time_heuristic_optimizer.cc

bool PathTimeHeuristicOptimizer::SearchPathTimeGraph(SpeedData* speed_data) const {// 变道和非变道加载不同的dp参数const auto& dp_st_speed_optimizer_config = reference_line_info_->IsChangeLanePath()? config_.lane_change_speed_config(): config_.default_speed_config();// 构建ST栅格图GriddedPathTimeGraph st_graph(reference_line_info_->st_graph_data(), dp_st_speed_optimizer_config,reference_line_info_->path_decision()->obstacles().Items(), init_point_);// ST栅格图搜素if (!st_graph.Search(speed_data).ok()) {AERROR << "failed to search graph with dynamic programming.";return false;}return true;
}

2. 采样

接下来就是在ST的维度进行采样:近端稠密一些,远端稀疏一点,这样可以节省计算量。同时变道和非变道场景,S方向采样的稠密点间隔和范围都不一样,非变道场景更稠密,范围更广一些,这样可以更好应对cut-in和cut-out场景,让纵向速度计算更精细。

在这里插入图片描述

上图中,最左下角的点,T肯定是0,而S是速度规划的起点:
在这里插入图片描述

S e n d {\color{Green} S_{end} } Send:速度规划的终点,是路径规划的path长度;
T e n d T_{end} Tend:很难计算,和道路的限速有关,也和动态障碍物有关。这里需要打破一个思维定式,在路径规划里面,必须要把比如60m路径全部规划完。在速度规划里面,不强求在 T e n d T_{end} Tend里面完全规划完 S e n d {\color{Green} S_{end} } Send。比如说路径规划出了60m,但是速度规划可以只规划20m的距离,因为现实中动态障碍物的轨迹是千奇百怪的。所以,在这里设置 T e n d = 8 s T_{end}=8s Tend=8s,在这8s的时间内,不强求把 S e n d {\color{Green} S_{end} } Send全部规划完。

关于具体采样的步骤和内容可以见下面的代码,可以说加上注释已经非常容易理解了。唯一需要补充的一点是:cost_table_其实是一个dimension_t_行,dimension_s_列的二维表,也就是T是行维度,S是列维度,将上图所示的ST离散图顺时针旋转90°,就可以刚好对应上。但是后面在计算代价和回溯的时候行列S和T又有些混淆,不过没有关系,对照图是很好理解的。

Status GriddedPathTimeGraph::InitCostTable() {// Time dimension is homogeneous while Spatial dimension has two resolutions,// dense and sparse with dense resolution coming first in the spatial horizon// Sanity check for numerical stabilityif (unit_t_ < kDoubleEpsilon) {const std::string msg = "unit_t is smaller than the kDoubleEpsilon.";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}// Sanity check on s dimension settingif (dense_dimension_s_ < 1) {const std::string msg = "dense_dimension_s is at least 1.";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}dimension_t_ = static_cast<uint32_t>(std::ceil(total_length_t_ / static_cast<double>(unit_t_))) + 1;double sparse_length_s = total_length_s_ -static_cast<double>(dense_dimension_s_ - 1) * dense_unit_s_;sparse_dimension_s_ = sparse_length_s > std::numeric_limits<double>::epsilon()? static_cast<uint32_t>(std::ceil(sparse_length_s / sparse_unit_s_)): 0;dense_dimension_s_ = sparse_length_s > std::numeric_limits<double>::epsilon()? dense_dimension_s_: static_cast<uint32_t>(std::ceil(total_length_s_ / dense_unit_s_)) + 1;dimension_s_ = dense_dimension_s_ + sparse_dimension_s_;PrintCurves debug;// Sanity Checkif (dimension_t_ < 1 || dimension_s_ < 1) {const std::string msg = "Dp st cost table size incorrect.";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}// 定义dp代价表,注意这个表:常规ST图顺时针旋转90°,一共t行,每行s列 , total_cost_默认无穷大cost_table_ = std::vector<std::vector<StGraphPoint>>(dimension_t_, std::vector<StGraphPoint>(dimension_s_, StGraphPoint()));double curr_t = 0.0;for (uint32_t i = 0; i < cost_table_.size(); ++i, curr_t += unit_t_) {          // Tauto& cost_table_i = cost_table_[i];double curr_s = 0.0;// 稠密for (uint32_t j = 0; j < dense_dimension_s_; ++j, curr_s += dense_unit_s_) {  // Scost_table_i[j].Init(i, j, STPoint(curr_s, curr_t));debug.AddPoint("dp_node_points", curr_t, curr_s);}// 稀疏curr_s = static_cast<double>(dense_dimension_s_ - 1) * dense_unit_s_ + sparse_unit_s_;for (uint32_t j = dense_dimension_s_; j < cost_table_i.size(); ++j, curr_s += sparse_unit_s_) {cost_table_i[j].Init(i, j, STPoint(curr_s, curr_t));debug.AddPoint("dp_node_points", curr_t, curr_s);}}// 获取第一行的sconst auto& cost_table_0 = cost_table_[0];spatial_distance_by_index_ = std::vector<double>(cost_table_0.size(), 0.0);for (uint32_t i = 0; i < cost_table_0.size(); ++i) {spatial_distance_by_index_[i] = cost_table_0[i].point().s();}return Status::OK();
}

3. 代价函数

接下来就是在离散表上,进行“最短”路径搜索,也就是使用动态规划算法计算速度规划起点到每个点的最小代价,然后回溯(反推)获取最优路径。

注意:速度动态规划可以进行剪枝操作,以减少计算量,依据车辆动力学max_acceleration_和max_deceleration_找到下一列可能到达的范围[next_lowest_row, next_highest_row],只需要计算当前点到下一列该范围内的点的代价。

在这里插入图片描述

一个点的total_cost由两部分构成:

  1. 点代价:
    a. 障碍物代价:obstacle_cost
    b. 距离终点代价:spatial_potential_cost
    c. 前一个点的:total_cost
  2. 边代价:
    a. 速度代价:Speedcost
    b. 加速度代价:AccelCost
    c. 加加速度代价:JerkCost

3.1 障碍物代价

Apollo 9.0动态规划中障碍物代价计算做了简化,只需要计算该点到所有t时刻障碍物的代价,并进行累加。
如下图所示的 a 62 a_{62} a62点,该时刻有两个障碍物,通过公式计算障碍物代价即可:

  • 不在范围内:0
  • 在障碍物st边界框里:∞
  • 其他: c o s t + = c o n f i g _ . o b s t a c l e _ w e i g h t ( ) ∗ c o n f i g _ . d e f a u l t _ o b s t a c l e _ c o s t ( ) ∗ s _ d i f f ∗ s _ d i f f cost += config\_.obstacle\_weight() * config\_.default\_obstacle\_cost() * s\_diff * s\_diff cost+=config_.obstacle_weight()config_.default_obstacle_cost()s_diffs_diff

这里需要注意的是,跟车和超车需要与前后车保持的安全距离大小不一样,超车情况下,需要离后车距离更大。

在这里插入图片描述

double DpStCost::GetObstacleCost(const StGraphPoint& st_graph_point) {const double s = st_graph_point.point().s();const double t = st_graph_point.point().t();double cost = 0.0;if (FLAGS_use_st_drivable_boundary) {   // false // TODO(Jiancheng): move to configsstatic constexpr double boundary_resolution = 0.1;int index = static_cast<int>(t / boundary_resolution);const double lower_bound = st_drivable_boundary_.st_boundary(index).s_lower();const double upper_bound = st_drivable_boundary_.st_boundary(index).s_upper();if (s > upper_bound || s < lower_bound) {return kInf;}}// 遍历每个障碍物,计算t时刻障碍物st边界框的上界和下界,根据无人车的位置(t,s)与边界框是否重合,计算障碍物代价for (const auto* obstacle : obstacles_) {// Not applying obstacle approaching cost to virtual obstacle like created stop fencesif (obstacle->IsVirtual()) {continue;}// Stop obstacles are assumed to have a safety margin when mapping them out,// so repelling force in dp st is not needed as it is designed to have adc// stop right at the stop distance we design in prior mapping processif (obstacle->LongitudinalDecision().has_stop()) {continue;}auto boundary = obstacle->path_st_boundary();if (boundary.min_s() > FLAGS_speed_lon_decision_horizon) {  // 纵向决策的最远距离 200continue;}if (t < boundary.min_t() || t > boundary.max_t()) {continue;}if (boundary.IsPointInBoundary(st_graph_point.point())) {return kInf;}// 情况4:需要减速避让或加速超过的障碍物。计算障碍物在t时刻的上界和下界位置,即上下界的累积距离sdouble s_upper = 0.0;double s_lower = 0.0;// 为了避免其他节点(t,s)再一次计算t时刻的障碍物上下界,利用缓存加速计算。GetBoundarySRange函数可以用来计算t时刻障碍物上界和下界累积距离s,并缓存int boundary_index = boundary_map_[boundary.id()];if (boundary_cost_[boundary_index][st_graph_point.index_t()].first < 0.0) { // 还没有计算过boundary.GetBoundarySRange(t, &s_upper, &s_lower);boundary_cost_[boundary_index][st_graph_point.index_t()] = std::make_pair(s_upper, s_lower);} else {s_upper = boundary_cost_[boundary_index][st_graph_point.index_t()].first; // 之前计算过,直接取值s_lower = boundary_cost_[boundary_index][st_graph_point.index_t()].second;}// t时刻, 无人车在障碍物后方if (s < s_lower) {const double follow_distance_s = config_.safe_distance(); // 0.2AINFO << "follow_distance_s : " << follow_distance_s;if (s + follow_distance_s < s_lower) {                    // 如果障碍物和无人车在t时刻距离大于安全距离,距离比较远,cost=0continue;} else {                                                  // 否则距离小于安全距离,计算cost。obstacle_weight:1.0,default_obstacle_cost:1000,auto s_diff = follow_distance_s - s_lower + s;cost += config_.obstacle_weight() * config_.default_obstacle_cost() * s_diff * s_diff;}// t时刻, 无人车在障碍物前方} else if (s > s_upper) {const double overtake_distance_s = StGapEstimator::EstimateSafeOvertakingGap(); // 20if (s > s_upper + overtake_distance_s) {  // or calculated from velocitycontinue;                               // 如果障碍物和无人车在t时刻距离大于安全距离,距离比较远,cost=0} else {                                  // 否则距离小于安全距离,计算cost。obstacle_weight:1.0,default_obstacle_cost:1000,auto s_diff = overtake_distance_s + s_upper - s;cost += config_.obstacle_weight() * config_.default_obstacle_cost() * s_diff * s_diff;}}}return cost * unit_t_;  // unit_t_ = 1
}

3.2 距离终点代价

距离终点代价其实就是距离路径规划终点的一个惩罚,该点距离终点越近,代价越小;距离终点越远,代价越大。

目的很简单,就是希望速度规划能够就可能将路径规划的s全部路径都覆盖。
在这里插入图片描述

double DpStCost::GetSpatialPotentialCost(const StGraphPoint& point) {return (total_s_ - point.point().s()) * config_.spatial_potential_penalty();  // 距离终点惩罚 100 or 100000(变道)
}

3.3 速度代价

速度代价很简单,主要考虑两方面:

  • 不要超速,尽量贴近限速;
  • 尽量贴近巡航速度,也就是驾驶员设定的速度。
double DpStCost::GetSpeedCost(const STPoint& first, const STPoint& second,const double speed_limit,const double cruise_speed) const {double cost = 0.0;const double speed = (second.s() - first.s()) / unit_t_; //计算时间段[t-1,t]内的平均速度if (speed < 0) { // 倒车?速度代价无穷大return kInf;}const double max_adc_stop_speed = common::VehicleConfigHelper::Instance()->GetConfig().vehicle_param().max_abs_speed_when_stopped();AINFO << "max_adc_stop_speed : "<< max_adc_stop_speed; // 如果速度接近停车,并且在禁停区内 max_stop_speed = 0.2  if (speed < max_adc_stop_speed && InKeepClearRange(second.s())) {// first.s in range  在KeepClear区域低速惩罚 10 * * 1000cost += config_.keep_clear_low_speed_penalty() * unit_t_ * config_.default_speed_cost();}// 计算当前速度和限速的差值比,大于0,超速;小于0,未超速double det_speed = (speed - speed_limit) / speed_limit;if (det_speed > 0) {cost += config_.exceed_speed_penalty() * config_.default_speed_cost() * (det_speed * det_speed) * unit_t_;} else if (det_speed < 0) {cost += config_.low_speed_penalty() * config_.default_speed_cost() * -det_speed * unit_t_;}if (config_.enable_dp_reference_speed()) {double diff_speed = speed - cruise_speed;cost += config_.reference_speed_penalty() * config_.default_speed_cost() * fabs(diff_speed) * unit_t_;  // 10 * 1000 *  Δv * }return cost;
}

3.4 加速度代价

要计算边的加速度代价,首先需要计算边的加速度。
[图片]

以起点为例,计算起点到坐标点的加速度代价,直接用差分来代替速度、加速度、Jerk:
[图片]

double DpStCost::GetAccelCostByThreePoints(const STPoint& first,const STPoint& second,const STPoint& third) {// 利用3个节点的累积距离s1,s2,s3来计算加速度   double accel = (first.s() + third.s() - 2 * second.s()) / (unit_t_ * unit_t_);return GetAccelCost(accel);
}double DpStCost::GetAccelCostByTwoPoints(const double pre_speed,const STPoint& pre_point,const STPoint& curr_point) {// 利用2个节点的累积距离s1,s2来计算加速度double current_speed = (curr_point.s() - pre_point.s()) / unit_t_;double accel = (current_speed - pre_speed) / unit_t_;return GetAccelCost(accel);
}

边的加速度代价:
( w c o s t _ d e c ∗ s ¨ ) 2 1 + e ( a − m a x _ d e c ) + ( w c o s t _ a c c ∗ s ¨ ) 2 1 + e − ( a − m a x _ a c c ) \frac{\left ( w_{cost\_dec} *\ddot{s} \right ) ^{2} }{1+e^{(a- max\_dec)}} +\frac{\left ( w_{cost\_acc} *\ddot{s} \right ) ^{2} }{1+e^{-(a- max\_acc)}} 1+e(amax_dec)(wcost_decs¨)2+1+e(amax_acc)(wcost_accs¨)2

double DpStCost::GetAccelCost(const double accel) {double cost = 0.0;// 将给定的加速度 accel 标准化并加上一个偏移量 kShift 来计算得到。这样做可以确保不同的 accel 值映射到 accel_cost_ 中不同的索引位置。static constexpr double kEpsilon = 0.1; // 表示对加速度的精度要求static constexpr size_t kShift = 100;const size_t accel_key = static_cast<size_t>(accel / kEpsilon + 0.5 + kShift);DCHECK_LT(accel_key, accel_cost_.size());if (accel_key >= accel_cost_.size()) {return kInf;}if (accel_cost_.at(accel_key) < 0.0) {const double accel_sq = accel * accel;double max_acc = config_.max_acceleration();          // 3.0  m/s^2double max_dec = config_.max_deceleration();          // -4.0 m/s^2double accel_penalty = config_.accel_penalty();       // 1.0double decel_penalty = config_.decel_penalty();       // 1.0if (accel > 0.0) {          // 计算加速度正情况下costcost = accel_penalty * accel_sq;} else {                    // 计算加速度负情况下costcost = decel_penalty * accel_sq;}// 总体costcost += accel_sq * decel_penalty * decel_penalty / (1 + std::exp(1.0 * (accel - max_dec))) +accel_sq * accel_penalty * accel_penalty / (1 + std::exp(-1.0 * (accel - max_acc)));accel_cost_.at(accel_key) = cost;} else {cost = accel_cost_.at(accel_key); // 该加速度之前计算过,直接索引}return cost * unit_t_;
}

3.5 jerk代价

加加速度代价的计算更简单:
[图片]

double DpStCost::JerkCost(const double jerk) {double cost = 0.0;static constexpr double kEpsilon = 0.1;static constexpr size_t kShift = 200;const size_t jerk_key = static_cast<size_t>(jerk / kEpsilon + 0.5 + kShift);  // 原理同acc的计算if (jerk_key >= jerk_cost_.size()) {return kInf;}if (jerk_cost_.at(jerk_key) < 0.0) {double jerk_sq = jerk * jerk;if (jerk > 0) {cost = config_.positive_jerk_coeff() * jerk_sq * unit_t_;} else {cost = config_.negative_jerk_coeff() * jerk_sq * unit_t_;}jerk_cost_.at(jerk_key) = cost;} else {cost = jerk_cost_.at(jerk_key);}// TODO(All): normalize to unit_t_return cost;
}

4. 回溯

回溯:也就是根据最小代价,从后往前倒推,得到最优的路径。这是动态规划算法中常用的一种手段。

因为 T e n d T_{end} Tend是人为给定的一个值,所以可能在 T e n d T_{end} Tend之前,速度规划就已经到达路径规划的终点。所以需要遍历最上边和最右边区域,找到cost最小的作为速度规划的终点。
[图片]

由于我们不要求一定要规划完所有路径,所以最优的速度规划可以是紫色那种,也有可能是黄色那种,还有可能是橙色那种。

Status GriddedPathTimeGraph::RetrieveSpeedProfile(SpeedData* const speed_data) {// Step 1 : 计算规划终点double min_cost = std::numeric_limits<double>::infinity();const StGraphPoint* best_end_point = nullptr;PrintPoints debug("dp_node_edge");for (const auto& points_vec : cost_table_) {for (const auto& pt : points_vec) {debug.AddPoint(pt.point().t(), pt.point().s());}}// for debug plot// debug.PrintToLog();// 寻找最上一行(s=S)和最右一列(t=T)中最小的cost对应的节点,作为规划终点for (const StGraphPoint& cur_point : cost_table_.back()) {  // 最右一列(t=T)if (!std::isinf(cur_point.total_cost()) &&cur_point.total_cost() < min_cost) {best_end_point = &cur_point;min_cost = cur_point.total_cost();}}for (const auto& row : cost_table_) {                      // 每一个t,也就是每一列const StGraphPoint& cur_point = row.back();              // 每一列的最上一行(s=S)if (!std::isinf(cur_point.total_cost()) &&cur_point.total_cost() < min_cost) {best_end_point = &cur_point;min_cost = cur_point.total_cost();}}if (best_end_point == nullptr) {const std::string msg = "Fail to find the best feasible trajectory.";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}// Step 2 : 从规划终点开始回溯,找到最小cost的规划路径std::vector<SpeedPoint> speed_profile;const StGraphPoint* cur_point = best_end_point;PrintPoints debug_res("dp_result");while (cur_point != nullptr) {ADEBUG << "Time: " << cur_point->point().t();ADEBUG << "S: " << cur_point->point().s();ADEBUG << "V: " << cur_point->GetOptimalSpeed();SpeedPoint speed_point;debug_res.AddPoint(cur_point->point().t(), cur_point->point().s());speed_point.set_s(cur_point->point().s());speed_point.set_t(cur_point->point().t());speed_profile.push_back(speed_point);cur_point = cur_point->pre_point();}//  for debug plot//   debug_res.PrintToLog();std::reverse(speed_profile.begin(), speed_profile.end());  // 颠倒容器中元素的顺序static constexpr double kEpsilon = std::numeric_limits<double>::epsilon();  // 返回的是 double 类型能够表示的最小正数if (speed_profile.front().t() > kEpsilon ||speed_profile.front().s() > kEpsilon) {const std::string msg = "Fail to retrieve speed profile.";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}AINFO << "front: " << speed_profile.front().t() << " " << speed_profile.front().s();// Step 3 : 计算速度 vfor (size_t i = 0; i + 1 < speed_profile.size(); ++i) {const double v = (speed_profile[i + 1].s() - speed_profile[i].s()) /(speed_profile[i + 1].t() - speed_profile[i].t() + 1e-3);  // 斜率 speed_profile[i].set_v(v);AINFO << "v: " << v;}*speed_data = SpeedData(speed_profile);return Status::OK();
}

相关文章:

Apollo 9.0 速度动态规划决策算法 – path time heuristic optimizer

文章目录 1. 动态规划2. 采样3. 代价函数3.1 障碍物代价3.2 距离终点代价3.3 速度代价3.4 加速度代价3.5 jerk代价 4. 回溯 这一章将来讲解速度决策算法&#xff0c;也就是SPEED_HEURISTIC_OPTIMIZER task里面的内容。Apollo 9.0使用动态规划算法进行速度决策&#xff0c;从类名…...

Apache Iceberg 与 Apache Hudi:数据湖领域的双雄对决

在数据存储和处理不断发展的领域中&#xff0c;数据湖仓的概念已经崭露头角&#xff0c;成为了一种变革性的力量。数据湖仓结合了数据仓库和数据湖的最佳元素&#xff0c;提供了一个统一的平台&#xff0c;支持数据科学、商业智能、人工智能/机器学习以及临时报告等多种关键功能…...

【LeetCode Hot100 普通数组】最大子数组和、合并区间、旋转数组、除自身以外数组的乘积、缺失的第一个正整数

普通数组 1. 最大子数组和&#xff08;Maximum Subarray&#xff09;解题思路动态规划的优化解法&#xff08;Kadane 算法&#xff09;核心思想 代码解析 2. 合并区间&#xff08;Merge Intervals&#xff09;解题思路代码实现 3. 旋转数组&#xff08;Rotate Array&#xff09…...

共享存储-一步一步部署ceph分布式文件系统

一、Ceph 简介 Ceph 是一个开源的分布式文件系统。因为它还支持块存储、对象存储&#xff0c;所以很自 然的被用做云计算框架 openstack 或 cloudstack 整个存储后端。当然也可以单独作 为存储&#xff0c;例如部署一套集群作为对象存储、SAN 存储、NAS 存储等。 二、ceph 支…...

19.Python实战:实现对博客文章的点赞系统

Flask博客点赞系统 一个基于Flask的简单博客系统&#xff0c;具有文章展示和点赞功能。系统使用MySQL存储数据&#xff0c;支持文章展示、点赞/取消点赞等功能。 功能特点 文章列表展示文章详情查看&#xff08;模态框展示&#xff09;点赞/取消点赞功能&#xff08;每个IP只…...

【stm32】定时器输出PWM波形(hal库)

一. PWM基本原理 PWM是一种通过调节信号的占空比&#xff08;Duty Cycle&#xff09;来控制输出平均电压的技术。占空比是指高电平时间与整个周期时间的比值。例如&#xff1a; - 占空比为50%时&#xff0c;输出平均电压为电源电压的一半。 - 占空比为100%时&#xff0c;输出始…...

当Ollama遇上划词翻译:我的Windows本地AI服务搭建日记

&#x1f680; 实现Windows本地大模型翻译服务 - 基于OllamaFlask的划词翻译实践 &#x1f6e0;️ 步骤概要1️⃣ python 环境准备2️⃣ Ollama 安装3️⃣ 一个 Flask 服务4️⃣ Windows 服务化封装5️⃣ 测试本地接口6️⃣ 配置划词翻译自定义翻译源7️⃣ 效果展示8️⃣ debug…...

Linux上Elasticsearch 集群部署指南

Es 集群部署 Es 集群部署 Es 集群部署 准备好三台服务器。示例使用&#xff1a;110.0.5.141/142/143 1、es用户和用户组创建&#xff0c;使用root账号 groupadd esuseradd -g es es2、将es安装包和ik分词器上传到&#xff1a;/home/es/目录下&#xff08;任意目录都行&#…...

字节Trae使用感想(后端)

前言 昨天分享了字节哥的Trae从0到1创作模式构建一个vue前端项目&#xff0c;今天又来试试她的后端项目能力。不是我舔&#xff0c;不得不说确实不错。可惜现在曾经没有好好学习&#xff0c;进不了字节。既然进不了字节&#xff0c;那我就用字节哥的产品吧。 后面有惊喜…...

国产编辑器EverEdit - 二进制模式下观察Window/Linux/MacOs换行符差异

1 换行符格式 1.1 应用场景 稍微了解计算机历史的人都知道&#xff0c; 计算机3大操作系统&#xff1a; Windows、Linux/Unix、MacOS&#xff0c;这3大系统对文本换行的定义各不相同&#xff0c;且互不相让&#xff0c;导致在文件的兼容性方面存在一些问题&#xff0c;比如它们…...

文心一言4月起全面免费,6月底开源新模型:AI竞争进入新阶段?

名人说&#xff1a;莫听穿林打叶声&#xff0c;何妨吟啸且徐行。—— 苏轼 Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、文心一言免费化的背后&#xff1a;AI成本与应用的双重驱动1️⃣成本下降&#xff0c;推动文心一言普及2…...

解锁机器学习算法 | 线性回归:机器学习的基石

在机器学习的众多算法中&#xff0c;线性回归宛如一块基石&#xff0c;看似质朴无华&#xff0c;却稳稳支撑起诸多复杂模型的架构。它是我们初涉机器学习领域时便会邂逅的算法之一&#xff0c;其原理与应用广泛渗透于各个领域。无论是预测房价走势、剖析股票市场波动&#xff0…...

如何使用Three.js制作3D月球与星空效果

目录 1. 基本设置2. 创建星空效果3. 创建月球模型4. 添加中文3D文字5. 光照与相机配置6. 动画与控制7. 响应式布局8. 结语 在本文中&#xff0c;我们将一起学习如何利用Three.js实现一个3D月球与星空的效果&#xff0c;并添加一些有趣的元素&#xff0c;比如中文3D文字和互动功…...

SQL语句语法

SQL数据库的结构为 库database 表table 段segment 行row 列column 或field SQL 语句主要分为以下几类&#xff1a; 数据定义语言&#xff08;DDL&#xff09;&#xff1a;用于定义数据库对象&#xff0c;如数据库、表、视图、索引等。数据操作语言&#xff08;DML&#xff09;&…...

github上文件过大无法推送问题

GitHub 对文件大小有限制&#xff0c;超过 100 MB 的文件无法直接推送到仓库中。 解决思路&#xff1a; 使用 Git Large File Storage (Git LFS) 来管理大文件不上传对应的大文件 使用Git LFS&#xff1a; 1. 安装 Git LFS 首先&#xff0c;你需要安装 Git LFS。可以按照以…...

微信小程序的请求函数封装(ts版本,uniapp开发)

主要封装函数代码&#xff1a; interface HttpOptions {url: string;method?: string;headers?: { [key: string]: string };data?: any; }class Http {private timeout: number;private baseUrl: string;public constructor() { this.timeout 60 * 1000;this.baseUrl ht…...

Visual Studio Code支持WSL,直接修改linux/ubuntu中的文件

步骤1 开始通过 WSL 使用 VS Code | Microsoft Learn 点击远程开发扩展包。 步骤2 Remote Development - Visual Studio Marketplace 点击install&#xff0c; 允许打开Visual Studio Code。 步骤3 共有4项&#xff0c;一齐安装。 步骤4 在WSL Linux(Ubuntu)中&#xf…...

openAI最新o1模型 推理能力上表现出色 准确性方面提升 API如何接入?

OpenAI o1模型在回答问题前会进行深入思考&#xff0c;并生成一条内部推理链&#xff0c;使其在尝试解决问题时可以识别并纠正错误&#xff0c;将复杂的步骤分解为更简单的部分&#xff0c;并在当前方法无效时尝试不同的途径。据悉&#xff0c;o1不仅数学水平与美国奥林匹克竞赛…...

GC 基础入门

什么是GC&#xff08;Garbage Collection&#xff09;&#xff1f; 内存管理方式通常分为两种&#xff1a; 手动内存管理&#xff08;Manual Memory Management&#xff09;自动内存管理&#xff08;Garbage Collection, GC&#xff09; 手动内存管理 手动内存管理是指开发…...

Go语言协程Goroutine高级用法(一)

什么协程 在Go语言中&#xff0c;协程就是一种轻量的线程&#xff0c;是并发编程的单元&#xff0c;由Go来管理&#xff0c;所以在GO层面的协程会更加的轻量、高效、开销更小&#xff0c;并且更容易实现并发编程。 轻量级线程 Go语言中协程&#xff08;线程&#xff09;与传…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...