当前位置: 首页 > 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;从类名…...

利用AI智能体创建云端文档知识库并集成第三方数据源(上)

许多开发者在管理和集成多种云端的数据源时经常面对各种各样的困难&#xff0c;所以希望能够构建一个聊天机器人来协调这些数据源&#xff0c;针对业务问题并提供全面的答案。本文介绍了一种解决方案&#xff0c;帮助大家开发一个能够从文档和数据库中回答查询的聊天机器人&…...

Cursor AI开发微信小程序教程

1. 准备工作 在开始开发之前&#xff0c;需要完成以下准备工作&#xff1a; 1.1 安装微信开发者工具 前往微信开发者工具官网下载并安装适合操作系统的开发者工具。注册微信小程序账号&#xff0c;登录微信公众平台&#xff08;https://mp.weixin.qq.com&#xff09;&#…...

2025常用的SEO工具有哪些?

在互联网时代&#xff0c;如何让自己的网站或内容脱颖而出&#xff0c;成为许多企业和个人站长们最关注的问题。而在这个过程中&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;作为一种有效的提升网站曝光度和吸引流量的手段&#xff0c;已经成为了网站运营的核心之一。对…...

支持直接升级到21c的 Oracle 数据库版本

一、支持直接升级的版本 可以从以下版本直接升级到新版本&#xff1a; • 19c (所有版本) • 18c (所有版本) • 12c Release 2 (12.2) 升级到最新Oracle数据库版本必须采用的路径取决于当前数据库的版本号。 如果当前的Oracle数据库版本早于12.2 版&#xff0c;则无法直接将…...

线程池-抢票系统性能优化

