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/bracket-minimum.js bracket-minimum.js}
*
* Unit Test: {@link https://github.com/yuda-lyu/w-optimization/blob/master/test/setpBracket.test.js Github}
* @memberOf w-optimization
* @param {Function} fun 輸入適應函數,將傳入變數x,需回傳適應函數值,以求解最小值所在起訖範圍為目標
* @param {Number} x 輸入初始值數字
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Number} [opt.delta=0.1] 輸入步長數字,預設0.1
* @param {Number} [opt.min=-1e20] 輸入搜尋範圍最小值數字,預設-1e20
* @param {Number} [opt.max=1e20] 輸入搜尋範圍最大值數字,預設1e20
* @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 setpBracket(fun, 0, { min: -360, max: 360 }))
* // => {
* // count: 13,
* // bounded: true,
* // min: -204.70000000000002,
* // max: 0.1,
* // guess: -102.30000000000001,
* // fMin: 0.4178670738010769,
* // fMax: 0.0017453283658983088,
* // fGuess: -0.9770455744352636
* // }
*
* console.log(await setpBracket(fun, 87, { min: -360, max: 360 }))
* // => {
* // count: 14,
* // bounded: true,
* // min: -322.5,
* // max: 87.1,
* // guess: -117.70000000000002,
* // fMin: 0.6087614290087209,
* // fMax: 0.9987193571841863,
* // fGuess: -0.8853936257544158
* //}
*
* console.log(await setpBracket(fun, 90, { min: -360, max: 360 }))
* // => {
* // count: 20,
* // bounded: true,
* // min: -319.5,
* // max: 102.7,
* // guess: -114.70000000000002,
* // fMin: 0.6494480483301841,
* // fMax: 0.9755345439458566,
* // fGuess: -0.9085081775267217
* //}
*
* }
*
* test()
* .catch((err) => {
* console.log(err)
* })
*
*/
async function setpBracket(fun, x, opt = {}) {
//check fun
if (!isfun(fun)) {
return Promise.reject('invalid fun')
}
//check x
if (!isnum(x)) {
return Promise.reject('x is not a number')
}
//dx
let dx = get(opt, 'delta')
if (!isnum(dx)) {
dx = 0.1 //2 / (1 + Math.sqrt(5)) = 0.6180339887498948
}
dx = cdbl(dx)
//dxR
let dxR = get(opt, 'dxR')
if (isnum(dxR) && dxR === 'speed') {
if (isnum(dxR)) {
dxR = cdbl(dxR)
}
}
else {
dxR = 2
}
//xMin
let xMin = get(opt, 'min')
if (!isnum(xMin)) {
xMin = -1e20
// console.log('Number.MIN_VALUE', Number.MIN_VALUE)
}
xMin = cdbl(xMin)
//xMax
let xMax = get(opt, 'max')
if (!isnum(xMax)) {
xMax = 1e20
// console.log('Number.MAX_VALUE', Number.MAX_VALUE)
}
xMax = cdbl(xMax)
//countCalc
let countCalc = 0
//func
async function func() { //要使用arguments不能用箭頭函數
let r = fun(...arguments)
if (ispm(r)) {
r = await r
}
countCalc++
return r
}
let xL = x
let xU = x
let xC = x
let fMin = await func(x)
let fL = fMin
let fU = fMin
let bounded = false
let n = 1
while (!bounded) {
++n
bounded = true
if (fL <= fMin) {
// console.log('fL <= fMin', fL, fMin)
xC = xL
fMin = fL
xL = Math.max(xMin, xL - dx)
fL = await func(xL)
bounded = false
}
if (fU <= fMin) {
// console.log('fU <= fMin', fU, fMin)
xC = xU
fMin = fU
xU = Math.min(xMax, xU + dx)
fU = await func(xU)
bounded = false
}
//update
fMin = Math.min(fMin, fL, fU)
//check
if ((fL === fMin && xL === xMin) || (fU === fMin && xU === xMax)) {
bounded = true
}
//調整步長
if (isNumber(dxR)) {
dx *= dxR
}
else if (dxR === 'speed') {
dx *= n < 4 ? 2 : Math.exp(n * 0.5)
}
// console.log('dx', dx)
//check
if (!isFinite(dx)) {
return null
}
}
return {
count: countCalc,
bounded,
min: xL,
max: xU,
guess: xC,
fMin: fL,
fMax: fU,
fGuess: fMin,
}
}
export default setpBracket