poker-mcp 1.0.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.
Files changed (50) hide show
  1. package/README.md +241 -0
  2. package/config/mcp-manifest.json +1713 -0
  3. package/config/package-lock.json +1335 -0
  4. package/config/poker_mcp.code-workspace +8 -0
  5. package/package.json +51 -0
  6. package/src/config/ConfigManager.js +120 -0
  7. package/src/config/default.json +62 -0
  8. package/src/mcp/handlers/bodyHandlers.js +63 -0
  9. package/src/mcp/handlers/buildupFactorHandlers.js +43 -0
  10. package/src/mcp/handlers/calculationHandlers.js +265 -0
  11. package/src/mcp/handlers/detectorHandlers.js +201 -0
  12. package/src/mcp/handlers/index.js +43 -0
  13. package/src/mcp/handlers/sourceHandlers.js +25 -0
  14. package/src/mcp/handlers/transformHandlers.js +26 -0
  15. package/src/mcp/handlers/unitHandlers.js +305 -0
  16. package/src/mcp/handlers/zoneHandlers.js +83 -0
  17. package/src/mcp/middleware/errorHandler.js +49 -0
  18. package/src/mcp/middleware/requestValidator.js +129 -0
  19. package/src/mcp/server.js +76 -0
  20. package/src/mcp/tools/bodyTools.js +185 -0
  21. package/src/mcp/tools/buildupFactorTools.js +82 -0
  22. package/src/mcp/tools/calculationTools.js +58 -0
  23. package/src/mcp/tools/commonTools.js +22 -0
  24. package/src/mcp/tools/detectorTools.js +131 -0
  25. package/src/mcp/tools/index.js +34 -0
  26. package/src/mcp/tools/sourceTools.js +726 -0
  27. package/src/mcp/tools/sourceTools_fixed_part2.js +41 -0
  28. package/src/mcp/tools/transformTools.js +73 -0
  29. package/src/mcp/tools/unitTools.js +137 -0
  30. package/src/mcp/tools/zoneTools.js +65 -0
  31. package/src/mcp_server_stdio_v4.js +45 -0
  32. package/src/routes/restApi.js +184 -0
  33. package/src/services/CalculationService.js +525 -0
  34. package/src/services/DataManager.js +738 -0
  35. package/src/services/TaskManager.js +1622 -0
  36. package/src/utils/MaterialAlternatives.js +189 -0
  37. package/src/utils/errorHandler.js +68 -0
  38. package/src/utils/errors.js +37 -0
  39. package/src/utils/logger.js +24 -0
  40. package/src/utils/logger_no_color.js +28 -0
  41. package/src/utils/mcpErrors.js +225 -0
  42. package/src/validators/CMBValidator.js +191 -0
  43. package/src/validators/DetectorValidator.js +512 -0
  44. package/src/validators/ManifestValidator.js +249 -0
  45. package/src/validators/NuclideValidator.js +171 -0
  46. package/src/validators/PhysicsValidator.js +401 -0
  47. package/src/validators/PhysicsValidatorSimple.js +87 -0
  48. package/src/validators/SourceValidator.js +515 -0
  49. package/src/validators/TransformValidator.js +472 -0
  50. package/src/validators/UnitValidator.js +562 -0
