簡介
本文展示了用C++(Eigen)實現的Nelder-Mead算法,該實現仿照了Python SciPy庫中的scipy.optimize.fmin函數。雖然目前僅完成了基礎功能(fmin不支持full_output和retall),但已經可以應用於實際優化問題。
Nelder-Mead算法簡介
Nelder-Mead算法(也稱單純形法)是一種常用的無導數優化方法,特別適合於:
- 函數導數難以計算或不存在的情況
- 尋找非線性函數的局部最小值
- 維度適中的問題
這種算法被廣泛應用於機器學習、計算機視覺、信號處理等領域,特別是當目標函數計算成本高昂時尤為有用。
SciPy版本的特點:
- 邊界約束支持:SciPy版本添加了對變量邊界的支持,通過bounds參數實現。它使用np.clip確保所有單純形頂點都在指定邊界內,這是標準Nelder-Mead算法的擴展。
- 自適應參數策略:通過adaptive參數啓用,基於Gao和Han
2012年的論文。這種策略根據問題維度自動調整反射、擴展和收縮參數,使算法在高維問題上表現更好。 - 雙重收斂條件:同時使用函數值容差(fatol)和參數值容差(xatol)作為終止條件,這比只用單一條件更穩健。
- 完善的終止處理:支持通過最大迭代次數(maxiter)或最大函數評估次數(maxfev)限制算法運行時間,並提供詳細的終止狀態。
更詳細的分析見本文結尾。
代碼
純頭文件庫,包含opti.h,opti.inl。還有一個測試程序main.cpp
opti.h:
#ifndef MY_OPTIMIZE_H
#define MY_OPTIMIZE_H
#include <Eigen/Core>
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <limits>
#include <vector>
// NelderMead算法選項
struct NelderMeadOptions {
double xatol = 1e-4; // x收斂容差
double fatol = 1e-4; // 函數值收斂容差
int maxiter = -1; // 最大迭代次數,-1表示使用默認值
int maxfev = -1; // 最大函數評估次數,-1表示使用默認值
bool disp = false; // 是否顯示收斂消息
bool return_all = false; // 是否返回所有迭代的結果
bool adaptive = false; // 是否使用自適應參數
// 邊界,每個變量的(min, max)對,std::nullopt表示無邊界
std::vector<std::pair<double, double>> bounds;
};
// NelderMead算法結果
struct NelderMeadResult {
Eigen::VectorXd x; // 最優解
double fun; // 最優解對應的函數值
int nit; // 迭代次數
int nfev; // 函數評估次數
int status; // 狀態碼:0=成功,1=達到最大函數評估次數,2=達到最大迭代次數
bool success; // 是否成功
std::string message; // 狀態信息
std::vector<Eigen::VectorXd> allvecs; // 所有迭代的結果(如果return_all=true)
};
/**
* Nelder-Mead單純形算法
*
* @param func 目標函數
* @param x0 初始猜測點
* @param initial_simplex 初始單純形,如果提供則覆蓋x0
* @param options 算法參數選項
* @param callback 回調函數,每次迭代後調用
* @return 優化結果
*/
template <typename Scalar = double>
NelderMeadResult
minimize_neldermead(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex = nullptr,
const NelderMeadOptions& options = NelderMeadOptions(),
std::function<bool(const NelderMeadResult&)> callback = nullptr);
/**
* 簡化版Nelder-Mead優化函數,類似於scipy的fmin
*
* @param func 目標函數
* @param x0 初始猜測點
* @param initial_simplex 初始單純形,如果提供則覆蓋x0
* @param xtol x收斂容差
* @param ftol 函數值收斂容差
* @param maxiter 最大迭代次數
* @param maxfun 最大函數評估次數
* @param full_output 是否返回完整輸出
* @param disp 是否顯示收斂消息
* @param retall 是否返回所有迭代的結果
* @param callback 回調函數
* @return 優化結果點或完整結果(取決於full_output)
*/
template <typename Scalar = double>
Eigen::Matrix<Scalar, Eigen::Dynamic, 1>
fmin(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex = nullptr,
double xtol = 1e-4,
double ftol = 1e-4,
int maxiter = -1,
int maxfun = -1,
bool full_output = false,
bool disp = true,
bool retall = false,
std::function<bool(const NelderMeadResult&)> callback = nullptr);
#include "opti.inl"
#endif //MY_OPTIMIZE_H
opti.inl:
#ifndef MY_OPTIMIZE_INL
#define MY_OPTIMIZE_INL
#include <algorithm>
#include <cmath>
#include <iostream>
template <typename Scalar>
NelderMeadResult minimize_neldermead(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex,
const NelderMeadOptions& options,
std::function<bool(const NelderMeadResult&)> callback) {
using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
// 提取選項參數
double xatol = options.xatol;
double fatol = options.fatol;
int maxiter = options.maxiter;
int maxfev = options.maxfev;
bool disp = options.disp;
bool return_all = options.return_all;
bool adaptive = options.adaptive;
const auto& bounds = options.bounds;
// 設置算法參數
double rho, chi, psi, sigma;
if (adaptive) {
double dim = static_cast<double>(x0.size());
rho = 1.0;
chi = 1.0 + 2.0 / dim;
psi = 0.75 - 1.0 / (2.0 * dim);
sigma = 1.0 - 1.0 / dim;
} else {
rho = 1.0;
chi = 2.0;
psi = 0.5;
sigma = 0.5;
}
double nonzdelt = 0.05;
double zdelt = 0.00025;
// 檢查邊界
bool has_bounds = !bounds.empty();
Vector lower_bound, upper_bound;
if (has_bounds) {
if (bounds.size() != static_cast<size_t>(x0.size())) {
throw std::invalid_argument("Bounds size does not match x0 size");
}
lower_bound(x0.size());
upper_bound(x0.size());
for (int i = 0; i < x0.size(); ++i) {
lower_bound(i) = bounds[i].first;
upper_bound(i) = bounds[i].second;
}
if ((lower_bound.array() > upper_bound.array()).any()) {
throw std::invalid_argument("Nelder Mead - one of the lower bounds is greater than an upper bound.");
}
if (((x0.array() < lower_bound.array()) || (x0.array() > upper_bound.array())).any() && disp) {
std::cerr << "Warning: Initial guess is not within the specified bounds" << std::endl;
}
}
// 將點裁剪到邊界內
auto clip = [&](const Vector& x) -> Vector {
if (!has_bounds)
return x;
return x.cwiseMax(lower_bound).cwiseMin(upper_bound);
};
// 按函數值排序單純形頂點
auto sort_simplex = [](Vector& fsim, Matrix& sim) {
const int N = sim.cols();
std::vector<std::pair<Scalar, int>> pairs(N + 1);
for (int i = 0; i < N + 1; ++i) {
pairs[i] = {fsim(i), i};
}
std::sort(pairs.begin(), pairs.end());
// 重排fsim和sim
Vector fsim_sorted(N + 1);
Matrix sim_sorted(N + 1, N);
for (int i = 0; i < N + 1; ++i) {
fsim_sorted(i) = pairs[i].first;
sim_sorted.row(i) = sim.row(pairs[i].second);
}
fsim = fsim_sorted;
sim = sim_sorted;
};
// 創建初始單純形
Vector x0_copy = x0;
if (has_bounds) {
x0_copy = clip(x0_copy);
}
int N = x0_copy.size();
Matrix sim;
if (initial_simplex == nullptr) {
sim = Matrix(N + 1, N);
sim.row(0) = x0_copy;
for (int k = 0; k < N; ++k) {
Vector y = x0_copy;
if (y(k) != 0) {
y(k) = (1.0 + nonzdelt) * y(k);
} else {
y(k) = zdelt;
}
sim.row(k + 1) = y;
}
} else {
if (initial_simplex->rows() != N + 1 || initial_simplex->cols() != N) {
throw std::invalid_argument("`initial_simplex` should be an array of shape (N+1,N)");
}
sim = *initial_simplex;
}
// 如果邊界存在,確保所有單純形頂點都在邊界內
if (has_bounds) {
for (int i = 0; i < sim.rows(); ++i) {
Vector vertex = sim.row(i);
sim.row(i) = clip(vertex);
}
}
// 如果既沒有設置maxiter也沒有設置maxfev,設置它們為默認值
if (maxiter < 0 && maxfev < 0) {
maxiter = N * 200;
maxfev = N * 200;
} else if (maxiter < 0) {
if (maxfev == std::numeric_limits<int>::max()) {
maxiter = N * 200;
} else {
maxiter = std::numeric_limits<int>::max();
}
} else if (maxfev < 0) {
if (maxiter == std::numeric_limits<int>::max()) {
maxfev = N * 200;
} else {
maxfev = std::numeric_limits<int>::max();
}
}
// 計算初始單純形中每個點的函數值
Vector fsim = Vector::Constant(N + 1, std::numeric_limits<Scalar>::max());
int fcalls = 0;
for (int k = 0; k < N + 1; ++k) {
fsim(k) = func(sim.row(k).transpose());
fcalls++;
if (fcalls >= maxfev) {
break;
}
}
// 按函數值排序單純形頂點
sort_simplex(fsim, sim);
// 保存所有迭代結果
std::vector<Vector> allvecs;
if (return_all) {
allvecs.push_back(sim.row(0).transpose());
}
// 開始迭代
int iterations = 1;
while (fcalls < maxfev && iterations < maxiter) {
// 檢查收斂
double max_dist = 0.0;
for (int i = 1; i < N + 1; ++i) {
max_dist = std::max(max_dist, (sim.row(i) - sim.row(0)).norm());
}
double max_diff = 0.0;
for (int i = 1; i < N + 1; ++i) {
max_diff = std::max(max_diff, std::abs(fsim(i) - fsim(0)));
}
if (max_dist <= xatol && max_diff <= fatol) {
break;
}
// 計算質心(除了最差點)
// 1. sim.topRows(N) - 選擇前N行,等價於Python的sim[:-1]
// (因為單純形有N+1個頂點,選擇前N行就是除了最後一行外的所有行)
// 2. .colwise().sum() - 對每一列分別求和,結果是一個行向量
// (等價於Python的np.add.reduce(..., 0))
// 3. .transpose() - 將行向量轉置為列向量
// (因為在Eigen中Vector通常表示列向量)
// 4. / static_cast<Scalar>(N) - 除以N得到平均值(質心)
Vector xbar = sim.topRows(N).colwise().sum().transpose() / static_cast<Scalar>(N);
// 執行反射
Vector xr = (1 + rho) * xbar - rho * sim.row(N).transpose();
if (has_bounds) {
xr = clip(xr);
}
Scalar fxr = func(xr);
fcalls++;
bool doshrink = false;
if (fxr < fsim(0)) {
// 反射點比最好點還好,嘗試擴展
Vector xe = (1 + rho * chi) * xbar - rho * chi * sim.row(N).transpose();
if (has_bounds) {
xe = clip(xe);
}
Scalar fxe = func(xe);
fcalls++;
if (fxe < fxr) {
// 擴展點更好,接受擴展
sim.row(N) = xe.transpose();
fsim(N) = fxe;
} else {
// 反射點更好,接受反射
sim.row(N) = xr.transpose();
fsim(N) = fxr;
}
} else {
// 反射點不比最好點好
if (fxr < fsim(N - 1)) {
// 反射點至少比次差點好,接受反射
sim.row(N) = xr.transpose();
fsim(N) = fxr;
} else {
// 執行收縮
if (fxr < fsim(N)) {
// 外收縮
Vector xc = (1 + psi * rho) * xbar - psi * rho * sim.row(N).transpose();
if (has_bounds) {
xc = clip(xc);
}
Scalar fxc = func(xc);
fcalls++;
if (fxc <= fxr) {
sim.row(N) = xc.transpose();
fsim(N) = fxc;
} else {
doshrink = true;
}
} else {
// 內收縮
Vector xcc = (1 - psi) * xbar + psi * sim.row(N).transpose();
if (has_bounds) {
xcc = clip(xcc);
}
Scalar fxcc = func(xcc);
fcalls++;
if (fxcc < fsim(N)) {
sim.row(N) = xcc.transpose();
fsim(N) = fxcc;
} else {
doshrink = true;
}
}
if (doshrink) {
// 收縮整個單純形
for (int j = 1; j < N + 1; ++j) {
sim.row(j) = sim.row(0) + sigma * (sim.row(j) - sim.row(0));
if (has_bounds) {
sim.row(j) = clip(sim.row(j).transpose()).transpose();
}
fsim(j) = func(sim.row(j).transpose());
fcalls++;
if (fcalls >= maxfev) {
break;
}
}
}
}
}
iterations++;
// 重新排序單純形
sort_simplex(fsim, sim);
// 保存當前最優解
if (return_all) {
allvecs.push_back(sim.row(0).transpose());
}
if (callback) {
NelderMeadResult intermediate_result;
intermediate_result.x = sim.row(0).transpose();
intermediate_result.fun = fsim(0);
intermediate_result.nit = iterations;
intermediate_result.nfev = fcalls;
if (callback(intermediate_result)) {
break;
}
}
}
// 準備結果
NelderMeadResult result;
result.x = sim.row(0).transpose();
result.fun = fsim(0);
result.nit = iterations;
result.nfev = fcalls;
result.success = true;
result.status = 0;
result.message = "Optimization terminated successfully.";
if (fcalls >= maxfev) {
result.status = 1;
result.success = false;
result.message = "Maximum number of function evaluations exceeded.";
if (disp) {
std::cerr << "Warning: " << result.message << std::endl;
}
} else if (iterations >= maxiter) {
result.status = 2;
result.success = false;
result.message = "Maximum number of iterations exceeded.";
if (disp) {
std::cerr << "Warning: " << result.message << std::endl;
}
} else if (disp) {
std::cout << result.message << std::endl;
std::cout << " Current function value: " << result.fun << std::endl;
std::cout << " Iterations: " << result.nit << std::endl;
std::cout << " Function evaluations: " << result.nfev << std::endl;
}
if (return_all) {
result.allvecs = allvecs;
}
return result;
}
template <typename Scalar>
Eigen::Matrix<Scalar, Eigen::Dynamic, 1>
fmin(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex,
double xtol,
double ftol,
int maxiter,
int maxfun,
bool full_output,
bool disp,
bool retall,
std::function<bool(const NelderMeadResult&)> callback) {
NelderMeadOptions options;
options.xatol = xtol;
options.fatol = ftol;
options.maxiter = maxiter;
options.maxfev = maxfun;
options.disp = disp;
options.return_all = retall;
NelderMeadResult result = minimize_neldermead(func, x0, initial_simplex, options, callback);
if (full_output) {
throw std::runtime_error(
"Full output mode not implemented in wrapper function. Use minimize_neldermead directly.");
} else {
if (retall) {
throw std::runtime_error(
"Return all mode not implemented in wrapper function. Use minimize_neldermead directly.");
} else {
return result.x;
}
}
}
#endif //MY_OPTIMIZE_INL
main.cpp:
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <limits>
#include <vector>
#include <Eigen/Core>
#include "opti.h"
int main() {
// Rosenbrock函數
std::function<double(const Eigen::VectorXd&)> rosenbrock = [](const Eigen::VectorXd& x) -> double {
return 100 * std::pow(x(1) - x(0) * x(0), 2) + std::pow(1 - x(0), 2);
};
// 初始猜測
Eigen::VectorXd x0(2);
x0 << -1.2, 1.0;
// 設置選項
NelderMeadOptions options;
options.disp = true;
// 運行優化
NelderMeadResult result = minimize_neldermead<double>(rosenbrock, x0, nullptr, options);
// 輸出結果
std::cout << "Optimal solution: " << result.x.transpose() << std::endl;
std::cout << "Function value: " << result.fun << std::endl;
return 0;
}
SciPy版Nelder-Mead算法與標準版的區別
(來自gpt o1,不保證正確性,用於輔助理解)
邊界約束(bounds參數)
SciPy的Nelder-Mead實現支持簡單的邊界約束,而標準的Nelder-Mead算法沒有原生的邊界處理能力。在SciPy的minimize(method="Nelder-Mead")中,你可以傳入邊界參數,求解器會在每次迭代時將單純形頂點裁剪到給定的邊界內。實際上,這意味着如果反射或擴展的點在任何座標上超出了[min, max]範圍,它會被拉回到邊界(在邊界處飽和)。這是一種簡單的方法來保持搜索在邊界內(正如SciPy開發者確認的:"這只是簡單地裁剪到邊界")。標準的Nelder-Mead算法(按照最初描述)不包含任何約束處理機制——任何邊界處理都需要外部修改或變體算法。因此,SciPy添加的邊界處理是對傳統Nelder-Mead算法的顯著擴展。
自適應參數選項(adaptive=True)
SciPy的Nelder-Mead提供了一種"自適應"模式,可以調整算法在高維問題中的行為。當adaptive=True時,SciPy使用來自Gao和Han(2012)的修改參數方案。在代碼中,這改變了單純形變換系數如下:反射係數ρ = 1(保持不變),擴展係數χ = 1 + 2/N(而不是2),收縮係數ψ = 0.75 - 1/(2N)(而不是0.5),縮小因子σ = 1 - 1/N(而不是0.5),其中N是維度數量。如果adaptive=False(默認值),SciPy使用標準的Nelder-Mead係數:ρ = 1,χ = 2,ψ = 0.5,σ = 0.5。這些經典值對應於原始的Nelder-Mead算法。自適應模式不會在運行過程中改變係數;相反,它在開始時根據問題維度選擇這些依賴於維度的值(使方法在高維中更加穩健)。總之,標準Nelder-Mead對所有問題使用固定參數,而SciPy的版本在需要時可以使用Gao-Han自適應參數集,改善高維搜索的性能(同時在adaptive=False時對2D或低維問題保持與標準Nelder-Mead相同)。
收斂準則(fatol和xatol)
SciPy的實現使用兩個收斂容差閾值——一個用於函數值(fatol),一個用於解的座標(xatol)——並要求同時滿足這兩個準則才能收斂。在每次迭代中,SciPy檢查當前最佳頂點與其他單純形頂點之間的最大座標距離是否<= xatol,以及最佳頂點與其他頂點之間的函數值最大差異是否<= fatol。只有當這兩個條件都成立時,才宣佈收斂並停止。這種"雙重停止條件"確保單純形在空間中充分收縮,且函數值已經趨於平穩。相比之下,標準Nelder-Mead描述通常使用單一容差(例如,檢查單純形直徑或函數值範圍是否低於閾值)。許多實現(包括早期的SciPy fmin)也使用了兩個容差(xtol和ftol)——SciPy繼續這種方法但將它們重命名為xatol和fatol(明確表示"絕對"容差)。這種雙重標準更加穩健:它可以防止過早停止,例如,如果單純形很小但函數值仍然存在差異,或者反之亦然。簡而言之,SciPy的Nelder-Mead只有在參數變化和函數改進都低於各自的閾值時才停止,而簡單的Nelder-Mead可能使用其中之一。(事實上,SciPy的文檔指出:"ftol和xtol標準必須同時滿足才能收斂。")
終止規則(maxiter和maxfun)
SciPy的Nelder-Mead包含迭代次數和函數評估次數的明確限制,如果算法停滯或收斂緩慢,可以安全終止。maxiter(最大迭代次數)和maxfun(又稱maxfev,最大函數評估次數)選項將在達到任一限制時停止優化(以先到者為準)。如果兩者都設置了,SciPy會同時監控兩者,一旦超過其中一個限制就會退出;如果只設置了一個,另一個實際上被視為無窮(或默認值),以便指定的限制起作用。默認情況下,如果用户沒有提供這些參數,SciPy使用啓發式規則:maxiter = maxfun = 200 * N(其中N是變量數量)。這個默認值(類似於MATLAB的fminsearch默認值)是一個合理的上限,以防容差標準永遠不滿足而導致無限循環。相比之下,已發表的標準Nelder-Mead算法沒有定義特定的迭代限制——理論上它可以一直運行到滿足收斂標準。然而,在實踐中,各種實現(包括SciPy和其他如MATLAB)都施加了這樣的限制,以確保即使對於困難或平坦的目標函數,程序也能終止。SciPy的結果對象會通過設置警告標誌和消息(例如,warnflag=1表示達到最大函數評估次數,warnflag=2表示達到最大迭代次數)來指示優化器是否因達到maxiter或maxfun而停止。這是一個實現細節,增強了SciPy的Nelder-Mead求解器相比基礎Nelder-Mead的穩健性。
結果存儲(return_all)和回調機制
SciPy提供了方便的鈎子來檢查優化進度,這些鈎子不是理論Nelder-Mead算法的一部分,但在實踐中很有用。如果設置了return_all=True,SciPy的求解器會記錄每次迭代中的最佳解決方案,並將其作為結果對象中的一個列表返回(result.allvecs)。這允許用户檢查單純形在搜索空間中所走的路徑。此外,SciPy接受一個回調函數:如果提供了回調函數,它會在每次迭代結束時使用當前最佳點xk作為參數調用該函數。回調機制啓用了用户定義的監控或日誌記錄(例如,打印進度,或在自定義條件下提前停止求解器)。這些特性在標準Nelder-Mead描述中沒有等效項,後者只關注於找到最小值。它們純粹是SciPy中的實現增強功能。例如,SciPy代碼顯示,在每次迭代時,當return_all為True時,它會將sim[0](當前最佳頂點)附加到allvecs,如果提供了回調函數,則調用callback(sim[0])。這些特性使SciPy的Nelder-Mead更加用户友好,允許在求解過程中進行自省和交互。
結論和其他總結的準確性
總之,SciPy的Nelder-Mead與"教科書"Nelder-Mead在幾個方面有所不同:它通過將單純形更新裁剪到邊界內來支持有界約束變量,它提供了自適應參數變體(Gao-Han 2012)以在高維中獲得更好的性能,它採用必須同時滿足的雙重收斂標準(xatol和fatol),它使用可配置(和默認)的迭代和函數調用限制以確保終止,它提供return_all/callback用於結果跟蹤和用户干預。這些增強功能使SciPy的版本比Nelder-Mead的基本算法描述更加穩健和功能豐富。
如果其他人沿着這些線索總結了SciPy與標準Nelder-Mead的差異,那麼他們的總結可能是準確的。上述每個點都由SciPy代碼和文檔支持(如所示)。然而,任何缺少這些關鍵差異或不正確描述它們的總結都不會完全準確。例如,必須注意SciPy的終止同時需要fatol和xatol條件(不是二選一),並且"自適應"模式只是基於問題維度使用不同的固定係數集(除了初始選擇外,它不會在迭代過程中動態改變它們)。同樣,聲明SciPy通過"投影"或"反射回"處理邊界應該明確意味着裁剪到邊界,而不是更復雜的約束處理。總的來説,上述列出的點與正確的區別相符。因此,涵蓋這些內容的總結——邊界裁剪、自適應係數、雙重容差、迭代/函數評估限制以及可選的回調/軌跡輸出——是準確的。任何其他人的總結如果反映了這些概念可能是正確的,而遺漏或錯誤(如誤解收斂檢查方式或自適應如何工作)需要更正。代碼分析確認了SciPy實現的細節,因此任何總結的正確性都可以根據上述事實進行判斷。
參考文獻
以下是從SciPy中提取的論文引用,並附上中文翻譯:
-
Nelder, J.A. and Mead, R. (1965), "A simplex method for function
minimization", The Computer Journal, 7, pp. 308-313中文:Nelder, J.A. 和 Mead, R.
(1965),《一種用於函數最小化的單純形方法》,計算機雜誌,第7卷,308-313頁 -
Wright, M.H. (1996), "Direct Search Methods: Once Scorned, Now
Respectable", in Numerical Analysis 1995, Proceedings of the 1995
Dundee Biennial Conference in Numerical Analysis, D.F. Griffiths and
G.A. Watson (Eds.), Addison Wesley Longman, Harlow, UK, pp. 191-208.中文:Wright, M.H.
(1996),《直接搜索方法:曾經被鄙視,如今受人尊敬》,載於1995年數值分析文集,1995年鄧迪數值分析兩年會議論文集,D.F.
Griffiths和G.A. Watson(編輯),Addison Wesley Longman出版社,英國哈洛,191-208頁 -
Gao, F. and Han, L. (2012), "Implementing the Nelder-Mead simplex
algorithm with adaptive parameters", Computational Optimization and
Applications, 51:1, pp. 259-277中文:Gao, F. 和 Han, L.
(2012),《使用自適應參數實現Nelder-Mead單純形算法》,計算優化與應用,第51卷第1期,259-277頁