IPOPT (Interior Point OPTimizer- đọc là I-P-Opt) là một phần mềm mã nguồn mở sử dụng phương pháp tối ưu điểm trong IP, được phát triển bởi nhĩm nghiên cứu dưới sự chỉ đạo của Giáo sư Lorenz Biegler tại Đại học Carnegie Mellon (CMU), Hoa Kỳ. Nĩ được dùng để giải bài tốn tối ưu tổng quát cĩ dạng sau: min ( ) (2.7a) sao cho ( ) (2.7b) , (2.7c) n x R L U L U f x g g x g x x x trong đĩ: n
xR là các biến tối ưu của bài tốn, mà cĩ thể cĩ các giới hạn dưới ( { })
L n
Lựa chọn phương pháp giải và các cơng cụ 46 tiêu, và g R: n Rmlà các ràng buộc phi tuyến tổng quát. Các hàm f(x) và g(x) cĩ thể là hàm tuyến tính hay phi tuyến, nhưng nên cĩ đạo hàm đến cấp 2. Các ràng buộc g(x) cĩ thể cĩ các giới hạn dưới gL(R { })m và các giới hạn trên gU(R { })m. Nếu gi(x) là các ràng buộc phương trình ( )g xi gi thì ta đặt L U
i i i
g g g .
IPOPT được trình bày dưới dạng mã nguồn mở viết bằng C/C++ nằm trong sự án COIN-OR tại địa chỉ Internet https://projects.coin-or.org/Ipopt. IPOPT cĩ thể được biên dịch trên các mơi trường hệ điều hành LINUX, UNIX hoặc WINDOWS. Trong luận văn này, tác giả sử dụng IPOPT chạy trên WINDOWS với trình dịch VISUAL STUDIO của Microsoft.
Về mặt cấu trúc, IPOPT được chia thành các chương trình con nằm trong một Solution lớn mang tên IPOPT, trong đĩ là các Project nhỏ hơn. Người sử dụng giao tiếp với IPOPT thơng qua 09 chương trình con được mơ tả trong phần tiếp theo.
2.3.2.1 Method get_nlp_info:
cĩ dạng thức như sau:
virtual bool get_nlp_info(Index& n, Index& m, Index& nnz_jac_g,
Index& nnz_h_lag, IndexStyleEnum& index_style) Hàm cung cấp cho IPOPT những thơng tin về kích thước bài tốn:
n: (ra), số lượng biến x của bài tốn tối ưu. m: (ra), số lượng hàm ràng buộc g(x).
nnz_jac_g: (ra), số phần tử khác 0 của Jacobian. nnz_h_lag: (ra), số phần tử khác 0 của Hessian.
index_style: (ra), kiểu đánh chỉ số cho hàng và cột của ma trận (C STYLE: bắt đầu từ 0, FORTRAN STYLE: bắt đầu từ 1).
//*********************************************
bool HS071_NLP::get_nlp_info( Index& n, Index& m, Index& nnz_jac_g,
Index& nnz_h_lag, IndexStyleEnum& index_style) { // số lượng các biến, x[0],…, x[n-1] n = ...; // số lượng các ràng buộc m = ...; nnz_jac_g = ...;
// chỉ dùng phần thấp bên dưới trái (ma trận đối xứng) nnz_h_lag = ...;
Lựa chọn phương pháp giải và các cơng cụ 47 // sử dụng kiểu phần tử ma trận trong C (0-based)
index_style = TNLP::C_STYLE; return true; } // ***************************************************** 2.3.2.2 Method get_bounds_info: cĩ dạng thức như sau:
virtual bool get_bounds_info( Index n, Number* x_l, Number* x_u, Index m, Number* g_l, Number* g_u)
Hàm cung cấp cho IPOPT những thơng tin về giá trị các giới hạn của các biến và các ràng buộc của bài tốn:
n: (vào), số lượng biến x của bài tốn tối ưu. x_l: (ra) giá trị giới hạn dưới xL của biến x. x_u: (ra) giá trị giới hạn trên xU của biến x. m: (vào), số lượng hàm ràng buộc g(x). g_l: (ra) giá trị giới hạn dưới gL của g(x). g_u: (ra) giá trị giới hạn trên gU của g(x).
// ***************************************************** bool HS071_NLP::get_bounds_info(Index n, Number* x_l, Number* x_u, Index m, Number* g_l, Number* g_u)
{
assert(n == ...); assert(m == ...);
// Giá trị giới hạn dưới của các biến
for (Index i=0; i<n; i++) { x_l[i] = ...;
}
// Giá trị giới hạn trên của các biến
for (Index i=0; i<n; i++) { x_u[i] = ...;
}
// Giá trị giới hạn dưới của các ràng buộc
g_l[i] = ...;
// nếu các ràng buộc khơng cĩ giới hạn trên thì đặt bằng 2e19.
g_u[...] = 2e19;
// nếu các ràng buộc là phuowng trình thì đặt giới hạn dưới bằng giới
// hạn trên.
Lựa chọn phương pháp giải và các cơng cụ 48 return true; } // ***************************************************** 2.3.2.3 Method get_starting_point: cĩ dạng thức:
virtual bool get_starting_point(Index n, bool init_x, Number* x,
bool init_z, Number* z_L, Number* z_U, Index m, bool init_lambda, Number* lambda) Hàm trả về các giá trị khởi tạo của bài tốn trước khi lặp.
n: (vào), số lượng biến của bài tốn x.
init_x: (vào), đặt là true thì hàm phải đặt giá trị khởi tạo cho x. x: (ra), giá trị khởi tạo của biến x.
init_z: (vào), đặt là true thì hàm phải đặt giá trị khởi tạo cho các nhân tử ràng buộc zL và zU.
z_L: (ra), giá trị khởi tạo của biến zL. z_U: (ra), giá trị khởi tạo của biến zU. m: (in), số lượng các ràng buộc g(x).
init_lambda: (vào), đặt là true thì hàm phải đặt giá trị khởi tạo cho các nhân tử ràng buộc .
lambda: (ra), giá trị khởi tạo của biến .
// ***************************************************** bool HS071_NLP::get_starting_point( Index n, bool init_x, Number* x,
bool init_z, Number* z_L, Number* z_U, Index m, bool init_lambda,
Number* lambda) {
// Here, we assume we only have starting values for x, if you code // your own NLP, you can provide starting values for the dual variables // if you wish to use a warmstart option
assert(init_x == true); assert(init_z == false); assert(init_lambda == false);
// initialize to the given starting point x[0] = ...;
x[1] = ...;
Lựa chọn phương pháp giải và các cơng cụ 49 x[n-1] = ...; return true; } // ***************************************************** 2.3.2.4 Method eval_f: cĩ dạng thức:
virtual bool eval_f(Index n, const Number* x, bool new_x, Number& obj_value) Hàm trả về giá trị hàm mục tiêu tại x.
n: (vào), số lượng biến x.
x: (vào), giá trị của biến x dùng để tính f(x).
new x: (vào), là false nếu x đã được dùng để tính trước đĩ, true trong trường hợp ngược lại.
obj value: (ra) giá trị hàm mục tiêu f(x).
// ***************************************************** bool HS071_NLP::eval_f(Index n, const Number* x, bool new_x, Number&
obj_value) { assert(n == ...); obj_value = ...; return true; } // ***************************************************** 2.3.2.5 Method eval_grad_f: cĩ dạng thức:
virtual bool eval_grad_f(Index n, const Number* x, bool new_x, Number* grad_f)
Hàm trả về gradient của hàm muc tiêu tại x. n: (vào), số lượng biến x.
x: (vào), giá trị x dùng để tính f x( ).
new_x: (vào), là false nếu x đã được dùng để tính trước đĩ, true trong trường hợp ngược lại.
Lựa chọn phương pháp giải và các cơng cụ 50 grad_f: (ra), giá trị của mảng f x( ).
// *****************************************************
bool HS071_NLP::eval_grad_f(Index n, const Number* x, bool new_x, Number* grad_f) { assert(n == ...); grad_f[0] = ...; grad_f[1] = ...; ... grad_f[n-2] = ...; grad_f[n-1] = ...; return true; } // ***************************************************** 2.3.2.6 Method eval_g: cĩ dạng thức:
virtual bool eval_g(Index n, const Number* x,
bool new_x, Index m, Number* g) Hàm trả về giá trị của các ràng buộc tại x.
n: (vào), số lượng biến x.
x: (vào), giá trị x dùng để tính các ràng buộc g(x).
new_x: (vào), là false nếu x đã được dùng để tính trước đĩ, true trong trường hợp ngược lại.
m: (vào), số lượng các ràng buộc g(x). g: (ra) giá trị các ràng buộc g(x).
// ***************************************************** bool HS071_NLP::eval_g(Index n, const Number* x, bool new_x, Index m,
Number* g) { assert(n == ...); assert(m == ...); g[...] = ...; ... ... ... ... ... ... ... ... ... ... g[...] = ...; return true;
Lựa chọn phương pháp giải và các cơng cụ 51 }
// *****************************************************
2.3.2.7 Method eval_jac_g:
cĩ dạng thức:
virtual bool eval_jac_g(Index n, const Number* x, bool new_x, Index m, Index nele_jac, Index* iRow, Index *jCol, Number* values)
Hàm trả về cấu trúc của Jacobian của các ràng buộc, hoặc trả về giá trị các phần tử của Jacobian của các ràng buộc tại x.
n: (vào), số lượng biến x.
x: (vào), giá trị biến x dùng để tính Jacobian g x( )T.
new_x: (vào), là false nếu x đã được dùng để tính trước đĩ, true trong trường hợp ngược lại.
m: (vào), số lượng các ràng buộc g(x).
n_ele_jac: (vào), số lượng phần tử khác 0 trong Jacobian (kích thước của iRow, jCol, và values).
iRow: (ra), giá trị chỉ số hàng của Jacobian của ràng buộc. jCol: (ra), giá trị chỉ số cột của Jacobian của ràng buộc.
values: (ra), giá trị của các phần tử trong Jacobian của các ràng buộc. // *****************************************************
bool HS071_NLP::eval_jac_g(Index n, const Number* x, bool new_x,
Index m, Index nele_jac, Index* iRow, Index *jCol, Number* values)
{
if (values == NULL) {
// trả về cấu trúc của Jacobian iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; iRow[...] = ...; jCol[...] = ...; } else {
Lựa chọn phương pháp giải và các cơng cụ 52 // trả về giá trị của Jacobian của các ràng buộc
values[0] = ...; // 0,0 ... values[...] = ...; // 0,1 values[...] = ...; // 0,2 values[...] = ...; // 0,3 } return true; } // ***************************************************** 2.3.2.8 Method eval_h: cĩ dạng thức:
virtual bool eval_h( Index n, const Number* x, bool new_x,
Number obj_factor, Index m, const Number* lambda, bool new_lambda, Index nele_hess, Index* iRow, Index* jCol, Number* values)
Hàm trả về cấu trúc của Hessian của hàm Lagrange, hoặc trả về giá của Hessian của hàm Lagrange với các giá trị đã cho của x, f , và .
n: (vào), số lượng các biến của bài tốn x.
x: (vào), giá trị của biến x, dùng để tính giá trị của Hessian.
new_x: (vào), là false nếu x đã được dùng để tính trước đĩ, true trong trường hợp ngược lại.
obj_factor: (vào), hệ số nhân đứng trước phần hàm mục tiêu trong Hessian, sigmaf.
m: (vào), số lượng các ràng buộc g(x).
lambda: (vào), giá trị của các nhân tử ràng buộc để tính Hessian.
New_lambda: (vào), là false nếu đã được dùng để tính trước đĩ, true trong trường hợp ngược lại.
nele_hess: (vào), số lượng phần tử khác 0 trong Hessian (kích thước của iRow, jCol, và values).
iRow: (ra), chỉ số hàng của các phần tử trong Hessian. jCol: (ra), chỉ số cột của các phần tử trong Hessian. values: (ra), giá trị của các phần tử trong Hessian.
Lựa chọn phương pháp giải và các cơng cụ 53 // *****************************************************
bool HS071_NLP::eval_h(Index n, const Number* x, bool new_x,
Number obj_factor, Index m, const Number* lambda, bool new_lambda, Index nele_hess, Index* iRow, Index* jCol, Number* values)
{
if (values == NULL) {
// trả về cấu trúc, đây là ma trận đối xứng, chỉ trả lại nửa tam // giác thấp bên dưới
Index idx=0;
for (Index row = 0; row < 4; row++) { for (Index col = 0; col <= row; col++) { iRow[idx] = row; jCol[idx] = col; idx++; } } assert(idx == nele_hess); } else {
// trả về các giá trị, đây là ma trận đối xứng, chỉ trả lại nửa tam // giác thấp bên dưới
// Phần hàm mục tiêu values[0] = obj_factor * ...; // 0,0 values[1] = obj_factor * ...; // 1,0 ... values[i] = ...; // 1,1 values[j] = obj_factor * ...; // 2,0 // Phần hàm ràng buộc values[...] += lambda[0] * ...; // 1,0 values[...] += lambda[0] * ...; // 2,0 ... // Phần hàm ràng buộc values[...] += lambda[1] * ...; // 0,0 values[...] += lambda[1] * ...; // 1,1 values[...] += lambda[1] * ...; // 2,2 values[...] += lambda[1] * ...; // 3,3 } return true; } // *****************************************************
Lựa chọn phương pháp giải và các cơng cụ 54
2.3.2.9 Method finalize_solution:
cĩ dạng thức:
virtual void finalize_solution(SolverReturn status, Index n, const Number* x, const Number* z_L,
const Number* z_U, Index m, const Number* g, const Number* lambda, Number obj_value, const IpoptData* ip_data,
IpoptCalculatedQuantities* ip_cq)
Đây là một hàm method được gọi bởi Ipopt sau khi việc giải bài tốn đã hồn thành, ngay cả khi khơng tìm được nghiệm.
status: (vào), trạng thái của thuật tốn cài đặt trong IpAlgTypes.hpp. n: (vào), số lượng các biến của bài tốn x.
x: (vào), giá trị cuối cùng của các biến x.
z_L: (vào), giá trị cuối cùng của các nhân tử giới hạn dưới z*L. z_U: (vào), giá trị cuối cùng của các nhân tử giới hạn trên z*U. m: (vào), số lượng các ràng buộc g(x).
g: (vào), giá trị cuối cùng của các ràng buộc, g(x*).
lambda: (vào), giá trị cuối cùng của nhân tử của các ràng buộc *. obj value: (vào), giá trị cuối cùng của hàm mục tiêu, f(x*).
ip_data và ip_cq được cung cấp bởi người sử dụng. // ***************************************************** void HS071_NLP::finalize_solution(SolverReturn status,
Index n, const Number* x, const Number* z_L, const Number* z_U, Index m, const Number* g, const Number* lambda, Number obj_value) {
// Ta cĩ thể lưu các biến ra file để sử dụng sau này. // Viết các giá trị ra màn hình
printf("\n\n Nghiệm của bài tốn, x\n"); for (Index i=0; i<n; i++) {
printf("x[%d] = %e\n", i, x[i]); }
for (Index i=0; i<n; i++) {
printf("z_U[%d] = %e\n", i, z_U[i]); }
printf("\n\n Giá trị hàm mục tiêu là:\n");
Lựa chọn phương pháp giải và các cơng cụ 55 }
// *****************************************************
2.3.2.10 Hàm main():
Cuối cùng, tồn bộ các các chương trình con sẽ được gọi thơng qua hàm main() trong chương trình chính:
// ***************************************************** #include "IpIpoptApplication.hpp"
#include "hs071_nlp.hpp" using namespace Ipopt;
int main(int argv, char* argc[]) {
// Tạo bài tốn nlp // (sử dụng SmartPtr)
SmartPtr<TNLP> mynlp = new HS071_NLP(); // tạo mới IpoptApplication
// (sử dụng SmartPtr)
SmartPtr<IpoptApplication> app = new IpoptApplication(); // Thay đổi các lựa chọn
app->Options()->SetNumericValue("tol", 1e-9);
app->Options()->SetStringValue("mu_strategy", "adaptive"); app->Options()->SetStringValue("output_file", "ipopt.out"); // Khởi tạo IpoptApplication với các lựa chọn
ApplicationReturnStatus status; status = app->Initialize(); if (status != Solve_Succeeded) {
printf("\n\n*** Xuất hiện lỗi trong quá trình khởi tạo!\n"); return (int) status;
}
// Yêu cầu Ipopt giải bài tốn
status = app->OptimizeTNLP(mynlp); if (status == Solve_Succeeded) {
printf("\n\n*** Bài tốn đã được giải xong!\n");
} else {
printf("\n\n*** Khơng tìm được nghiệm!\n");
}
// Khi SmartPtrs ra khỏi phạm vi kiểm sốt, các đối tượng sẽ tự động bị
// xĩa.
return (int) status; }
Ví dụ minh họa 56
Chƣơng 3
CÁC VÍ DỤ MINH HỌA & KẾT QUẢ MƠ PHỎNG 3.1Ví dụ minh họa
Để minh họa cho thuật tốn QS-SQA [12], tác giả luận văn đã sử dụng các ví dụ sẵn cĩ được trích từ chương 10 của tài liệu [18]. Đây là một loạt các ví dụ được sử dụng trong [18] để minh họa cho thuật tốn tối ưu động lặp (Iterative Dynamic Programming-IDP). Các kết quả của phương pháp QS-SQA được phân tích, đánh giá và so sánh với các kết quả trong [18]. Qua đĩ, các kết luận cũng như những kiến nghị về hướng phát triển tiếp theo của đề tài sẽ được đề cập trong chương 4 của luận văn này.
Các bài tốn được giải bằng phương pháp QS-SQA với những thao tác như sau: Xác định số lượng các khoảng thời gian tivới i0, 1, ..., (NL 1) cần thiết
để rời rạc hĩa bài tốn; NL phụ thuộc vào khoảng thời gian cần xét của bài tốn tối ưu; NL càng lớn thì kết quả càng chính xác nhưng lại làm tăng thời gian tính tốn.
Rời rạc hĩa các biến trạng thái bằng các đoạn giá trị hằng số trong mỗi khoảng thời gian ti, biến điều khiển bằng phương pháp collocation (với NC=3 cho tất cả các bài tốn).
Viết các chương trình con thực hiện thuật tốn nhúng vào chương trình IPOPT đã trình bày trong chương 2.
Chạy chương trình, thay đổi các thơng số để đạt được kết quả tốt nhất, đánh giá, kết luận nghiệm của bài tốn.
Các kết quả hiển thị bằng hình ảnh được vẽ trong MATLAB. Các chương trình chạy trên máy PC cĩ cấu hình CPU Pentium (chạy trên một lõi đơn) D830 3.0GHz, 2GB RAM.
3.1.1 Ví dụ 1
Ví dụ đầu tiên là một ví dụ điều khiển tối ưu động đơn giản cĩ thể giải bằng phương pháp giải tích dựa vào trực giác. Bài tốn này được giới thiệu trong [18] với lưu ý là mặc dù nĩ cĩ thể được giải một cách dễ dàng bằng trực giác thì việc tìm lời giải bằng phương pháp số lại khơng đơn giản.
Ví dụ minh họa 57 2 2 1 0 1 1 min 0.5 ( ) (3.1a) sao cho (3.1b) 1.0 1.0 (3.1c) (0) 1 (3.1d) J x t dt x u u x
Bài tốn tối ưu động (3.1) cĩ thể được giải như sau: để tối thiểu hĩa (3.1a), ta cần giảm giá trị của x1 tại t=0, x1(0)=1.0 càng nhanh càng tốt, tức là chọn u=-1 đến khi x1=0, sau đĩ chọn u=0. Ta cĩ:
1 1 1
x x a t
Từ điều kiện đầu x1(0)1.0 a 1.0 x1 1.0t.
Đến t=1.0 thì x1=0 nên trong đoạn t=[1.0; 2.0] chọn u=0 ta cĩ x1=0. Giá trị cực tiểu 1 2 2 min 0 1 1 0.5 (1 ) 0. 0.16666667 (3.1e) 6 J t dt dt
Chuyển bài tốn (3.1) về dạng dễ thực hiện hơn với phương pháp số, ta đặt: 2 2 0.5 1 x x Bài tốn (3.1) trở thành: 2 1 2 2 1 min ( ) (3.2a) sao cho (3.2b) 0.5 (3.2c) 1.0 1.0 (3.2d) (0) [1, 0] (3.2e) f T J x t x u x x u x với tf=2.0.
Bài tốn được rời rạc hĩa với NL=20, tức là t 2.0 200.01 (đơn vị thời gian). Sau khoảng thời gian CPUtime=210ms với 24 lần lặp, chương trình cho kết quả Jmin=1.666667. Các đồ thị biến trạng thái và biến điều khiển được vẽ trong MATLAB trình bày trong hình 3.1.
Hình 3.1 cho thấy kết quả tính tốn bằng phương pháp số phù hợp với kết quả