本人也是刚开始探索,大家一起讨论一起进步!
项目介绍:
教程为北理工的无人驾驶车辆模型预测控制第2版,代码为开源代码。所用的仿真软件为Carsim2020.0和MatlabR2021a。使用MPC控制思想对车辆进行速度控制,并给出仿真结果。
效果如下:

基于MPC的速度控制
Carsim一共可以分为三大部分:

车型选择E-Class SUV,最好开一个新的Dataset。


新开一个Dataset。因为我们需要在MATLAB中控制汽车,不需要模型自身施加驱动力和制动力所以驱动控制和制动控制设置为无。挡位选择自动升档。本项目无横向控制,所以转向盘选择”Driver path follower“控制。道路设置长一点,仿真设置120s,具体参数设置如下:

在设置完前处理之后,准备使用MATLAB中的Simulink对其进行控制和计算。在计算那一栏的Model中选择Model:Simulink。之后建立一个新的Dataset。具体设置如下:

这里的输入设置为IMP_THROTTLE_ENGINE(油门输入)、IMP_PCON_BK制动主缸压力[MPa]。

输出设置为车速和加速度:

设置好之后点击:Send to Simulink

在使用Carsim发送过去数据之后,点击MATLAB中的Simulink,此时的Library Browser中应该会出现Carsim S-Function:

将Carsim S-Functioin拖入Simulink模块,同时在User-Defined Functions中找到S-Function并拖入Simulink模块。构建如下图所示的框架:

这里稍微解释一下这个框架:
我们希望使用MPC对汽车的速度进行控制。对于MPC,我们的输入是自己设定的期望速度,汽车当前的速度和加速度,这些量暂且统一称为决策变量,根据这些变量,MPC计算之后会给出相应的控制,让汽车去调整到我们期望的速度,这里的控制是油门和制动主缸压力,这样就构成了一个反馈。因为在使用MPC的时候,我们还希望看一些其他量的变化,所以在MPC模块的输出不仅仅是两个控制量,还会有其他的量,使用Terminator模块将不需要输入进Carsim模块终止掉。使用To Workspace模块将MPC所有输出模块导入到MATLAB里面,方便画图。

如果不需要看其他参数,可以直接构建如下框架:

汽车的速度控制可以参考下图(来自北理工的书):其可以分为上位控制器和下位控制器。

