numopt-js 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -1
- package/dist/core/bfgs.d.ts +16 -0
- package/dist/core/bfgs.d.ts.map +1 -0
- package/dist/core/bfgs.js +167 -0
- package/dist/core/bfgs.js.map +1 -0
- package/dist/core/cmaEs.d.ts +17 -0
- package/dist/core/cmaEs.d.ts.map +1 -0
- package/dist/core/cmaEs.js +671 -0
- package/dist/core/cmaEs.js.map +1 -0
- package/dist/core/constrainedUtils.d.ts +5 -3
- package/dist/core/constrainedUtils.d.ts.map +1 -1
- package/dist/core/constrainedUtils.js +5 -3
- package/dist/core/constrainedUtils.js.map +1 -1
- package/dist/core/convergence.d.ts.map +1 -1
- package/dist/core/convergence.js +13 -6
- package/dist/core/convergence.js.map +1 -1
- package/dist/core/lbfgs.d.ts +17 -0
- package/dist/core/lbfgs.d.ts.map +1 -0
- package/dist/core/lbfgs.js +199 -0
- package/dist/core/lbfgs.js.map +1 -0
- package/dist/core/lineSearch.d.ts +15 -11
- package/dist/core/lineSearch.d.ts.map +1 -1
- package/dist/core/lineSearch.js +138 -15
- package/dist/core/lineSearch.js.map +1 -1
- package/dist/core/types.d.ts +215 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.browser.js +1365 -111
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +1372 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/random.d.ts +20 -0
- package/dist/utils/random.d.ts.map +1 -0
- package/dist/utils/random.js +71 -0
- package/dist/utils/random.js.map +1 -0
- package/dist/utils/resultFormatter.d.ts +11 -1
- package/dist/utils/resultFormatter.d.ts.map +1 -1
- package/dist/utils/resultFormatter.js +40 -0
- package/dist/utils/resultFormatter.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ A flexible numerical optimization library for JavaScript/TypeScript that works s
|
|
|
11
11
|
|
|
12
12
|
- **Gradient Descent**: Simple, robust optimization algorithm with line search support
|
|
13
13
|
- **Line Search**: Backtracking line search with Armijo condition for optimal step sizes (following Nocedal & Wright, *Numerical Optimization* (2nd ed.), Algorithm 3.1)
|
|
14
|
+
- **Strong Wolfe Line Search**: Robust line search satisfying Strong Wolfe conditions (recommended for quasi-Newton methods)
|
|
15
|
+
- **BFGS**: Quasi-Newton method that updates a dense inverse Hessian approximation (unconstrained smooth optimization)
|
|
16
|
+
- **L-BFGS**: Memory-efficient quasi-Newton method (two-loop recursion) for larger parameter counts
|
|
17
|
+
- **CMA-ES**: Derivative-free black-box optimization using a covariance-adapting search distribution (unconstrained)
|
|
14
18
|
- **Gauss-Newton Method**: Efficient method for nonlinear least squares problems
|
|
15
19
|
- **Levenberg-Marquardt Algorithm**: Robust algorithm combining Gauss-Newton with damping
|
|
16
20
|
- **Constrained Gauss-Newton**: Efficient constrained nonlinear least squares using effective Jacobian
|
|
@@ -32,6 +36,35 @@ A flexible numerical optimization library for JavaScript/TypeScript that works s
|
|
|
32
36
|
npm install numopt-js
|
|
33
37
|
```
|
|
34
38
|
|
|
39
|
+
## Start Here (Pick Your Path)
|
|
40
|
+
|
|
41
|
+
- **Minimize a scalar cost** (smooth unconstrained optimization): use **Gradient Descent** (`cost: (p) => number`, `grad: (p) => Float64Array`). Start at [Gradient Descent](#gradient-descent).
|
|
42
|
+
- **Minimize a scalar cost (faster than GD for many problems)**: use **BFGS** or **L-BFGS** (`cost: (p) => number`, `grad: (p) => Float64Array`). Start at [BFGS / L-BFGS](#bfgs--l-bfgs).
|
|
43
|
+
- **Minimize a scalar cost (black-box, no gradient)**: use **CMA-ES** (`cost: (p) => number`). Start at [CMA-ES](#cma-es-black-box-optimization).
|
|
44
|
+
- **Fit a model with residuals** (nonlinear least squares): use **Levenberg–Marquardt** or **Gauss–Newton** (`residual: (p) => Float64Array`, optional `jacobian: (p) => Matrix`). Start at [Levenberg-Marquardt](#levenberg-marquardt-nonlinear-least-squares).
|
|
45
|
+
- **Equality-constrained problems** \(c(p, x) = 0\): use **Adjoint** / **Constrained GN/LM** (`constraint: (p, x) => Float64Array`). Start at [Adjoint Method](#adjoint-method-constrained-optimization).
|
|
46
|
+
- **Browser usage**: start at [Browser Usage](#browser-usage).
|
|
47
|
+
|
|
48
|
+
**Why `Float64Array`?** This library uses `Float64Array` for predictable numeric performance. You can always convert from normal arrays with `new Float64Array([1, 2, 3])` (more details below).
|
|
49
|
+
|
|
50
|
+
## Cost Function vs Residual Function (Important)
|
|
51
|
+
|
|
52
|
+
- **Cost function**: `cost(p) -> number` (used by `gradientDescent`)
|
|
53
|
+
- **Residual function**: `residual(p) -> Float64Array` (used by `gaussNewton` / `levenbergMarquardt`), where the library minimizes \(f(p) = 1/2 \|r(p)\|^2\)
|
|
54
|
+
|
|
55
|
+
## Result Object (What to Look At)
|
|
56
|
+
|
|
57
|
+
Most algorithms return a `result` with these fields:
|
|
58
|
+
|
|
59
|
+
- **Common (all algorithms)**: `finalParameters`, `converged`, `iterations`, `finalCost`
|
|
60
|
+
- **Gradient Descent**: `finalGradientNorm`, `usedLineSearch`
|
|
61
|
+
- **BFGS / L-BFGS**: `finalGradientNorm`
|
|
62
|
+
- **CMA-ES**: `functionEvaluations`, `finalStepSize`, `finalMaxStdDev`
|
|
63
|
+
- **Gauss-Newton / Levenberg–Marquardt**: `finalResidualNorm` (and LM also has `finalLambda`)
|
|
64
|
+
- **Constrained algorithms / Adjoint**: `finalStates`, `finalConstraintNorm`
|
|
65
|
+
|
|
66
|
+
Note: `result.parameters` is a deprecated alias of `result.finalParameters` and will be removed in a future release.
|
|
67
|
+
|
|
35
68
|
## 60-second Quick Start (Node)
|
|
36
69
|
|
|
37
70
|
Pick **one** of the following and run it.
|
|
@@ -55,6 +88,30 @@ const result = gradientDescent(new Float64Array([5, -3]), cost, grad, {
|
|
|
55
88
|
console.log(result.finalParameters);
|
|
56
89
|
```
|
|
57
90
|
|
|
91
|
+
## BFGS / L-BFGS
|
|
92
|
+
|
|
93
|
+
Use these for smooth unconstrained problems when you can provide a gradient.
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
import { bfgs, lbfgs } from 'numopt-js';
|
|
97
|
+
|
|
98
|
+
const cost = (params) => (params[0] - 1) ** 2 + (params[1] + 2) ** 2;
|
|
99
|
+
const grad = (params) => new Float64Array([2 * (params[0] - 1), 2 * (params[1] + 2)]);
|
|
100
|
+
|
|
101
|
+
const bfgsResult = bfgs(new Float64Array([10, 10]), cost, grad, {
|
|
102
|
+
maxIterations: 200,
|
|
103
|
+
tolerance: 1e-8
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const lbfgsResult = lbfgs(new Float64Array([10, 10]), cost, grad, {
|
|
107
|
+
maxIterations: 200,
|
|
108
|
+
tolerance: 1e-8,
|
|
109
|
+
historySize: 10
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
console.log(bfgsResult.finalParameters, lbfgsResult.finalParameters);
|
|
113
|
+
```
|
|
114
|
+
|
|
58
115
|
Run:
|
|
59
116
|
|
|
60
117
|
```bash
|
|
@@ -86,6 +143,48 @@ Run:
|
|
|
86
143
|
node quick.cjs
|
|
87
144
|
```
|
|
88
145
|
|
|
146
|
+
## CMA-ES (Black-box Optimization)
|
|
147
|
+
|
|
148
|
+
Use CMA-ES when your cost function is a **black box** and you **don’t have a gradient**.
|
|
149
|
+
|
|
150
|
+
Two important inputs:
|
|
151
|
+
- `initialStepSize` (sigma0): your initial error guess / search radius
|
|
152
|
+
- `randomSeed`: set this for reproducible runs (recommended for testing and debugging)
|
|
153
|
+
- `restartStrategy`: use `"ipop"` for multi-modal problems (λ doubles per restart)
|
|
154
|
+
- `profiling`: enable lightweight timing breakdown in the result
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
import { cmaEs } from 'numopt-js';
|
|
158
|
+
|
|
159
|
+
const sphere = (params) => params.reduce((sum, v) => sum + v * v, 0);
|
|
160
|
+
|
|
161
|
+
const result = cmaEs(new Float64Array([10, -7, 3, 5]), sphere, {
|
|
162
|
+
maxIterations: 200,
|
|
163
|
+
populationSize: 20,
|
|
164
|
+
initialStepSize: 2.0,
|
|
165
|
+
randomSeed: 123456,
|
|
166
|
+
targetCost: 1e-10,
|
|
167
|
+
restartStrategy: "none",
|
|
168
|
+
profiling: true,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
console.log(result.finalParameters, result.finalCost, result.stopReason, result.profiling);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### IPOP (Restart Strategy)
|
|
175
|
+
|
|
176
|
+
For multi-modal problems (e.g., Rastrigin), use IPOP restarts:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
const result = cmaEs(new Float64Array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5]), sphere, {
|
|
180
|
+
maxIterations: 1200,
|
|
181
|
+
populationSize: 20,
|
|
182
|
+
initialStepSize: 2.5,
|
|
183
|
+
randomSeed: 123456,
|
|
184
|
+
restartStrategy: "ipop",
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
89
188
|
## Node Usage (ESM + CommonJS)
|
|
90
189
|
|
|
91
190
|
numopt-js supports both ESM (`import`) and CommonJS (`require`) in Node.js.
|
|
@@ -113,6 +212,10 @@ const { gradientDescent } = require('numopt-js');
|
|
|
113
212
|
|
|
114
213
|
numopt-js is designed to work seamlessly in browser environments. The library automatically provides a browser-optimized bundle that includes all dependencies.
|
|
115
214
|
|
|
215
|
+
**Important**:
|
|
216
|
+
- **Don’t use `file://`** for the import-maps / direct-import examples. Serve your files via a local static server (for example: `npx serve`, `python -m http.server`, or Vite) so ES modules load correctly.
|
|
217
|
+
- **SSR frameworks (Next.js, etc.)**: run numopt-js on the client side. If you hit SSR errors, move the code into a client component (`"use client"`) or dynamically import it with SSR disabled.
|
|
218
|
+
|
|
116
219
|
### Option 1: Bundler (Recommended)
|
|
117
220
|
|
|
118
221
|
If you're using a bundler (Vite/Webpack/Rollup), just import from the package and the bundler will resolve the browser build via `package.json` exports.
|
|
@@ -177,6 +280,15 @@ After installing dependencies with `npm install`, you can run the example script
|
|
|
177
280
|
- `npm run example:constrained-gauss-newton` — constrained least squares via effective Jacobian
|
|
178
281
|
- `npm run example:constrained-lm` — constrained Levenberg–Marquardt
|
|
179
282
|
|
|
283
|
+
**Start with these (recommended reading order):**
|
|
284
|
+
|
|
285
|
+
- `npm run example:gradient` — smallest end-to-end example (scalar cost + gradient)
|
|
286
|
+
- `npm run example:rosenbrock` — shows why line search matters on a classic non-convex problem
|
|
287
|
+
- `npm run example:lm` — first least-squares example (residuals, optional numeric Jacobian)
|
|
288
|
+
- `npm run example:constrained-gauss-newton` — first constrained least-squares example
|
|
289
|
+
- `npm run example:constrained-lm` — robust constrained least-squares (damping)
|
|
290
|
+
- `npm run example:adjoint` — constrained optimization with states \(x\) and parameters \(p\)
|
|
291
|
+
|
|
180
292
|
**Pick an algorithm:**
|
|
181
293
|
|
|
182
294
|
- Gradient Descent — stable first choice for smooth problems (see below)
|
|
@@ -350,6 +462,10 @@ const gradientFn = createFiniteDiffGradient(costFn, { stepSize: 1e-8 });
|
|
|
350
462
|
const gradient = finiteDiffGradient(params, costFn, { stepSize: 1e-8 });
|
|
351
463
|
```
|
|
352
464
|
|
|
465
|
+
**Practical tips (finite differences)**:
|
|
466
|
+
- **Scale your parameters** so typical values are around \(O(1)\). If one parameter is \(10^{-6}\) and another is \(10^{6}\), a single global `stepSize` will often fail.
|
|
467
|
+
- If you work in physical units, consider **normalizing inputs/parameters** first, then convert back after optimization.
|
|
468
|
+
|
|
353
469
|
### Adjoint Method (Constrained Optimization)
|
|
354
470
|
|
|
355
471
|
The adjoint method efficiently solves constrained optimization problems by solving for an adjoint variable λ instead of explicitly inverting matrices. This requires solving only one linear system per iteration, making it much more efficient than naive approaches.
|
|
@@ -636,7 +752,7 @@ Extends `ConstrainedGaussNewtonOptions` with:
|
|
|
636
752
|
- `tolStep?: number` - Tolerance for step size convergence (default: 1e-6)
|
|
637
753
|
- `tolResidual?: number` - Tolerance for residual norm convergence (default: 1e-6)
|
|
638
754
|
|
|
639
|
-
**Note**: The constraint function `c(p, x)` does not need to return a vector with the same length as the state vector `x`. The
|
|
755
|
+
**Note**: The constraint function `c(p, x)` does not need to return a vector with the same length as the state vector `x`. The constrained solvers support both square and non-square constraint Jacobians (overdetermined and underdetermined systems) by solving the relevant linear systems in a least-squares sense (with regularization when needed). If you see instability, try scaling/normalizing your states/constraints.
|
|
640
756
|
|
|
641
757
|
#### Numerical Differentiation Options
|
|
642
758
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file implements the (dense) BFGS algorithm for unconstrained smooth optimization.
|
|
3
|
+
*
|
|
4
|
+
* Role in system:
|
|
5
|
+
* - Quasi-Newton optimizer for scalar cost functions with user-provided gradients
|
|
6
|
+
* - Uses Strong Wolfe line search to encourage curvature conditions needed for stable updates
|
|
7
|
+
* - Dense method: stores a full inverse Hessian approximation (O(n^2) memory)
|
|
8
|
+
*
|
|
9
|
+
* For first-time readers:
|
|
10
|
+
* - Start with `bfgs` (main entry point)
|
|
11
|
+
* - Then read `updateInverseHessianApproximation` (core BFGS update)
|
|
12
|
+
* - Finally, check safeguard helpers (descent direction / curvature checks)
|
|
13
|
+
*/
|
|
14
|
+
import type { BfgsOptions, CostFn, GradientFn, OptimizationResult } from './types.js';
|
|
15
|
+
export declare function bfgs(initialParameters: Float64Array, costFunction: CostFn, gradientFunction: GradientFn, options?: BfgsOptions): OptimizationResult;
|
|
16
|
+
//# sourceMappingURL=bfgs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bfgs.d.ts","sourceRoot":"","sources":["../../src/core/bfgs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgItF,wBAAgB,IAAI,CAClB,iBAAiB,EAAE,YAAY,EAC/B,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,UAAU,EAC5B,OAAO,GAAE,WAAgB,GACxB,kBAAkB,CA2FpB"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file implements the (dense) BFGS algorithm for unconstrained smooth optimization.
|
|
3
|
+
*
|
|
4
|
+
* Role in system:
|
|
5
|
+
* - Quasi-Newton optimizer for scalar cost functions with user-provided gradients
|
|
6
|
+
* - Uses Strong Wolfe line search to encourage curvature conditions needed for stable updates
|
|
7
|
+
* - Dense method: stores a full inverse Hessian approximation (O(n^2) memory)
|
|
8
|
+
*
|
|
9
|
+
* For first-time readers:
|
|
10
|
+
* - Start with `bfgs` (main entry point)
|
|
11
|
+
* - Then read `updateInverseHessianApproximation` (core BFGS update)
|
|
12
|
+
* - Finally, check safeguard helpers (descent direction / curvature checks)
|
|
13
|
+
*/
|
|
14
|
+
import { Matrix } from 'ml-matrix';
|
|
15
|
+
import { strongWolfeLineSearch } from './lineSearch.js';
|
|
16
|
+
import { Logger } from './logger.js';
|
|
17
|
+
import { checkGradientConvergence, createConvergenceResult } from './convergence.js';
|
|
18
|
+
import { addVectors, dotProduct, scaleVector, subtractVectors, vectorNorm } from '../utils/matrix.js';
|
|
19
|
+
const DEFAULT_MAX_ITERATIONS = 1000;
|
|
20
|
+
const DEFAULT_TOLERANCE = 1e-6;
|
|
21
|
+
const DEFAULT_USE_LINE_SEARCH = true;
|
|
22
|
+
const DEFAULT_FIXED_STEP_SIZE = 1.0;
|
|
23
|
+
const INVALID_STEP_SIZE = 0.0;
|
|
24
|
+
const NEGATIVE_GRADIENT_DIRECTION = -1.0;
|
|
25
|
+
const MINIMUM_CURVATURE_THRESHOLD = 1e-10;
|
|
26
|
+
function createIdentityMatrix(dimension) {
|
|
27
|
+
// NOTE: Provide both dimensions to stay compatible with our (older) local typing history
|
|
28
|
+
// and with ml-matrix's API where `columns` is optional.
|
|
29
|
+
return Matrix.eye(dimension, dimension);
|
|
30
|
+
}
|
|
31
|
+
function multiplyMatrixVector(matrix, vector) {
|
|
32
|
+
const result = new Float64Array(vector.length);
|
|
33
|
+
for (let rowIndex = 0; rowIndex < matrix.rows; rowIndex++) {
|
|
34
|
+
let sum = 0.0;
|
|
35
|
+
for (let columnIndex = 0; columnIndex < matrix.columns; columnIndex++) {
|
|
36
|
+
sum += matrix.get(rowIndex, columnIndex) * vector[columnIndex];
|
|
37
|
+
}
|
|
38
|
+
result[rowIndex] = sum;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function computeBfgsSearchDirection(inverseHessianApproximation, currentGradient) {
|
|
43
|
+
const approximateNewtonDirection = multiplyMatrixVector(inverseHessianApproximation, currentGradient);
|
|
44
|
+
return scaleVector(approximateNewtonDirection, NEGATIVE_GRADIENT_DIRECTION);
|
|
45
|
+
}
|
|
46
|
+
function ensureDescentDirectionOrFallback(currentGradient, proposedSearchDirection, currentInverseHessianApproximation, logger, iteration, currentCost) {
|
|
47
|
+
const directionalDerivative = dotProduct(currentGradient, proposedSearchDirection);
|
|
48
|
+
const isDescentDirection = directionalDerivative < 0.0;
|
|
49
|
+
if (isDescentDirection) {
|
|
50
|
+
return { searchDirection: proposedSearchDirection, inverseHessianApproximation: currentInverseHessianApproximation };
|
|
51
|
+
}
|
|
52
|
+
// WHY: If numerical issues yield a non-descent direction, reset H to identity and fall back to -g.
|
|
53
|
+
logger.warn('bfgs', iteration, 'Non-descent direction detected; resetting inverse Hessian and using negative gradient.', [
|
|
54
|
+
{ key: 'Cost:', value: currentCost },
|
|
55
|
+
{ key: 'Directional derivative:', value: directionalDerivative }
|
|
56
|
+
]);
|
|
57
|
+
return {
|
|
58
|
+
searchDirection: scaleVector(currentGradient, NEGATIVE_GRADIENT_DIRECTION),
|
|
59
|
+
inverseHessianApproximation: createIdentityMatrix(currentGradient.length)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function updateInverseHessianApproximation(inverseHessianApproximation, stepVector, gradientChangeVector, logger, iteration, currentCost) {
|
|
63
|
+
const stepDotGradientChange = dotProduct(stepVector, gradientChangeVector);
|
|
64
|
+
const curvatureIsTooWeak = stepDotGradientChange <= MINIMUM_CURVATURE_THRESHOLD;
|
|
65
|
+
if (curvatureIsTooWeak) {
|
|
66
|
+
// WHY: If curvature is weak/negative, the BFGS update can break positive definiteness.
|
|
67
|
+
logger.warn('bfgs', iteration, 'Curvature condition too weak; resetting inverse Hessian approximation.', [
|
|
68
|
+
{ key: 'Cost:', value: currentCost },
|
|
69
|
+
{ key: 'stepDotGradientChange:', value: stepDotGradientChange }
|
|
70
|
+
]);
|
|
71
|
+
return createIdentityMatrix(stepVector.length);
|
|
72
|
+
}
|
|
73
|
+
const curvatureScaling = 1.0 / stepDotGradientChange;
|
|
74
|
+
const stepMatrix = Matrix.columnVector(Array.from(stepVector));
|
|
75
|
+
const gradientChangeMatrix = Matrix.columnVector(Array.from(gradientChangeVector));
|
|
76
|
+
const identityMatrix = createIdentityMatrix(stepVector.length);
|
|
77
|
+
const stepGradientOuterProduct = stepMatrix.mmul(gradientChangeMatrix.transpose()).mul(curvatureScaling);
|
|
78
|
+
const gradientStepOuterProduct = gradientChangeMatrix.mmul(stepMatrix.transpose()).mul(curvatureScaling);
|
|
79
|
+
const leftFactor = identityMatrix.sub(stepGradientOuterProduct);
|
|
80
|
+
const rightFactor = identityMatrix.sub(gradientStepOuterProduct);
|
|
81
|
+
const rankTwoPart = leftFactor.mmul(inverseHessianApproximation).mmul(rightFactor);
|
|
82
|
+
const rankOnePart = stepMatrix.mmul(stepMatrix.transpose()).mul(curvatureScaling);
|
|
83
|
+
return rankTwoPart.add(rankOnePart);
|
|
84
|
+
}
|
|
85
|
+
function computeNextParameters(currentParameters, searchDirection, stepSize) {
|
|
86
|
+
const stepVector = scaleVector(searchDirection, stepSize);
|
|
87
|
+
return addVectors(currentParameters, stepVector);
|
|
88
|
+
}
|
|
89
|
+
function handleLineSearchFailure(currentParameters, iteration, currentCost, gradientNorm, logger) {
|
|
90
|
+
logger.warn('bfgs', iteration, 'Line search failed (non-descent direction).', [
|
|
91
|
+
{ key: 'Cost:', value: currentCost },
|
|
92
|
+
{ key: 'Gradient norm:', value: gradientNorm }
|
|
93
|
+
]);
|
|
94
|
+
return {
|
|
95
|
+
finalParameters: currentParameters,
|
|
96
|
+
parameters: currentParameters,
|
|
97
|
+
iterations: iteration + 1,
|
|
98
|
+
converged: false,
|
|
99
|
+
finalCost: currentCost,
|
|
100
|
+
finalGradientNorm: gradientNorm
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function bfgs(initialParameters, costFunction, gradientFunction, options = {}) {
|
|
104
|
+
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
105
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE;
|
|
106
|
+
const useLineSearch = options.useLineSearch ?? DEFAULT_USE_LINE_SEARCH;
|
|
107
|
+
const fixedStepSize = options.stepSize ?? DEFAULT_FIXED_STEP_SIZE;
|
|
108
|
+
const onIteration = options.onIteration;
|
|
109
|
+
const logger = new Logger(options.logLevel, options.verbose);
|
|
110
|
+
let currentParameters = new Float64Array(initialParameters);
|
|
111
|
+
let currentCost = costFunction(currentParameters);
|
|
112
|
+
let inverseHessianApproximation = createIdentityMatrix(currentParameters.length);
|
|
113
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
114
|
+
const currentGradient = gradientFunction(currentParameters);
|
|
115
|
+
const gradientNorm = vectorNorm(currentGradient);
|
|
116
|
+
if (onIteration)
|
|
117
|
+
onIteration(iteration, currentCost, currentParameters);
|
|
118
|
+
if (checkGradientConvergence(gradientNorm, tolerance, iteration)) {
|
|
119
|
+
logger.info('bfgs', iteration, 'Converged', [
|
|
120
|
+
{ key: 'Cost:', value: currentCost },
|
|
121
|
+
{ key: 'Gradient norm:', value: gradientNorm }
|
|
122
|
+
]);
|
|
123
|
+
return createConvergenceResult(currentParameters, iteration, true, currentCost, gradientNorm);
|
|
124
|
+
}
|
|
125
|
+
const proposedSearchDirection = computeBfgsSearchDirection(inverseHessianApproximation, currentGradient);
|
|
126
|
+
const descentResult = ensureDescentDirectionOrFallback(currentGradient, proposedSearchDirection, inverseHessianApproximation, logger, iteration, currentCost);
|
|
127
|
+
const searchDirection = descentResult.searchDirection;
|
|
128
|
+
inverseHessianApproximation = descentResult.inverseHessianApproximation;
|
|
129
|
+
const stepSize = useLineSearch
|
|
130
|
+
? strongWolfeLineSearch(costFunction, gradientFunction, currentParameters, searchDirection, options.lineSearchOptions)
|
|
131
|
+
: fixedStepSize;
|
|
132
|
+
if (stepSize === INVALID_STEP_SIZE) {
|
|
133
|
+
return handleLineSearchFailure(currentParameters, iteration, currentCost, gradientNorm, logger);
|
|
134
|
+
}
|
|
135
|
+
const newParameters = computeNextParameters(currentParameters, searchDirection, stepSize);
|
|
136
|
+
const stepVector = subtractVectors(newParameters, currentParameters);
|
|
137
|
+
const stepNorm = vectorNorm(stepVector);
|
|
138
|
+
const newCost = costFunction(newParameters);
|
|
139
|
+
const newGradient = gradientFunction(newParameters);
|
|
140
|
+
const gradientChangeVector = subtractVectors(newGradient, currentGradient);
|
|
141
|
+
inverseHessianApproximation = updateInverseHessianApproximation(inverseHessianApproximation, stepVector, gradientChangeVector, logger, iteration, newCost);
|
|
142
|
+
logger.debug('bfgs', iteration, 'Progress', [
|
|
143
|
+
{ key: 'Cost:', value: currentCost },
|
|
144
|
+
{ key: 'Gradient norm:', value: gradientNorm },
|
|
145
|
+
{ key: 'Step size:', value: stepSize },
|
|
146
|
+
{ key: 'Step norm:', value: stepNorm }
|
|
147
|
+
]);
|
|
148
|
+
currentParameters = new Float64Array(newParameters);
|
|
149
|
+
currentCost = newCost;
|
|
150
|
+
}
|
|
151
|
+
const finalGradient = gradientFunction(currentParameters);
|
|
152
|
+
const finalGradientNorm = vectorNorm(finalGradient);
|
|
153
|
+
logger.warn('bfgs', undefined, 'Maximum iterations reached', [
|
|
154
|
+
{ key: 'Iterations:', value: maxIterations },
|
|
155
|
+
{ key: 'Final cost:', value: currentCost },
|
|
156
|
+
{ key: 'Final gradient norm:', value: finalGradientNorm }
|
|
157
|
+
]);
|
|
158
|
+
return {
|
|
159
|
+
finalParameters: currentParameters,
|
|
160
|
+
parameters: currentParameters,
|
|
161
|
+
iterations: maxIterations,
|
|
162
|
+
converged: false,
|
|
163
|
+
finalCost: currentCost,
|
|
164
|
+
finalGradientNorm: finalGradientNorm
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=bfgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bfgs.js","sourceRoot":"","sources":["../../src/core/bfgs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file implements vanilla CMA-ES and IPOP-CMA-ES restart strategy
|
|
3
|
+
* for unconstrained black-box optimization (no gradients required).
|
|
4
|
+
*
|
|
5
|
+
* Role in system:
|
|
6
|
+
* - Provides a derivative-free optimizer for scalar cost functions
|
|
7
|
+
* - Adds IPOP restarts (λ doubles per restart) while preserving libcmaes semantics
|
|
8
|
+
* - Mirrors libcmaes default parameter formulas and core stop criteria
|
|
9
|
+
*
|
|
10
|
+
* For first-time readers:
|
|
11
|
+
* - Start with `cmaEs()` (public entry point)
|
|
12
|
+
* - `runSingleCmaEs()` executes one CMA-ES run (no restarts)
|
|
13
|
+
* - Restart logic wraps `runSingleCmaEs()` when `restartStrategy: "ipop"`
|
|
14
|
+
*/
|
|
15
|
+
import type { CostFn, CmaEsOptions, CmaEsResult } from './types.js';
|
|
16
|
+
export declare function cmaEs(initialParameters: Float64Array, costFunction: CostFn, options?: CmaEsOptions): CmaEsResult;
|
|
17
|
+
//# sourceMappingURL=cmaEs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cmaEs.d.ts","sourceRoot":"","sources":["../../src/core/cmaEs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyvBpE,wBAAgB,KAAK,CACnB,iBAAiB,EAAE,YAAY,EAC/B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,YAAiB,GACzB,WAAW,CAsIb"}
|