建模接口
该章节介绍以下内容:
- OPTIMake支持的问题类型
- 如何通过OPTIMake提供的符号化建模接口定义问题
- 如何通过外部C/C++函数定义问题
问题形式
OPTIMake求解以下的优化问题:
该优化问题为一个具有 N 个stage的优化问题, 该优化问题可能为一个非线性非凸的优化问题. 其中:
- v_1,\cdots,v_N \in \mathbb{R}^{n_{v}} 为优化变量
- p\in \mathbb{R}^{n_{p}} 为参数
- l:\mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{p}} \rightarrow \mathbb{R} 为stage objective
- l_e:\mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{p}} \rightarrow \mathbb{R} 为终点处额外的objective
- f_s:\mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{p}} \rightarrow \mathbb{R}^{n_{f_s}} 为起点等式约束
- f:\mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{p}} \rightarrow \mathbb{R}^{n_{f}} 为等式约束
- f_e:\mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{p}} \rightarrow \mathbb{R}^{n_{f_e}} 为终点等式约束
- g:\mathbb{R}^{n_{v}} \times \mathbb{R}^{n_{p}} \rightarrow \mathbb{R}^{n_{g}} 为不等式约束
问题定义
下面为定义问题的例子, 该例子指定了问题的名称为vehicle且具有10个stage:
prob = multi_stage_problem(name='vehicle', N=10)
其中, 函数入参的定义如下:
-
name: str 问题的名称, 该名称会用在生成代码的文件名, 函数名等.
-
N: int 问题的stage数目, 必须大于等于1.
在完成问题名称和stage数的定义后, 下面说明如何定义优化问题中的参数, 优化变量及约束.
Note
因为上述的优化问题为一个多stage的优化问题, 等式约束与不等式约束的表达式在所有stage上都一致, 所以不需要在每个stage上都单独定义, 只需要定义一次该表达式即可 (参数与优化变量同理) .
parameter定义
parameter为在优化过程中不变的量, 由用户在调用求解前给定, 比如车身长度length, 质量mass.
OPTIMake支持两种类型的parameter:stage-independent parameter与stage-dependent parameter. stage-dependent parameter在不同stage可以有不同的值, 而stage-independent parameter在所有stage的值都一致 (可以节约存储与简化设置) .
下面为定义优化变量的例子:
length = prob.parameter(name='length', stage_dependent=False)
mass = prob.parameter('mass')
xLowerBound = prob.parameter('xLowerBound', stage_dependent=True)
其中, 函数入参的定义如下:
-
name: str parameter的名称.
-
stage_dependent: bool, optional parameter是否为stage-dependent. 默认值为True, 表示parameter为stage-dependent.
亦或者通过list的方式定义:
length, mass, xLowerBound = prob.parameters(
['length', 'mass', 'xLowerBound'],
stage_dependent=False)
优化变量定义
优化变量为在优化过程中变化的量, 比如车辆的转角控制量delta, 位置状态x, y. 在定义优化变量时, 可以同时定义优化变量的硬边界、软边界以及违反软边界时的惩罚 (见建模接口 > 约束软化了解OPTIMake中的软约束定义) .
下面为定义优化变量的例子:
delta = prob.variable(name='delta', hard_lowerbound=-0.5, hard_upperbound=0.5)
# xLowerBound与xUpperBound为已定义的parameter
x = prob.variable(
name = 'x',
hard_lowerbound=xLowerBound,
hard_upperbound=xUpperBound,
soft_lowerbound=-0.2,
soft_upperbound=0.2,
weight_soft_lowerbound=100.0,
weight_soft_upperbound=100.0,
penalty_type_soft_lowerbound='quadratic',
penalty_type_soft_upperbound='l1')
y = prob.variable('y')
theta = prob.variable('theta')
其中, 函数入参的定义如下:
- name: 优化变量的名称, string类型
- hard_lowerbound (optional): 优化变量的硬下界, 可以为常数或关于参数p的表达式, 默认值为-inf, 表示无下界
- hard_upperbound (optional): 优化变量的硬上界, 可以为常数或关于参数p的表达式, 默认值为inf, 表示无上界
- soft_lowerbound (optional): 优化变量的软下界, 可以为常数或关于参数p的表达式, 默认值为-inf, 表示无下界
- soft_upperbound (optional): 优化变量的软上界, 可以为常数或关于参数p的表达式, 默认值为inf, 表示无上界
- weight_soft_lowerbound (optional): 优化变量的软下界的惩罚权重, 必须为非负, 可以为常数或关于参数p的表达式, 默认值为0.0, 表示无惩罚
- weight_soft_upperbound (optional): 优化变量的软上界的惩罚权重, 必须为非负, 可以为常数或关于参数p的表达式, 默认值为0.0, 表示无惩罚
- penalty_type_soft_lowerbound (optional): 优化变量的软下界的惩罚类型, 可选值为'quadratic'或'l1', 默认值为'quadratic'
- penalty_type_soft_upperbound (optional): 优化变量的软上界的惩罚类型, 可选值为'quadratic'或'l1', 默认值为'quadratic'
objective定义
objective为需要最小化的目标函数. OPTIMake支持以下类型的objective定义:
- general_objective:通用类型, 通过表达式直接定义
- least_square_objective:least square类型
- external_general_objective:通用类型, 通过外部C/C++函数定义
在完成objective定义后, 通过以下接口最小化该objective:
prob.objective(obj)
同理, 通过以下接口加入对最后一个点的objective:
prob.end_objective(obj)
Note
- 只能调用一次prob.objective或prob.end_objective设置objective
general_objective
下面为通过表达式直接定义objective的例子:
# wyref, wphi为已定义的parameter
obj = general_objective(wxref * (x * sin(phi) - xref)**2 + wyref * (y - yref)**2 + wphi * phi**2 + 0.01 * delta**2 + 0.1 * v**2)
prob.objective(obj)
least_square_objective
当objective为以下形式时, 可通过least square接口定义, 其中r_j(v, p)为残差项, w_j(p)为其对应的权重项. \begin{equation*} l(v_i, p) = \frac{1}{2}\sum_{j}w_j(p) r^2_j(v_i, p) \end{equation*}
下面为通过least square接口定义objective的例子:
r = [x * sin(phi) - xref, y - yref, phi, delta, v]
w = [wxref, wyref, wphi, 0.01, 0.1]
obj = least_square_objective(residuals=r, weights=w)
prob.objective(obj)
其中, least_square_objective接口的函数入参的定义如下:
- residuals: 残差项, 对应r(v, p), 类型为list
- weights (optional): 残差权重, 对应w(p), 可以为常数或关于参数p的表达式的list, 其默认值为全为1.0的list
Note
当objective通过least square接口定义时, 其Hessian通过Gauss-Newton方式近似计算.
external_general_objective
通过外部C/C++函数定义的general_objective. 在建模时, 可以指定其Hessian的稀疏性加速运算.
下面为通过external_general_objective接口定义objective的例子:
obj_spy_hess = \
[[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]
obj = external_general_objective(sparsity_hessian=obj_spy_hess)
prob.objective(obj)
其中, external_general_objective接口的入参如下:
- sparsity_hessian (optional): objective的Hessian的sparsity pattern, 维度为n_{v} \times n_{v}的对称矩阵, 0代表稀疏, 1代表稠密;该参数为optional参数, 其默认值为全1.0
等式约束定义
等式约束描述了相邻stage之间优化变量的等式关系, OPTIMake支持以下类型的等式约束:
- differential_equation:微分方程
- discrete_equation:离散方程
- external_discrete_equation:离散方程, 通过外部C/C++函数定义
在完成约束定义后, 通过以下接口添加该约束:
prob.equality(eq, weight_soft, penalty_type)
其中, equality接口的函数入参的定义如下 (约束软化详见 建模接口 > 约束软化) :
- eq: 等式约束, 类型为differential_equation, discrete_equation, external_discrete_equation
- weight_soft: 该约束软化时的惩罚权重list, 必须为非负, 可以为常数或关于参数p的表达式, 默认值为全inf, 表示硬约束
- penalty_type: 该约束软化的惩罚类型list, 可选值为'none', 'quadratic', 'l1', 默认值为全'none', 表示硬约束
Note
- 当约束类型为external_discrete_equation或differential_equation (discretization_method为'irk2'或'irk4') 时, 只能调用一次prob.equality设置约束
- 当约束为其他类型时, 可调用多次prob.equality添加多个约束
differential_equation
differential_equation定义了\dot x = h(v,p)的形式的等式约束, 需要指定离散步长与离散方法将其离散化.
下面为通过differential_equation接口定义等式约束的例子:
# ts, length为已定义的parameter
eq = differential_equation(
state=[x, y, phi],
state_dot=[v * cos(phi), v * sin(phi), v * tan(delta) / length],
stepsize=ts,
discretization_method='forward_euler')
prob.equality(eq)
其中, differential_equation接口的函数入参的定义如下:
- state: 状态量, 对应x, 类型为优化变量的list
- state_dot: 状态微分量, 对应h(v,p), 类型为list
- stepsize: 离散步长, 可以为常数或关于参数p的表达式
-
discretization_method: 离散方法, 可选值为:
- 'forward_euler':显式欧拉
- 'erk4':显式四阶龙格库塔
- 'backward_euler':隐式欧拉
- 'trapezoid':梯形积分 (trapezoidal rule)
- 'irk2':隐式二阶龙格库塔 (midpoint rule)
- 'irk4':隐式四阶龙格库塔
discrete_equation
discrete equation为 h_{next}(v_{i+1}, p_{i+1}) = h_{this}(v_i, p), 下面为通过discrete equation接口定义等式约束的例子 (使用forward_euler方法的离散系统) :
# ts, length为已定义的parameter
eq = discrete_equation(
expr_this_stage=[
x + ts * v * cos(phi),
y + ts * v * sin(phi),
phi + ts * v * tan(delta) / length],
expr_next_stage=[x, y, phi])
prob.equality(eq)
其中, discrete equation接口的入参如下:
- expr_this_stage: 当前stage的函数表达式, 对应 h_{this}(v_i, p), 类型为list
- expr_next_stage: 下一个stage的函数表达式, 对应 h_{next}(v_{i+1}, p_{i+1}), 类型为list
Note
若只需要描述当前stage内优化变量的等式关系, 可将expr_this_stage或expr_next_stage设置为全零.
external_discrete_equation
通过外部C/C++函数定义的discrete_equation, 其形式为h_{next}(v_{i+1}, p_{i+1}) = h_{this}(v_i, p). 在建模时, 可以指定其Jacobian & Hessian的稀疏性加速运算.
下面为external_discrete_equation的例子:
eq_spy_jac_this = \
[[1, 0, 1, 1],
[0, 1, 1, 1],
[0, 0, 1, 1]]
eq_spy_jac_next = \
[[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0]]
eq_spy_hess_this = \
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 1, 1],
[0, 0, 1, 1]]
eq_spy_hess_next = \
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
eq = external_discrete_equation(
dim = 3,
sparsity_jacobian_this_stage=eq_spy_jac_this,
sparsity_jacobian_next_stage=eq_spy_jac_next,
sparsity_hessian_this_stage=eq_spy_hess_this,
sparsity_hessian_next_stage=eq_spy_hess_next)
prob.equality(eq)
其中, external_discrete_equation接口的入参如下:
- dim:该约束的维度
- sparsity_jacobian_this_stage (optional): h_{this}(v_i, p)的Jacoboian的sparsity pattern, 维度为dim\times n_{v}的矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
- sparsity_jacobian_next_stage (optional): h_{next}(v_{i+1}, p_{i+1})的Jacoboian的sparsity pattern, 维度为dim\times n_{v}的矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
- sparsity_hessian_this_stage (optional): y^T h_{this}(v_i, p)的Hessian的sparsity pattern (y为维度为dim\times 1的拉格朗日乘子) , 维度为n_{v} \times n_{v}的对称矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
- sparsity_hessian_next_stage (optional): y^T h_{next}(v_{i+1}, p_{i+1})的Hessian的sparsity pattern, 维度为n_{v} \times n_{v}的对称矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
不等式约束定义
OPTIMake支持以下类型的不等式约束:
- general_inequality:通用类型, 通过表达式直接定义
- external_general_inequality:通用类型, 通过外部C/C++函数定义
在完成约束定义后, 通过以下接口添加该约束:
prob.inequality(ineq, weight_soft, penalty_type)
其中, inequality接口的函数入参的定义如下 (约束软化下详见 建模接口 > 约束软化) :
- ineq: 等式约束, 类型为general_inequality, external_general_inequality
- weight_soft: 该约束软化时的惩罚权重list, 必须为非负, 可以为常数或关于参数p的表达式, 默认值为全inf, 表示硬约束
- penalty_type: 该约束软化的惩罚类型list, 可选值为'none', 'quadratic', 'l1', 默认值为全'none', 表示硬约束
Note
- 当约束类型为external_general_inequality时, 只能调用一次prob.inequality设置约束
- 当约束为其他类型时, 可调用多次prob.inequality添加多个约束
general_inequality
下面为通过general_inequality定义不等式约束的例子:
# -1.0 <= x + y <= 1.0
ineq = general_inequality(
expr = [x + y, x + y],
sign = ['>=', '<='],
bound = [-1.0, 1.0])
prob.inequality(ineq)
其中, general_inequality接口的入参如下:
- expr: 不等式约束的表达式的list
- sign: 不等式约束的符号, 可选值为">=", "<="的list
- bound: 不等式约束的边界, 可以为常数或关于参数p的表达式的list
当所有的不等式约束都具备一样的sign和bound时, 可简化sign和bound的定义. 如以上的不等式约束也可通过以下实现:
# -1.0 <= x + y <= 1.0
ineq = general_inequality(
expr = [x + y, -(x + y)],
sign = '<=',
bound = 1.0)
prob.inequality(ineq)
external_general_inequality
通过外部C/C++函数定义的general_inequality, 其表达式为g(v,p) \geq 0. 在建模时, 可以指定其Jacobian & Hessian的稀疏性加速运算.
下面为external_general_inequality的例子:
ineq_spy_jac = \
[[1, 1, 0, 0],
[1, 1, 0, 0]]
ineq_spy_hess = \
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
ineq = external_general_inequality(
dim = 2,
sparsity_jacobian=ineq_spy_jac,
sparsity_hessian=ineq_spy_hess)
prob.inequality(ineq)
其中, external_general_inequality接口的入参如下:
- dim:该约束的维度
- sparsity_jacobian (optional): g(v, p)的Jacoboian的sparsity pattern, 维度为dim\times n_{v}的矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
- sparsity_hessian (optional): z^T g(v, p)的Hessian的sparsity pattern (z为维度为dim\times 1的拉格朗日乘子) , 维度为n_{v} \times n_{v}的对称矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
起点与终点约束定义
起点约束描述了第一个优化变量 v_1 的等式约束, 比如在车辆轨迹规划问题中的车辆初始状态约束.
终点约束描述了最后一个优化变量 v_N 的等式约束, 比如在火箭着陆轨迹规划问题中的末端零速度约束.
OPTIMake支持以下类型的等式约束:
- general_equality:通用类型, 通过表达式直接定义
- external_general_equality:通用类型, 通过外部C/C++函数定义
在完成约束定义后, 通过以下接口添加起点与终点约束:
prob.start_equality(eq, weight_soft, penalty_type)
prob.end_equality(eq, weight_soft, penalty_type)
其中, start_equality/end_equality接口的函数入参的定义如下 (约束软化下详见 建模接口 > 约束软化) :
- eq: 等式约束, 类型为general_equality, external_general_equality
- weight_soft: 该约束软化时的惩罚权重list, 必须为非负, 可以为常数或关于参数p的表达式, 默认值为全inf, 表示硬约束
- penalty_type: 该约束软化的惩罚类型list, 可选值为'none', 'quadratic', 'l1', 默认值为全'none', 表示硬约束
Note
- 当约束类型为external_general_equality时, 只能调用一次prob.start_equality/end_equality设置约束
- 当约束为其他类型时, 可调用多次prob.start_equality/end_equality添加多个约束
general_equality
下面为通过general_equality定义起点与终点约束的例子:
# x0, y0, phi0为已定义的parameter
start_eq = general_equality(expr = [x - x0, y - y0, phi - phi0])
prob.start_equality(start_eq)
# xN, yN, phiN为已定义的parameter
end_eq = general_equality(expr = [x - xN, y - yN, phi - phiN])
prob.end_equality(end_eq)
其中, 函数入参的定义如下:
- expr: 约束的表达式的list
external_general_equality
下面为通过external_general_equality定义起点与终点约束的例子:
eq_spy_jac = \
[[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0]]
eq_spy_hess = \
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
start_eq = external_general_equality(
dim = 3,
sparsity_jacobian=seq_spy_jac,
sparsity_hessian=seq_spy_hess)
end_eq = external_general_equality(
dim = 3,
sparsity_jacobian=seq_spy_jac,
sparsity_hessian=seq_spy_hess)
其中, external_general_equality接口的入参如下:
- dim:该约束的维度
- sparsity_jacobian (optional): f(v, p)的Jacoboian的sparsity pattern, 维度为dim\times n_{v}的矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
- sparsity_hessian (optional): y^T f(v, p)的Hessian的sparsity pattern (z为维度为dim\times 1的拉格朗日乘子) , 维度为n_{v} \times n_{v}的对称矩阵, 0代表稀疏, 1代表稠密;其默认值为全1.0
外部C/C++函数支持
上述章节主要介绍了如何通过符号表达式显式地定义问题, 当优化问题不能通过符号定义时 (例如约束为神经网络或者复杂的机器人动力学模型时) , 可通过外部C/C++函数直接定义.
OPTIMake通过external接口支持以下函数的C/C++定义:
- objective
- inequality
- equality
- start_equality
- end_equality
在定义外部函数时, 需要提供该函数以及其Jacobian实现. 在某些场景中 (根据option中hessian_approximation的配置), 需要提供Hessian实现:
hessian_approximation配置 | 外部objective是否提供Hessian | 外部约束是否提供Hessian |
---|---|---|
'gauss-newton' |
是 | 否 |
'exact' |
是 | 是 |
'bfgs' |
否 | 否 |
在使用external接口定义objective或约束为外部函数后, 代码生成功能会自动生成该external函数的C/C++模板.
以下为objective的外部C/C++函数的模板:
void vehicle_l(
const Vehicle_SolverInfo *_solverinfo,
const Vehicle_Problem *_prob,
const double _var[4],
const double _scaling_factor[1],
double _l[1],
double _jac_l[4],
double _hess[16])
{
if (_l != NULL) {
/* define objective here */
}
if (_jac_l != NULL) {
/* define Jacobian here */
}
if (_hess != NULL) {
/* define Hessian increment here */
}
}
其中, C/C++函数入参的定义如下:
- _solverinfo: solver info的结构体, 记录了该函数被调用的thread_id, stage以及迭代
- _prob: 存储了该问题的参数
- _var: 该stage的优化变量
- _scaling_factor: objective的scaling factor, 需要在_l, _jac_l, _hess_l上乘以该factor
- _l: objective的函数值
- _jac_l: objective的Jacobian函数值
- _hess: 将objective的Hessian通过增量方式加入 (_hess += _hess_l)
Note
-
在计算C/C++函数中的_hess时, objective或约束函数的Hessian通过增量而不是赋值计算, 即: _hess += _hess_l
-
优化问题中函数在求解过程中的调用顺序为: l, le, fs, fe, f, g
-
矩阵通过列优先存储
约束软化
OPTIMake中内置了对约束软化的支持, 通过该接口能够减少内存占用与计算时间.
约束软化可分为对等式约束的软化与对不等式约束的软化, OPTIMake在以下接口中提供了约束软化支持:
- 优化变量上下限 (不等式约束)
- inequality (不等式约束)
- equality (等式约束)
- start_equality (等式约束)
- end_equality (等式约束)
软化的惩罚类型支持:
- quadratic
- l1
上面的问题定义章节中介绍了如何使用这些接口软化约束, 我们以以下的通用问题形式介绍OPTIMake中约束软化的问题设定. 需要注意的是, 下面的p代表slack变量而非参数.
等式约束软化
当软化的惩罚类型为'quadratic'时, 软化问题的定义如下 (w_i 为惩罚权重, p为约束软化的slack变量):
当软化的惩罚类型为'l1'时, 软化问题的定义如下 (w_i 为惩罚权重, p,n为约束软化的slack变量):
不等式约束软化
当软化的惩罚类型为'quadratic'时, 软化问题的定义如下 (w_i 为惩罚权重, p为约束软化的slack变量):
当软化的惩罚类型为'l1'时, 软化问题的定义如下 (w_i 为惩罚权重, p为约束软化的slack变量):