在机器人控制中可以分为底层控制-感知-决策。感知方面其实我们默认是做好的,而且本例也不涉及。这个上位控制可以理解为决策,根据车辆的当前速度加速度状态和期望的速度,判断该如何进行加速度的选择。下位控制器可以理解为底层控制,当车辆做出决策给出应当进行的控制的时候,需要准确的进行这个控制实现。
车辆的纵向控制可以使用一阶惯性系统来表示:
a
˙
=
K
τ
d
(
a
d
e
s
−
a
)
\dot{a}=\frac{K}{τ_d}(a_{des}-a)
a˙=τdK(ades−a)
其中
K
=
1
K=1
K=1为系统的增益;
τ
d
τ_d
τd为时间常数。将上式和速度加速度关系写成矩阵形式,变成状态方程:
x
˙
=
A
x
+
B
u
\dot{x}=Ax+Bu
x˙=Ax+Bu
展开表示为:
(
v
˙
a
˙
)
=
(
0
1
0
−
1
τ
d
)
(
v
a
)
+
(
0
K
τ
d
)
a
d
e
s
\left( \begin{array}{c} \dot{v} \\ \dot{a} \end{array} \right)= \left( \begin{array}{ccc} 0 &1\\ 0 & \frac{-1}{τ_d} \end{array} \right) \left( \begin{array}{c} v \\ a \end{array} \right)+ \left( \begin{array}{c} 0 \\ \frac{K}{τ_d} \end{array} \right)a_{des}
(v˙a˙)=(001τd−1)(va)+(0τdK)ades
使用前向欧拉法,进行离散:
x
(
k
+
1
)
=
A
k
x
(
k
)
+
B
k
u
(
k
)
x(k+1)=A_kx(k)+B_ku(k)
x(k+1)=Akx(k)+Bku(k)
展开表示为:
(
v
(
k
+
1
)
a
(
k
+
1
)
)
=
(
1
T
s
0
1
−
T
s
τ
d
)
(
v
(
k
)
a
(
k
)
)
+
(
0
K
T
s
τ
d
)
a
d
e
s
(
k
)
\left( \begin{array}{c} v(k+1) \\ a(k+1) \end{array} \right)= \left( \begin{array}{ccc} 1 &T_s\\ 0 & 1-\frac{T_s}{τ_d} \end{array} \right) \left( \begin{array}{c} v(k) \\ a(k) \end{array} \right)+ \left( \begin{array}{c} 0 \\ \frac{KT_s}{τ_d} \end{array} \right)a_{des}(k)
(v(k+1)a(k+1))=(10Ts1−τdTs)(v(k)a(k))+(0τdKTs)ades(k)
速度
v
v
v作为系统的输出,输出方程可以表示为:
y
(
k
)
=
C
x
(
k
)
,
C
=
[
1
0
]
y(k)=Cx(k),C=[1 0]
y(k)=Cx(k),C=[1 0]
以上就是模型预测控制中的模型建立。
系统的控制目标定义为:速度跟踪精度,同时还要避免过大的加速度和冲击。
J
=
∑
i
=
1
N
p
(
y
p
(
k
+
i
∣
k
)
−
y
r
e
f
(
k
+
i
∣
k
)
)
2
+
∑
i
=
1
N
c
(
Δ
u
(
k
+
i
)
)
2
J = \sum_{i=1}^{N_p}(y_p(k+i|k)-y_{ref}(k+i|k))^2+ \sum_{i=1}^{N_c}(Δu(k+i))^2
J=i=1∑Np(yp(k+i∣k)−yref(k+i∣k))2+i=1∑Nc(Δu(k+i))2
系统的约束为加速度及其变化率;
u
m
i
n
≤
u
(
k
+
i
)
≤
u
m
a
x
,
i
=
0
,
1
,
.
.
.
,
N
e
−
1
Δ
u
m
i
n
≤
Δ
u
(
k
+
i
)
≤
Δ
u
m
a
x
,
i
=
0
,
1
,
.
.
.
,
N
c
−
1
u_{min}≤u(k+i)≤u_{max},i=0,1,...,N_e-1\\[1.5mm] Δu_{min}≤Δu(k+i)≤Δu_{max},i=0,1,...,N_c-1
umin≤u(k+i)≤umax,i=0,1,...,Ne−1Δumin≤Δu(k+i)≤Δumax,i=0,1,...,Nc−1
这样我们就把MPC的模型建立完毕了,基本框架为:
m
i
n
J
s
.
t
.
x
(
k
+
1
)
=
A
k
x
(
k
)
+
B
k
u
(
k
)
u
m
i
n
≤
u
(
k
+
i
)
≤
u
m
a
x
,
i
=
0
,
1
,
.
.
.
,
N
e
−
1
Δ
u
m
i
n
≤
Δ
u
(
k
+
i
)
≤
Δ
u
m
a
x
,
i
=
0
,
1
,
.
.
.
,
N
c
−
1
min J\\[1.5mm] s.t. x(k+1)=A_kx(k)+B_ku(k)\\[1.5mm] u_{min}≤u(k+i)≤u_{max},i=0,1,...,N_e-1\\[1.5mm] Δu_{min}≤Δu(k+i)≤Δu_{max},i=0,1,...,N_c-1
minJs.t. x(k+1)=Akx(k)+Bku(k) umin≤u(k+i)≤umax,i=0,1,...,Ne−1 Δumin≤Δu(k+i)≤Δumax,i=0,1,...,Nc−1
这部分理论性比较强,MPC理论推导参考如何理解MPC模型预测控制理论,这里主要说一下思路和编程所用的公式。
首先为了推导方便,我们进行如下定义:
ξ
(
k
∣
t
)
=
[
x
(
k
)
,
u
(
k
−
1
)
]
T
ξ(k|t)=[x(k),u(k-1)]^T
ξ(k∣t)=[x(k),u(k−1)]T
这样我们的离散模型可以写成:
ξ
(
k
+
1
)
=
A
k
~
ξ
(
k
)
+
B
k
~
Δ
u
(
k
)
η
(
k
)
=
C
k
~
ξ
(
k
)
ξ(k+1) = \tilde{A_k}ξ(k)+\tilde{B_k}Δu(k)\\[2mm] η(k) = \tilde{C_k}ξ(k)
ξ(k+1)=Ak~ξ(k)+Bk~Δu(k)η(k)=Ck~ξ(k)
其中这里面的
A
k
~
=
(
A
k
B
k
0
m
×
n
I
m
)
,
B
k
~
=
(
B
k
I
m
)
,
C
k
~
=
(
C
k
0
)
\tilde{A_k}= \left( \begin{array}{ccc} A_k &B_k\\ 0_{m×n} & I_m \end{array} \right),\tilde{B_k}=\left( \begin{array}{c} B_k \\ I_m \end{array} \right),\tilde{C_k}=(C_k 0)
Ak~=(Ak0m×nBkIm),Bk~=(BkIm),Ck~=(Ck 0)。这里
m
=
1
,
n
=
2
m=1,n=2
m=1,n=2
证明的具体过程就不在这里赘述,主要思路是,我们希望通过当前的状态来给出控制,那么观察离散模型,可以发现,通过当前的状态,根据离散模型,其实是能给出下一时刻的状态的,甚至是输入,而当得到下一时刻状态的时候又可以根据离散的方程进行预测,得到下下时刻的状态,如此迭代,可以得到预测时域内所有时刻的状态和输入,换句话说这些都可以用初始状态和输入来表示。我们将这些统一写成矩阵形式:
Y
=
Φ
ξ
(
k
)
+
Θ
Δ
U
Y=Φξ(k)+ΘΔU
Y=Φξ(k)+ΘΔU
具体形式为:

