import get from 'lodash-es/get.js'
import isNumber from 'lodash-es/isNumber.js'
import isnum from 'wsemi/src/isnum.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import ispm from 'wsemi/src/ispm.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
/**
* 黃金比例搜尋法(1維),求解最小值所在
*
* Fork: {@link https://github.com/scijs/minimize-golden-section-1d/blob/master/src/golden-section-minimize.js golden-section-minimize.js}
*
* Unit Test: {@link https://github.com/yuda-lyu/w-optimization/blob/master/test/goldenSection.test.js Github}
* @memberOf w-optimization
* @param {Function} fun 輸入適應函數,將傳入變數x,需回傳適應函數值,以求解最小值所在起訖範圍為目標
* @param {Number} xL 輸入初始最小值數字
* @param {Number} xU 輸入初始最大值數字
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Number} [opt.minTolerance=1e-8] 輸入最小收斂門檻值數字,預設1e-8
* @param {Number} [opt.maxIterations=100] 輸入最大迭代次數整數,預設100
* @returns {Promise} 回傳Promise,resolve為求解後結果物件,含鍵值x,y,x為求解後變數組,y為最優適應函數值,reject為失敗訊息
* @example
*
* async function test() {
*
* function fun(param) {
* let x = param / 180 * Math.PI
* return Math.sin(x)
* }
*
* console.log(await goldenSection(fun, -360, 360))
* // => { count: 56, y: -1, x: -89.99999939558693 }
*
* }
*
* test()
* .catch((err) => {
* console.log(err)
* })
*
*/
async function goldenSection(fun, xL, xU, opt = {}) {
//check fun
if (!isfun(fun)) {
return Promise.reject('invalid fun')
}
//check xL
if (!isnum(xL)) {
return Promise.reject('xL is not a number')
}
//check xU
if (!isnum(xU)) {
return Promise.reject('xU is not a number')
}
//minTolerance
let minTolerance = get(opt, 'minTolerance')
if (!isnum(minTolerance)) {
minTolerance = 1e-8
}
minTolerance = cdbl(minTolerance)
//maxIterations
let maxIterations = get(opt, 'maxIterations')
if (!isnum(maxIterations)) {
maxIterations = 100
}
maxIterations = cdbl(maxIterations)
//countCalc
let countCalc = 0
//func
async function func() { //要使用arguments不能用箭頭函數
let r = fun(...arguments)
if (ispm(r)) {
r = await r
}
countCalc++
return r
}
let xF
let fF
let PHI_RATIO = 2 / (1 + Math.sqrt(5))
let iteration = 0
// Initial bounds
let x1 = xU - PHI_RATIO * (xU - xL)
let x2 = xL + PHI_RATIO * (xU - xL)
let f1 = await func(x1)
let f2 = await func(x2)
// console.log('x1', x1, 'f1', f1, 'x2', x2, 'f2', f2)
// Store these values so that we can return these if they're better.
// This happens when the minimization falls *approaches* but never
// actually reaches one of the bounds
let fL = await func(xL)
let fU = await func(xU)
let xL0 = xL
let xU0 = xU
// console.log('xL', xL, 'fL', fL, 'xU', xU, 'fU', fU)
// Simple, robust golden section minimization:
while (++iteration < maxIterations && Math.abs(xU - xL) > minTolerance) {
if (f2 > f1) {
xU = x2
x2 = x1
f2 = f1
x1 = xU - PHI_RATIO * (xU - xL)
f1 = await func(x1)
}
else {
xL = x1
x1 = x2
f1 = f2
x2 = xL + PHI_RATIO * (xU - xL)
f2 = await func(x2)
}
}
//update xF, fF
xF = 0.5 * (xU + xL)
fF = 0.5 * (f1 + f2)
// console.log('xF', xF, 'fF', fF)
//check
if (!isNumber(f2) || !isNumber(f1) || iteration === maxIterations) {
return null
}
//bestx, besty
let bestx = null
let besty = null
if (fL < fF) {
bestx = xL0
besty = fL
}
else if (fU < fF) {
bestx = xU0
besty = fU
}
else {
bestx = xF
besty = fF
}
return {
count: countCalc,
y: besty,
x: bestx,
}
}
export default goldenSection