CasADi - 最优控制开源 Python/MATLAB 库3:https://developer.aliyun.com/article/1584026
6.3. 导入外部函数
CasADi 外部函数的基本用法已在第 5.2 节的自动生成代码中进行了演示。同样的函数也可用于导入用户定义的函数,只要该函数也使用第 5.3 节所述的 C API。
下文将对此进行进一步阐述。
6.3.1. 默认函数
通常无需定义第 5.3 节中定义的所有函数。如果没有定义 fname_incref 和 fname_decref,则假定不需要内存管理。如果没有提供输入和输出的名称,它们将被赋予默认名称。一般情况下,稀疏性模式默认为标量模式,除非函数对应于另一个函数的导数(见下文),在这种情况下,稀疏性模式默认为密集模式且维度正确。
此外,如果没有实现 fname_work,则假定不需要工作矢量。
6.3.2. 作为注释的元信息
如果您依赖于 CasADi 的即时编译器,您可以在 C 代码中以注释的形式提供元信息,而不是实现实际的回调函数。
元信息的结构如下:
/*CASADIMETA :fname_N_IN 1 :fname_N_OUT 2 :fname_NAME_IN[0] x :fname_NAME_OUT[0] r :fname_NAME_OUT[1] s :fname_SPARSITY_IN[0] 2 1 0 2 */
6.3.3. 导数
外部函数可以通过提供导数计算函数来实现微分。在导数计算过程中,CasADi 将在同一文件/共享库中寻找符合特定命名规则的符号。例如,您可以通过执行名为 jac_fname 的函数,为名为 fname 的函数指定相对于所有输入的所有输出的雅各比。同样,您可以通过提供名为 fwd1_fname 的函数来指定计算一个正向导数的函数,其中 1 可以用 2、4、8、16、32 或 64 代替,以便一次计算多个正向导数。对于反向模式方向导数,可将 fwd 替换为 adj。
这是一项试验性功能。
6.4. 即时编译 C 语言字符串
在上一节中,我们介绍了如何指定一个包含数值计算函数和元信息的 C 语言文件。如前所述,CasADi 与 Clang 的接口可以对该文件进行即时编译。用户只需将源代码指定为一个 C 语言字符串即可。
body =\ 'r[0] = x[0];'+\ 'while (r[0]<s[0]) {'+\ ' r[0] *= r[0];'+\ '}' f = Function.jit('f',body,\ ['x','s'],['r'],\ {"compiler":"shell"}) print(f) f:(x,s)->(r) JitFunction
Function.jit 的这四个参数是必须的: 函数名称、字符串形式的 C 代码以及输入和输出名称。在 C 代码中,输入/输出名称对应于 casadi_real_t 类型的数组,其中包含函数输入和输出的非零元素。默认情况下,所有输入和输出都是标量(即 1-by-1 且密集)。要指定不同的稀疏性模式,请提供两个额外的函数参数,其中包含稀疏性模式的向量/列表:
sp = Sparsity.scalar() f = Function.jit('f',body,\ ['x','s'], ['r'],\ [sp,sp], [sp],\ {"compiler":"shell"}) print(f) f:(x,s)->(r) JitFunction
两种变体都接受以选项字典形式出现的第 5 个(或第 7 个)可选参数。
6.5. 使用查找表
可以使用 CasADi 的插值函数创建查找表。不同的插值方案以插件形式实现,类似于 nlpsol 或 integrator 对象。除了标识符名称和插件外,插值函数还需要一组网格点和相应的数值。
内插函数调用的结果是一个可微分的 CasADi 函数对象,可以通过调用 MX 参数嵌入到 CasADi 计算图形中。此外,此类图形完全支持 C 代码生成。
目前,插值法有两个插件: 线性 "和 “bspline”。它们的作用类似于 MATLAB/Octave 的插值,方法设置为 "线性 "或 “样条”–对应于多线性插值和非结边界条件下的样条插值(默认为立方)。
在使用 bspline 的情况下,将在构建时寻找符合所提供数据的系数。或者,您也可以使用更低级的 Function.bspline 来自行提供系数。默认情况下,bspline 的每个维度的阶数都是 3。您可以通过阶数选项偏离默认值。
我们将介绍一维和二维版本的插值法语法,但实际上该语法可用于任意维数。
6.5.1. 一维查找表
与 SciPy 中的相应方法相比,CasADi/Python 中的一维样条曲线拟合方法如下:
import casadi as ca import numpy as np xgrid = np.linspace(1,6,6) V = [-1,-1,-2,-3,0,2] lut = ca.interpolant('LUT','bspline',[xgrid],V) print(lut(2.5)) # Using SciPy import scipy.interpolate as ip interp = ip.InterpolatedUnivariateSpline(xgrid, V) print(interp(2.5)) -1.35 -1.3500000000000005
在 MATLAB/Octave 中,相应的代码为
xgrid = 1:6; V = [-1 -1 -2 -3 0 2]; lut = casadi.interpolant('LUT','bspline',{xgrid},V); lut(2.5) % Using MATLAB/Octave builtin interp1(xgrid,V,2.5,'spline') ans = -1.35 ans = -1.3500
请特别注意,内插法的网格和数值参数必须是数值参数。
6.5.2. 二维查找表
在 Python 中,我们可以得到以下二维查找表,也可以与 SciPy 进行比较,以供参考:
xgrid = np.linspace(-5,5,11) ygrid = np.linspace(-4,4,9) X,Y = np.meshgrid(xgrid,ygrid,indexing='ij') R = np.sqrt(5*X**2 + Y**2)+ 1 data = np.sin(R)/R data_flat = data.ravel(order='F') lut = ca.interpolant('name','bspline',[xgrid,ygrid],data_flat) print(lut([0.5,1])) # Using Scipy interp = ip.RectBivariateSpline(xgrid, ygrid, data) print(interp.ev(0.5,1)) 0.245507 0.24550661674668917
或在 MATLAB/Octave 中与内置函数进行比较:
xgrid = -5:1:5; ygrid = -4:1:4; [X,Y] = ndgrid(xgrid, ygrid); R = sqrt(5*X.^2 + Y.^2)+ 1; V = sin(R)./R; lut = interpolant('LUT','bspline',{xgrid, ygrid},V(:)); lut([0.5 1]) % Using Matlab builtin interpn(X,Y,V,0.5,1,'spline') ans = 0.245507 ans = 0.2455
特别要注意的是,values 参数必须扁平化为一维数组。
6.6. 使用有限差分进行衍生计算
CasADi 3.3 为所有函数对象引入了有限差分计算支持,特别是包括第 6.2 节、第 6.3 节或第 6.4 节中定义的外部函数(对于第 6.5 节中的查找表,可使用解析导数)。
除 Function.jit 外,有限差分导数默认为禁用,要启用有限差分导数,必须将 "enable_fd "选项设置为 True/true:
f = external('f', './gen.so',\ dict(enable_fd=True)) e = jacobian(f(x),x) D = Function('d',[x],[e]) print(D(0)) 1
参见第 5.1 节。
如果没有分析导数,"enable_fd "选项可以让 CasADi 使用有限差分。要强制 CasADi 使用有限差分,可以将 “enable_fd”、"enable_reverse "和 "enable_jacobian "设置为 “假”/“假”,分别对应于 CasADi 所使用的三种分析导数信息。
默认方法是中心差法,步长由函数的舍入误差和截断误差估算值决定。您可以通过将 "fd_method "选项设置为 “forward”(对应一阶正向差分)、“backward”(对应一阶反向差分)和 “smoothing”(二阶精确非连续性避免方案)来更改方法,适用于需要计算域边缘导数的情况。通过设置 "fd_options "选项,可以获得有限差分的其他算法选项。
七、 DaeBuilder 类
CasADi 中的 DaeBuilder 类是一个辅助类,旨在方便复杂动力系统的建模,以便日后与最优控制算法配合使用。该类的抽象层级低于物理建模语言,如 Modelica(参见第 7.2 节),但仍高于直接使用 CasADi 符号表达式的层级。特别是,它可以用来连接以功能模拟接口(FMI)格式提供的物理模型,也可以用作构建特定领域建模环境的构件。
DaeBuilder 原则上有三种不同的使用方法:
可以使用该类逐步构建微分代数方程(DAE)结构系统,然后将其用于连接 CasADi 中的仿真或优化。该类还支持以 FMI 格式导出模型,以便在其他工具中使用。
还可以使用该类导入 FMI 格式的现有模型。从 CasADi 3.6 开始,我们支持导入标准 FMU,其中的模型方程只能通过调用 DLL 来获得。FMU 导入支持已针对具有挑战性的 FMU 进行了测试,但截至本文撰写时仍在开发中。
我们可以使用该类以 XML 符号格式导入现有模型。JModelica.org 工具支持这种格式,OpenModelica 也试验性地支持这种格式。由于生成这种格式的模型的工具有限,因此这种格式没有得到积极的开发。
7.1. 构建 DaeBuilder 实例
下面是一个简单的 DAE,它对应于一个受二次空气摩擦项和重力作用的可控火箭,火箭在耗尽燃料后质量会逐渐减小:
其中,三种状态分别对应高度、速度和质量。
是火箭的推力,(a、 b ) 为参数。
要构建该问题的 DAE 公式,请从一个空的 DaeBuilder 实例开始,按如下步骤逐步添加输入和输出表达式。
dae = DaeBuilder('rocket') # Add input expressions a = dae.add_p('a') b = dae.add_p('b') u = dae.add_u('u') h = dae.add_x('h') v = dae.add_x('v') m = dae.add_x('m') # Add output expressions hdot = v vdot = (u-a*v**2)/m-g mdot = -b*u**2 dae.set_ode('h', hdot) dae.set_ode('v', vdot) dae.set_ode('m', mdot) # Specify initial conditions dae.set_start('h', 0) dae.set_start('v', 0) dae.set_start('m', 1) # Add meta information dae.set_unit('h','m') dae.set_unit('v','m/s') dae.set_unit('m','kg')
其他输入和输出表达式也可以类似方式添加。有关函数的完整列表,请参见 DaeBuilder 的 C++ API 文档。
7.2. 从 Modelica 符号导入 OCP*
注:Modelon 不再提供 JModelica.org。但其闭源后继代码 OCT 保留了 CasADi 的互操作性。关于如何使用 OCT 生成 CasADi 表达式的详细信息,请参阅 OCT 的用户指南。以下文字指的是传统的 Modelica 互操作性支持。
7.2.1. 从 XML 文件的传统符号导入
除了直接在 CasADi 中建模(如上所述),还有一种方法是使用高级物理建模语言(如 Modelica)来指定模型。为此,CasADi 提供了与开源 JModelica.org 编译器的互操作性,该编译器是专门为优化控制而编写的。从 JModelica.org 导入模型有两种不同的方式:使用 JModelica.org 的 CasadiInterface 或通过 DaeBuilder 的 parse_fmi 命令。
我们推荐使用前一种方法,因为它正在得到积极维护,有关如何提取 CasADi 表达式的详细信息,请参阅 JModelica.org 的用户指南。
下面,我们将概述使用 parse_fmi 的传统方法。
7.2.2. 从传统方法导入 modelDescription.xml 文件¶。
要了解如何使用 Modelica 导入,请查看 CasADi 示例集中的 thermodynamics_example.py。
假设 Modelica/Optimica 模型 ModelicaClass.ModelicaModel 定义在 file1.mo 和 file2.mop 文件中,Python 编译命令为
from pymodelica import compile_jmu jmu_name=compile_jmu('ModelicaClass.ModelicaModel', \ ['file1.mo','file2.mop'],'auto','ipopt',\ {'generate_xml_equations':True, 'generate_fmi_me_xml':False})
这将生成一个 jmu 文件,实质上是一个压缩文件,其中包含 modelDescription.xml 文件。该 XML 文件包含最优控制问题的符号表示,可在标准 XML 编辑器中查看。
from zipfile import ZipFile sfile = ZipFile(jmu_name','r') mfile = sfile.extract('modelDescription.xml','.')
一旦有了 modelDescription.xml 文件,就可以使用 parse_fmi 命令将其导入:
ocp = DaeBuilder() ocp.parse_fmi('modelDescription.xml')
7.3. 函数工厂
一旦一个 DaeBuilder 被提出,并可能被重新表述为一个令人满意的形式,我们就可以生成与 sec-daebuilder_io 中列出的输入和输出表达式相对应的 CasADi 函数。例如,要为第 7.1 节中的火箭模型的 ODE 右侧创建一个函数,只需提供所创建函数的显示名称、输入表达式列表和输出表达式列表:
f = dae.create('f',\ ['x','u','p'],['ode'])
利用命名规则,我们还可以创建雅各布函数,例如 "ode "输出相对于 "x "的雅各布函数:
f = dae.create('f',\ ['x','u','p'],\ ['jac_ode_x'])
首先使用 add_lc 创建输出表达式的命名线性组合,然后请求其 Hessian,即可提取具有二阶信息的函数:
dae.add_lc('gamma',['ode']) hes = dae.create('hes',\ ['x','u','p','lam_ode'],\ ['hes_gamma_x_x'])
也可以简单地从 DaeBuilder 实例中提取符号表达式,然后手动创建 CasADi 函数。例如,dae.x 包含与 "x "相对应的所有表达式,dae.ode 包含与 "ode "相对应的表达式,等等。
脚注
[1]
FMI 开发小组。Functional Mock-up Interface for Model Exchange and Co-Simulation. https://www.fmi-standard.org/, July 2014. 规范,FMI 2.0。第 3.1 节,第 71-72 页。
八、使用 CasADi 进行优化控制
CasADi 可用于使用多种方法解决最优控制问题(OCP),包括直接(又称离散化-优化)和间接(又称优化-离散化)方法、一次求解(如搭配)方法以及需要嵌入 ODE 或 DAE 初值求解器的射击方法。作为用户,通常需要编写自己的 OCP 求解器,而 CasADi 的目标是通过提供功能强大的高级构建模块,尽可能简化这一过程。由于是自己编写求解器(而不是调用现有的 "黑盒 "求解器),因此对如何求解 OCP 的基本了解是必不可少的。Biegler [1] 或 Betts 最近出版的教科书对数值最优控制做了很好的、自成一体的介绍。
[1] 或 Betts
[2] 或 Moritz Diehl 的数值最优控制讲义。
8.1. 一个简单的测试问题
为了说明某些方法,我们将考虑以下测试问题,即驱动一个范德尔波尔振荡器到原点,同时试图最小化二次费用:
8.1.1. 直接单射法
在直接单次射击法中,控制轨迹使用某种片断平滑近似值(通常是片断常数)进行参数化。
使用控制的显式表达,我们就可以从优化问题中消除整个状态轨迹,最终只对离散化的控制进行 NLP。
在 CasADi 的示例集中,您可以找到分别用于 Python 和 MATLAB/Octave 的 direct_single_shooting.py 和 direct_single_shooting.m 代码。这些代码实现了直接单次射击法,并使用 IPOPT 进行求解,依靠 CasADi 计算导数。为了从连续时间动力学获得离散时间动力学,使用 CasADi 符号实现了一个简单的固定步 Runge-Kutta 4 (RK4) 积分器。类似这样的简单积分器代码在优化控制中通常很有用,但必须注意准确解决初值问题。
该代码还展示了如何用一种更先进的积分器(即 SUNDIALS 软件包中的 CVODES 积分器)取代 RK4 方案,后者实现了一种可变步长、可变阶次的后向微分公式 (BDF) 方案。这种高级积分器适用于较大系统、具有刚性动力学的系统、DAEs 以及检查较简单方案的一致性。
8.1.2. 直接多重射击
在 CasADi 示例集中的 direct_multiple_shooting.py 和 direct_multiple_shooting.m 代码实现了直接多重射击法。这种方法与直接单次射击法非常相似,但将某些射击节点的状态作为 NLP 中的决策变量,并包含相等约束以确保轨迹的连续性。
由于将问题 "提升 "到一个更高的维度通常会提高收敛性,因此直接多重射击法通常优于直接单次射击法。用户还可以使用已知的状态轨迹猜测进行初始化。
缺点是所求解的 NLP 会变得更大,不过这通常会被更稀疏的事实所弥补。
8.1.3. 直接定位
最后,direct_collocation.py 和 direct_collocation.m 代码实现了直接搭配法。在这种情况下,整个状态轨迹的参数化(如片断低阶多项式)将作为 NLP 的决策变量。这样就完全不需要离散时间动力学公式了。
直接配准的 NLP 比直接多重射击的 NLP 更大,但也更稀疏。
脚注
[1]
Lorenz T. Biegler, Nonlinear Programming: Concepts, Algorithms, and Applications to Chemical Processes , SIAM 2010
[2]
John T. Betts, Practical Methods for Optimal Control Using Nonlinear Programming , SIAM 2001
[3]
您可以在 CasADi 的下载区以名为 examples_pack.zip 的压缩包形式获取此合集。
九、Opti 栈
Opti 栈是一组 CasADi 辅助类,提供了数学 NLP 符号之间的紧密对应关系,例如
opti = casadi.Opti() x = opti.variable() y = opti.variable() opti.minimize( (y-x**2)**2 ) opti.subject_to( x**2+y**2==1 ) opti.subject_to( x+y>=1 ) opti.solver('ipopt') sol = opti.solve() print(sol.value(x)) print(sol.value(y)) 0.7861513776531158 0.6180339888825889
Opti 栈的主要特点是
允许约束的自然语法。
隐藏决策变量的索引/簿记。
数字数据类型与宿主语言之间的映射更紧密:不会与 DM 发生冲突。
默认情况下,Opti 将假定程序为非线性程序。要求解二次方程式程序,请将字符串 "conic "作为 Opti 构造函数的参数。
9.1. 问题说明
变量 声明任意数量的决策变量:
x = opti.variable(): 标量
x = opti.variable(5):列向量
x = opti.variable(5,3):矩阵
x = opti.variable(5,5,‘symmetric’):对称矩阵
解算器会遵守变量声明的顺序。请注意,变量实际上是普通的 MX 符号。您可以对它们执行任何 CasADi MX 操作,例如嵌入积分器调用。
参数: 声明任意数量的参数。在求解之前必须将其固定为一个特定数值,并且可以随时覆盖该数值。
p = opti.parameter() opti.set_value(p, 3)
目标: 使用可能涉及所有变量或参数的表达式声明目标。再次调用该命令将丢弃旧目标。
opti.minimize( sin(x*(y-p))
约束 声明任意数量的相等/不相等约束:
opti.subject_to( sqrt(x+y) >= 1): 不等式
opti.subject_to( sqrt(x+y) > 1): 同上
opti.subject_to( 1<= sqrt(x+y) ): 同上
opti.subject_to( 5*x+y==1 ): 相等
您还可以同时抛出多个约束条件:
opti.subject_to([x*y>=1,x==3])
您可以声明双重不等式:
opti.subject_to( opti.bounded(0,x,1) )
当双不等式的边界不含变量时,约束条件将被有效地传递给支持它们的求解器(特别是 IPOPT)。
您可以使用向量进行元素(不)相等:
x = opti.variable(5,1) opti.subject_to( x*p<=3 )
矩阵的元素(不)等式不支持自然语法,因为半定义性约束存在歧义。解决方法是先进行矢量化:
A = opti.variable(5,5) opti.subject_to( vec(A)<=3 )
每条 subject_to 命令都会增加问题说明中的约束条件集。使用 subject_to() 命令可以清空约束条件集,然后重新开始。
求解器 必须始终声明求解器(数值后端)。第二个参数可以是一个可选的 CasADi 插件选项字典。第三个参数可以是求解器选项的可选字典。
p_opts = {"expand":True} s_opts = {"max_iter": 100} opti.solver("ipopt",p_opts, s_opts)
初始猜测:您可以提供决策变量(或决策变量的简单映射)的初始猜测。如果没有提供初始值,则假定数值为零。
opti.set_initial(x, 2) opti.set_initial(10*x[0], 2)
9.2. 解决问题和检索
9.2.1. 求解
设置问题后,您可以调用求解方法,该方法会构造一个 CasADi nlpsol 并调用它。
sol = opti.solve() This is Ipopt version 3.14.11, running with linear solver MUMPS 5.4.1. Number of nonzeros in equality constraint Jacobian...: 2 Number of nonzeros in inequality constraint Jacobian.: 2 Number of nonzeros in Lagrangian Hessian.............: 3 Total number of variables............................: 2 variables with only lower bounds: 0 variables with lower and upper bounds: 0 variables with only upper bounds: 0 Total number of equality constraints.................: 1 Total number of inequality constraints...............: 1 inequality constraints with only lower bounds: 1 inequality constraints with lower and upper bounds: 0 inequality constraints with only upper bounds: 0 iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls 0 0.0000000e+00 1.00e+00 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0 1 1.5180703e+00 2.32e-01 3.95e+08 -1.0 1.11e+00 - 9.90e-01 1.00e+00h 1 2 9.1720733e-01 1.38e-02 8.65e+08 -1.0 1.05e-01 10.0 1.00e+00 1.00e+00f 1 3 8.9187743e-01 4.67e-05 9.29e+06 -1.0 7.19e-03 - 1.00e+00 1.00e+00h 1 4 8.9179090e-01 5.46e-10 2.17e+02 -1.0 2.42e-05 - 1.00e+00 1.00e+00h 1 5 8.5961118e-01 2.43e-04 3.39e+00 -1.0 1.56e-02 - 1.00e+00 1.00e+00f 1 6 5.0768568e-01 3.58e-02 2.18e+00 -1.0 1.89e-01 - 3.86e-01 1.00e+00f 1 7 4.4751644e-01 4.12e-04 6.30e+07 -1.0 1.96e-02 9.5 1.00e+00 1.00e+00h 1 8 4.4707538e-01 4.25e-08 2.27e+04 -1.0 2.53e-04 - 1.00e+00 1.00e+00h 1 9 4.4688522e-01 9.34e-09 2.20e+00 -1.0 9.32e-05 - 1.00e+00 1.00e+00h 1 iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls 10 2.9995484e-02 3.46e-01 1.19e+00 -1.0 5.68e-01 - 5.45e-01 1.00e+00f 1 11 3.9964883e-04 3.56e-02 5.81e-02 -1.0 2.09e-01 - 1.00e+00 1.00e+00h 1 12 3.7581923e-06 5.63e-04 5.70e-03 -2.5 2.69e-02 - 1.00e+00 1.00e+00h 1 13 7.6573980e-10 1.48e-06 3.13e-05 -3.8 1.11e-03 - 1.00e+00 1.00e+00h 1 14 4.8778322e-14 2.53e-10 7.52e-09 -5.7 1.29e-05 - 1.00e+00 1.00e+00h 1 15 8.8029044e-20 1.60e-14 6.47e-13 -8.6 9.88e-08 - 1.00e+00 1.00e+00h 1 Number of Iterations....: 15 (scaled) (unscaled) Objective...............: 8.8029044107981477e-20 8.8029044107981477e-20 Dual infeasibility......: 6.4749359014643689e-13 6.4749359014643689e-13 Constraint violation....: 1.5987211554602254e-14 1.5987211554602254e-14 Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00 Complementarity.........: 2.5060006300485104e-09 2.5060006300485104e-09 Overall NLP error.......: 2.5060006300485104e-09 2.5060006300485104e-09 Number of objective function evaluations = 16 Number of objective gradient evaluations = 16 Number of equality constraint evaluations = 16 Number of inequality constraint evaluations = 16 Number of equality constraint Jacobian evaluations = 16 Number of inequality constraint Jacobian evaluations = 16 Number of Lagrangian Hessian evaluations = 15 Total seconds in IPOPT = 0.009 EXIT: Optimal Solution Found. solver : t_proc (avg) t_wall (avg) n_eval nlp_f | 125.00us ( 7.81us) 30.37us ( 1.90us) 16 nlp_g | 254.00us ( 15.88us) 61.01us ( 3.81us) 16 nlp_grad_f | 161.00us ( 9.47us) 40.36us ( 2.37us) 17 nlp_hess_l | 237.00us ( 15.80us) 57.98us ( 3.87us) 15 nlp_jac_g | 218.00us ( 12.82us) 55.16us ( 3.24us) 17 total | 39.25ms ( 39.25ms) 9.88ms ( 9.88ms) 1
如果求解器收敛失败,调用将失败并提示错误。你仍然可以检查未收敛的解法(参见第 9.3 节)。
您可以多次调用求解。您将始终获得一份不可更改的问题规范及其解决方案。连续调用求解无助于问题的收敛。
要热启动求解器,需要明确地将一个问题的解转移到下一个问题的初始值中。
sol1 = opti.solve() print(sol1.stats()["iter_count"]) # Solving again makes no difference sol1 = opti.solve() print(sol1.stats()["iter_count"]) # Passing initial makes a difference opti.set_initial(sol1.value_variables()) sol2 = opti.solve() print(sol2.stats()["iter_count"]) 15 15 4 sol1 = opti.solve(); sol1.stats.iter_count % Solving again makes no difference sol1 = opti.solve(); sol1.stats.iter_count % Passing initial makes a difference opti.set_initial(sol1.value_variables()); sol2 = opti.solve(); sol2.stats.iter_count ans = 15 ans = 15 ans = 4
为了初始化对偶变量,例如在求解一组相似的优化问题时,可以使用以下语法:
sol = opti.solve() lam_g0 = sol.value(opti.lam_g) opti.set_initial(opti.lam_g, lam_g0) sol = opti.solve(); lam_g0 = sol.value(opti.lam_g); opti.set_initial(opti.lam_g, lam_g0);
9.2.2. 求解时的数值
之后,您可以获取变量(或变量的表达式)在解时的数值:
sol.value(x):决策变量的数值
sol.value§:参数值
sol.value(sin(x+p)):表达式的值
sol.value(jacobian(opti.g,opti.x)):约束条件 jacobian 的值
请注意,在适用情况下,值的返回类型是稀疏的。
9.2.3. 其他点的数值
您可以向 value 传递一系列推翻赋值表达式。在下面的代码中,我们要求得到目标值,并使用解决方案中的所有最优值,但 y 除外,我们将其设置为 2。请注意,此语句不会永久修改 y 的实际最优值。`
obj = (y-x**2)**2 opti.minimize(obj) print(sol.value(obj,[y==2])) 1.909830056703819 obj = (y-x^2)^2; opti.minimize(obj); sol.value(obj,{y==2}) ans = 1.9098
一个相关的使用模式是在初始猜测时对表达式进行评估:
print(sol.value(x**2+y,opti.initial())) 0.0 sol.value(x**2+y,opti.initial()) ans = 0
9.2.4. 对偶变量
为了获得约束条件的对偶变量(拉格朗日乘数),请确保先保存约束条件表达式:
con = sin(x+y)>=1 opti.subject_to(con) sol = opti.solve() print(sol.value(opti.dual(con))) 0.258679501736367 con = sin(x+y)>=1; opti.subject_to(con); sol = opti.solve(); sol.value(opti.dual(con)) ans = 0.2587
9.3. 额外内容
求解器很可能找不到最优解。在这种情况下,您仍然可以通过调试模式访问未求和的解:
opti.debug.value(x)
与此相关的是,你还可以根据提供给求解器的初始猜测,查看表达式的值:
opti.debug.value(x,opti.initial())
如果求解器因问题不可行而停止,您可以使用
opti.debug.show_infeasibilities()
如果求解器在某个位置报告了 NaN/Inf,您可以通过查看其描述找出是哪个约束或变量的问题:
opti.debug.x_describe(index) opti.debug.g_describe(index)
您可以指定一个回调函数;求解器每次迭代时都会调用该函数,参数为当前迭代次数。要绘制求解器的进度图,可以通过调试模式访问未求和的解决方案:
opti.callback(lambda i: plot(opti.debug.value(x))) opti.callback(@(i) plot(opti.debug.value(x)))
可以通过调用不带参数的回调函数,从 Opti 堆栈中清除回调。
您可以通过调用 to_function 函数,从 Opti 对象中构造一个普通的 CasADi 函数。所提供的输入参数列表可能包含参数、决策变量和双重变量。与变量相对应的输入将作为初始猜测。如果希望在求解失败时抛出运行时错误,可以通过 error_on_fail 作为求解器选项。
f = opti.to_function("f",[x,y],[x**2+y]) print(f(0, 0)) 1.23607 f = opti.to_function('f',{x,y},{x^2+y}); disp(f(0, 0)) 1.23607
十、 不同语言的用法差异* 10.1.
10.1. 一般用法
例句:
10.2. 操作列表
以下是最重要的操作列表。不同语言中的不同操作用星号 (*) 标出。该列表并不完整,也未显示每个操作的所有变体。更多信息请参阅 API 文档。