# 仕様書 (SPEC.md)

## 1. 目的
本文書は、2次元線形弾性トラス構造解析を行うJuliaプログラムの一連の設計・実装・テスト仕様を定義する。

---

## 2. 関連要件
- [要件](PRD.md)
---

## 3. 入出力ファイル

### 3.1 入力ファイル構成
| ファイルパス | 内容 | 必須列 | 制約 |
|------|------|--------|------|
| `./input/nodes.csv` | 節点座標 | `id,x,y` | `id`は重複なし正の整数`x,y`は[m] |
| `./input/elements.csv` | 要素接続・特性 | `id,n1,n2,E,A` | `n1,n2 ∈ nodes.idE:[Pa]`, `A:[m²]` |
| `./input/loads.csv` | 集中荷重 | `node_id,fx,fy` | `node_id ∈ nodes.id`。単位は[N]。未適用は省略または0.0 |
| `./input/bcs.csv` | 境界条件 | `node_id,ux,uy` | `ux,uy ∈ {0,1}`。単位なしフラグ。未拘束は省略または0 |

- 結合は`DataFrames.join`に委ねず、事前に`setdiff`で外部キー検証。不一致時は`DomainError("対象CSV名: 不一致idリスト")`を送出し、修正箇所を明確にする。
- エンコーディング: `UTF-8`（BOMなし）。数値区切り`.`。改行`\n`。
- 順序維持: 各CSVの最終出力列は `id` 昇順。

### 3.2 出力ファイル構成
| ファイルパス | 内容 | 必須列 | 制約 |
|------|------|--------|------|
| `./output/displacements.csv` | 節点変位 | `node_id,ux,uy` | 単位[m]。 |
| `./output/reactions.csv` | 節点反力 | `node_id,rx,ry` | 単位[N]。拘束済み節点のみ記載 |
| `./output/stresses.csv` | 要素応力 | `element_id,sigma` | 単位[Pa]。引張:+, 圧縮:- |
| `./output/residual.csv` | 平衡残差 | `scalar_value` | 1行1数値`||K·U - F||_∞` |

---

## 4. 内部データ構造 (Julia)
```julia
struct Node
    id::Int
    x::Float64
    y::Float64
end
struct Element
    id::Int
    n1::Int
    n2::Int
    E::Float64  # [Pa]
    A::Float64  # [m²]
end
struct Load
    node_id::Int
    fx::Float64
    fy::Float64
end
struct BoundaryCondition
    node_id::Int
    ux::Int  # 0:自由, 1:固定
    uy::Int  # 0:自由, 1:固定
end
```
- 全ての数値計算は `Float64` で実行`BigFloat` は使用しない。
- 節点 `i` の自由度マッピング: `dof_x(i) = 2*(i-1) + 1`, `dof_y(i) = 2*i`
- グローバル自由度ベクトル順序: `U = [U1x, U1y, U2x, U2y, ..., UNx, UNy]`

---

## 5. 計算アルゴリズム

### 5.1 要素剛性行列 (Local → Global)
```
L = norm([x2-x1, y2-y1])
c = (x2-x1)/L,  s = (y2-y1)/L
k_e = (E*A/L) * [ c^2  c*s; c*s  s^2 ]
```
- 要素 `e` の局所自由度: `[dof_x(n1), dof_y(n1), dof_x(n2), dof_y(n2)]`

### 5.2 境界条件処理：置換法 (Replacement Method)
1. 拘束済み自由度 `j ∈ {dof_x(node), dof_y(node)}` に対し:
   - `K[j,j] = 1.0`
   - `F[j] = 0.0`
2. 連立一次方程式 `K * U = F` を求解 `LinearAlgebra.LU`)
3. 反力計算: `R = K_original * U - F_original`（拘束済み自由度のみ抽出）
   - BC適用前に `K_original`, `F_original` をディープコピーで保存

### 5.3 要素応力計算
```
σ = (E/L) * [ -c  -s   c   s ] * [ Ux1, Uy1, Ux2, Uy2 ]^T
  = (E/L) * ( c*(Ux2-Ux1) + s*(Uy2-Uy1) )
```
- 符号規約: `σ > 0` → 引張, `σ < 0` → 圧縮

### 5.4 剛性行列特異検出
- 境界条件適用後の減少数行列 `K_red` に対し:
  - `if LinearAlgebra.rank(K_red) < size(K_red, 1)` → `ErrorException("拘束不充分：剛性行列が特異です。")`
