@gkucmierz/utils 1.30.3 → 1.32.1

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  # @gkucmierz/utils
2
2
 
3
+ [![Socket Badge](https://socket.dev/api/badge/npm/package/@gkucmierz/utils)](https://socket.dev/npm/package/@gkucmierz/utils)
3
4
  ![NPM Version](https://img.shields.io/npm/v/@gkucmierz/utils)
4
5
  ![License](https://img.shields.io/npm/l/@gkucmierz/utils)
5
6
  ![Downloads](https://img.shields.io/npm/dm/@gkucmierz/utils)
package/main.mjs CHANGED
@@ -14,6 +14,9 @@ import {
14
14
  import {
15
15
  binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl
16
16
  } from './src/binary-search.mjs'
17
+ import {
18
+ consumeIteratorNonBlocking
19
+ } from './src/consume-iterator.mjs'
17
20
  import {
18
21
  copyCase
19
22
  } from './src/copy-case.mjs'
@@ -47,6 +50,9 @@ import {
47
50
  import {
48
51
  ListNode
49
52
  } from './src/list-node.mjs'
53
+ import {
54
+ axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball
55
+ } from './src/math3d.mjs'
50
56
  import {
51
57
  matrixAsArray
52
58
  } from './src/matrix.mjs'
@@ -56,6 +62,15 @@ import {
56
62
  import {
57
63
  mod, modBI
58
64
  } from './src/mod.mjs'
65
+ import {
66
+ naturalSearch
67
+ } from './src/natural-search.mjs'
68
+ import {
69
+ nelderMead
70
+ } from './src/nelder-mead.mjs'
71
+ import {
72
+ particleSwarmOptimization
73
+ } from './src/particle-swarm.mjs'
59
74
  import {
60
75
  phi, phiBI
61
76
  } from './src/phi.mjs'
@@ -65,21 +80,22 @@ import {
65
80
  import {
66
81
  array2range, range2array
67
82
  } from './src/range-array.mjs'
83
+ import {
84
+ simulatedAnnealing
85
+ } from './src/simulated-annealing.mjs'
68
86
  import {
69
87
  squareRoot, squareRootBI
70
88
  } from './src/square-root.mjs'
71
89
  import {
72
90
  tonelliShanksBI
73
91
  } from './src/tonelli-shanks.mjs'
74
- import {
75
- naturalSearch
76
- } from './src/natural-search.mjs'
77
92
 
78
93
  export * from './src/SetCnt.mjs';
79
94
  export * from './src/Trie.mjs';
80
95
  export * from './src/base64.mjs';
81
96
  export * from './src/bijective-numeration.mjs';
82
97
  export * from './src/binary-search.mjs';
98
+ export * from './src/consume-iterator.mjs';
83
99
  export * from './src/copy-case.mjs';
84
100
  export * from './src/egcd.mjs';
85
101
  export * from './src/factors.mjs';
@@ -91,16 +107,20 @@ export * from './src/heap.mjs';
91
107
  export * from './src/herons-formula.mjs';
92
108
  export * from './src/lcm.mjs';
93
109
  export * from './src/list-node.mjs';
110
+ export * from './src/math3d.mjs';
94
111
  export * from './src/matrix.mjs';
95
112
  export * from './src/memoize.mjs';
96
113
  export * from './src/mod.mjs';
114
+ export * from './src/natural-search.mjs';
115
+ export * from './src/nelder-mead.mjs';
116
+ export * from './src/particle-swarm.mjs';
97
117
  export * from './src/phi.mjs';
98
118
  export * from './src/pow-mod.mjs';
99
119
  export * from './src/range-array.mjs';
120
+ export * from './src/simulated-annealing.mjs';
100
121
  export * from './src/square-root.mjs';
101
122
  export * from './src/tonelli-shanks.mjs';
102
- export * from './src/natural-search.mjs';
103
123
 
104
124
  export default [
105
- SetCnt, Trie, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, copyCase, egcd, factors, factorsBI, formatBigNumber, formatBigNumberBI, wrapFn, gcd, gcdBI, getType, gpn, gpnBI, Heap, heronsFormula, heronsFormulaBI, lcm, lcmBI, ListNode, matrixAsArray, memoize, mod, modBI, phi, phiBI, powMod, powModBI, array2range, range2array, squareRoot, squareRootBI, tonelliShanksBI, naturalSearch
125
+ SetCnt, Trie, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, consumeIteratorNonBlocking, copyCase, egcd, factors, factorsBI, formatBigNumber, formatBigNumberBI, wrapFn, gcd, gcdBI, getType, gpn, gpnBI, Heap, heronsFormula, heronsFormulaBI, lcm, lcmBI, ListNode, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, memoize, mod, modBI, naturalSearch, nelderMead, particleSwarmOptimization, phi, phiBI, powMod, powModBI, array2range, range2array, simulatedAnnealing, squareRoot, squareRootBI, tonelliShanksBI
106
126
  ];
package/package.json CHANGED
@@ -1,31 +1,34 @@
1
1
  {
2
2
  "name": "@gkucmierz/utils",
3
- "version": "1.30.3",
3
+ "version": "1.32.1",
4
+ "type": "module",
4
5
  "description": "Usefull functions for solving programming tasks",
5
6
  "keywords": [
6
- "utils",
7
7
  "algorithms",
8
- "math",
9
- "number-theory",
8
+ "base64",
9
+ "bijective",
10
+ "binary-search",
11
+ "competitive-programming",
12
+ "data-structures",
13
+ "factors",
10
14
  "gcd",
15
+ "heap",
16
+ "iterator",
17
+ "javascript",
11
18
  "lcm",
12
- "factors",
13
- "phi",
19
+ "math",
20
+ "memoize",
14
21
  "mod",
22
+ "node",
23
+ "non-blocking",
24
+ "number-theory",
25
+ "phi",
15
26
  "pow-mod",
16
- "binary-search",
17
- "heap",
18
- "trie",
19
- "base64",
20
- "bijective",
21
- "tonelli-shanks",
22
- "square-root",
23
- "memoize",
24
27
  "range-array",
25
- "javascript",
26
- "node",
27
- "competitive-programming",
28
- "data-structures"
28
+ "square-root",
29
+ "tonelli-shanks",
30
+ "trie",
31
+ "utils"
29
32
  ],
30
33
  "files": [
31
34
  "main.mjs",
@@ -35,20 +38,25 @@
35
38
  "scripts": {
36
39
  "test": "jasmine",
37
40
  "watch": "nodemon --exec 'npm test'",
38
- "main": "node scripts/generate_main.mjs",
39
41
  "docs": "npm run docs:build; npm run docs:open",
42
+ "build:main": "node scripts/generate_main.mjs",
40
43
  "docs:build": "jsdoc -c jsdoc.json --readme README.md",
41
- "docs:open": "open docs/index.html"
44
+ "docs:open": "open docs/index.html",
45
+ "lint": "eslint .",
46
+ "prepare": "husky"
42
47
  },
43
48
  "repository": {
44
49
  "type": "git",
45
- "url": "git+https://github.com/gkucmierz/utils"
50
+ "url": "git+https://gitea.7u.pl/gkucmierz/utils.git"
46
51
  },
47
52
  "author": "Grzegorz Kućmierz",
48
53
  "license": "MIT",
49
54
  "devDependencies": {
55
+ "@eslint/js": "^10.0.1",
50
56
  "docdash": "^2.0.2",
51
- "husky": "^8.0.1",
57
+ "eslint": "^10.1.0",
58
+ "globals": "^17.4.0",
59
+ "husky": "^8.0.3",
52
60
  "jasmine": "^4.4.0",
53
61
  "jsdoc": "^4.0.4",
54
62
  "nodemon": "^3.1.14"
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Consume iterator values asynchronously utilizing macro-task queue
3
+ * to prevent Event Loop blocking.
4
+ *
5
+ * @param {Iterator} iter - Iterator object with .next() method returning {value, done}
6
+ * @param {function} progressFn - Callback function executed for each iteration Step
7
+ * @returns {Promise} - Resolves when the iterator is fully consumed
8
+ */
9
+ export const consumeIteratorNonBlocking = (iter, progressFn) => {
10
+ return new Promise((resolve, reject) => {
11
+ const loop = () => {
12
+ try {
13
+ const { value, done } = iter.next();
14
+ if (done) {
15
+ resolve();
16
+ } else {
17
+ progressFn(value);
18
+ setTimeout(loop, 0);
19
+ }
20
+ } catch (err) {
21
+ reject(err);
22
+ }
23
+ };
24
+ loop();
25
+ });
26
+ };
package/src/math3d.mjs ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure 3D Math Engine for Arcball/Trackball raw matrix transformations
3
+ */
4
+
5
+ export const crossProduct = (u, v) => [
6
+ u[1]*v[2] - u[2]*v[1],
7
+ u[2]*v[0] - u[0]*v[2],
8
+ u[0]*v[1] - u[1]*v[0]
9
+ ];
10
+
11
+ export const dotProduct = (u, v) => u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
12
+
13
+ export const normalize = (v) => {
14
+ const len = Math.hypot(v[0], v[1], v[2]);
15
+ if (len === 0) return [0, 0, 0];
16
+ return [v[0]/len, v[1]/len, v[2]/len];
17
+ };
18
+
19
+ export const projectToTrackball = (x, y, radius = 1) => {
20
+ const d2 = x*x + y*y;
21
+ const r2 = radius * radius;
22
+ if (d2 <= r2 * 0.5) {
23
+ return normalize([x, y, Math.sqrt(r2 - d2)]);
24
+ } else {
25
+ return normalize([x, y, (r2 * 0.5) / Math.sqrt(d2)]);
26
+ }
27
+ };
28
+
29
+ export const axisAngleToMatrix4 = (axis, angle) => {
30
+ const [x, y, z] = normalize(axis);
31
+ const c = Math.cos(angle);
32
+ const s = Math.sin(angle);
33
+ const t = 1 - c;
34
+
35
+ // Column-major array mapping (standard for WebGL and Three.js matrices)
36
+ return [
37
+ c + x*x*t, y*x*t + z*s, z*x*t - y*s, 0,
38
+ x*y*t - z*s, c + y*y*t, z*y*t + x*s, 0,
39
+ x*z*t + y*s, y*z*t - x*s, c + z*z*t, 0,
40
+ 0, 0, 0, 1
41
+ ];
42
+ };
43
+
44
+ export const multiplyMatrix4 = (a, b) => {
45
+ const out = new Array(16);
46
+ for (let c = 0; c < 4; c++) {
47
+ for (let r = 0; r < 4; r++) {
48
+ let sum = 0;
49
+ for (let i = 0; i < 4; i++) {
50
+ sum += a[i*4 + r] * b[c*4 + i]; // Note: column-major alignment
51
+ }
52
+ out[c*4 + r] = sum;
53
+ }
54
+ }
55
+ return out;
56
+ };
57
+
58
+ export const getRotationMatrixFromVectors = (u, v) => {
59
+ const axis = crossProduct(u, v);
60
+ if (axis[0] === 0 && axis[1] === 0 && axis[2] === 0) {
61
+ return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]; // Identity
62
+ }
63
+ const dot = dotProduct(u, v);
64
+ const angle = Math.acos(Math.max(-1, Math.min(1, dot)));
65
+ return axisAngleToMatrix4(axis, angle);
66
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Nelder-Mead Simplex Algorithm (Direct Search Gradient-Free Optimization)
3
+ * Maximizes continuous N-dimensional spaces through contraction and expansion.
4
+ *
5
+ * @param {Function} evaluate - function(paramsArray) => Number (score to maximize)
6
+ * @param {Array<Array<Number>>} bounds - [[min1, max1], ... ] bounds per dimension
7
+ * @param {Object} options - { steps: 30, onProgress: async (state) => {} }
8
+ * @returns {Promise<{bestParams: Array<Number>, bestScore: Number}>}
9
+ */
10
+ export const nelderMead = async (evaluate, bounds, options = {}) => {
11
+ const steps = options.steps || 30;
12
+ const onProgress = options.onProgress || (async () => {});
13
+ const dim = bounds.length;
14
+
15
+ let simplex = [];
16
+ let baseCoords = bounds.map(b => b[0] + (b[1] - b[0]) * 0.5); // Start directly centered
17
+
18
+ // To build an initial N-dimensional simplex, we require N+1 vertices
19
+ for (let i = 0; i <= dim; i++) {
20
+ let p = [...baseCoords];
21
+ if (i > 0) {
22
+ // Offset each dimension successively to form the simplex
23
+ let range = bounds[i-1][1] - bounds[i-1][0];
24
+ p[i-1] += range * 0.2; // 20% span stretch
25
+ }
26
+ for(let d=0; d<dim; d++) p[d] = Math.max(bounds[d][0], Math.min(bounds[d][1], p[d]));
27
+
28
+ simplex.push({ position: p, score: evaluate(p) });
29
+ }
30
+
31
+ const alpha = 1.0; // Reflection
32
+ const gamma = 2.0; // Expansion
33
+ const rho = 0.5; // Contraction
34
+ const sigma = 0.5; // Shrink
35
+
36
+ for (let step = 0; step < steps; step++) {
37
+ // Sort descending layout (Simplex optimization seeks maximization)
38
+ simplex.sort((a, b) => b.score - a.score);
39
+
40
+ let best = simplex[0];
41
+ let worst = simplex[dim];
42
+ let secondWorst = simplex[dim - 1];
43
+
44
+ // Compute the spatial centroid of all nodes excluding the worst
45
+ let centroid = new Array(dim).fill(0);
46
+ for (let i = 0; i < dim; i++) {
47
+ for (let d = 0; d < dim; d++) centroid[d] += simplex[i].position[d];
48
+ }
49
+ for (let d = 0; d < dim; d++) centroid[d] /= dim;
50
+
51
+ // Reflection Hook
52
+ let xr = centroid.map((c, d) => c + alpha * (c - worst.position[d]));
53
+ xr = xr.map((v, d) => Math.max(bounds[d][0], Math.min(bounds[d][1], v)));
54
+ let rScore = evaluate(xr);
55
+
56
+ if (rScore > best.score) {
57
+ // Expansion Hook
58
+ let xe = centroid.map((c, d) => c + gamma * (xr[d] - c));
59
+ xe = xe.map((v, d) => Math.max(bounds[d][0], Math.min(bounds[d][1], v)));
60
+ let eScore = evaluate(xe);
61
+ simplex[dim] = eScore > rScore ? { position: xe, score: eScore } : { position: xr, score: rScore };
62
+ } else if (rScore > secondWorst.score) {
63
+ // Reflected vertex outperforms second-worst node
64
+ simplex[dim] = { position: xr, score: rScore };
65
+ } else {
66
+ // Contraction Hook (if reflection completely failed)
67
+ let xc = centroid.map((c, d) => c + rho * (worst.position[d] - c));
68
+ xc = xc.map((v, d) => Math.max(bounds[d][0], Math.min(bounds[d][1], v)));
69
+ let cScore = evaluate(xc);
70
+ if (cScore > worst.score) {
71
+ simplex[dim] = { position: xc, score: cScore };
72
+ } else {
73
+ // Complete Simplex Shrinkage towards the primary maximum best point
74
+ for (let i = 1; i <= dim; i++) {
75
+ simplex[i].position = simplex[i].position.map((v, d) => best.position[d] + sigma * (v - best.position[d]));
76
+ simplex[i].position = simplex[i].position.map((v, d) => Math.max(bounds[d][0], Math.min(bounds[d][1], v)));
77
+ simplex[i].score = evaluate(simplex[i].position);
78
+ }
79
+ }
80
+ }
81
+
82
+ simplex.sort((a, b) => b.score - a.score);
83
+
84
+ await onProgress({
85
+ step,
86
+ totalSteps: steps,
87
+ bestScore: simplex[0].score,
88
+ bestParams: simplex[0].position,
89
+ simplexVisNodes: simplex.map(s => s.position)
90
+ });
91
+ }
92
+
93
+ simplex.sort((a, b) => b.score - a.score);
94
+ return { bestParams: simplex[0].position, bestScore: simplex[0].score };
95
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Particle Swarm Optimization (PSO)
3
+ * A stochastic, population-based metaheuristic for maximizing complex multidimensional bounds.
4
+ *
5
+ * @param {Function} evaluate - function(paramsArray) => Number (score to maximize)
6
+ * @param {Array<Array<Number>>} bounds - [[min1, max1], [min2, max2], ...] bounds per dimension
7
+ * @param {Object} options - { particles: 10, steps: 30, w: 0.5, c1: 1.5, c2: 1.5, onProgress: async (state) => {} }
8
+ * @returns {Promise<{bestParams: Array<Number>, bestScore: Number}>}
9
+ */
10
+ export const particleSwarmOptimization = async (evaluate, bounds, options = {}) => {
11
+ const numParticles = options.particles || 12;
12
+ const steps = options.steps || 30;
13
+ const w = options.w || 0.5; // Inertia weight
14
+ const c1 = options.c1 || 1.5; // Cognitive (local) best weight
15
+ const c2 = options.c2 || 1.5; // Social (global) best weight
16
+ const onProgress = options.onProgress || (async () => {});
17
+
18
+ let swarm = [];
19
+ let globalBestParams = null;
20
+ let globalBestScore = -Infinity;
21
+
22
+ // Initialize swarm
23
+ for (let i = 0; i < numParticles; i++) {
24
+ let position = bounds.map(b => b[0] + Math.random() * (b[1] - b[0]));
25
+ let velocity = bounds.map(b => (Math.random() * 2 - 1) * (b[1] - b[0]) * 0.1); // Small initial velocity
26
+ let score = evaluate(position);
27
+
28
+ let particle = {
29
+ position,
30
+ velocity,
31
+ bestPosition: [...position],
32
+ bestScore: score
33
+ };
34
+ swarm.push(particle);
35
+
36
+ if (score > globalBestScore) {
37
+ globalBestScore = score;
38
+ globalBestParams = [...position];
39
+ }
40
+ }
41
+
42
+ for (let i = 0; i < steps; i++) {
43
+ for (let p of swarm) {
44
+ for (let d = 0; d < bounds.length; d++) {
45
+ let r1 = Math.random();
46
+ let r2 = Math.random();
47
+
48
+ // Velocity update matrix
49
+ p.velocity[d] = w * p.velocity[d] +
50
+ c1 * r1 * (p.bestPosition[d] - p.position[d]) +
51
+ c2 * r2 * (globalBestParams[d] - p.position[d]);
52
+
53
+ // Position update
54
+ p.position[d] = p.position[d] + p.velocity[d];
55
+
56
+ // Enforce physical parameter bounds
57
+ p.position[d] = Math.max(bounds[d][0], Math.min(bounds[d][1], p.position[d]));
58
+ }
59
+
60
+ let score = evaluate(p.position);
61
+
62
+ // Update local cognitive best
63
+ if (score > p.bestScore) {
64
+ p.bestScore = score;
65
+ p.bestPosition = [...p.position];
66
+ }
67
+ // Update social swarm global best
68
+ if (score > globalBestScore) {
69
+ globalBestScore = score;
70
+ globalBestParams = [...p.position];
71
+ }
72
+ }
73
+
74
+ await onProgress({
75
+ step: i,
76
+ totalSteps: steps,
77
+ bestScore: globalBestScore,
78
+ bestParams: globalBestParams,
79
+ swarm: swarm // Expose for 3D visualizers (Radar scatter generation)
80
+ });
81
+ }
82
+
83
+ return { bestParams: globalBestParams, bestScore: globalBestScore };
84
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Simulated Annealing (Derivative-Free Stochastic Optimization)
3
+ * Maximizes the evaluate function payload.
4
+ *
5
+ * @param {Function} evaluate - function(paramsArray) => Number (score to maximize)
6
+ * @param {Array<Array<Number>>} bounds - [[min1, max1], [min2, max2], ...] bounds per dimension
7
+ * @param {Object} options - { steps: 100, initialTemp: 100, finalTemp: 0.1, onProgress: async (state) => {} }
8
+ * @returns {Promise<{bestParams: Array<Number>, bestScore: Number}>}
9
+ */
10
+ export const simulatedAnnealing = async (evaluate, bounds, options = {}) => {
11
+ const steps = options.steps || 100;
12
+ const initialTemp = options.initialTemp || 100.0;
13
+ const finalTemp = options.finalTemp || 0.1;
14
+ const onProgress = options.onProgress || (async () => {});
15
+
16
+ let currentParams = bounds.map(b => (b[0] + b[1]) / 2);
17
+ let currentScore = evaluate(currentParams);
18
+
19
+ let bestParams = [...currentParams];
20
+ let bestScore = currentScore;
21
+
22
+ for (let i = 0; i < steps; i++) {
23
+ let temp = initialTemp * Math.pow((finalTemp / initialTemp), (i / steps));
24
+ let noiseScale = temp / initialTemp; // Gradually reduce the noise field radius
25
+
26
+ let candidateParams = currentParams.map((p, idx) => {
27
+ let b = bounds[idx];
28
+ let range = b[1] - b[0];
29
+ let nv = p + (Math.random() * 2 - 1) * (range * 0.2) * noiseScale;
30
+ return Math.max(b[0], Math.min(b[1], nv)); // enforce bounds constraint
31
+ });
32
+
33
+ let candidateScore = evaluate(candidateParams);
34
+
35
+ // Acceptance criteria (accept worse scores with diminishing probability to escape local maxima)
36
+ if (candidateScore > currentScore || Math.random() < Math.exp((candidateScore - currentScore) / temp)) {
37
+ currentParams = candidateParams;
38
+ currentScore = candidateScore;
39
+
40
+ if (candidateScore > bestScore) {
41
+ bestScore = candidateScore;
42
+ bestParams = [...currentParams];
43
+ }
44
+ }
45
+
46
+ await onProgress({
47
+ step: i,
48
+ totalSteps: steps,
49
+ score: currentScore,
50
+ bestScore: bestScore,
51
+ params: currentParams,
52
+ bestParams: bestParams
53
+ });
54
+ }
55
+
56
+ return { bestParams, bestScore };
57
+ };