@@ -0,0 +1,191 @@
1
+ // CMB専用バリデーションクラス
2
+ import { ValidationError } from '../utils/errors.js';
3
+ import { logger } from '../utils/logger.js';
4
+
5
+ export class CMBValidator {
6
+ /**
7
+ * CMB演算式の完全バリデーション
8
+ * @param {string} expression - 演算式
9
+ * @param {string} cmbName - CMB立体名
10
+ * @param {Array} existingBodies - 既存立体一覧
11
+ * @returns {object} バリデーション結果
12
+ */
13
+ static validateExpression(expression, cmbName, existingBodies) {
14
+ // 1. 基本構文チェック
15
+ this.validateBasicSyntax(expression);
16
+
17
+ // 2. 立体名抽出と存在確認
18
+ const referencedBodies = this.extractBodyNames(expression);
19
+ this.validateReferencedBodies(referencedBodies, existingBodies);
20
+
21
+ // 3. 循環参照チェック
22
+ this.checkCircularReference(cmbName, referencedBodies, existingBodies);
23
+
24
+ return {
25
+ valid: true,
26
+ referencedBodies,
27
+ warnings: this.generateWarnings(referencedBodies, existingBodies)
28
+ };
29
+ }
30
+
31
+ /**
32
+ * 基本構文チェック
33
+ */
34
+ static validateBasicSyntax(expression) {
35
+ // 空文字チェック
36
+ if (!expression || expression.trim() === '') {
37
+ throw new ValidationError(
38
+ 'CMB演算式は空にできません',
39
+ 'expression',
40
+ expression
41
+ );
42
+ }
43
+
44
+ // 使用可能文字チェック(マニフェストのpatternと一致)
45
+ const allowedChars = /^[a-zA-Z0-9_()\s+&-]+$/;
46
+ if (!allowedChars.test(expression)) {
47
+ throw new ValidationError(
48
+ 'CMB演算式に不正な文字が含まれています。使用可能: 英数字、アンダースコア、括弧、演算子(+,-,&)',
49
+ 'expression',
50
+ expression
51
+ );
52
+ }
53
+
54
+ // 禁止演算子チェック
55
+ const forbiddenOperators = ['*', '/', '^', '|'];
56
+ for (const op of forbiddenOperators) {
57
+ if (expression.includes(op)) {
58
+ throw new ValidationError(
59
+ `禁止された演算子 '${op}' が使用されています。使用可能: +, -, &`,
60
+ 'expression',
61
+ expression
62
+ );
63
+ }
64
+ }
65
+
66
+ // 括弧バランスチェック
67
+ this.validateParentheses(expression);
68
+ }
69
+
70
+ /**
71
+ * 括弧バランスの確認
72
+ */
73
+ static validateParentheses(expression) {
74
+ let balance = 0;
75
+ for (const char of expression) {
76
+ if (char === '(') balance++;
77
+ if (char === ')') balance--;
78
+ if (balance < 0) {
79
+ throw new ValidationError(
80
+ '括弧が正しく対応していません',
81
+ 'expression',
82
+ expression
83
+ );
84
+ }
85
+ }
86
+ if (balance !== 0) {
87
+ throw new ValidationError(
88
+ '括弧が正しく対応していません',
89
+ 'expression',
90
+ expression
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 演算式から立体名を抽出
97
+ */
98
+ static extractBodyNames(expression) {
99
+ // 演算子と括弧、空白で分割して立体名を抽出
100
+ const tokens = expression
101
+ .replace(/[+\-&()]/g, ' ')
102
+ .split(/\s+/)
103
+ .filter(token => token.length > 0);
104
+
105
+ return [...new Set(tokens)]; // 重複除去
106
+ }
107
+
108
+ /**
109
+ * 参照立体の存在確認
110
+ */
111
+ static validateReferencedBodies(referencedBodies, existingBodies) {
112
+ const existingBodyNames = existingBodies.map(body => body.name || body);
113
+ const missingBodies = referencedBodies.filter(
114
+ name => !existingBodyNames.includes(name)
115
+ );
116
+
117
+ if (missingBodies.length > 0) {
118
+ throw new ValidationError(
119
+ `演算式で参照されている立体が存在しません: ${missingBodies.join(', ')}`,
120
+ 'expression',
121
+ referencedBodies
122
+ );
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 循環参照チェック
128
+ */
129
+ static checkCircularReference(cmbName, referencedBodies, allBodies) {
130
+ const visited = new Set();
131
+ const recursionStack = new Set();
132
+
133
+ const hasCycle = (bodyName, depth = 0) => {
134
+ if (depth > 10) { // 最大深度制限
135
+ throw new ValidationError(
136
+ 'CMB演算式の参照が複雑すぎます(最大深度10を超過)',
137
+ 'expression',
138
+ bodyName
139
+ );
140
+ }
141
+
142
+ if (recursionStack.has(bodyName)) {
143
+ return true; // 循環検出
144
+ }
145
+
146
+ if (visited.has(bodyName)) {
147
+ return false; // 既に処理済み
148
+ }
149
+
150
+ visited.add(bodyName);
151
+ recursionStack.add(bodyName);
152
+
153
+ // この立体が参照している他の立体をチェック
154
+ const body = allBodies.find(b => (b.name || b) === bodyName);
155
+ if (body && body.type === 'CMB') {
156
+ const referencedInThis = this.extractBodyNames(body.expression);
157
+ for (const ref of referencedInThis) {
158
+ if (hasCycle(ref, depth + 1)) {
159
+ return true;
160
+ }
161
+ }
162
+ }
163
+
164
+ recursionStack.delete(bodyName);
165
+ return false;
166
+ };
167
+
168
+ // 参照される立体から循環をチェック
169
+ for (const refBody of referencedBodies) {
170
+ if (hasCycle(refBody)) {
171
+ throw new ValidationError(
172
+ `CMB演算式で循環参照が検出されました: ${cmbName} → ${referencedBodies.join(' → ')}`,
173
+ 'expression',
174
+ cmbName
175
+ );
176
+ }
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 警告の生成
182
+ */
183
+ static generateWarnings(referencedBodies, existingBodies) {
184
+ const warnings = [];
185
+
186
+ // VOID材料との組み合わせ警告などを実装
187
+ // TODO: 材料情報が必要な場合は後で実装
188
+
189
+ return warnings;
190
+ }
191
+ }
@@ -0,0 +1,512 @@
1
+ // validators/DetectorValidator.js
2
+ import { PokerMcpError } from '../utils/mcpErrors.js';
3
+ import { ManifestValidator } from './ManifestValidator.js';
4
+ import { TransformValidator } from './TransformValidator.js';
5
+
6
+ /**
7
+ * Detector系のグリッド機能完全実装バリデーター
8
+ * マニフェスト仕様に完全準拠した高度なDetector構造検証
9
+ */
10
+ export class DetectorValidator {
11
+
12
+ /**
13
+ * サポートされるDetector次元定義
14
+ */
15
+ static SUPPORTED_DIMENSIONS = {
16
+ 1: {
17
+ description: '1次元検出器(線形グリッド)',
18
+ requiredGrids: 1,
19
+ gridNames: ['edge_1'],
20
+ maxDivisions: 10000
21
+ },
22
+ 2: {
23
+ description: '2次元検出器(平面グリッド)',
24
+ requiredGrids: 2,
25
+ gridNames: ['edge_1', 'edge_2'],
26
+ maxDivisions: 1000000 // 1000 x 1000
27
+ },
28
+ 3: {
29
+ description: '3次元検出器(立体グリッド)',
30
+ requiredGrids: 3,
31
+ gridNames: ['edge_1', 'edge_2', 'edge_3'],
32
+ maxDivisions: 1000000000 // 1000 x 1000 x 1000
33
+ }
34
+ };
35
+
36
+ /**
37
+ * グリッドエッジの方向性定義
38
+ */
39
+ static EDGE_ORIENTATIONS = {
40
+ 'orthogonal': '直交グリッド(推奨)',
41
+ 'oblique': '斜交グリッド(高度)'
42
+ };
43
+
44
+ /**
45
+ * Detector基本情報の検証
46
+ */
47
+ static validateDetectorBasics(detectorData) {
48
+ const { name, origin, grid, show_path_trace } = detectorData;
49
+
50
+ // 基本フィールドの検証
51
+ ManifestValidator.validateObjectName(name, 'detector name');
52
+
53
+ if (!origin) {
54
+ throw PokerMcpError.validationError(
55
+ 'Detector requires origin coordinate',
56
+ 'origin',
57
+ origin
58
+ );
59
+ }
60
+
61
+ ManifestValidator.validateCoordinateString(origin, 'origin');
62
+
63
+ // show_path_traceの検証(必須パラメータ)
64
+ if (show_path_trace === undefined) {
65
+ throw PokerMcpError.validationError(
66
+ 'show_path_trace parameter is required for radiation path analysis',
67
+ 'show_path_trace',
68
+ show_path_trace,
69
+ -32051
70
+ );
71
+ }
72
+
73
+ if (typeof show_path_trace !== 'boolean') {
74
+ throw PokerMcpError.validationError(
75
+ 'show_path_trace must be boolean',
76
+ 'show_path_trace',
77
+ show_path_trace
78
+ );
79
+ }
80
+
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * グリッド次元の検証
86
+ */
87
+ static validateGridDimension(grid) {
88
+ if (!Array.isArray(grid)) {
89
+ throw PokerMcpError.validationError(
90
+ 'grid must be an array',
91
+ 'grid',
92
+ grid
93
+ );
94
+ }
95
+
96
+ const dimension = grid.length;
97
+
98
+ if (dimension < 1 || dimension > 3) {
99
+ throw PokerMcpError.validationError(
100
+ 'Detector dimension must be 1, 2, or 3',
101
+ 'grid.length',
102
+ dimension
103
+ );
104
+ }
105
+
106
+ const dimensionInfo = this.SUPPORTED_DIMENSIONS[dimension];
107
+ if (grid.length !== dimensionInfo.requiredGrids) {
108
+ throw PokerMcpError.validationError(
109
+ `${dimension}D detector requires exactly ${dimensionInfo.requiredGrids} grid edges`,
110
+ 'grid',
111
+ grid
112
+ );
113
+ }
114
+
115
+ return {
116
+ dimension,
117
+ info: dimensionInfo
118
+ };
119
+ }
120
+
121
+ /**
122
+ * グリッドエッジの検証
123
+ */
124
+ static validateGridEdge(edge, edgeName, index) {
125
+ if (!edge || typeof edge !== 'object') {
126
+ throw PokerMcpError.validationError(
127
+ `${edgeName} must be a grid edge object`,
128
+ edgeName,
129
+ edge
130
+ );
131
+ }
132
+
133
+ const { edge: edgeVector, number } = edge;
134
+
135
+ // エッジベクトルの検証
136
+ if (!edgeVector) {
137
+ throw PokerMcpError.validationError(
138
+ `${edgeName} requires edge vector`,
139
+ `${edgeName}.edge`,
140
+ edgeVector
141
+ );
142
+ }
143
+
144
+ ManifestValidator.validateCoordinateString(edgeVector, `${edgeName}.edge`);
145
+
146
+ // 分割数の検証
147
+ ManifestValidator.validateGridDivision(number, `${edgeName}.number`);
148
+
149
+ // エッジベクトルの物理的妥当性チェック
150
+ const [x, y, z] = edgeVector.split(/\s+/).map(Number);
151
+ const magnitude = Math.sqrt(x*x + y*y + z*z);
152
+
153
+ if (magnitude < 1e-10) {
154
+ throw PokerMcpError.validationError(
155
+ `${edgeName} edge vector cannot be zero`,
156
+ `${edgeName}.edge`,
157
+ edgeVector
158
+ );
159
+ }
160
+
161
+ // 過度に大きなエッジベクトルの警告
162
+ if (magnitude > 10000) {
163
+ console.warn(`DetectorValidator: ${edgeName} has very large edge vector (${magnitude.toFixed(2)})`);
164
+ }
165
+
166
+ return {
167
+ vector: [x, y, z],
168
+ magnitude,
169
+ divisions: number
170
+ };
171
+ }
172
+
173
+ /**
174
+ * グリッド全体の検証
175
+ */
176
+ static validateCompleteGrid(grid) {
177
+ const dimensionResult = this.validateGridDimension(grid);
178
+ const { dimension, info } = dimensionResult;
179
+
180
+ const edgeResults = [];
181
+ let totalDivisions = 1;
182
+
183
+ // 各エッジの検証
184
+ for (let i = 0; i < dimension; i++) {
185
+ const edgeName = info.gridNames[i];
186
+ const edgeResult = this.validateGridEdge(grid[i], edgeName, i);
187
+ edgeResults.push(edgeResult);
188
+ totalDivisions *= edgeResult.divisions;
189
+ }
190
+
191
+ // 総分割数の妥当性チェック
192
+ if (totalDivisions > info.maxDivisions) {
193
+ throw PokerMcpError.validationError(
194
+ `Total grid divisions (${totalDivisions}) exceeds maximum for ${dimension}D detector (${info.maxDivisions})`,
195
+ 'grid',
196
+ totalDivisions
197
+ );
198
+ }
199
+
200
+ // グリッドエッジの直交性チェック
201
+ const orthogonality = this.checkGridOrthogonality(edgeResults);
202
+
203
+ return {
204
+ dimension,
205
+ dimensionInfo: info,
206
+ edges: edgeResults,
207
+ totalDivisions,
208
+ orthogonality,
209
+ gridVolume: this.calculateGridVolume(edgeResults),
210
+ gridDensity: totalDivisions / this.calculateGridVolume(edgeResults)
211
+ };
212
+ }
213
+
214
+ /**
215
+ * グリッドエッジの直交性チェック
216
+ */
217
+ static checkGridOrthogonality(edgeResults) {
218
+ if (edgeResults.length < 2) {
219
+ return { type: 'single_edge', orthogonal: true };
220
+ }
221
+
222
+ const angles = [];
223
+ const tolerance = 1e-6;
224
+
225
+ // すべてのエッジペアの角度を計算
226
+ for (let i = 0; i < edgeResults.length; i++) {
227
+ for (let j = i + 1; j < edgeResults.length; j++) {
228
+ const v1 = edgeResults[i].vector;
229
+ const v2 = edgeResults[j].vector;
230
+
231
+ const dot = v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
232
+ const angle = Math.acos(Math.abs(dot) / (edgeResults[i].magnitude * edgeResults[j].magnitude));
233
+ const angleDegrees = angle * 180 / Math.PI;
234
+
235
+ angles.push({
236
+ edges: [i, j],
237
+ angle: angleDegrees,
238
+ isOrthogonal: Math.abs(angleDegrees - 90) < tolerance || Math.abs(angleDegrees - 0) < tolerance
239
+ });
240
+ }
241
+ }
242
+
243
+ const allOrthogonal = angles.every(a => a.isOrthogonal);
244
+ const orthogonalCount = angles.filter(a => a.isOrthogonal).length;
245
+
246
+ return {
247
+ type: allOrthogonal ? 'orthogonal' : 'oblique',
248
+ orthogonal: allOrthogonal,
249
+ angles,
250
+ orthogonalRatio: orthogonalCount / angles.length
251
+ };
252
+ }
253
+
254
+ /**
255
+ * グリッド体積の計算
256
+ */
257
+ static calculateGridVolume(edgeResults) {
258
+ if (edgeResults.length === 1) {
259
+ // 1D: 線の長さ
260
+ return edgeResults[0].magnitude;
261
+ } else if (edgeResults.length === 2) {
262
+ // 2D: 平行四辺形の面積
263
+ const [v1, v2] = [edgeResults[0].vector, edgeResults[1].vector];
264
+ const cross = [
265
+ v1[1]*v2[2] - v1[2]*v2[1],
266
+ v1[2]*v2[0] - v1[0]*v2[2],
267
+ v1[0]*v2[1] - v1[1]*v2[0]
268
+ ];
269
+ return Math.sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]);
270
+ } else if (edgeResults.length === 3) {
271
+ // 3D: 平行六面体の体積
272
+ const [v1, v2, v3] = [edgeResults[0].vector, edgeResults[1].vector, edgeResults[2].vector];
273
+ const cross = [
274
+ v2[1]*v3[2] - v2[2]*v3[1],
275
+ v2[2]*v3[0] - v2[0]*v3[2],
276
+ v2[0]*v3[1] - v2[1]*v3[0]
277
+ ];
278
+ return Math.abs(v1[0]*cross[0] + v1[1]*cross[1] + v1[2]*cross[2]);
279
+ }
280
+ return 0;
281
+ }
282
+
283
+ /**
284
+ * Transform参照の検証
285
+ */
286
+ static validateDetectorTransform(transform, yamlData, detectorName) {
287
+ if (!transform) {
288
+ return true; // transform参照はオプション
289
+ }
290
+
291
+ try {
292
+ TransformValidator.validateContextTransformReference(
293
+ transform,
294
+ yamlData,
295
+ 'detector',
296
+ detectorName
297
+ );
298
+ } catch (error) {
299
+ // エラーメッセージにコンテキスト情報を追加
300
+ if (error instanceof PokerMcpError) {
301
+ throw new PokerMcpError(
302
+ error.code,
303
+ `Detector '${detectorName}' references invalid transform: ${error.message}`,
304
+ 'transform',
305
+ transform
306
+ );
307
+ }
308
+ throw error;
309
+ }
310
+
311
+ return true;
312
+ }
313
+
314
+ /**
315
+ * Detector構造全体の包括的検証
316
+ */
317
+ static validateCompleteDetectorStructure(detectorData, yamlData = null) {
318
+ const { name, origin, grid, transform, show_path_trace } = detectorData;
319
+
320
+ // 基本情報の検証
321
+ this.validateDetectorBasics(detectorData);
322
+
323
+ // グリッドの完全検証
324
+ let gridAnalysis = null;
325
+ if (grid && grid.length > 0) {
326
+ gridAnalysis = this.validateCompleteGrid(grid);
327
+ }
328
+
329
+ // Transform参照の検証
330
+ if (transform && yamlData) {
331
+ this.validateDetectorTransform(transform, yamlData, name);
332
+ }
333
+
334
+ return {
335
+ name,
336
+ origin: origin.split(/\s+/).map(Number),
337
+ hasGrid: !!(grid && grid.length > 0),
338
+ gridAnalysis,
339
+ hasTransform: !!transform,
340
+ pathTrace: !!show_path_trace,
341
+ complexity: gridAnalysis ? gridAnalysis.totalDivisions : 1,
342
+ type: this.classifyDetectorType(gridAnalysis, transform, show_path_trace)
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Detector型の分類
348
+ */
349
+ static classifyDetectorType(gridAnalysis, transform, pathTrace) {
350
+ let type = 'point'; // デフォルト
351
+
352
+ if (gridAnalysis) {
353
+ const { dimension, orthogonality } = gridAnalysis;
354
+ type = `${dimension}d_${orthogonality.type}`;
355
+
356
+ if (gridAnalysis.totalDivisions > 10000) {
357
+ type += '_high_resolution';
358
+ }
359
+ }
360
+
361
+ if (transform) {
362
+ type += '_transformed';
363
+ }
364
+
365
+ if (pathTrace) {
366
+ type += '_path_traced';
367
+ }
368
+
369
+ return type;
370
+ }
371
+
372
+ /**
373
+ * Detector最適化分析
374
+ */
375
+ static analyzeDetectorOptimization(detectorData, yamlData = null) {
376
+ const analysis = this.validateCompleteDetectorStructure(detectorData, yamlData);
377
+ const suggestions = [];
378
+
379
+ // グリッド最適化の提案
380
+ if (analysis.gridAnalysis) {
381
+ const { totalDivisions, orthogonality, gridDensity } = analysis.gridAnalysis;
382
+
383
+ // 高解像度グリッドの警告
384
+ if (totalDivisions > 100000) {
385
+ suggestions.push({
386
+ type: 'performance',
387
+ priority: 'high',
388
+ message: `Very high grid resolution (${totalDivisions} divisions). Consider reducing for better performance.`
389
+ });
390
+ }
391
+
392
+ // 非直交グリッドの警告
393
+ if (!orthogonality.orthogonal) {
394
+ suggestions.push({
395
+ type: 'geometry',
396
+ priority: 'medium',
397
+ message: `Non-orthogonal grid detected (${(orthogonality.orthogonalRatio * 100).toFixed(1)}% orthogonal). May affect calculation accuracy.`
398
+ });
399
+ }
400
+
401
+ // グリッド密度の分析
402
+ if (gridDensity > 1000) {
403
+ suggestions.push({
404
+ type: 'resolution',
405
+ priority: 'low',
406
+ message: `High grid density (${gridDensity.toFixed(1)} divisions/unit³). Verify if this resolution is necessary.`
407
+ });
408
+ }
409
+ }
410
+
411
+ // パス追跡機能の使用提案
412
+ if (!analysis.pathTrace && analysis.complexity > 1000) {
413
+ suggestions.push({
414
+ type: 'feature',
415
+ priority: 'medium',
416
+ message: 'Consider enabling path tracing for high-resolution detector analysis.'
417
+ });
418
+ }
419
+
420
+ // Transform使用の最適化
421
+ if (analysis.hasTransform && yamlData) {
422
+ // Transform使用統計を取得できる場合の分析
423
+ suggestions.push({
424
+ type: 'geometry',
425
+ priority: 'low',
426
+ message: 'Transform detected. Verify geometric transformation is necessary for this detector.'
427
+ });
428
+ }
429
+
430
+ return {
431
+ analysis,
432
+ suggestions,
433
+ isOptimal: suggestions.filter(s => s.priority === 'high').length === 0,
434
+ performance: {
435
+ complexity: analysis.complexity,
436
+ estimatedMemory: this.estimateMemoryUsage(analysis),
437
+ recommendedCpuCores: this.recommendCpuCores(analysis)
438
+ }
439
+ };
440
+ }
441
+
442
+ /**
443
+ * メモリ使用量の推定
444
+ */
445
+ static estimateMemoryUsage(analysis) {
446
+ const baseMemory = 1; // MB base
447
+ const gridMemory = analysis.complexity * 0.001; // MB per division
448
+ const transformMemory = analysis.hasTransform ? 0.1 : 0;
449
+ const pathTraceMemory = analysis.pathTrace ? analysis.complexity * 0.01 : 0;
450
+
451
+ return {
452
+ base: baseMemory,
453
+ grid: gridMemory,
454
+ transform: transformMemory,
455
+ pathTrace: pathTraceMemory,
456
+ total: baseMemory + gridMemory + transformMemory + pathTraceMemory
457
+ };
458
+ }
459
+
460
+ /**
461
+ * 推奨CPU コア数
462
+ */
463
+ static recommendCpuCores(analysis) {
464
+ if (analysis.complexity < 100) return 1;
465
+ if (analysis.complexity < 1000) return 2;
466
+ if (analysis.complexity < 10000) return 4;
467
+ if (analysis.complexity < 100000) return 8;
468
+ return 16;
469
+ }
470
+
471
+ /**
472
+ * グリッド互換性チェック
473
+ */
474
+ static checkDetectorCompatibility(detector1, detector2) {
475
+ const analysis1 = this.validateCompleteDetectorStructure(detector1);
476
+ const analysis2 = this.validateCompleteDetectorStructure(detector2);
477
+
478
+ const compatibility = {
479
+ dimensionMatch: false,
480
+ resolutionCompatible: false,
481
+ geometryCompatible: false,
482
+ overall: 'incompatible'
483
+ };
484
+
485
+ // 次元マッチング
486
+ if (analysis1.gridAnalysis && analysis2.gridAnalysis) {
487
+ compatibility.dimensionMatch =
488
+ analysis1.gridAnalysis.dimension === analysis2.gridAnalysis.dimension;
489
+ }
490
+
491
+ // 解像度互換性
492
+ const ratio = analysis1.complexity / analysis2.complexity;
493
+ compatibility.resolutionCompatible = ratio >= 0.1 && ratio <= 10;
494
+
495
+ // 幾何学的互換性
496
+ compatibility.geometryCompatible =
497
+ analysis1.type.includes('orthogonal') === analysis2.type.includes('orthogonal');
498
+
499
+ // 総合評価
500
+ const compatibilityScore = [
501
+ compatibility.dimensionMatch,
502
+ compatibility.resolutionCompatible,
503
+ compatibility.geometryCompatible
504
+ ].filter(Boolean).length;
505
+
506
+ if (compatibilityScore === 3) compatibility.overall = 'fully_compatible';
507
+ else if (compatibilityScore === 2) compatibility.overall = 'mostly_compatible';
508
+ else if (compatibilityScore === 1) compatibility.overall = 'partially_compatible';
509
+
510
+ return compatibility;
511
+ }
512
+ }