- または `LinearAlgebra.isposdef(K_red)` で検証。両者を採用し、真を条件に続行。

---

## 6. バリデーションとエラー処理

**計算前**
| チェック項目 | 条件 | アクション |
|------|------|-- |------ |
| 節点/要素ID妥当性 | `id <= 0` | `DomainError("idは正の整数を入力してください。")` |
| 節点ID重複チェック | `if length(nodes.id) != length(unique(nodes.id))` | `ErrorException("節点IDが重複しています。")` |
| 要素ID重複チェック | `if length(elements.id) != length(unique(elements.id))` | `ErrorException("要素IDが重複しています。")` |
| ノード重複座標 | 各要素`norm(n1.coords - n2.coords) < 1e-9` | `ErrorException("要素が縮退しています。ノード座標を確認してください。")` |
| 要素ID整合性 | `elements.n1, n2 ∉ nodes.id` | `ArgumentError("要素n1/n2が節点リストに存在しません。")` |
| 荷重/拘束ID整合性 | `loads/bcs.node_id ∉ nodes.id` | `ArgumentError("節点IDが一致しません。")` |
| 拘束フラグ妥当性 | `ux, uy ∉ {0, 1}` | `ArgumentError("bc.csv: 拘束フラグは0または1のみ許容します。")` |
| 物理的妥当性 | `E <= 0 || A <= 0` | `DomainError("EとAは正の値を入力してください。")` |

**計算後**
| チェック項目 | 条件 | アクション |
|------|------|------ |
| 特異行列 | `!isposdef(K_red)` | `ErrorException("拘束不足により剛性行列が特異です。")` |

- エラーメッセージ: 該当IDを付与（例: `"重複節点ID: [3, 7]"`）
- 数値安定性: 行列演算は `Float64` 標準。ピボット選択付き `lu()` を使用。

---

## 7. 設計補足（コード生成基準）

### 7.1 プロジェクト構造
| ディレクトリ/ファイル | 責務 |
|:--|:--|
| `src/main.jl` | CLIエントリー、パイプライン orchestration |
| `src/data.jl` | CSV読込、構造体変換、バリデーション関数群 |
| `src/assemble.jl` | グローバル剛性行列・荷重ベクトル構築、BC適用 |
| `src/solve.jl` | 連立方程式求解、反力・応力計算 |
| `src/output.jl` | CSV出力、残差計算、ファイル保存 |

### 7.2 主要関数インターフェース（型明示必須）
```julia
# data.jl
function load_and_validate()::Tuple{Vector{Node}, Vector{Element}, Vector{Load}, Vector{BoundaryCondition}}
# assemble.jl
function assemble_global(nodes, elements)::Tuple{Matrix{Float64}, Vector{Float64}}
function apply_bc(K::Matrix{Float64}, F::Vector{Float64}, bcs)::Tuple{Matrix{Float64}, Vector{Float64}}
# solve.jl
function solve_system(K::Matrix{Float64}, F::Vector{Float64})::Vector{Float64}
function compute_reactions(K_orig::Matrix{Float64}, U::Vector{Float64}, F::Vector{Float64}, bcs)::Vector{Float64}
function compute_stresses(elements::Vector{Element}, U::Vector{Float64})::Vector{Float64}
# output.jl
function save_outputs(u::Vector{Float64}, r::Vector{Float64}, σ::Vector{Float64}, residual::Float64)
```

### 7.3 グローバル行列構築アルゴリズム（実装順序固定）
1. `N = 2 * length(nodes)` と初期化`K = zeros(N, N)`, `F = zeros(N)`
2. DOFマッピング：節点 `i` の座標自由度は `dof = [2i-1, 2i]`
3. 要素ループ`for e in elements`）:
   - `c = cos(θ)`, `s = sin(θ)`, `L = norm(nodes[e.n2] - nodes[e.n1])`
   - `k_e = (elements[e].E * elements[e].A / L) * [c^2 c*s; c*s s^2]`
   - 要素DOFインデックス取得`I = [dof[e.n1]; dof[e.n2]]`
   - 行列組込`for i in 1:4, j in 1:4; K[I[i], I[j]] += k_e[i,j]; end`