文章目录 引言-购票系统线程池购票系统-线程池优化 池化 vs 未池化 引言-购票系统 public class App implements Runnable {private static int tickets 100;private static int users 10000;private final ReentrantLock lock new ReentrantLock(true);public void run() …...

回顾Golang的Channel与Select第一篇

深入解析Golang Channel与Select&#xff1a;并发编程的艺术与哲学 一、通信顺序进程&#xff08;CSP&#xff09;的Go实现 Go语言创始人Rob Pike将CSP理论具象化为channel原语&#xff0c;实现了"不要通过共享内存来通信&#xff0c;而要通过通信来共享内存"的哲学…...

乐理笔记(持续更新)

单音与音程 单音&#xff1a;由一个音组成。 音程&#xff1a;由两个音组成&#xff0c;表示两个音之间的音高距离。 如何数音程&#xff1a; 单音程&#xff1a;9 - X&#xff0c;性质相反。例如&#xff0c;9度音程减去某个数&#xff0c;性质会相反。 复音程&#xff1a…...

FastExcel + Java:打造高效灵活的Excel数据导入导出解决方案

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 基于AOP的数据字典实现…...

【数据结构入门】一、数组

一、数组的概念 数组&#xff1a;一种由相同类型的数据元素组成的基本数据类型&#xff0c;为引用类型 二、数据的顺序 这里很奇怪&#xff0c;讲了一个寻址函数&#xff0c;就是怎样用坐标求该元素的内存地址。说实话我不知道求这个能干什么&#xff0c;但是感觉还挺好玩的…...

编码格式大全解释以及相关编码特性

目录 说明: 1. Base64 Base64编码的字符集通常包括&#xff1a; Base64的工作原理&#xff1a; Base64编码在安全渗透中的应用场景 常见的Base64编码绕过场景 如何防范Base64绕过攻击 2. URL编码&#xff08;Percent Encoding&#xff09; URL编码与安全渗透的关系 示…...

正则表达式(Regular expresssion)

正则表达式 匹配单次 . &#xff1a;匹配任意一个字符 [ ] &#xff1a;匹配[ ]里举例的任意一个字符 /d &#xff1a;匹配数字0-9 /D &#xff1a;匹配非数字 /s &#xff1a;匹配空白或tab建 /S &#xff1a;匹配非空白 /w &#xff1a;…...

JavaScript 发起网络请求 axios、fetch、async / await

目录 fetch 发送 GET 请求&#xff08;fetch&#xff09; 发送 POST 请求&#xff08;fetch&#xff09; 处理后台异常响应 async / await async await 发送 GET 请求&#xff08;async / await fetch&#xff09; 发送 POST 请求&#xff08;async / await fetch&…...

安装OpenJDK21(linux、macos)

文章目录 安装OpenJDK21java21linux下安装配置mac下安装 安装OpenJDK21 java21 封神&#xff01;Java 21正式发布了&#xff0c;迎来了史诗级新特性&#xff0c;堪称版本最强&#xff01;&#xff01;&#xff01; 视频链接&#xff1a;https://www.bilibili.com/video/BV1E8…...

Java面试宝典:说下Spring Bean的生命周期?

Java面试宝典专栏范围&#xff1a;JAVA基础&#xff0c;面向对象编程&#xff08;OOP&#xff09;&#xff0c;异常处理&#xff0c;集合框架&#xff0c;Java I/O&#xff0c;多线程编程&#xff0c;设计模式&#xff0c;网络编程&#xff0c;框架和工具等全方位面试题详解 每…...

k8s向容器内传文件与下载文件

1、下载&#xff1a; kubectl cp <namespace>/<pod-name>:<container-path> <local-path>示例&#xff1a; kubectl cp mynamespace/mypod:/tmp/testfile.txt ./testfile.txt如果 Pod 中有多个容器&#xff0c;可以通过 -c 标志指定容器: kubectl c…...

A4988一款带转换器和过流保护的 DMOS 微步驱动器的使用方式

A4988是一款带转换器和过流保护的 DMOS 微步驱动器&#xff0c;用于驱动双极步进电动机。它支持全、半、1/4、1/8 及 1/16 步进模式&#xff0c;输出驱动性能可达 35 V 及 2 A。其特点包括简单的步进和方向控制接口、可调电位器调节最大电流输出、自动电流衰减模式检测/选择以及…...

Elasticsearch+Logstash+Kibana可视化集群部署

文章目录 1.组件介绍简述2.集群规划3.Es组件部署4.Logstash组件部署5.Kibana组件部署6.Kibana的基础使用 1.组件介绍简述 Elasticsearch&#xff1a;开源实时分布式搜索和分析引擎&#xff0c;支持大规模数据存储和高吞吐量&#xff0c;提供丰富的搜索功能和可扩展性。 Logsta…...

数据结构之二叉树

数据结构之二叉树 数据结构之二叉树1. 什么是二叉树&#xff1f;2. 基本概念3. 二叉树的基本形态4. 二叉树的性质5. 特殊二叉树6. 二叉树的存储7. 二叉树的遍历7.1 前序遍历7.2 中序遍历7.3 后序遍历7.4 层次遍历 8. 应用场景9. 实例代码9.1 示例树结构9.2 输出结果 数据结构之…...

华为云+硅基流动使用Chatbox接入DeepSeek-R1满血版671B

华为云硅基流动使用Chatbox接入DeepSeek-R1满血版671B 硅基流动 1.1 注册登录 1.2 实名认证 1.3 创建API密钥 1.4 客户端工具 OllamaChatboxCherry StudioAnythingLLM 资源包下载&#xff1a; AI聊天本地客户端 接入Chatbox客户端 点击设置 选择SiliconFloW API 粘贴1.3创…...

《图解设计模式》笔记(十)用类来表现

二十二、Command模式:命令也是类 一个类调用某方法,虽然调用结果会反映在对象的状态中,但不会留下工作的历史记录。 若有一个类表示“请进行这项工作”的“命令”,每一项想做的工作就不再是“方法的调用”这种动态处理了,而是一个表示命令的类的实例,即可以用“物”来表…...

Scrapy:任务队列底层设计详解

Scrapy 中队列设计详解 1. 概述 Scrapy 的队列系统是其调度器&#xff08;Scheduler&#xff09;的核心组件之一&#xff0c;负责存储和管理待抓取的请求。Scrapy 实现了两种类型的队列&#xff1a; 内存队列&#xff1a;请求存储在内存中&#xff0c;重启后数据丢失磁盘队列…...

zola + github page,用 workflows 部署

之前的Zola都是本地build之后&#xff0c;再push到github上&#xff0c;这种方式很明显的弊端就是只能在本地编辑&#xff0c;而不能通过github编辑&#xff0c;再pull到本地&#xff0c;缺乏了灵活性。因此将zola用workflows来部署。 repo地址&#xff1a;https://github.com/…...

pytest测试专题 - 1.2 如何获得美观的测试报告

<< 返回目录 1 pytest测试专题 - 1.2 如何获得美观的测试报告 1.1 背景 虽然pytest命令的报文很详细&#xff0c;用例在执行调试时还算比较方便阅读和提取失败信息&#xff0c; 但对于大量测试用例运行时&#xff0c;可能会存在以下不足 报文被冲掉测试日志没法归档 …...

关闭浏览器安全dns解决访问速度慢的问题

谷歌浏览器加载速度突然变慢了&#xff1f;检查安全DNS功能(DoH)是否被默认开启。 谷歌浏览器在去年已经推出安全DNS功能(即DoH) , 启用此功能后可以通过加密的DNS增强网络连接安全性。例如查询请求被加密后网络运营商将无法嗅探用户访问的地址&#xff0c;因此对于增强用户的…...

今日AI和商界事件(2025-02-14)

今日AI大事件主要包括以下几个方面&#xff1a; 一、苹果新品预告 事件概述&#xff1a;苹果CEO蒂姆库克在社交媒体发布7秒视频&#xff0c;配文“准备好迎接家庭的新成员”&#xff0c;并宣布2月19日将有新品发布。知名科技记者马克古尔曼称&#xff0c;新款低端iPhone SE将…...

【黑马点评优化】1-使用JWT登录认证+redis实现自动续期

1-使用JWT登录认证redis实现自动续期 0 前言1 原先的redis实现登录鉴权2 JWT登录认证Redis自动续期2.1 认证&#xff08;identification&#xff09;授权 &#xff08;authorization&#xff09;和鉴权&#xff08;Authorization&#xff09;2.1.1 认证&#xff08;identificat…...

一个让Stable Diffusion更稳定、更易用的Github开源项目

2023除了ChatGPT大火&#xff0c;Stable Diffusion同样也是非常火热&#xff0c;Stable Diffusion是一个Github开源项目&#xff0c;很多爱好者都会本地安装&#xff0c;但面对一些初学者来说&#xff0c;在安装、配置和使用过程中还是会经常出现很多问题&#xff0c;特别不了解…...

Mac之JDK安装

Mac之JDK安装 一.安装 jdk 打开终端输入命令:java -version 查看是否已安装 JDK Oracle 官方下载地址 根据自己Mac 系统安装 查看 Mac 系统&#xff0c;打开中断命令&#xff0c;输入: uname -a Compressed Archive 是压缩文档&#xff0c;下载的是一个 .tar.gz 压缩包 D…...

深入Flask:如何优雅地处理HTTP请求与响应

哈喽,大家好,我是木头左! 本文将带你深入了解如何在Flask中优雅地处理HTTP请求和响应,让你的应用更加高效、安全和用户友好。 创建一个简单的Flask应用 让从创建一个最简单的Flask应用开始: from flask import Flaskapp = Flask(__name__)@app.route(/) def...

kron积计算mask类别矩阵

文章目录 1. 生成类别矩阵如下2. pytorch 代码3. 循环移动矩阵 1. 生成类别矩阵如下 2. pytorch 代码 import torch import torch.nn as nn import torch.nn.functional as Ftorch.set_printoptions(precision3, sci_modeFalse)if __name__ "__main__":run_code 0…...

Redis实现消息队列

什么是消息列队。 消息队列是一种应用间的异步协作机制&#xff0c;同时消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削峰等问题。实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构。市面上的 M…...

vue+springboot+webtrc+websocket实现双人音视频通话会议

前言 最近一些时间我有研究&#xff0c;如何实现一个视频会议功能&#xff0c;但是找了好多资料都不太理想&#xff0c;最终参考了一个文章 WebRTC实现双端音视频聊天&#xff08;Vue3 SpringBoot&#xff09; 只不过&#xff0c;它的实现效果里面只会播放本地的mp4视频文件&…...

【免费送书活动】《MySQL 9从入门到性能优化(视频教学版)》

本博主免费赠送读者3本书&#xff0c;书名为《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》。 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 这本书已经公开…...

【设计模式】【行为型模式】命令模式(Command)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…...

1.14学习总结

日常刷题单 刷了题目后&#xff0c;对于排序方法更加熟练&#xff0c;手搓代码的速度也得到了提高。 感觉字符串还不熟练&#xff0c;高精度更是云里雾里&#xff0c;上升空间极大。 同时看见今晚有个入门难度的测试&#xff0c;去练了练手&#xff0c;想看看自己是什么成分&…...

QxOrm生成json

下载Qxorm-1.5版本 使用vs打开项目&#xff0c;直接生成即可&#xff1a; lib目录中会生成dll和lib文件 新建Qt项目使用Qxorm: 将QxOrm中上面三个目录拷贝到新建的Qt项目中 pro文件添加使用QxOrm第三方库 INCLUDEPATH $$PWD/include/ LIBS -L"$$PWD/lib" LIBS…...

伯克利 CS61A 课堂笔记 09 —— Data Abstraction

本系列为加州伯克利大学著名 Python 基础课程 CS61A 的课堂笔记整理&#xff0c;全英文内容&#xff0c;文末附词汇解释。 目录 01 Data Abstraction 数据抽象 Ⅰ Rational Numbers Ⅱ Rational Number Arithmetic 02 Pairs 对 Ⅰ Representing Pairs Using Lists Ⅱ Re…...

高频 SQL 50 题(基础版)

高频 SQL 50 题&#xff08;基础版&#xff09; 查询连接聚合函数排序和分组高级查询和连接子查询高级字符串函数 / 正则表达式 / 子句 查询 链接: link 链接: link 链接: link 链接: link 链接: link 连接 链接: 高频 SQL 50 题基础版_1378. 使用唯一标识码替换员工ID 链接…...

HtmlRAG:RAG系统中,HTML比纯文本效果更好

HtmlRAG 方法通过使用 HTML 而不是纯文本来增强 RAG 系统中的知识表示能力。通过 HTML 清洗和两步块树修剪方法&#xff0c;在保持关键信息的同时缩短了 HTML 文档的长度。这种方法优于现有基于纯文本的RAG的性能。 方法 其实主要看下围绕html提纯思路&#xff0c;将提纯后的…...

python学opencv|读取图像(六十二)使用cv2.morphologyEx()形态学函数实现图像梯度处理

【1】引言 前序已经学习了腐蚀和膨胀的单独作用函数&#xff0c;还研究了按照不同顺序调用腐蚀和膨胀函数调整图像效果&#xff0c;相关文章包括且不限于&#xff1a; python学opencv|读取图像&#xff08;六十一&#xff09;先后使用cv2.dilate()函数和cv2.erode()函数实现图…...

10G EPON光模块

一、10G EPON对称光模块 工作模式&#xff1a;上行突发接收、下行连续发射。 工作原理&#xff1a;当需要发送信号时&#xff0c;系统信号通过光模块的电接口把信号传送到驱动芯片&#xff0c;芯片处理后&#xff0c;驱动激光器发出调制光信号&#xff0c;经光纤发到远端&…...

RocketMQ与kafka如何解决消息丢失问题?

0 前言 消息丢失基本是分布式MQ中需要解决问题&#xff0c;消息丢失时保证数据可靠性的范畴。如何保证消息不丢失程序员面试中几乎不可避免的问题。本文主要说明RocketMQ和Kafka在解决消息丢失问题时&#xff0c;在生产者、Broker和消费者之间如何解决消息丢失问题。 1.Rocket…...

每日Attention学习23——KAN-Block

模块出处 [SPL 25] [link] [code] KAN See In the Dark 模块名称 Kolmogorov-Arnold Network Block (KAN-Block) 模块作用 用于vision的KAN结构 模块结构 模块代码 import torch import torch.nn as nn import torch.nn.functional as F import mathclass Swish(nn.Module)…...

【前端】ES6新特性汇总

本文作者&#xff1a; slience_me ES6新特性汇总 1. let声明变量 1&#xff09;let作用域 // var 声明的变量往往会越域 // let 声明的变量有严格的局部作用域 {var a 1;let b 2; } console.log(a); // 1 console.log(b); // 报错 b is not defined2&#xff09;声明次数 …...

2024 CyberHost 语音+图像-视频

项目&#xff1a;CyberHost: Taming Audio-driven Avatar Diffusion Model with Region Codebook Attention 音频驱动的身体动画面临两个主要挑战&#xff1a;&#xff08;1&#xff09;关键人体部位&#xff0c;如面部和手部&#xff0c;在视频帧中所占比例较小&#x…...

Git命令摘录

使用 Git 升级软件通常是指通过 Git 仓库获取软件的最新版本或更新代码。以下是详细的步骤和方法&#xff1a; 1. 克隆软件仓库 如果这是你第一次获取软件代码&#xff0c;可以使用 git clone 命令将远程仓库克隆到本地。 git clone <仓库地址> 例如&#xff1a; git cl…...

DeepSeek24小时写作机器人,持续创作高质量文案

内容创作已成为企业、自媒体和创作者的核心竞争力。面对海量的内容需求&#xff0c;人工创作效率低、成本高、质量参差不齐等问题日益凸显。如何在有限时间内产出高质量内容&#xff1f;DeepSeek写作机器人&#xff0c;一款24小时持续创作的智能工具&#xff0c;为企业和个人提…...

Python 面向对象的三大特征

前言&#xff1a;本篇讲解面向对象的三大特征&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff0c;还有比较细致的&#xff08;类属性类方法&#xff0c;静态方法&#xff09;&#xff0c;分步骤讲解&#xff0c;比较适合理清楚三大特征的思路 面向对象的…...

在mac中安装Colima使用docker(替代Docker Desktop)

目录 推荐方案&#xff1a;Colima Docker CLI&#xff08;原生 ARM 支持&#xff09; 步骤 1: 安装必需工具 步骤 2: 启动 Colima (优化 ARM 虚拟机) 步骤 3: 绑定 Docker CLI 到 Colima 步骤 4: 验证 Docker 运行 方案对比与注意事项 常见陷阱 卸载残留配置&#xff…...