我们目前拿到了预测的速度和输入,将其带入到目标函数中,经过改写可以变成:

其中:

其实相当于把模型约束放进目标函数中!!!
现在还剩的约束时控制量和控制增量的约束。而控制量和控制增量存在如下关系:
u
(
k
+
i
)
=
u
(
k
+
i
−
1
)
+
Δ
u
(
k
)
u(k+i)=u(k+i-1)+Δu(k)
u(k+i)=u(k+i−1)+Δu(k)
这样可以将最后两个约束写成:

这里:

A
k
A_k
Ak为:

通过上述三个步骤,就可以把MPC问题变成一个二次型求解问题:

总结一下具体思路:一句话就是把模型放进目标函数中。分步骤说就是首先用建立的模型,当前状态写出预测内的状态和控制。之后将其带入目标函数,并进行整理。最后将剩下的两个约束写成一个统一的形式。这样构建出来的问题使用MATLAB自带的二次规划求解器就可以求解。
注:上述公式也是代码里面的核心公式,如果代码看不懂,可以从这里面寻找答案。
我们希望跟踪的速度为:

后处理有两种方式:
一种是使用MATLAB:
在命令行窗口输入plot((1:2400),out.y.signals.values(:,4))或者plot(out.y.time,out.y.signals.values(:,4))(因为采样频率是0.05秒一次,一共运行120s)。

做出图像为:

还可以使用Carsim的后处理模块,这里主要是速度跟踪,所以需要Longitudinal speed和time的关系图像。