4. 荷重ベクトル組込`F[dof[e.node_id]] += e.fx` / `e.fy`
5. **BC適用（置換法）**: `K[dof[bc.node_id], :] = 0.0; K[:, dof[bc.node_id]] = 0.0; K[dof[bc.node_id], dof[bc.node_id]] = 1.0; F[dof[bc.node_id]] = bc.ux==1 ? 0.0 : bc.uy==1 ? 0.0 : 0.0`
6. **行列を保存**: `K_orig = copy(K)`（反力計算用）
7. 求解: `U = lu(K) \ F`（後処理で `!isposdef(K)` 検証）

> 📌 **AI実行指示**: 上記順序をコード変換時に変更不能。DOFマッピングと置換法ロジックは仕様表と1対1対応させること。

---

## 8. テスト仕様と検証基準
本仕様では Julia 標準 `Test` モジュールのみを使用し、外部テストフレームワークに依存しない。

### 8.1 テスト構成・ディレクトリ
| 分類 | 対象ファイル | 責務 |
|:--|:--|:--|
| `tests/test_validation.jl` | バリデーション系 | `@test_throws` による全異常系網羅 |
| `tests/test_solver.jl` | 計算・出力系 | 解析解照合・CSV構造検証・残差検証 |

### 8.2 受入基準 (Pass/Fail)
| 項目 | 条件 |
|:--|:--|
| 数値精度 | `@test u ≈ u_exact atol=1e-10` 全自由度で成立 |
| 行列特性 | `@test isposdef(K_red) == true` |
| 出力構造 | `names(df) == expected_names` 完全一致 |
| CSV整合性 | `df.node_id` が `1..N` の昇順で構成されている |
| 残差許容 | `@test residual < 1e-10` |

### 8.3 必須テストケース一覧
| ケースID | カテゴリ | 内容 |
|------|------|------ |
| TC-01 | 正常系 | 3節点1要素トラス：解析解一致 |
| TC-02 | 正常系 | 4節点2要素トラス：荷重・拘束複合 |
| TC-03 | 正常系 | 剛体移動なし：拘束充分 |
| TC-04 | 異常系 | 節点ID重複 → `ErrorException` |
| TC-05 | 異常系 | 要素ID未整合 → `ArgumentError` |
| TC-06 | 異常系 | 座標縮退 | `ErrorException` |
| TC-07 | 異常系 | 拘束フラグ不正 | `ArgumentError` |
| TC-08 | 異常系 | 特異行列（拘束不足）→ `ErrorException` |

### 8.4 バリデーション異常系テスト網羅表
| エラー名 | エラーメッセージパターン | 入力条件 |
|------|------|------|
| `DomainError` | `「idは正の整数を入力してください。」` | `id <= 0` |
| `ErrorException` | `「節点IDが重複しています。」` | `id == id'` |
| `ErrorException` | `「要素IDが重複しています。」` | `id == id'` |
| `ErrorException` | `「要素が縮退しています。」` | `norm(p1-p2) < 1e-9` |
| `ArgumentError` | `「要素n1/n2が節点リストに存在しません。」` | `n1 ∉ nodes.id` |
| `ArgumentError` | `「節点IDが一致しません。」` | `bc.node_id ∉ nodes.id` |
| `ArgumentError` | `「bc.csv: 拘束フラグは0または1のみ許容します。」` | `ux=2` |
| `DomainError` | `「EとAは正の値を入力してください。」` | `E <= 0` |
| `ErrorException` | `「拘束不足により剛性行列が特異です。」` | 拘束条件不足 |

### 8.5 CSV出力検証項目
| 出力CSV | 検証項目 | 期待値 |
|------|------|------ |
| `displacements.csv` | 全節点の変位値 | 解析解一致 `atol=1e-10` |
| `reactions.csv` | 反力ベクトル平衡 `||R|| ≈ ||F||` |
| `stresses.csv` | 応力符号・大小 | `σ = (E/L) * ΔL` |
| `residual.csv` | 平衡残差 `||K·U - F||_∞` | `< 1e-10` |

---

## 9. Julia実装ガイドライン
- 型明示: 全関数の戻り値型（例: `::Vector{Float64}`）を明記。`Any` は禁止。
- メモリ効率: 行列構築は `zeros(N, N)` 初期化。`push!` は使用禁止。
- 行列操作: `K[I, J] = val` によるブロードキャスト代入。
- 命名規則: 変数 `camelCase`, 関数 `snake_case`。
- モジュール分割: `src/` 配下に機能ごとにファイル分割。
- 外部依存: `DataFrames`, `LinearAlgebra`, `DelimitedFiles` のみ許可。他は禁止。
