自定义训练循环与Simulink动作噪声
这个例子展示了如何使用自定义强化学习(RL)训练循环为车辆排队应用程序调优控制器。对于这个应用程序,在Simulink®模型中产生动作噪声,以促进训练过程中的探索。
有关调优基于pid的车辆排队系统的示例,请参见<一个href="//www.ru-cchi.com/help/slcontrol/ug/control-design-for-platooning.html" data-docid="slcontrol_ug#mw_28870ef5-32d3-4817-82f5-f93ef7fca6a8" class="a">车辆排车控制器设计(仿真软件控制设计).
排队有以下控制目标[1]。
每辆车的稳定性——如果前面的车以恒定的速度行驶,后面每辆车的间距误差收敛于零。
管柱稳定性——间距误差在向管柱尾部传播时不会放大。
连环境模型
在这个例子中,排中有5辆车。每辆车都被建模为一个卡车-拖车系统,具有以下参数。所有长度都以米为单位。
L1 = 6;<年代pan style="color:#228B22">%卡车长度L2 = 10;<年代pan style="color:#228B22">%拖车长度M1 = 1;<年代pan style="color:#228B22">%结长度L = l1 + l2 + m1 + 5;<年代pan style="color:#228B22">%期望的前后车辆间距
前车按照给定的加速度曲线行驶。每辆尾随车辆都有一个控制其加速的控制器。
打开Simulink®模型。
mdl =<年代pan style="color:#A020F0">“fiveVehiclePlatoonEnv”;open_system (mdl)
该模型包含一个RL Agent块及其<年代tr在g class="emphasis bold">最后的动作输入端口启用。该输入端口允许在Simulink模型中为非策略RL代理(如深度确定性策略梯度(DDPG)代理)定制噪声规范。
指定RL Agent块的路径。
agentBlk = mdl +<年代pan style="color:#A020F0">“/ RL代理”;
控制器结构
在本例中,每个尾随车辆(自我车辆)具有相同的连续时间控制器结构和参数化。
在这里:
,<年代pan class="inlineequation"> ,<年代pan class="inlineequation"> 分别是自我飞行器的加速度,速度和位置。
,<年代pan class="inlineequation"> ,<年代pan class="inlineequation"> 分别是在自我车辆正前方的车辆的加速度,速度和位置。
每辆车都可以完全访问自己的速度和位置状态,但只能通过无线通信直接访问前面车辆的加速度、速度和位置。
该控制器使速度误差最小化<年代pan class="inlineequation"> 利用速度增益<年代pan class="inlineequation"> 使间距误差最小化<年代pan class="inlineequation"> 利用间距增益<年代pan class="inlineequation"> .前馈增益<年代pan class="inlineequation"> 用于改善对前车的跟踪。
假设前车加速度为正弦波
在这里:
为前车加速度(m/s^2)。
为正弦波的振幅(m/s)。
是正弦波的频率(rad/s)。
为模拟时间(s)。
强化学习代理设计
该代理的目标是计算自适应增益,以便每辆车都能相对于前面的车辆跟踪所需的间距。因此,模型的配置如下:
动作信号由增益组成<年代pan class="inlineequation"> 除前车外,所有车辆共用。每个增益的下界分别为0,上界分别为1,20和20。代理每秒钟计算一次新的增益。为了鼓励训练过程中的探索,增益受到零均值正态分布随机噪声的干扰:<年代pan class="inlineequation"> 的方差<年代pan class="inlineequation"> .
观测信号由车辆间距(<年代pan class="inlineequation"> 减去目标间距(<年代pan class="inlineequation"> ,车辆速度(<年代pan class="inlineequation"> )和车辆加速度(<年代pan class="inlineequation"> ).
在每个时间步计算的奖励<年代pan class="inlineequation"> 是
地点:
车辆间距(<年代pan class="inlineequation"> )时间步长<年代pan class="inlineequation"> .
根据实际车辆间距计算所有车辆的最大超调量<年代pan class="inlineequation"> 和期望的间距<年代pan class="inlineequation"> .在这种情况下,超调被定义为当车辆间距小于所需间距时<年代pan class="inlineequation"> .
指示是否发生车辆碰撞。如果,模拟将终止<年代pan class="inlineequation"> 是真的。
奖励函数中的第一项鼓励车辆间距匹配<年代pan class="inlineequation"> .第二项惩罚时间步之间增益的巨大变化。第三项惩罚超过目标间距(太接近前面的车辆)。最后,第四项惩罚碰撞。
对于本例,为了适应模型中指定的自定义噪声,您实现了一个自定义DDPG训练循环。
定义模型参数
定义在训练过程中保持固定的训练和模拟参数。
Ts = 1;<年代pan style="color:#228B22">%采样时间(秒)Tf = 100;<年代pan style="color:#228B22">模拟长度(秒)AccelNoiseV = 1(1、5)* 0.01;<年代pan style="color:#228B22">%加速度输入噪声方差VelNoiseV = 1(1、5)* 0.01;<年代pan style="color:#228B22">%速度传感器噪声方差PosNoiseV = 1(1、5)* 0.01;<年代pan style="color:#228B22">%位置传感器噪声方差ParamLowerLimit = [0 0 0]';<年代pan style="color:#228B22">控制器增益的下限ParamUpperLimit = [1 20 20]';<年代pan style="color:#228B22">%控制器增益上限UseParamNoise = 1;<年代pan style="color:#228B22">%选项,指示是否注入噪声
定义改变每个训练集的参数。这些参数的值在环境重置函数中更新resetFunction
.
LeadA = 2;<年代pan style="color:#228B22">先导车辆加速度幅值LeadF = 1;<年代pan style="color:#228B22">%引导车辆加速频率ParamNoiseV = [0.02 0.1 0.1];<年代pan style="color:#228B22">控制器增益的%方差%随机噪声种子ParamNoiseSeed = 1:3;<年代pan style="color:#228B22">%控制器增益噪声种子accelernoiseseed = 1:5 + 100;<年代pan style="color:#228B22">%加速输入噪声种子VelNoiseSeed = 1:5 + 200;<年代pan style="color:#228B22">%速度传感器噪声种子PosNoiseSeed = 1:5 + 300;<年代pan style="color:#228B22">%位置传感器噪声种子每辆车的初始位置和速度。InitialPositions = [200 150 100 50 0] + 50;<年代pan style="color:#228B22">%的位置initialvelocity = [10 10 10 10 10 10];<年代pan style="color:#228B22">%的速度
创建环境
使用以下方法创建环境<一个href="//www.ru-cchi.com/help/reinforcement-learning/ref/rlsimulinkenv.html" data-docid="rl_ref#mw_12cc40dc-3191-4a23-af71-1625696c1a14" class="a">rlSimulinkEnv
.
为此,首先定义环境的观察和操作规范。
obsInfo = rlNumericSpec([14 1]);actInfo = rlNumericSpec([3 1],<年代pan style="color:#0000FF">...LowerLimit = ParamLowerLimit,<年代pan style="color:#0000FF">...UpperLimit = ParamUpperLimit);obsInfo。Name =<年代pan style="color:#A020F0">“测量”;actInfo。Name =<年代pan style="color:#A020F0">“control_gains”;
接下来,创建环境对象。
env = rlSimulinkEnv (mdl agentBlk、obsInfo actInfo);
设置环境复位功能为本地功能resetFunction
包括在这个例子中。这个函数改变每一集的训练条件。
env。ResetFcn = @resetFunction;
方法指定模型中的噪声<一个href="//www.ru-cchi.com/help/simulink/slref/randomnumber.html" data-docid="simulink_ref#f7-892059" class="a">随机数(模型)块。每个块都有自己的随机数生成器,因此也有自己的起始种子参数。为了确保噪声流在不同的片段之间变化,种子变量使用resetFunction
.
创造演员、评论家和政策
使用局部函数为代理创建角色函数和临界函数逼近器createNetworks
包括在这个例子中。
[评论家,演员]= createNetworks (obsInfo actInfo);
创建优化器对象以更新参与者和批评家。对两个优化器使用相同的选项。
optimizerOpt = rlOptimizerOptions (<年代pan style="color:#0000FF">...LearnRate = 1 e - 3,<年代pan style="color:#0000FF">...GradientThreshold = 1,<年代pan style="color:#0000FF">...L2RegularizationFactor = 1 e - 3);criticOptimizer = rlOptimizer (optimizerOpt);actorOptimizer = rlOptimizer (optimizerOpt);
为参与者近似器创建一个确定性策略。
政策= rlDeterministicActorPolicy(演员);
指定策略采样时间。
政策。SampleTime = t;
创建经验的缓冲
为代理创建最大长度为的经验缓冲区1 e6
.
replayMemory = rlReplayMemory (obsInfo actInfo 1 e6);
学习所需数据
为了在训练中更新演员和评论家的信息runEpisode
功能处理从环境接收到的每一种体验。对于本例,处理函数是processExperienceFcn
本地函数。
这个函数需要额外的数据来执行它的处理。创建一个结构来存储这些额外的数据。
processExpData。评论家=评论家;processExpData。TargetCritic =评论家;processExpData。演员=演员;processExpData。TargetActor =演员;processExpData。ReplayMemory = ReplayMemory;processExpData。CriticOptimizer = criticOptimizer; processExpData.ActorOptimizer = actorOptimizer; processExpData.MiniBatchSize = 128; processExpData.DiscountFactor = 0.99; processExpData.TargetSmoothFactor = 1e-3;
每一集,processExperienceFcn
函数更新批评者、参与者、重放内存和优化器。更新后的数据将作为下一集的输入。
循环训练
为了训练代理,自定义训练循环在环境中模拟代理的最大值为maxEpisodes
集。
maxEpisodes = 1000;
使用模拟时间和样本时间计算每集的最大步骤数。
maxSteps =装天花板(Tf / Ts);
对于这个自定义训练循环:
的
runEpisode
函数模拟环境中的代理为一集。方法从环境接收经验时处理经验
processExperienceFcn
函数。经验不会被记录下来
runEpisode
因为经验是在接受的过程中被处理的。为了加快训练速度,打电话的时候
runEpisode
,CleanupPostSim
选项设置为假
.这样做可以使模型在章节之间编译。的
PlatooningTrainingCurvePlotter
对象是一个helper对象,用于在训练运行时绘制训练数据。你可以停止训练使用<年代tr在g class="emphasis bold">停止按钮在训练图。
在所有的章节完成后,
清理
函数清理环境并终止模型编译。
训练策略是一个计算密集的过程,可能需要几分钟到几个小时才能完成。要在运行此示例时节省时间,请通过设置加载预先训练过的代理doTraining
来假
.要自己训练策略,请设置doTraining
来真正的
.
doTraining = false;<年代pan style="color:#0000FF">如果doTraining<年代pan style="color:#228B22">创建绘图助手对象。plotObj = PlatooningTrainingCurvePlotter ();<年代pan style="color:#228B22">%的训练循环为我= 1:maxEpisodes<年代pan style="color:#228B22">运行这一集。= runEpisode (<年代pan style="color:#0000FF">...env、政策<年代pan style="color:#0000FF">...MaxSteps = MaxSteps,<年代pan style="color:#0000FF">...ProcessExperienceFcn = @processExperienceFcn,<年代pan style="color:#0000FF">...ProcessExperienceData = processExpData,<年代pan style="color:#0000FF">...LogExperiences = false,<年代pan style="color:#0000FF">...CleanupPostSim = false);<年代pan style="color:#228B22">提取片段信息用于更新训练曲线。。episodeInfo = out.AgentData.EpisodeInfo;<年代pan style="color:#228B22">为下一集提取更新的processExpData。processExpData = out.AgentData.ProcessExperienceData;<年代pan style="color:#228B22">为下一集提取更新的策略。。政策= out.AgentData.Agent;<年代pan style="color:#228B22">从processExpData中提取批评家和参与者网络。评论家= processExpData.Critic;演员= processExpData.Actor;<年代pan style="color:#228B22">%提取累积奖励并计算平均奖励%为本集的每一步。cumulativeRwd = episodeInfo.CumulativeReward;avgRwdPerStep = cumulativeRwd / episodeInfo.StepsTaken;<年代pan style="color:#228B22">从发作的最初观察评估q0。obs0 = episodeInfo.InitialObservation;q0 =评估(评论家,[obs0、评估(演员,obs0)]);q0 =双(q0 {1});<年代pan style="color:#228B22">更新剧情。更新(plotObj, avgRwdPerStep、cumulativeRwd q0);<年代pan style="color:#228B22">%如果按下按钮退出训练。如果plotObj。停止培训<年代pan style="color:#0000FF">打破;<年代pan style="color:#0000FF">结束结束清理环境。清理(env);<年代pan style="color:#228B22">%保存策略。保存(<年代pan style="color:#A020F0">“PlatooningDDPGPolicy.mat”,<年代pan style="color:#A020F0">“政策”);<年代pan style="color:#0000FF">其他的加载预训练的策略。负载(<年代pan style="color:#A020F0">“PlatooningDDPGPolicy.mat”);<年代pan style="color:#0000FF">结束
验证培训政策
通过使用reset函数指定的随机初始条件运行五个模拟,验证学习到的策略。
首先,关闭模型中的参数噪声。
UseParamNoise = 0;
根据训练过的策略对模型进行五次模拟。
N = 5;simOpts = rlSimulationOptions (<年代pan style="color:#0000FF">...MaxSteps = MaxSteps,<年代pan style="color:#0000FF">...NumSimulations = N);经验= sim (env、政策simOpts);
绘制车辆间距误差,增益和奖励经历
输出结构。
f = figure(Position=[100 100 1024 768]);tiledlayout (f (N 3);<年代pan style="color:#0000FF">为i = 1: N<年代pan style="color:#228B22">%得到间隔tspacing =(我).Observation.measurements.Time经验;间隔=挤压(经验(i) .Observation.measurements.Data (1:4,:,:));<年代pan style="color:#228B22">%获得收益tgains =(我).Action.control_gains.Time经验;收益=挤压(经验(我).Action.control_gains.Data);<年代pan style="color:#228B22">%得到奖励trwd =(我).Reward.Time经验;rwd =(我).Reward.Data经验;<年代pan style="color:#228B22">%绘制间距nexttile楼梯(tspacing,间距);标题(sprintf (<年代pan style="color:#A020F0">车辆间距误差模拟%u,我)网格<年代pan style="color:#A020F0">在%绘制收益图nexttile楼梯(tgains,收益”);标题(sprintf (<年代pan style="color:#A020F0">“车辆增益模拟%u”,我)网格<年代pan style="color:#A020F0">在%绘制奖励图nexttile楼梯(trwd rwd);标题(sprintf (<年代pan style="color:#A020F0">“车辆奖励模拟%u”,我)网格<年代pan style="color:#A020F0">在结束
从图中,您可以看到经过训练的策略产生的自适应增益能够充分跟踪所有车辆的所需间距。
本地函数
每当RL Agent块处理一个经验时,流程经验函数就会被调用。在这里,processExperienceFcn
将经验追加到重播记忆中,从重播记忆中抽样一小批经验,并更新批评家、参与者和目标网络。
函数(政策、processExpData) = processExperienceFcn (processExpData exp、episodeInfo、政策)<年代pan style="color:#228B22">将经验附加到回放内存缓冲区。追加(processExpData.ReplayMemory exp);<年代pan style="color:#228B22">从回放记忆中抽取一小批经验。示例(processExpData miniBatch =。ReplayMemory,<年代pan style="color:#0000FF">...processExpData。MiniBatchSize,<年代pan style="color:#0000FF">...DiscountFactor = processExpData.DiscountFactor);<年代pan style="color:#0000FF">如果~ isempty (miniBatch)<年代pan style="color:#228B22">使用小批量更新网络参数。[processExpData, actorParams] = learnFcn (processExpData miniBatch);<年代pan style="color:#228B22">使用参与者参数更新策略参数。政策= setLearnableParameters(政策、actorParams);<年代pan style="color:#0000FF">结束结束
的learnFcn
函数在给定采样的小批处理的情况下更新批评者、参与者和目标网络
函数[processExpData, actorParams] = learnFcn (processExpData miniBatch)<年代pan style="color:#228B22">找到终端体验。doneidx = (miniBatch。结束= = 1);<年代pan style="color:#228B22">根据下一个观察结果计算下一个行动目标。。nextAction =评估(processExpData.TargetActor miniBatch.NextObservation);<年代pan style="color:#228B22">% compute qtarget = reward + gamma*Q(nextobvation,nextAction)% =奖励+伽马*预期未来回报targetq = miniBatch.Reward;<年代pan style="color:#228B22">在非终端体验中引导目标。expectedFutureReturn =<年代pan style="color:#0000FF">...getValue (processExpData.TargetCritic miniBatch.NextObservation nextAction);Targetq (~doneidx) = Targetq (~doneidx) +<年代pan style="color:#0000FF">...processExpData.DiscountFactor。* expectedFutureReturn (~ doneidx);<年代pan style="color:#228B22">使用deepCriticLoss函数计算临界梯度。criticGradient =梯度(processExpData。评论家,@deepCriticLoss,<年代pan style="color:#0000FF">...[miniBatch.Observation, miniBatch.Action], targetq);<年代pan style="color:#228B22">更新批评家参数。[processExpData.Critic, processExpData。CriticOptimizer] =更新(<年代pan style="color:#0000FF">...processExpData.CriticOptimizer processExpData.Critic,<年代pan style="color:#0000FF">...criticGradient);<年代pan style="color:#228B22">使用deepActorGradient函数计算角色梯度。来%加速的deepActorGradient函数,批判网络是函数外部提取的%,并作为字段传递给% actorGradData输入结构。actorGradData。CriticNet = getModel (processExpData.Critic);actorGradData。米iniBatchSize = processExpData.MiniBatchSize; actorGradient = customGradient(processExpData.Actor,@deepActorGradient,<年代pan style="color:#0000FF">...miniBatch.Observation actorGradData);<年代pan style="color:#228B22">更新参与者参数。[processExpData.Actor, processExpData。ActorOptimizer] =更新(<年代pan style="color:#0000FF">...processExpData.ActorOptimizer processExpData.Actor,<年代pan style="color:#0000FF">...actorGradient);actorParams = getLearnableParameters (processExpData.Actor);<年代pan style="color:#228B22">使用给定的TargetSmoothFactor超参数更新目标。processExpData。TargetCritic = syncParameters (processExpData。TargetCritic,<年代pan style="color:#0000FF">...processExpData.Critic processExpData.TargetSmoothFactor);processExpData。TargetActor = syncParameters (processExpData。TargetActor,<年代pan style="color:#0000FF">...processExpData。一个ctor ,processExpData.TargetSmoothFactor);<年代pan style="color:#0000FF">结束
临界梯度是根据deepCriticLoss
函数。
函数损失= deepCriticLoss(q,targetq) q = q{1};<年代pan style="color:#228B22">%损失是q = q的半均方误差(观察,动作)%对qtarget损失= mse (q,重塑(targetq、大小(q)));<年代pan style="color:#0000FF">结束
在给定的策略参数下,计算行为者梯度以使观察-行为对的期望值最大化。这里,负号是用来最大化的<年代pan class="inlineequation"> 关于<年代pan class="inlineequation"> .
在这里:
是批量观察
是批处理动作
批评家网络是否参数化<年代pan class="inlineequation">
参与者网络是否参数化<年代pan class="inlineequation">
是小批量吗
函数dQdTheta = deepActorGradient (actorNet、观察、gradData)<年代pan style="color:#228B22">根据当前的观察评估行动。action =前进(actorNet、观察{:});<年代pan style="color:#228B22">%计算:q = q (s,a)q =预测(gradData.CriticNet,观察{:},行动);<年代pan style="color:#228B22">% Compute: qsum = -sum(q)/N以最大化qqsum =总和(q,<年代pan style="color:#A020F0">“所有”) / gradData.MiniBatchSize;<年代pan style="color:#228B22">%计算:d(总和(q) / N) / dActorParamsdQdTheta = dlgradient (qsum actorNet.Learnables);<年代pan style="color:#0000FF">结束
环境重置函数为每一集改变初始条件、参考轨迹和噪声种子。
函数= resetFunction(中)<年代pan style="color:#228B22">干扰标称参考振幅和频率。LeadA = max(2 + 0.1*randn,0.1);LeadF = max(1 + 0.1*randn,0.1);<年代pan style="color:#228B22">干扰标称间距。L = 22 + 3*randn;<年代pan style="color:#228B22">扰乱初始状态。initiallocations = [250 200 150 100 50] + 5*randn(1,5);initialvelocity = [10 10 10 10 10 10] + 1*randn(1,5);<年代pan style="color:#228B22">更新噪声种子。ParamNoiseSeed =兰迪(100、1、3);AccelNoiseSeed = randi(100,1,5) + 100;VelNoiseSeed = randi(100,1,5) + 200;PosNoiseSeed = randi(100,1,5) + 300;<年代pan style="color:#228B22">更新模型变量。= setVariable (,<年代pan style="color:#A020F0">“L”L);= setVariable (,<年代pan style="color:#A020F0">“LeadA”, LeadA);= setVariable (,<年代pan style="color:#A020F0">“LeadF”, LeadF);= setVariable (,<年代pan style="color:#A020F0">“InitialPositions”, InitialPositions);= setVariable (,<年代pan style="color:#A020F0">“InitialVelocities”, InitialVelocities);= setVariable (,<年代pan style="color:#A020F0">“ParamNoiseSeed”, ParamNoiseSeed);= setVariable (,<年代pan style="color:#A020F0">“AccelNoiseSeed”, AccelNoiseSeed);= setVariable (,<年代pan style="color:#A020F0">“VelNoiseSeed”, VelNoiseSeed);= setVariable (,<年代pan style="color:#A020F0">“PosNoiseSeed”, PosNoiseSeed);<年代pan style="color:#0000FF">结束
创建评论家和演员网络。
函数[批评家,演员]= createNetworks(obsInfo,actInfo) rng(0);hiddenLayerSize = 64;numObs = prod (obsInfo.Dimension);numAct = prod (actInfo.Dimension);<年代pan style="color:#228B22">创建批评家网络。obsInput = featureInputLayer (numObs,<年代pan style="color:#0000FF">...归一化=<年代pan style="color:#A020F0">“没有”,<年代pan style="color:#0000FF">...Name = obsInfo.Name);actInput = featureInputLayer (numAct,<年代pan style="color:#0000FF">...归一化=<年代pan style="color:#A020F0">“没有”,<年代pan style="color:#0000FF">...Name = actInfo.Name);catPath = [concatenationLayer(1,2,Name= .<年代pan style="color:#A020F0">“concat”) fullyConnectedLayer (hiddenLayerSize Name =<年代pan style="color:#A020F0">“fc1”) reluLayer (Name =<年代pan style="color:#A020F0">“relu1”) fullyConnectedLayer (hiddenLayerSize Name =<年代pan style="color:#A020F0">“取得”) reluLayer (Name =<年代pan style="color:#A020F0">“relu2”) fullyConnectedLayer (Name =<年代pan style="color:#A020F0">“q”));网= layerGraph ();网= addLayers(净,obsInput);网= addLayers(净,actInput);网= addLayers(净,catPath);网= connectLayers(净,obsInfo。的名字,<年代pan style="color:#A020F0">“concat /三机一体”);网= connectLayers(净,actInfo。的名字,<年代pan style="color:#A020F0">“concat / in2”);网= dlnetwork(净);<年代pan style="color:#228B22">创建临界函数逼近器。。评论家= rlQValueFunction(网,obsInfo, actInfo);评论家=加速(评论家,真的);<年代pan style="color:#228B22">创建演员网络。= (actInfo规模。UpperLimit - actInfo.LowerLimit) / 2;偏见= actInfo。lowerLimit + scale; obsPath = [ featureInputLayer(numObs,<年代pan style="color:#0000FF">...归一化=<年代pan style="color:#A020F0">“没有”,<年代pan style="color:#0000FF">...Name = obsInfo.Name) fullyConnectedLayer (hiddenLayerSize Name =<年代pan style="color:#A020F0">“fc1”) reluLayer (Name =<年代pan style="color:#A020F0">“relu1”) fullyConnectedLayer (numAct Name =<年代pan style="color:#A020F0">“取得”) reluLayer (Name =<年代pan style="color:#A020F0">“relu2”) fullyConnectedLayer (numAct Name =<年代pan style="color:#A020F0">“一个fc3”文件) tanhLayer (Name =<年代pan style="color:#A020F0">“tanh1”) scalingLayer(规模=规模,<年代pan style="color:#0000FF">...偏差=偏差,<年代pan style="color:#0000FF">...Name = actInfo.Name)];网= layerGraph;网= addLayers(净,obsPath);网= dlnetwork(净);<年代pan style="color:#228B22">创建角色函数逼近器。演员= rlContinuousDeterministicActor(网,obsInfo, actInfo);演员=加速(演员,真的);<年代pan style="color:#0000FF">结束
参考文献
[1] Rajamani,拉杰什。<年代pan class="emphasis">车辆动力学与控制.2.机械工程系列。海德堡:施普林格,2012。
另请参阅
功能
runEpisode
|<年代pan itemscope itemtype="//www.ru-cchi.com/help/schema/MathWorksDocPage/SeeAlso" itemprop="seealso">设置
|<年代pan itemscope itemtype="//www.ru-cchi.com/help/schema/MathWorksDocPage/SeeAlso" itemprop="seealso">清理