同时Carsim中还提供了仿真的汽车跟踪动画。(如本文开头展示的动画)
function [sys,x0,str,ts] =MPCSpeedTrackingControl(t,x,u,flag)
%***************************************************************%
% Input:
% t是采样时间, x是状态变量, u是输入(是做成simulink模块的输入,即CarSim的输出),
% flag是仿真过程中的状态标志(以它来判断当前是初始化还是运行等)
% Output:
% sys输出根据flag的不同而不同(下面将结合flag来讲sys的含义),
% x0是状态变量的初始值,
% str是保留参数,设置为空
% ts是一个1×2的向量, ts(1)是采样周期, ts(2)是偏移量
%---------------------------------------------------------------%
%***************************************************************%
switch flag
case 0 %初始化%
[sys,x0,str,ts]= mdlInitializeSizes;
case 2 %更新%
sys = mdlUpdates(t,x,u);%更新离散变量
case 3 %输出%
sys = mdlOutputs(t,x,u);%计算输出
case {1,4,9} %不用的flag
sys = [];
otherwise % 其他flag情况 %
error(['unhandled flag = ',num2str(flag)]); % Error handling
end
% End sfuntmpl
%====================================================================
%初始化,flag=0
%====================================================================
function [sys,x0,str,ts]= mdlInitializeSizes
sizes = simsizes;%用于设置模块参数的结构体用simsizes来生成
sizes.NumContStates = 0; %模块连续状态变量的个数
sizes.NumDiscStates = 2; %模块离散状态变量的个数,实际上没有用到这个数值,只是用这个来表示离散模块
sizes.NumOutputs = 6; %输出量的个数,包括控制量和其他检测量
sizes.NumInputs = 2; %输入量的个数,对应Carsim输出量
sizes.DirFeedthrough= 1; %没太懂,好像是时变就需要设为1
sizes.NumSampleTimes= 1; %采样时间的个数
sys = simsizes(sizes);
x0 = zeros(sizes.NumDiscStates,1); %初始化状态变量
str = [];
ts = [0.05 0]; %采样时间:[period,offset]
%设置一些全局变量
global InitialGapflag;
InitialGapflag = 0;
global MPCParameters;
MPCParameters.Np =30; %预测时域
MPCParameters.Nc =30; %控制时域
MPCParameters.Nx =2; %状态变量
MPCParameters.Nu =1; %控制输入
MPCParameters.Ny =1; %输出变量
MPCParameters.Ts =0.05; %设置采样时间
MPCParameters.Q =100; %权重
MPCParameters.R =1; %权重
MPCParameters.S =1; %权重
MPCParameters.qp_solver = 0; %0: default, quadprog; 1:qpOASES; 2:CVXGEN
MPCParameters.refspeedT = 0; %0: default, step speed profile;
%1:sine-wave speed profile
MPCParameters.umin = -5.0; % 最大减速
MPCParameters.umax = 3.5; % 最大加速
MPCParameters.dumin = -5.0; % minimum limits of jerk
MPCParameters.dumax = 5.0; % maximum limits of jerk
global WarmStart;
WarmStart = zeros(MPCParameters.Np,1);
%结束
%=============================================================================
%更新离散状态量子函数
function sys=mdlUpdates(t,x,u)
sys = x;
%结束
%=============================================================================
%计算输出子函数,是速度跟踪控制的主体。
function sys=mdlOutputs(t,x,u)
global InitialGapflag;
global MPCParameters;
global WarmStart;
a_des = 0; %初始化最优加速度控制量
a_x = 0; %初始化加速度状态量
Vx = 0; %初始化车速
t_Start = tic; %开始计时
if InitialGapflag < 2 %去掉前两次输入
InitialGapflag = InitialGapflag + 1;
else
InitialGapflag = InitialGapflag + 1;
%------------------Step(1).更新车辆纵向状态--------------------%
Vx = u(1)/3.6; %单位换算km/h->m/s
a_x = u(2)*9.8; %单位换算g's->m/s^2
kesi = [Vx;a_x]; %更新车辆状态向量
%-------------------Step(2).设定期望速度-----------------------%
switch MPCParameters.refspeedT
%------------------设定阶跃式目标车速--------------------------%
case 0
SpeedProfile = func_ConstantSpeed(InitialGapflag,MPCParameters);
%------------------设定sine式目标车速--------------------------%
otherwise % Unexpected flags %
error(['unexpected speed-profile:',num2str(MPCParameters.refspeedT)]); % Error handling
end % end of switch
%-------------------Step(3).调用MPC优化求解函数得到最优控制量-----------------------%
Ts = MPCParameters.Ts;
StateSpaceModel.A = [1 Ts;
0 1];
StateSpaceModel.B = [0; 1];
StateSpaceModel.C = [1,0];
[PHI, THETA] = func_Update_PHI_THETA(StateSpaceModel, MPCParameters);
[H, ~, g] = func_Update_H_f(kesi, SpeedProfile, PHI, THETA, MPCParameters);
switch MPCParameters.qp_solver
case 0 %default qp-solver:quadprog
[A,b,Aeq,beq,lb,ub] = func_Constraints_du_quadprog(MPCParameters,a_x);
options = optimset('Display','off', ...
'TolFun', 1e-8, ...
'MaxIter', 2000, ...
'Algorithm', 'active-set', ...
'FinDiffType', 'forward', ...
'RelLineSrchBnd', [], ...
'RelLineSrchBndDuration', 1, ...
'TolConSQP', 1e-8);
warning off all % close the warnings during computation
U0 = WarmStart;
[U, ~, EXITFLAG] = quadprog(H, g, A, b, Aeq, beq, lb, ub, U0, options); %
WarmStart = shiftHorizon(U);
if (1 ~= EXITFLAG) %if optimization NOT succeeded.
U(1) = 0.0;
fprintf('MPC solver not converged!\n');
end
a_des = U(1);
otherwise % Unexpected flags %
error(['unexpected qp-solver, Sol_method=',num2str(flag)]); % Error handling
end % end of switch
end % end of if Initialflag < 1 %
%****Step(6): 由期望的加速度生成Throttle和Brake;********************%
[Throttle, Brake] = func_AccelerationTrackingController(a_des);
t_Elapsed = toc( t_Start ); %computation time
sys = [Throttle; Brake;t_Elapsed; Vx; a_x; a_des];
% end %End of mdlOutputs
%==============================================================
% sub functions
%==============================================================
function [Vref] = func_ConstantSpeed(InitialGapflag, MPCParameters)
%Ts = MPCParameters.Ts; %采样时间=0.05s
Np = MPCParameters.Np; %预测时域:30
Vref = cell(Np,1);
% 自定义阶梯的形式
if InitialGapflag < 400
Vset = 10;
else
if InitialGapflag < 800
Vset = 10;
else
if InitialGapflag < 1500
Vset = 20;
else
Vset = 5;
end
end
end
for i = 1:1:Np
Vref{i,1} = Vset;
end
function [PHI, THETA] = func_Update_PHI_THETA(StateSpaceModel, MPCParameters)
%***************************************************************%
% 预测输出表达式 Y(t)=PHI*kesi(t)+THETA*DU(t)
% Y(t) = [Eta(t+1|t) Eta(t+2|t) Eta(t+3|t) ... Eta(t+Np|t)]'
%***************************************************************%
Np = MPCParameters.Np;
Nc = MPCParameters.Nc;
Nx = MPCParameters.Nx;
Ny = MPCParameters.Ny;
Nu = MPCParameters.Nu;
A = StateSpaceModel.A;
B = StateSpaceModel.B;
C = StateSpaceModel.C;
PHI_cell = cell(Np,1); %PHI=[CA CA^2 CA^3 ... CA^Np]T
THETA_cell = cell(Np,Nc);
for j = 1:1:Np
PHI_cell{j,1}=C*A^j;
for k = 1:1:Nc
if k<=j
THETA_cell{j,k}=C*A^(j-k)*B;
else
THETA_cell{j,k} = zeros(Ny,Nu);
end
end
end
PHI=cell2mat(PHI_cell); % size(PHI)=[(Ny*Np) * Nx]
THETA=cell2mat(THETA_cell);% size(THETA)=[Ny*Np Nu*Nc]
% end %EoF
function[H, f, g] = func_Update_H_f(kesi, SpeedProfile, PHI, THETA, MPCParameters)
%***************************************************************%
% trajectory planning
%***************************************************************%
Np = MPCParameters.Np;
Nc = MPCParameters.Nc;
Q = MPCParameters.Q;
R = MPCParameters.R;
Qq = kron(eye(Np),Q);
Rr = kron(eye(Nc),R);
Vref = cell2mat(SpeedProfile);
error1 = PHI*kesi;
H = THETA'*Qq*THETA +Rr;
f = (error1'-Vref')*Qq*THETA;
g = f';
% end %EoF
function [A, b, Aeq, beq, lb, ub] = func_Constraints_du_quadprog(MPCParameters, um)
%************************************************************************%
% generate the constraints of the vehicle
%
%************************************************************************%
Np = MPCParameters.Np;
Nc = Np;
dumax = MPCParameters.dumax;
umin = MPCParameters.umin;
umax = MPCParameters.umax;
Umin = kron(ones(Nc,1),umin);
Umax = kron(ones(Nc,1),umax);
Ut = kron(ones(Nc,1),um);
%----(1) A*x<=b----------%
A_t=zeros(Nc,Nc);
for p=1:1:Nc
for q=1:1:Nc
if p >= q
A_t(p,q)=1;
else
A_t(p,q)=0;
end
end
end
A_cell=cell(2,1);
A_cell{1,1} = A_t; %
A_cell{2,1} = -A_t;
A=cell2mat(A_cell); %
b_cell=cell(2, 1);
b_cell{1,1} = Umax - Ut; %
b_cell{2,1} = -Umin + Ut;
b=cell2mat(b_cell); %
%----(2) Aeq*x=beq----------%
Aeq = [];
beq = [];
%----(3) lb=<x<=ub----------%
lb=kron(ones(Nc,1),-dumax);
ub=kron(ones(Nc,1),dumax);
% end %EoF
function u0 = shiftHorizon(u) %shift control horizon
u0 = [u(:,2:size(u,2)), u(:,size(u,2))]; % size(u,2))
function [Throttle, Brake] = func_AccelerationTrackingController(ahopt)
% 车辆下位控制器将期望加速度转化为油门控制量和制动主缸压力控制量
K_brake = 0.3; %0.3
K_throttle = 1; %0.05;
Brake_Sat = 15;
Throttle_Sat = 1;
if ahopt < 0 % Brake control
Brake = -K_brake * ahopt;
if Brake > Brake_Sat
Brake = Brake_Sat;
end
Throttle = 0;
else % throttle control
Brake = 0;
Throttle = K_throttle *ahopt;
if Throttle > Throttle_Sat
Throttle = Throttle_Sat;
end
if Throttle < 0
Throttle = 0;
end
end
% end %EoF
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新rubygems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc