@graphty/layout 1.2.8 → 1.3.2
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 +394 -404
- package/dist/layout.js +881 -821
- package/dist/layout.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +2 -2
- package/dist/src/algorithms/index.js +2 -2
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/optimization/index.d.ts +4 -4
- package/dist/src/algorithms/optimization/index.js +3 -3
- package/dist/src/algorithms/optimization/index.js.map +1 -1
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +2 -5
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
- package/dist/src/algorithms/optimization/lbfgs.d.ts +0 -1
- package/dist/src/algorithms/optimization/lbfgs.js +3 -5
- package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
- package/dist/src/algorithms/optimization/line-search.d.ts +0 -1
- package/dist/src/algorithms/optimization/line-search.js +0 -1
- package/dist/src/algorithms/optimization/line-search.js.map +1 -1
- package/dist/src/algorithms/optimization/types.d.ts +1 -1
- package/dist/src/algorithms/planarity/check.d.ts +3 -3
- package/dist/src/algorithms/planarity/check.js +7 -7
- package/dist/src/algorithms/planarity/check.js.map +1 -1
- package/dist/src/algorithms/planarity/embedding.d.ts +3 -5
- package/dist/src/algorithms/planarity/embedding.js +20 -16
- package/dist/src/algorithms/planarity/embedding.js.map +1 -1
- package/dist/src/algorithms/planarity/index.d.ts +4 -4
- package/dist/src/algorithms/planarity/index.js +4 -4
- package/dist/src/algorithms/planarity/index.js.map +1 -1
- package/dist/src/algorithms/planarity/lr-test.d.ts +3 -3
- package/dist/src/algorithms/planarity/lr-test.js +5 -5
- package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
- package/dist/src/algorithms/planarity/special-graphs.d.ts +1 -4
- package/dist/src/algorithms/planarity/special-graphs.js +18 -15
- package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
- package/dist/src/generators/basic.d.ts +1 -1
- package/dist/src/generators/basic.js +4 -4
- package/dist/src/generators/basic.js.map +1 -1
- package/dist/src/generators/bipartite.d.ts +1 -1
- package/dist/src/generators/bipartite.js +2 -2
- package/dist/src/generators/bipartite.js.map +1 -1
- package/dist/src/generators/grid.d.ts +1 -1
- package/dist/src/generators/grid.js +1 -1
- package/dist/src/generators/grid.js.map +1 -1
- package/dist/src/generators/index.d.ts +5 -5
- package/dist/src/generators/index.js +5 -5
- package/dist/src/generators/index.js.map +1 -1
- package/dist/src/generators/random.d.ts +1 -1
- package/dist/src/generators/random.js +2 -2
- package/dist/src/generators/random.js.map +1 -1
- package/dist/src/generators/scale-free.d.ts +1 -1
- package/dist/src/generators/scale-free.js +4 -4
- package/dist/src/generators/scale-free.js.map +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +4 -4
- package/dist/src/layouts/basic/index.d.ts +1 -1
- package/dist/src/layouts/basic/index.js +1 -1
- package/dist/src/layouts/basic/random.d.ts +1 -2
- package/dist/src/layouts/basic/random.js +3 -4
- package/dist/src/layouts/basic/random.js.map +1 -1
- package/dist/src/layouts/force-directed/arf.d.ts +1 -2
- package/dist/src/layouts/force-directed/arf.js +13 -8
- package/dist/src/layouts/force-directed/arf.js.map +1 -1
- package/dist/src/layouts/force-directed/forceatlas2.d.ts +1 -2
- package/dist/src/layouts/force-directed/forceatlas2.js +58 -35
- package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +11 -12
- package/dist/src/layouts/force-directed/fruchterman-reingold.js +17 -17
- package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/force-directed/index.d.ts +5 -5
- package/dist/src/layouts/force-directed/index.js +5 -5
- package/dist/src/layouts/force-directed/index.js.map +1 -1
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts +2 -3
- package/dist/src/layouts/force-directed/kamada-kawai.js +12 -10
- package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
- package/dist/src/layouts/force-directed/spring.d.ts +11 -12
- package/dist/src/layouts/force-directed/spring.js +11 -12
- package/dist/src/layouts/force-directed/spring.js.map +1 -1
- package/dist/src/layouts/geometric/circular.d.ts +1 -2
- package/dist/src/layouts/geometric/circular.js +10 -9
- package/dist/src/layouts/geometric/circular.js.map +1 -1
- package/dist/src/layouts/geometric/index.d.ts +3 -3
- package/dist/src/layouts/geometric/index.js +3 -3
- package/dist/src/layouts/geometric/shell.d.ts +1 -2
- package/dist/src/layouts/geometric/shell.js +5 -5
- package/dist/src/layouts/geometric/shell.js.map +1 -1
- package/dist/src/layouts/geometric/spiral.d.ts +1 -2
- package/dist/src/layouts/geometric/spiral.js +5 -9
- package/dist/src/layouts/geometric/spiral.js.map +1 -1
- package/dist/src/layouts/hierarchical/bfs.d.ts +2 -3
- package/dist/src/layouts/hierarchical/bfs.js +5 -6
- package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
- package/dist/src/layouts/hierarchical/bipartite.d.ts +2 -3
- package/dist/src/layouts/hierarchical/bipartite.js +8 -9
- package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
- package/dist/src/layouts/hierarchical/index.d.ts +3 -3
- package/dist/src/layouts/hierarchical/index.js +3 -3
- package/dist/src/layouts/hierarchical/index.js.map +1 -1
- package/dist/src/layouts/hierarchical/multipartite.d.ts +2 -3
- package/dist/src/layouts/hierarchical/multipartite.js +7 -8
- package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
- package/dist/src/layouts/index.d.ts +5 -5
- package/dist/src/layouts/index.js +5 -5
- package/dist/src/layouts/index.js.map +1 -1
- package/dist/src/layouts/specialized/index.d.ts +2 -2
- package/dist/src/layouts/specialized/index.js +2 -2
- package/dist/src/layouts/specialized/index.js.map +1 -1
- package/dist/src/layouts/specialized/planar.d.ts +3 -3
- package/dist/src/layouts/specialized/planar.js +8 -8
- package/dist/src/layouts/specialized/planar.js.map +1 -1
- package/dist/src/layouts/specialized/spectral.d.ts +1 -2
- package/dist/src/layouts/specialized/spectral.js +30 -20
- package/dist/src/layouts/specialized/spectral.js.map +1 -1
- package/dist/src/types/embedding.d.ts +1 -1
- package/dist/src/types/index.d.ts +3 -3
- package/dist/src/types/index.js +3 -3
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/layout.d.ts +1 -1
- package/dist/src/utils/graph.d.ts +1 -5
- package/dist/src/utils/graph.js +4 -6
- package/dist/src/utils/graph.js.map +1 -1
- package/dist/src/utils/index.d.ts +5 -5
- package/dist/src/utils/index.js +5 -5
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/numpy.js +22 -16
- package/dist/src/utils/numpy.js.map +1 -1
- package/dist/src/utils/params.d.ts +1 -2
- package/dist/src/utils/params.js +0 -1
- package/dist/src/utils/params.js.map +1 -1
- package/dist/src/utils/random.d.ts +17 -0
- package/dist/src/utils/random.js +18 -1
- package/dist/src/utils/random.js.map +1 -1
- package/dist/src/utils/rescale.d.ts +1 -3
- package/dist/src/utils/rescale.js +12 -10
- package/dist/src/utils/rescale.js.map +1 -1
- package/dist/vitest.config.js +14 -14
- package/dist/vitest.config.js.map +1 -1
- package/package.json +29 -24
- package/src/algorithms/index.ts +2 -2
- package/src/algorithms/optimization/index.ts +4 -8
- package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
- package/src/algorithms/optimization/lbfgs.ts +41 -48
- package/src/algorithms/optimization/line-search.ts +27 -28
- package/src/algorithms/optimization/types.ts +2 -2
- package/src/algorithms/planarity/check.ts +22 -21
- package/src/algorithms/planarity/embedding.ts +159 -165
- package/src/algorithms/planarity/index.ts +4 -8
- package/src/algorithms/planarity/lr-test.ts +46 -45
- package/src/algorithms/planarity/special-graphs.ts +73 -83
- package/src/generators/basic.ts +57 -57
- package/src/generators/bipartite.ts +37 -31
- package/src/generators/grid.ts +28 -28
- package/src/generators/index.ts +5 -11
- package/src/generators/random.ts +26 -25
- package/src/generators/scale-free.ts +56 -55
- package/src/index.ts +5 -5
- package/src/layouts/basic/index.ts +1 -1
- package/src/layouts/basic/random.ts +20 -16
- package/src/layouts/force-directed/arf.ts +107 -105
- package/src/layouts/force-directed/forceatlas2.ts +365 -351
- package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
- package/src/layouts/force-directed/index.ts +5 -5
- package/src/layouts/force-directed/kamada-kawai.ts +78 -80
- package/src/layouts/force-directed/spring.ts +24 -25
- package/src/layouts/geometric/circular.ts +63 -57
- package/src/layouts/geometric/index.ts +3 -3
- package/src/layouts/geometric/shell.ts +58 -53
- package/src/layouts/geometric/spiral.ts +60 -64
- package/src/layouts/hierarchical/bfs.ts +58 -59
- package/src/layouts/hierarchical/bipartite.ts +62 -63
- package/src/layouts/hierarchical/index.ts +3 -3
- package/src/layouts/hierarchical/multipartite.ts +62 -63
- package/src/layouts/index.ts +5 -5
- package/src/layouts/specialized/index.ts +2 -2
- package/src/layouts/specialized/planar.ts +42 -41
- package/src/layouts/specialized/spectral.ts +110 -99
- package/src/types/embedding.ts +5 -5
- package/src/types/graph.ts +4 -4
- package/src/types/index.ts +3 -3
- package/src/types/layout.ts +2 -2
- package/src/utils/graph.ts +31 -35
- package/src/utils/index.ts +5 -5
- package/src/utils/numpy.ts +96 -92
- package/src/utils/params.ts +14 -11
- package/src/utils/random.ts +57 -40
- package/src/utils/rescale.ts +169 -174
|
@@ -2,155 +2,146 @@
|
|
|
2
2
|
* Kamada-Kawai layout algorithm optimization functions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Graph, Node
|
|
6
|
-
import {
|
|
7
|
-
import { _lbfgsDirection } from
|
|
8
|
-
import { _backtrackingLineSearch } from
|
|
9
|
-
import {
|
|
5
|
+
import { Edge,Graph, Node } from "../../types";
|
|
6
|
+
import { getEdgesFromGraph,getNodesFromGraph } from "../../utils/graph";
|
|
7
|
+
import { _lbfgsDirection } from "./lbfgs";
|
|
8
|
+
import { _backtrackingLineSearch } from "./line-search";
|
|
9
|
+
import { DistanceMap } from "./types";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Compute all-pairs shortest path distances for the graph
|
|
13
|
-
*
|
|
14
13
|
* @param G - NetworkX graph
|
|
15
14
|
* @param weight - Edge attribute for weight
|
|
16
15
|
* @returns Dictionary of dictionaries of shortest path distances
|
|
17
16
|
*/
|
|
18
|
-
export function _computeShortestPathDistances(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
distances[node][other] = Infinity;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Add direct edges
|
|
39
|
-
for (const [source, target] of edges) {
|
|
40
|
-
// In a real implementation, we would get the weight from the graph
|
|
41
|
-
// For now, assume weight = 1 or use weight attribute if available
|
|
42
|
-
let edgeWeight = 1;
|
|
43
|
-
if (G.getEdgeData) {
|
|
44
|
-
edgeWeight = G.getEdgeData(source, target, weight) || 1;
|
|
17
|
+
export function _computeShortestPathDistances(G: Graph, weight: string): DistanceMap {
|
|
18
|
+
const distances: DistanceMap = {};
|
|
19
|
+
const nodes = getNodesFromGraph(G);
|
|
20
|
+
const edges = getEdgesFromGraph(G);
|
|
21
|
+
|
|
22
|
+
// Initialize distances with direct edges
|
|
23
|
+
for (const node of nodes) {
|
|
24
|
+
distances[node] = {};
|
|
25
|
+
distances[node][node] = 0;
|
|
26
|
+
|
|
27
|
+
for (const other of nodes) {
|
|
28
|
+
if (node !== other) {
|
|
29
|
+
distances[node][other] = Infinity;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
45
32
|
}
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
// Add direct edges
|
|
35
|
+
for (const [source, target] of edges) {
|
|
36
|
+
// In a real implementation, we would get the weight from the graph
|
|
37
|
+
// For now, assume weight = 1 or use weight attribute if available
|
|
38
|
+
let edgeWeight = 1;
|
|
39
|
+
if (G.getEdgeData) {
|
|
40
|
+
edgeWeight = G.getEdgeData(source, target, weight) || 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
distances[source][target] = edgeWeight;
|
|
44
|
+
distances[target][source] = edgeWeight; // Assuming undirected graph
|
|
45
|
+
}
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
// Floyd-Warshall algorithm for all-pairs shortest paths
|
|
48
|
+
for (const k of nodes) {
|
|
49
|
+
for (const i of nodes) {
|
|
50
|
+
for (const j of nodes) {
|
|
51
|
+
if (distances[i][k] + distances[k][j] < distances[i][j]) {
|
|
52
|
+
distances[i][j] = distances[i][k] + distances[k][j];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
57
55
|
}
|
|
58
|
-
}
|
|
59
56
|
}
|
|
60
|
-
}
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
return distances;
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
/**
|
|
66
62
|
* Solve the Kamada-Kawai layout optimization problem
|
|
67
|
-
*
|
|
68
63
|
* @param distMatrix - Matrix of desired distances between nodes
|
|
69
64
|
* @param positions - Initial node positions
|
|
70
65
|
* @param dim - Dimension of layout
|
|
71
66
|
* @returns Optimized node positions
|
|
72
67
|
*/
|
|
73
|
-
export function _kamadaKawaiSolve(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (let i = 0; i < posVec.length; i++) {
|
|
119
|
-
posVec[i] += alpha * direction[i];
|
|
120
|
-
}
|
|
68
|
+
export function _kamadaKawaiSolve(distMatrix: number[][], positions: number[][], dim: number): number[][] {
|
|
69
|
+
// Implementation of L-BFGS optimization for Kamada-Kawai
|
|
70
|
+
const nNodes = positions.length;
|
|
71
|
+
const meanWeight = 1e-3;
|
|
72
|
+
|
|
73
|
+
// Convert distances to inverse distances (with protection against division by zero)
|
|
74
|
+
const invDistMatrix = distMatrix.map((row) => row.map((d) => (d === 0 ? 0 : 1 / (d + 1e-3))));
|
|
75
|
+
|
|
76
|
+
// Flatten positions for optimization
|
|
77
|
+
const posVec = positions.flat();
|
|
78
|
+
|
|
79
|
+
// Optimization parameters
|
|
80
|
+
const maxIter = 500;
|
|
81
|
+
const gtol = 1e-5;
|
|
82
|
+
const m = 10; // L-BFGS memory size
|
|
83
|
+
|
|
84
|
+
// Implement a simplified L-BFGS-B algorithm
|
|
85
|
+
let alpha = 1.0;
|
|
86
|
+
const oldValues: number[][] = [];
|
|
87
|
+
const oldGrads: number[][] = [];
|
|
88
|
+
|
|
89
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
90
|
+
// Calculate cost and gradient
|
|
91
|
+
const [cost, grad] = _kamadaKawaiCostfn(posVec, invDistMatrix, meanWeight, dim);
|
|
92
|
+
|
|
93
|
+
// Compute search direction using L-BFGS approximation
|
|
94
|
+
const direction = _lbfgsDirection(grad, oldValues, oldGrads, m);
|
|
95
|
+
|
|
96
|
+
// Simple line search for step size
|
|
97
|
+
alpha = _backtrackingLineSearch(
|
|
98
|
+
posVec,
|
|
99
|
+
direction,
|
|
100
|
+
cost,
|
|
101
|
+
grad,
|
|
102
|
+
(x: number[]) => _kamadaKawaiCostfn(x, invDistMatrix, meanWeight, dim)[0],
|
|
103
|
+
alpha,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Save current position and gradient for next iteration
|
|
107
|
+
const oldPos = [...posVec];
|
|
108
|
+
|
|
109
|
+
// Update position
|
|
110
|
+
for (let i = 0; i < posVec.length; i++) {
|
|
111
|
+
posVec[i] += alpha * direction[i];
|
|
112
|
+
}
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
// Calculate new gradient
|
|
115
|
+
const [, newGrad] = _kamadaKawaiCostfn(posVec, invDistMatrix, meanWeight, dim);
|
|
124
116
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
// Update L-BFGS memory
|
|
118
|
+
oldValues.push(posVec.map((val, i) => val - oldPos[i]));
|
|
119
|
+
oldGrads.push(newGrad.map((val, i) => val - grad[i]));
|
|
128
120
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
// Keep only m most recent updates
|
|
122
|
+
if (oldValues.length > m) {
|
|
123
|
+
oldValues.shift();
|
|
124
|
+
oldGrads.shift();
|
|
125
|
+
}
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
// Check convergence
|
|
128
|
+
const gradNorm = Math.sqrt(newGrad.reduce((sum, g) => sum + g * g, 0));
|
|
129
|
+
if (gradNorm < gtol) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
139
132
|
}
|
|
140
|
-
}
|
|
141
133
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
134
|
+
// Reshape result back into positions array
|
|
135
|
+
const result: number[][] = [];
|
|
136
|
+
for (let i = 0; i < nNodes; i++) {
|
|
137
|
+
result.push(posVec.slice(i * dim, (i + 1) * dim));
|
|
138
|
+
}
|
|
147
139
|
|
|
148
|
-
|
|
140
|
+
return result;
|
|
149
141
|
}
|
|
150
142
|
|
|
151
143
|
/**
|
|
152
144
|
* Cost function and gradient for Kamada-Kawai layout algorithm
|
|
153
|
-
*
|
|
154
145
|
* @param posVec - Flattened position array
|
|
155
146
|
* @param invDist - Inverse distance matrix
|
|
156
147
|
* @param meanWeight - Weight for centering positions
|
|
@@ -158,74 +149,74 @@ export function _kamadaKawaiSolve(
|
|
|
158
149
|
* @returns Array with [cost, gradient]
|
|
159
150
|
*/
|
|
160
151
|
export function _kamadaKawaiCostfn(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
152
|
+
posVec: number[],
|
|
153
|
+
invDist: number[][],
|
|
154
|
+
meanWeight: number,
|
|
155
|
+
dim: number,
|
|
165
156
|
): [number, number[]] {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
157
|
+
const nNodes = invDist.length;
|
|
158
|
+
const positions: number[][] = [];
|
|
159
|
+
|
|
160
|
+
// Reshape flat vector into positions array
|
|
161
|
+
for (let i = 0; i < nNodes; i++) {
|
|
162
|
+
positions.push(posVec.slice(i * dim, (i + 1) * dim));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Calculate cost
|
|
166
|
+
let cost = 0;
|
|
167
|
+
|
|
168
|
+
// Add mean position penalty term
|
|
169
|
+
const sumPos = Array(dim).fill(0);
|
|
170
|
+
for (let i = 0; i < nNodes; i++) {
|
|
171
|
+
for (let d = 0; d < dim; d++) {
|
|
172
|
+
sumPos[d] += positions[i][d];
|
|
173
|
+
}
|
|
182
174
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
175
|
+
cost += 0.5 * meanWeight * sumPos.reduce((sum, val) => sum + val * val, 0);
|
|
176
|
+
|
|
177
|
+
// Add distance penalty terms
|
|
178
|
+
for (let i = 0; i < nNodes; i++) {
|
|
179
|
+
for (let j = i + 1; j < nNodes; j++) {
|
|
180
|
+
// Calculate actual distance
|
|
181
|
+
const diff = positions[i].map((val, d) => val - positions[j][d]);
|
|
182
|
+
const distance = Math.sqrt(diff.reduce((sum, d) => sum + d * d, 0));
|
|
183
|
+
|
|
184
|
+
// Add penalty for difference between actual and ideal distance
|
|
185
|
+
const idealInvDist = invDist[i][j];
|
|
186
|
+
const offset = distance * idealInvDist - 1.0;
|
|
187
|
+
cost += 0.5 * offset * offset;
|
|
188
|
+
}
|
|
197
189
|
}
|
|
198
|
-
}
|
|
199
190
|
|
|
200
|
-
|
|
201
|
-
|
|
191
|
+
// Calculate gradient
|
|
192
|
+
const grad = new Array(posVec.length).fill(0);
|
|
202
193
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
194
|
+
// Add gradient of mean position penalty
|
|
195
|
+
for (let i = 0; i < nNodes; i++) {
|
|
196
|
+
for (let d = 0; d < dim; d++) {
|
|
197
|
+
grad[i * dim + d] += meanWeight * sumPos[d];
|
|
198
|
+
}
|
|
207
199
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
200
|
+
|
|
201
|
+
// Add gradient of distance penalties
|
|
202
|
+
for (let i = 0; i < nNodes; i++) {
|
|
203
|
+
for (let j = i + 1; j < nNodes; j++) {
|
|
204
|
+
// Calculate actual distance and direction
|
|
205
|
+
const diff = positions[i].map((val, d) => val - positions[j][d]);
|
|
206
|
+
const distance = Math.sqrt(diff.reduce((sum, d) => sum + d * d, 0)) || 1e-10;
|
|
207
|
+
const direction = diff.map((d) => d / distance);
|
|
208
|
+
|
|
209
|
+
// Calculate contribution to gradient
|
|
210
|
+
const idealInvDist = invDist[i][j];
|
|
211
|
+
const offset = distance * idealInvDist - 1.0;
|
|
212
|
+
|
|
213
|
+
for (let d = 0; d < dim; d++) {
|
|
214
|
+
const force = idealInvDist * offset * direction[d];
|
|
215
|
+
grad[i * dim + d] += force;
|
|
216
|
+
grad[j * dim + d] -= force;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
227
219
|
}
|
|
228
|
-
}
|
|
229
220
|
|
|
230
|
-
|
|
231
|
-
}
|
|
221
|
+
return [cost, grad];
|
|
222
|
+
}
|
|
@@ -4,65 +4,58 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Compute the search direction using L-BFGS approximation
|
|
7
|
-
*
|
|
8
7
|
* @param grad - Current gradient
|
|
9
8
|
* @param sList - List of position differences (s_k)
|
|
10
9
|
* @param yList - List of gradient differences (y_k)
|
|
11
10
|
* @param m - Memory size
|
|
12
11
|
* @returns Direction vector
|
|
13
12
|
*/
|
|
14
|
-
export function _lbfgsDirection(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
): number[] {
|
|
20
|
-
if (sList.length === 0) {
|
|
21
|
-
// First iteration - use negative gradient
|
|
22
|
-
return grad.map(g => -g);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const q = grad.slice();
|
|
26
|
-
const alpha = Array(sList.length).fill(0);
|
|
27
|
-
const rho: number[] = [];
|
|
13
|
+
export function _lbfgsDirection(grad: number[], sList: number[][], yList: number[][], m: number): number[] {
|
|
14
|
+
if (sList.length === 0) {
|
|
15
|
+
// First iteration - use negative gradient
|
|
16
|
+
return grad.map((g) => -g);
|
|
17
|
+
}
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const y = yList[i];
|
|
33
|
-
rho.push(1 / y.reduce((sum, val, j) => sum + val * s[j], 0));
|
|
34
|
-
}
|
|
19
|
+
const q = grad.slice();
|
|
20
|
+
const alpha = Array(sList.length).fill(0);
|
|
21
|
+
const rho: number[] = [];
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
q[j] -= alpha[i] * yList[i][j];
|
|
23
|
+
// Compute rho values
|
|
24
|
+
for (let i = 0; i < sList.length; i++) {
|
|
25
|
+
const s = sList[i];
|
|
26
|
+
const y = yList[i];
|
|
27
|
+
rho.push(1 / y.reduce((sum, val, j) => sum + val * s[j], 0));
|
|
42
28
|
}
|
|
43
|
-
}
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
// Forward pass
|
|
31
|
+
for (let i = sList.length - 1; i >= 0; i--) {
|
|
32
|
+
const s = sList[i];
|
|
33
|
+
alpha[i] = rho[i] * s.reduce((sum, val, j) => sum + val * q[j], 0);
|
|
34
|
+
for (let j = 0; j < q.length; j++) {
|
|
35
|
+
q[j] -= alpha[i] * yList[i][j];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
53
38
|
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
// Scale initial Hessian approximation
|
|
40
|
+
let gamma = 1;
|
|
41
|
+
if (sList.length > 0 && yList.length > 0) {
|
|
42
|
+
const y = yList[yList.length - 1];
|
|
43
|
+
const s = sList[sList.length - 1];
|
|
44
|
+
gamma = s.reduce((sum, val, i) => sum + val * y[i], 0) / y.reduce((sum, val) => sum + val * val, 0);
|
|
45
|
+
}
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
// Initialize direction with scaled negative gradient
|
|
48
|
+
const direction = q.map((val) => -gamma * val);
|
|
49
|
+
|
|
50
|
+
// Backward pass
|
|
51
|
+
for (let i = 0; i < sList.length; i++) {
|
|
52
|
+
const s = sList[i];
|
|
53
|
+
const y = yList[i];
|
|
54
|
+
const beta = rho[i] * y.reduce((sum, val, j) => sum + val * direction[j], 0);
|
|
55
|
+
for (let j = 0; j < direction.length; j++) {
|
|
56
|
+
direction[j] += s[j] * (alpha[i] - beta);
|
|
57
|
+
}
|
|
64
58
|
}
|
|
65
|
-
}
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
}
|
|
60
|
+
return direction;
|
|
61
|
+
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Backtracking line search to find step size
|
|
7
|
-
*
|
|
8
7
|
* @param x - Current position
|
|
9
8
|
* @param direction - Search direction
|
|
10
9
|
* @param f - Function value at current position
|
|
@@ -14,37 +13,37 @@
|
|
|
14
13
|
* @returns Optimal step size
|
|
15
14
|
*/
|
|
16
15
|
export function _backtrackingLineSearch(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
x: number[],
|
|
17
|
+
direction: number[],
|
|
18
|
+
f: number,
|
|
19
|
+
grad: number[],
|
|
20
|
+
func: (x: number[]) => number,
|
|
21
|
+
alpha0: number,
|
|
23
22
|
): number {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const c1 = 1e-4;
|
|
24
|
+
const c2 = 0.9;
|
|
25
|
+
const initialSlope = grad.reduce((sum, g, i) => sum + g * direction[i], 0);
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
if (initialSlope >= 0) {
|
|
28
|
+
return 1e-8; // Direction is not a descent direction
|
|
29
|
+
}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
let alpha = alpha0;
|
|
32
|
+
const maxIter = 20;
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
for (let i = 0; i < maxIter; i++) {
|
|
35
|
+
// Try step
|
|
36
|
+
const newX = x.map((val, i) => val + alpha * direction[i]);
|
|
37
|
+
const newF = func(newX);
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
// Check sufficient decrease condition (Armijo condition)
|
|
40
|
+
if (newF <= f + c1 * alpha * initialSlope) {
|
|
41
|
+
return alpha;
|
|
42
|
+
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
// Reduce step size
|
|
45
|
+
alpha *= c2;
|
|
46
|
+
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
}
|
|
48
|
+
return alpha; // Return last alpha even if not optimal
|
|
49
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Type definitions for optimization algorithms
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Node } from
|
|
5
|
+
import { Node } from "../../types";
|
|
6
6
|
|
|
7
7
|
// Type definitions for distance structure used in Kamada-Kawai
|
|
8
|
-
export type DistanceMap = Record<Node, Record<Node, number>>;
|
|
8
|
+
export type DistanceMap = Record<Node, Record<Node, number>>;
|
|
@@ -2,37 +2,38 @@
|
|
|
2
2
|
* Main planarity checking function
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { lrPlanarityTest } from
|
|
8
|
-
import {
|
|
5
|
+
import { Edge, Embedding,Graph, Node } from "../../types";
|
|
6
|
+
import { createTriangulationEmbedding } from "./embedding";
|
|
7
|
+
import { lrPlanarityTest } from "./lr-test";
|
|
8
|
+
import { isK5, isK33 } from "./special-graphs";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Check if graph is planar using a simplified version of Boyer-Myrvold algorithm.
|
|
12
12
|
* Returns planarity and embedding information.
|
|
13
|
-
*
|
|
14
13
|
* @param G - Graph
|
|
15
14
|
* @param nodes - List of nodes
|
|
16
15
|
* @param edges - List of edges
|
|
16
|
+
* @param seed - Random seed for deterministic embedding generation
|
|
17
17
|
* @returns Object containing isPlanar flag and embedding
|
|
18
18
|
*/
|
|
19
19
|
export function checkPlanarity(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
G: Graph,
|
|
21
|
+
nodes: Node[],
|
|
22
|
+
edges: Edge[],
|
|
23
|
+
seed: number | null = null,
|
|
23
24
|
): { isPlanar: boolean; embedding: Embedding | null } {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// For small graphs (n <= 4), all are planar
|
|
26
|
+
if (nodes.length <= 4) {
|
|
27
|
+
return { isPlanar: true, embedding: createTriangulationEmbedding(nodes, edges, seed) };
|
|
28
|
+
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
// For K5 (complete graph with 5 nodes) and K3,3 (complete bipartite with 3,3 nodes)
|
|
31
|
+
// these are not planar by Kuratowski's theorem
|
|
32
|
+
if (isK5(nodes, edges) || isK33(nodes, edges)) {
|
|
33
|
+
return { isPlanar: false, embedding: null };
|
|
34
|
+
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
36
|
+
// For other graphs, use LR algorithm (Left-Right Planarity Test)
|
|
37
|
+
const result = lrPlanarityTest(nodes, edges, seed);
|
|
38
|
+
return result;
|
|
39
|
+
}
|