?? 本文中將詳細介紹如何使用Ipopt非線性求解器求解帶約束的最優(yōu)化問題,結(jié)合給出的帶約束的最優(yōu)化問題示例,給出相應(yīng)的完整的C++程序,并給出詳細的解釋和注釋,以及編譯規(guī)則等
?? 一、Ipopt庫的安裝和測試
?? 本部分內(nèi)容在之前的文章《Ubuntu20.04安裝Ipopt的流程介紹及報錯解決方法(親測簡單有效)》中已經(jīng)詳細介紹過了,鏈接如下:
?? https://blog.csdn.net/qq_44339029/article/details/133679131
?? 二、使用Ipopt非線性求解器求解帶約束的最優(yōu)化問題的程序示例
?? 0、明確要求解的帶約束的最優(yōu)化問題
?? 首先,我們來看一個簡單的帶約束的最優(yōu)化問題,其包含兩個不等式約束和1個等式約束,詳情如下:
?? f = ( x 1 ? 10.24 ) 2 + 5.21 x 2 + 9.9 ( x 3 ? x 4 ) 2 f=(x_1-10.24)^2+5.21x_2+9.9(x_3-x_4)^2 f=(x1??10.24)2+5.21x2?+9.9(x3??x4?)2
?? g 1 : 2 ≤ x 3 ? x 4 ≤ 10 g 2 : 2.99 ≤ x 2 ≤ 100 g 3 : x 2 = x 4 \begin{aligned}g_1 & : & 2\leq x_3-x_4\leq10\\ g_2 & : & 2.99\leq x_2\leq100\\ g_3 & : & x_2=x_4\end{aligned} g1?g2?g3??:::?2≤x3??x4?≤102.99≤x2?≤100x2?=x4??
?? 其中 x 1 x_1 x1?、 x 2 x_2 x2?、 x 3 x_3 x3?、 x 4 x_4 x4?的取值范圍均為0~100,易知使得上述目標函數(shù) f f f取值最小的解為:10.24、2.99、4.99、2.99。
?? 下面介紹,如何編程使用Ipopt非線性求解器求解該問題
?? 1. 引入頭文件和命名空間:
#include <iostream>
#include <cassert>
#include <cppad/ipopt/solve.hpp>
using CppAD::AD;
?? 引入必要的C++頭文件,包括iostream
(用于輸入輸出),cassert
(用于C風格的assert)以及cppad/ipopt/solve.hpp
(用于Ipopt求解器和CppAD庫的接口)。然后在一個匿名的命名空間中引入了AD
類型,這是CppAD庫中用于自動微分(Automatic Differentiation)的數(shù)據(jù)類型。
?? 2. 定義FG_eval
類:
namespace {
class FG_eval {
public:
typedef CPPAD_TESTVECTOR(AD<double>) ADvector;
void operator()(ADvector& fg, const ADvector& x) {
// ...
}
};
}
?? 在匿名命名空間中,定義一個FG_eval
類,用于計算目標函數(shù)和約束條件的值。這個類也是調(diào)用使用CppAD和Ipopt庫所需的最重要的接口,這個類中的operator()
函數(shù)是用于計算問題的目標函數(shù)和約束條件的核心部分。它接受兩個向量:fg
用于存儲目標函數(shù)值和約束條件值,x
用于存儲優(yōu)化變量。
?? 3. 定義operator()
函數(shù):
?? 接下來,我們根據(jù)第0步中明確的目標函數(shù)及約束條件來編寫核心的operator函數(shù),示例如下:
void FG_eval::operator()(ADvector& fg, const ADvector& x) {
assert(fg.size() == 4);
assert(x.size() == 4);
AD<double> x1 = x[0];
AD<double> x2 = x[1];
AD<double> x3 = x[2];
AD<double> x4 = x[3];
fg[0] = (x1 - 10.24) * (x1 - 10.24) + 5.21 * x2 + 9.9 * (x3 - x4) * (x3 - x4);
fg[1] = x3 - x4;
fg[2] = x2;
fg[3] = x2 - x4;
// 打印計算結(jié)果
std::cout << "fg[0]:" << fg[0] << std::endl;
std::cout << "fg[1]:" << fg[1] << std::endl;
std::cout << "fg[2]:" << fg[2] << std::endl;
std::cout << "fg[3]:" << fg[3] << std::endl;
}
?? operator()
函數(shù)接受fg
和x
向量,然后根據(jù)問題的定義計算目標函數(shù)和約束條件的值,并將它們存儲在fg
向量中。同時,它也打印出這些值, 其中fg[0]即為目標函數(shù)表達式、fg[1]、fg[2]、fg[3]中依次對應(yīng)了第0步中設(shè)定的三個約束,不等式約束直接寫即可,等式約束的等式左右兩邊同時減去右邊的項,使等式右邊變?yōu)?。
?? 4. 定義主函數(shù)get_started
(該函數(shù)名字可任?。?/font>
?? 定義一個主函數(shù),設(shè)定自變量的初始值,以及自變量和約束的上下限,設(shè)定和提供調(diào)用Ipopt非線性求解器求解所需要的變量,然后調(diào)用求解器進行求解,并進行驗證等操作,程序示例如下:
bool get_started(void)
{ bool ok = true;
size_t i;
typedef CPPAD_TESTVECTOR( double ) Dvector;
size_t nx = 4;
size_t ng = 3;
Dvector xi(nx);
xi[0] = 10.0;
xi[1] = 5.0;
xi[2] = 5.0;
xi[3] = 100.0;
Dvector xl(nx), xu(nx);
for(i = 0; i < nx; i++)
{ xl[i] = 0;
xu[i] = 100;
}
Dvector gl(ng), gu(ng);
gl[0] = 2; gu[0] = 10;
gl[1] = 2.99; gu[1] = 100;
gl[2] = 0; gu[2] = 0;
FG_eval fg_eval;
std::string options;
options += "Integer print_level 0\n";
options += "String sb yes\n";
options += "Integer max_iter 10\n";
options += "Numeric tol 1e-6\n";
options += "String derivative_test second-order\n";
options += "Numeric point_perturbation_radius 0.\n";
CppAD::ipopt::solve_result<Dvector> solution;
CppAD::ipopt::solve<Dvector, FG_eval>(
options, xi, xl, xu, gl, gu, fg_eval, solution
);
ok &= solution.status == CppAD::ipopt::solve_result<Dvector>::success;
double check_x[] = { 10.24, 2.99, 4.99, 2.99 };
double rel_tol = 1e-6; // relative tolerance
double abs_tol = 1e-6; // absolute tolerance
for(i = 0; i < nx; i++)
{
ok &= CppAD::NearEqual(
check_x[i], solution.x[i], rel_tol, abs_tol
);
std::cout << "x[" << i << "] = " << solution.x[i] << std::endl;
}
return ok;
}
?? 以下是程序的詳細解釋:
?? (1). bool get_started(void)
:程序邏輯流程的主要函數(shù),get_started
函數(shù)定義了問題的基本參數(shù),如變量數(shù)量、約束數(shù)量、變量的初始值,以及變量和約束的上下界。然后,它創(chuàng)建了一個FG_eval
對象來計算目標函數(shù)和約束條件,設(shè)置了Ipopt求解器的選項,并最終調(diào)用求解器來解決問題。
?? (2). bool ok = true;
:定義一個布爾變量 ok
,用于表示問題是否成功求解。一開始將其初始化為 true
。
?? (3). 類型別名 Dvector
:通過 typedef CPPAD_TESTVECTOR(double) Dvector;
定義了一個 Dvector
類型,它是CppAD庫中的向量類型,用于存儲雙精度(double)數(shù)值。
?? (4). size_t nx = 4;
:定義一個 size_t
類型的變量 nx
,表示問題中獨立變量(自變量)的數(shù)量,即問題的變量維度。在這個示例中,有4個獨立的自變量。
?? (5). size_t ng = 3;
:定義一個 size_t
類型的變量 ng
,表示問題中的約束數(shù)量,即約束的維度。在這個示例中,有3個約束條件。
?? (6). 創(chuàng)建 Dvector
向量 xi
:用于存儲問題的獨立變量(自變量)。這個向量有4個元素,對應(yīng)于4個自變量。
?? (7). 設(shè)置初始猜測值 xi
:為 xi
向量中的每個元素分別賦初值,為檢驗算法性能,這里設(shè)定了一個較差的初始值。
- `xi[0] = 10.0;`
- `xi[1] = 5.0;`
- `xi[2] = 5.0;`
- `xi[3] = 100.0;`
?? (8). 定義變量和約束條件的上下界:
?? - 創(chuàng)建 Dvector
向量 xl
和 xu
,它們分別表示變量的下界和上界,并根據(jù)第0步中的設(shè)定的自變量的取值范圍0~100進行設(shè)定
?? - 創(chuàng)建 Dvector
向量 gl
和 gu
,它們分別表示約束條件的下界和上界,并根據(jù)第0步中,三個約束的進行設(shè)定,對于前兩個不等式約束,直接設(shè)定即可,第三個等式約束,即
x
2
?
x
4
=
0
x_2-x_4=0
x2??x4?=0,因此,上下限均設(shè)為0即可。
?? (9). 創(chuàng)建 FG_eval
類的對象 fg_eval
:FG_eval
即我們第二步中設(shè)定的類,用于計算目標函數(shù)和約束條件的值。這是問題的目標函數(shù)和約束條件的具體定義。
?? (10). 創(chuàng)建字符串 options
:用于存儲Ipopt求解器的選項,包括設(shè)置輸出級別、最大迭代次數(shù)、收斂容差等,詳情如下所示:
- `options += "Integer print_level 0\n";`:將輸出級別設(shè)置為0,以關(guān)閉求解器的詳細輸出,只打印關(guān)鍵信息。
- `options += "String sb yes\n";`:使用平衡約束優(yōu)化方法。
- `options += "Integer max_iter 10\n";`:設(shè)置最大迭代次數(shù)為10次。
- `options += "Numeric tol 1e-6\n";`:設(shè)置迭代停止的收斂容差為1e-6。
- `options += "String derivative_test second-order\n";`:啟用二階導數(shù)測試,用于檢查目標函數(shù)和約束條件的導數(shù)是否正確。
- `options += "Numeric point_perturbation_radius 0.\n";`:將隨機擾動的半徑設(shè)置為0,表示不使用擾動進行數(shù)值近似求導。
?? (11). 創(chuàng)建 CppAD::ipopt::solve_result<Dvector> solution;
:用于存儲求解結(jié)果的對象。
?? (12). 調(diào)用 CppAD::ipopt::solve
函數(shù):使用Ipopt求解器解決非線性規(guī)劃問題。傳遞了問題選項、獨立變量的初始值、變量的上下界、約束條件的上下界、問題的定義(fg_eval
對象),以及存儲結(jié)果的 solution
對象。
?? (13). 檢查求解器的狀態(tài):如果狀態(tài)為成功(success
),則將 ok
變量保持為真,表示問題已成功求解。
?? 注:下面的第(14)~(16)部分,是為了驗證求解是否正確,為非必要步驟
?? (14) 創(chuàng)建 check_x
數(shù)組:包含問題的精確解。這些值是問題的已知精確解。
?? (15). 設(shè)置相對容差和絕對容差的閾值:這些值用于控制驗證解的精度。
?? (16). 遍歷問題中的每個變量,進行解的驗證:使用 CppAD::NearEqual
函數(shù)來比較問題的解與精確解是否足夠接近。如果它們的差距在相對容差和絕對容差的范圍內(nèi),ok
變量將保持為真,并打印每個變量的解。
?? (17).返回 ok
變量:表示問題是否成功求解。
??
?? 5. C++主函數(shù)main
:
int main(void) {
std::cout << "===== Ipopt with CppAD Testing =====" << std::endl;
bool result = get_started();
std::cout << "Final checking: " << result << std::endl;
}
?? main
函數(shù)是程序的入口點,它簡單地調(diào)用get_started
函數(shù)來執(zhí)行非線性規(guī)劃問題的求解,并打印結(jié)果。
?? 6. ☆☆☆帶詳細注釋的完整程序`☆☆☆
# include <iostream>
// C style asserts
# include <cassert>
// 包含Ipopt求解器頭文件
# include <cppad/ipopt/solve.hpp>
// 在一個匿名的命名空間中,引入了一個AD類型,它是CppAD庫中用于自動微分(Automatic Differentiation)的數(shù)據(jù)類型。AD類型可以用來表示變量和函數(shù),使其具備微分能力。
namespace {
using CppAD::AD;
class FG_eval {
public:
typedef CPPAD_TESTVECTOR( AD<double> ) ADvector;
// fg: function that evaluates the objective and constraints using the syntax
// 定義一個函數(shù)運算符,用于計算目標函數(shù)和約束條件的值
void operator()(ADvector& fg, const ADvector& x)
{
//使用assert來設(shè)定fg和x的大小,以確保它們與問題的維度匹配
//fg 向量用于存儲目標函數(shù)值和約束條件值,x向量用于存儲優(yōu)化變量
assert( fg.size() == 4 );
assert( x.size() == 4 );
// 將 x 中的優(yōu)化變量分配給 AD 類型的變量 x1 到 x4。這是在使用C++ Algorithmic Differentiation(CppAD)時定義問題中的獨立變量的方式。
AD<double> x1 = x[0];
AD<double> x2 = x[1];
AD<double> x3 = x[2];
AD<double> x4 = x[3];
// 計算目標函數(shù)的值,將其存儲在 fg[0] 中。這里使用了 x1 到 x4 這些 AD 類型的變量,這意味著這個表達式將被自動微分,以便后續(xù)的梯度計算。
fg[0] = (x1-10.24) * (x1-10.24) + 5.21*x2 + 9.9*(x3-x4)*(x3-x4);
// 分別計算三個約束條件的值,并將它們存儲在 fg[1] 和 fg[2]、 fg[3]中。
fg[1] = x3-x4;
fg[2] = x2;
fg[3] = x2-x4;
//
std::cout << "fg[0]:" << fg[0]<< std::endl;
std::cout << "fg[1]:" << fg[1]<< std::endl;
std::cout << "fg[2]:" << fg[2]<< std::endl;
std::cout << "fg[3]:" << fg[3]<< std::endl;
return;
}
};
}
// 該函數(shù)用于設(shè)置和解決非線性規(guī)劃問題
// 它首先定義了問題的一些基本參數(shù),如變量數(shù)量、約束數(shù)量、變量的初始值、變量和約束的上下界等
// 然后創(chuàng)建一個FG_eval對象用于計算目標函數(shù)和約束條件
// 最后,使用CppAD::ipopt::solve函數(shù)來解決問題,并將結(jié)果存儲在solution中
bool get_started(void)
{ bool ok = true;
size_t i;
// 創(chuàng)建了一個類型別名 Dvector,它是CppAD庫中的一個向量類型,用于存儲雙精度(double)數(shù)值。這個向量類型是CppAD庫的一部分,通常用于存儲問題的變量、約束和其他向量。
typedef CPPAD_TESTVECTOR( double ) Dvector;
// 聲明了一個 size_t 類型的變量 nx,它表示問題中獨立變量(自變量)的數(shù)量,也就是問題的變量維度。在這個示例中,有4個獨立變量,因此 nx 的值為4。
size_t nx = 4;
// 聲明了一個 size_t 類型的變量 ng,它表示問題中的約束數(shù)量,也就是約束的維度。在這個示例中,有3個約束條件,因此 ng 的值為3。
size_t ng = 3;
// 創(chuàng)建了一個名為 xi 的 Dvector 類型的向量,用于存儲問題的獨立變量(自變量)。這個向量有4個元素,對應(yīng)于4個自變量。
Dvector xi(nx);
// 分別為這4個獨立變量設(shè)置了初始值。這些值將用作問題的初始猜測,作為非線性規(guī)劃求解器的起點。
xi[0] = 10.0;
xi[1] = 5.0;
xi[2] = 5.0;
xi[3] = 100.0;
//設(shè)置問題的變量(自變量)和約束條件的上下界(限制條件)。
Dvector xl(nx), xu(nx);
for(i = 0; i < nx; i++)
{ xl[i] = 0;
xu[i] = 100;
}
Dvector gl(ng), gu(ng);
gl[0] = 2; gu[0] = 10;
gl[1] = 2.99; gu[1] = 100;
gl[2] = 0; gu[2] = 0;
// 創(chuàng)建了 FG_eval 類的對象 fg_eval,用于計算目標函數(shù)和約束條件的值。這是問題的目標函數(shù)和約束條件的具體定義。
FG_eval fg_eval;
// 創(chuàng)建了一個字符串 options,用于存儲Ipopt求解器的選項。
std::string options;
// 設(shè)置了求解器選項,將 print_level 參數(shù)設(shè)置為0,以關(guān)閉求解器的輸出,即不會在控制臺打印詳細信息,只打印關(guān)鍵信息。
options += "Integer print_level 0\n";
// 將 sb 參數(shù)設(shè)置為 "yes",這表示使用平衡約束優(yōu)化方法。
options += "String sb yes\n";
// 設(shè)置最大迭代次數(shù)為10次。
options += "Integer max_iter 10\n";
// approximate accuracy in first order necessary conditions;
// see Mathematical Programming, Volume 106, Number 1,
// Pages 25-57, Equation (6)
// 設(shè)置迭代停止的收斂容差為1e-6。
options += "Numeric tol 1e-6\n";
// 啟用了二階導數(shù)測試,用于檢查目標函數(shù)和約束條件的導數(shù)是否正確。
options += "String derivative_test second-order\n";
// maximum amount of random pertubation; e.g.,
// when evaluation finite diff
// 將隨機擾動的半徑設(shè)置為0,表示不使用擾動進行數(shù)值近似求導。
options += "Numeric point_perturbation_radius 0.\n";
// 創(chuàng)建了一個用于存儲求解結(jié)果的對象 solution,
CppAD::ipopt::solve_result<Dvector> solution;
// 調(diào)用了 CppAD::ipopt::solve 函數(shù),用于解決非線性規(guī)劃問題。它傳遞了問題選項、獨立變量的初始值、變量的上下界、約束條件的上下界、問題的定義(fg_eval 對象),以及存儲結(jié)果的 solution 對象。
CppAD::ipopt::solve<Dvector, FG_eval>(
options, xi, xl, xu, gl, gu, fg_eval, solution
);
//檢查求解器的狀態(tài),如果狀態(tài)為成功(success),則 ok 變量將保持為真。這表示問題已成功求解。
ok &= solution.status == CppAD::ipopt::solve_result<Dvector>::success;
// 創(chuàng)建一個名為 check_x 的數(shù)組,其中包含了問題的精確解。這個數(shù)組中的值是問題的已知精確解。
double check_x[] = { 10.24, 2.99, 4.99, 2.99 };
// 設(shè)置了相對容差和絕對容差的閾值。這些值用于控制驗證解的精度。
double rel_tol = 1e-6; // relative tolerance
double abs_tol = 1e-6; // absolute tolerance
// 遍歷問題中的每個變量,進行解的驗證。
for(i = 0; i < nx; i++)
{
//使用 CppAD::NearEqual 函數(shù)來比較問題的解 solution.x[i] 與精確解 check_x[i] 是否足夠接近。如果它們的差距在相對容差和絕對容差的范圍內(nèi),ok 變量將保持為真。
ok &= CppAD::NearEqual(
check_x[i], solution.x[i], rel_tol, abs_tol
);
// 使用 std::cout 打印每個變量的解,以便在控制臺上查看結(jié)果。
std::cout << "x[" << i << "] = " << solution.x[i] << std::endl;
}
return ok;
}
// main program that runs all the tests
int main(void)
{
std::cout << "===== Ipopt with CppAD Testing =====" << std::endl;
bool result = get_started();
std::cout << "Final checking: " << result << std::endl;
}
// END C++
?? 三、編譯驗證
?? 將上面第二部分,第6步中給出的完整的程序,保存為CppAD_Ipopt.cpp,然后在同一目錄下,創(chuàng)建一個名為CMakeLists.txt的文件,接下來,我們需要在CMakeLists.txt文件中,編寫編譯規(guī)則,如下所示:
# 設(shè)置CMake的最低版本要求
cmake_minimum_required(VERSION 3.5)
# 項目名稱
project(CppadIpoptDemo)
# 尋找Ipopt包(確保你已經(jīng)安裝了Ipopt和CppAD)
# find_package(Ipopt REQUIRED)
# 設(shè)置可執(zhí)行文件的名稱和源文件
add_executable(cppad_ipopt_demo CppAD_Ipopt.cpp)
# 包含Ipopt的頭文件
# target_include_directories(cppad_ipopt_demo PRIVATE ${IPOPT_INCLUDE_DIRS})
# 鏈接Ipopt庫
# target_link_libraries(cppad_ipopt_demo ${IPOPT_LIBRARIES})
TARGET_LINK_LIBRARIES(cppad_ipopt_demo ipopt)
?? 保存,并關(guān)掉CMakeLists.txt文件,接下來就利用該文件對CppAD_Ipopt.cpp進行編譯,在該目錄下空白處,右鍵打開終端,依次輸入以下四條語句
mkdir build
cd build
cmake ..
make

?? 以上編譯結(jié)束后,在build文件夾下,生成了可執(zhí)行文件cppad_ipopt_demo,如下圖所示

?? 在當前目錄下,右鍵打開終端,輸入以下指令運行該文件文章來源:http://www.zghlxwxcb.cn/news/detail-721600.html
./cppad_ipopt_demo
?? 運行結(jié)果如下,可以發(fā)現(xiàn)即使在給定的初始解很差的情況下,Ipopt非線性求解器依然能夠求解出第二部分第0部步中設(shè)定的帶約束優(yōu)化問題的最優(yōu)解。文章來源地址http://www.zghlxwxcb.cn/news/detail-721600.html

到了這里,關(guān)于詳細介紹如何使用Ipopt非線性求解器求解帶約束的最優(yōu)化問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!