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.
- package/README.md +241 -0
- package/config/mcp-manifest.json +1713 -0
- package/config/package-lock.json +1335 -0
- package/config/poker_mcp.code-workspace +8 -0
- package/package.json +51 -0
- package/src/config/ConfigManager.js +120 -0
- package/src/config/default.json +62 -0
- package/src/mcp/handlers/bodyHandlers.js +63 -0
- package/src/mcp/handlers/buildupFactorHandlers.js +43 -0
- package/src/mcp/handlers/calculationHandlers.js +265 -0
- package/src/mcp/handlers/detectorHandlers.js +201 -0
- package/src/mcp/handlers/index.js +43 -0
- package/src/mcp/handlers/sourceHandlers.js +25 -0
- package/src/mcp/handlers/transformHandlers.js +26 -0
- package/src/mcp/handlers/unitHandlers.js +305 -0
- package/src/mcp/handlers/zoneHandlers.js +83 -0
- package/src/mcp/middleware/errorHandler.js +49 -0
- package/src/mcp/middleware/requestValidator.js +129 -0
- package/src/mcp/server.js +76 -0
- package/src/mcp/tools/bodyTools.js +185 -0
- package/src/mcp/tools/buildupFactorTools.js +82 -0
- package/src/mcp/tools/calculationTools.js +58 -0
- package/src/mcp/tools/commonTools.js +22 -0
- package/src/mcp/tools/detectorTools.js +131 -0
- package/src/mcp/tools/index.js +34 -0
- package/src/mcp/tools/sourceTools.js +726 -0
- package/src/mcp/tools/sourceTools_fixed_part2.js +41 -0
- package/src/mcp/tools/transformTools.js +73 -0
- package/src/mcp/tools/unitTools.js +137 -0
- package/src/mcp/tools/zoneTools.js +65 -0
- package/src/mcp_server_stdio_v4.js +45 -0
- package/src/routes/restApi.js +184 -0
- package/src/services/CalculationService.js +525 -0
- package/src/services/DataManager.js +738 -0
- package/src/services/TaskManager.js +1622 -0
- package/src/utils/MaterialAlternatives.js +189 -0
- package/src/utils/errorHandler.js +68 -0
- package/src/utils/errors.js +37 -0
- package/src/utils/logger.js +24 -0
- package/src/utils/logger_no_color.js +28 -0
- package/src/utils/mcpErrors.js +225 -0
- package/src/validators/CMBValidator.js +191 -0
- package/src/validators/DetectorValidator.js +512 -0
- package/src/validators/ManifestValidator.js +249 -0
- package/src/validators/NuclideValidator.js +171 -0
- package/src/validators/PhysicsValidator.js +401 -0
- package/src/validators/PhysicsValidatorSimple.js +87 -0
- package/src/validators/SourceValidator.js +515 -0
- package/src/validators/TransformValidator.js +472 -0
- 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
|
+
}
|