delta-theory 6.9.0__py3-none-any.whl
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.
- apps/__init__.py +6 -0
- apps/delta_fatigue_app.py +337 -0
- core/Universal_Lindemann.py +422 -0
- core/__init__.py +35 -0
- core/dbt_unified.py +690 -0
- core/materials.py +688 -0
- core/unified_yield_fatigue_v6_9.py +762 -0
- delta_theory-6.9.0.dist-info/METADATA +640 -0
- delta_theory-6.9.0.dist-info/RECORD +15 -0
- delta_theory-6.9.0.dist-info/WHEEL +5 -0
- delta_theory-6.9.0.dist-info/entry_points.txt +3 -0
- delta_theory-6.9.0.dist-info/licenses/LICENSE +26 -0
- delta_theory-6.9.0.dist-info/top_level.txt +3 -0
- validation/__init__.py +10 -0
- validation/fatigue_redis_api.py +334 -0
core/dbt_unified.py
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
================================================================================
|
|
4
|
+
dbt_unified.py - 統一DBT/DBTT予測モデル
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
概要
|
|
8
|
+
----
|
|
9
|
+
σ_y(d, T) = σ_f(d, c, T) という単一の方程式を、異なる軸から解く統合モジュール。
|
|
10
|
+
|
|
11
|
+
【Core】共通の物理モデル
|
|
12
|
+
- σ_y(d, T): δ理論 + Hall-Petch
|
|
13
|
+
- σ_f(d, c, T): 粒界凝集破壊 + McLean偏析
|
|
14
|
+
- θ(c, T): McLean等温線
|
|
15
|
+
- g_seg(θ): 脆化関数(閾値的onset対応)
|
|
16
|
+
|
|
17
|
+
【View 1】粒径軸(T固定 → d* を求める)
|
|
18
|
+
- 延性窓 [d_low, d_high]
|
|
19
|
+
- c_crit(延性窓消失の臨界濃度)
|
|
20
|
+
- Re-entrant構造の検出
|
|
21
|
+
|
|
22
|
+
【View 2】温度軸(d固定 → T* を求める)
|
|
23
|
+
- DBTT(d, c) [K]
|
|
24
|
+
- DBTT相図(d-c-DBTT)
|
|
25
|
+
|
|
26
|
+
【View 3】時間軸(偏析発展)
|
|
27
|
+
- θ_eq(c, T_age): 平衡偏析
|
|
28
|
+
- PHR → θ 変換(AES校正)
|
|
29
|
+
|
|
30
|
+
統合の経緯
|
|
31
|
+
----------
|
|
32
|
+
v4.0(延性窓/Re-entrant)+ v5.2(DBTT threshold)+ Fe-P θ-DBTT
|
|
33
|
+
→ 全て同じ物理モデル、見る軸が違うだけ → 1本に統合
|
|
34
|
+
|
|
35
|
+
Author: 環 & ご主人さま (飯泉真道)
|
|
36
|
+
Date: 2026-02-02
|
|
37
|
+
================================================================================
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import argparse
|
|
43
|
+
from dataclasses import dataclass
|
|
44
|
+
from typing import Dict, List, Literal, Optional, Tuple
|
|
45
|
+
import numpy as np
|
|
46
|
+
|
|
47
|
+
# ==============================================================================
|
|
48
|
+
# 物理定数
|
|
49
|
+
# ==============================================================================
|
|
50
|
+
eV_to_J = 1.602176634e-19
|
|
51
|
+
k_B = 1.380649e-23
|
|
52
|
+
PI = np.pi
|
|
53
|
+
M_TAYLOR = 3.0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ==============================================================================
|
|
57
|
+
# 材料データクラス
|
|
58
|
+
# ==============================================================================
|
|
59
|
+
@dataclass
|
|
60
|
+
class Material:
|
|
61
|
+
"""材料パラメータ(Fe基準、他材料は拡張可能)"""
|
|
62
|
+
name: str
|
|
63
|
+
# δ理論パラメータ
|
|
64
|
+
E_bond_eV: float # 結合エネルギー [eV]
|
|
65
|
+
f_d: float # d電子方向性係数
|
|
66
|
+
a_lat: float # 格子定数 [m]
|
|
67
|
+
alpha0: float # 幾何係数
|
|
68
|
+
delta_L: float # Lindemannパラメータ
|
|
69
|
+
T_m: float # 融点 [K]
|
|
70
|
+
# 弾性定数
|
|
71
|
+
E_mod: float # ヤング率 [Pa]
|
|
72
|
+
nu: float # ポアソン比
|
|
73
|
+
# 粒界パラメータ
|
|
74
|
+
W_sep0: float # 基準分離仕事 [J/m²]
|
|
75
|
+
delta_sep: float # 分離変位 [m]
|
|
76
|
+
# 偏析パラメータ(デフォルト)
|
|
77
|
+
E_seg_eV: float # 偏析エネルギー [eV]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def b(self) -> float:
|
|
81
|
+
"""バーガースベクトル(BCC: a√3/2)"""
|
|
82
|
+
return self.a_lat * np.sqrt(3) / 2
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def V_act(self) -> float:
|
|
86
|
+
"""活性化体積"""
|
|
87
|
+
return self.b ** 3
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def G(self) -> float:
|
|
91
|
+
"""剛性率"""
|
|
92
|
+
return self.E_mod / (2 * (1 + self.nu))
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def E_eff(self) -> float:
|
|
96
|
+
"""有効結合エネルギー"""
|
|
97
|
+
return self.E_bond_eV * eV_to_J * self.alpha0 * self.f_d
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# デフォルト材料(Fe)
|
|
101
|
+
MATERIAL_FE = Material(
|
|
102
|
+
name="Fe",
|
|
103
|
+
E_bond_eV=4.28,
|
|
104
|
+
f_d=1.5,
|
|
105
|
+
a_lat=2.92e-10,
|
|
106
|
+
alpha0=0.289,
|
|
107
|
+
delta_L=0.18,
|
|
108
|
+
T_m=1811.0,
|
|
109
|
+
E_mod=211e9,
|
|
110
|
+
nu=0.29,
|
|
111
|
+
W_sep0=2.0,
|
|
112
|
+
delta_sep=0.2e-9,
|
|
113
|
+
E_seg_eV=0.45, # Fe-P系のデフォルト
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ==============================================================================
|
|
118
|
+
# Core: 共通の物理モデル
|
|
119
|
+
# ==============================================================================
|
|
120
|
+
|
|
121
|
+
class DBTCore:
|
|
122
|
+
"""DBT/DBTT計算のコアエンジン"""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
mat: Material = MATERIAL_FE,
|
|
127
|
+
beta_hp: float = 3.5e-3,
|
|
128
|
+
C_f: float = 5.0,
|
|
129
|
+
K_emb: float = 3.0,
|
|
130
|
+
n_emb: float = 1.0, # 脆化指数(n>1で閾値的)
|
|
131
|
+
):
|
|
132
|
+
self.mat = mat
|
|
133
|
+
self.beta_hp = beta_hp
|
|
134
|
+
self.C_f = C_f
|
|
135
|
+
self.K_emb = K_emb
|
|
136
|
+
self.n_emb = n_emb
|
|
137
|
+
|
|
138
|
+
# 事前計算
|
|
139
|
+
self._A0_noR = (mat.E_eff / mat.V_act) * mat.delta_L / (2 * PI * M_TAYLOR)
|
|
140
|
+
|
|
141
|
+
# --------------------------------------------------------------------------
|
|
142
|
+
# 基本関数
|
|
143
|
+
# --------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def R_block(self, d: float) -> float:
|
|
146
|
+
"""Hall-Petch遮断係数 R(d) = 1 + β/√d"""
|
|
147
|
+
return 1.0 + self.beta_hp / np.sqrt(d)
|
|
148
|
+
|
|
149
|
+
def HP(self, T: float) -> float:
|
|
150
|
+
"""熱的余白 HP = 1 - T/T_m"""
|
|
151
|
+
return max(1e-9, 1.0 - T / self.mat.T_m)
|
|
152
|
+
|
|
153
|
+
def theta_mclean(self, c: float, T: float, E_seg_eV: Optional[float] = None) -> float:
|
|
154
|
+
"""McLean等温線による粒界被覆率 θ(c, T)
|
|
155
|
+
|
|
156
|
+
θ = Kc / (1 - c + Kc), K = exp(E_seg / kT)
|
|
157
|
+
"""
|
|
158
|
+
c = float(np.clip(c, 0.0, 1.0 - 1e-9))
|
|
159
|
+
E_seg = E_seg_eV if E_seg_eV is not None else self.mat.E_seg_eV
|
|
160
|
+
K = np.exp((E_seg * eV_to_J) / (k_B * T))
|
|
161
|
+
return (K * c) / (1.0 - c + K * c + 1e-30)
|
|
162
|
+
|
|
163
|
+
def g_seg(self, theta: float) -> float:
|
|
164
|
+
"""偏析による脆化関数 g_seg(θ) = 1 / (1 + K_emb × θ^n)
|
|
165
|
+
|
|
166
|
+
n=1: 線形的脆化
|
|
167
|
+
n>1: 閾値的onset(パーコレーション的)
|
|
168
|
+
"""
|
|
169
|
+
return 1.0 / (1.0 + self.K_emb * (theta ** self.n_emb))
|
|
170
|
+
|
|
171
|
+
def g_T_cohesion(self, T: float) -> float:
|
|
172
|
+
"""温度による凝集力低下 g_T = 1 - T/T_m"""
|
|
173
|
+
return max(1e-3, 1.0 - T / self.mat.T_m)
|
|
174
|
+
|
|
175
|
+
# --------------------------------------------------------------------------
|
|
176
|
+
# 応力計算
|
|
177
|
+
# --------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def sigma_y(self, d: float, T: float) -> float:
|
|
180
|
+
"""降伏応力 σ_y(d, T) [Pa]
|
|
181
|
+
|
|
182
|
+
σ_y = A0 × HP × R(d)
|
|
183
|
+
"""
|
|
184
|
+
return self._A0_noR * self.HP(T) * self.R_block(d)
|
|
185
|
+
|
|
186
|
+
def sigma_GB(self, c: float, T: float, E_seg_eV: Optional[float] = None) -> float:
|
|
187
|
+
"""粒界強度 σ_GB(c, T) [Pa]
|
|
188
|
+
|
|
189
|
+
σ_GB = W_sep(c, T) / δ_sep
|
|
190
|
+
W_sep = W_sep0 × g_T × g_seg(θ)
|
|
191
|
+
"""
|
|
192
|
+
theta = self.theta_mclean(c, T, E_seg_eV)
|
|
193
|
+
W_sep = self.mat.W_sep0 * self.g_T_cohesion(T) * self.g_seg(theta)
|
|
194
|
+
return W_sep / self.mat.delta_sep
|
|
195
|
+
|
|
196
|
+
def sigma_f(self, d: float, c: float, T: float, E_seg_eV: Optional[float] = None) -> float:
|
|
197
|
+
"""破壊応力 σ_f(d, c, T) [Pa]
|
|
198
|
+
|
|
199
|
+
σ_f = C_f × √(σ_GB × G × b / (R × d))
|
|
200
|
+
"""
|
|
201
|
+
sig_gb = self.sigma_GB(c, T, E_seg_eV)
|
|
202
|
+
R = self.R_block(d)
|
|
203
|
+
return self.C_f * np.sqrt(sig_gb * self.mat.G * self.mat.b / (R * d))
|
|
204
|
+
|
|
205
|
+
def g_diff(self, d: float, c: float, T: float) -> float:
|
|
206
|
+
"""g(d, c, T) = σ_y - σ_f
|
|
207
|
+
|
|
208
|
+
g > 0: 脆性(σ_y > σ_f)
|
|
209
|
+
g < 0: 延性(σ_y < σ_f)
|
|
210
|
+
g = 0: 遷移点
|
|
211
|
+
"""
|
|
212
|
+
return self.sigma_y(d, T) - self.sigma_f(d, c, T)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ==============================================================================
|
|
216
|
+
# View 1: 粒径軸(T固定 → d* を求める)
|
|
217
|
+
# ==============================================================================
|
|
218
|
+
|
|
219
|
+
class GrainSizeView:
|
|
220
|
+
"""粒径軸からのDBT解析(v4.0相当)"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, core: DBTCore):
|
|
223
|
+
self.core = core
|
|
224
|
+
|
|
225
|
+
def find_all_crossings(
|
|
226
|
+
self,
|
|
227
|
+
T: float,
|
|
228
|
+
c: float,
|
|
229
|
+
d_min: float = 1e-8,
|
|
230
|
+
d_max: float = 1e-3,
|
|
231
|
+
n_pts: int = 1000,
|
|
232
|
+
) -> np.ndarray:
|
|
233
|
+
"""σ_y = σ_f の全交差点を検出(Re-entrant対応)"""
|
|
234
|
+
d_arr = np.logspace(np.log10(d_min), np.log10(d_max), n_pts)
|
|
235
|
+
g_arr = np.array([self.core.g_diff(d, c, T) for d in d_arr])
|
|
236
|
+
|
|
237
|
+
# 符号変化点を検出
|
|
238
|
+
sign_changes = np.where(np.diff(np.sign(g_arr)) != 0)[0]
|
|
239
|
+
|
|
240
|
+
crossings = []
|
|
241
|
+
for idx in sign_changes:
|
|
242
|
+
# 線形補間
|
|
243
|
+
d0, d1 = d_arr[idx], d_arr[idx + 1]
|
|
244
|
+
g0, g1 = g_arr[idx], g_arr[idx + 1]
|
|
245
|
+
d_cross = d0 + (d1 - d0) * (-g0) / (g1 - g0 + 1e-30)
|
|
246
|
+
crossings.append(d_cross)
|
|
247
|
+
|
|
248
|
+
return np.array(crossings)
|
|
249
|
+
|
|
250
|
+
def classify_mode(
|
|
251
|
+
self,
|
|
252
|
+
T: float,
|
|
253
|
+
c: float,
|
|
254
|
+
d_min: float = 1e-8,
|
|
255
|
+
d_max: float = 1e-3,
|
|
256
|
+
) -> Dict:
|
|
257
|
+
"""破壊モードを分類(延性窓検出)
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
mode: "brittle" / "ductile" / "transition" / "window"
|
|
261
|
+
d_low: 延性窓下限(存在しなければ nan)
|
|
262
|
+
d_high: 延性窓上限(存在しなければ nan)
|
|
263
|
+
"""
|
|
264
|
+
crossings = self.find_all_crossings(T, c, d_min, d_max)
|
|
265
|
+
|
|
266
|
+
# 境界でのg値
|
|
267
|
+
g_min = self.core.g_diff(d_min, c, T)
|
|
268
|
+
g_max = self.core.g_diff(d_max, c, T)
|
|
269
|
+
|
|
270
|
+
if len(crossings) == 0:
|
|
271
|
+
if g_min > 0:
|
|
272
|
+
return {"mode": "brittle", "d_low": np.nan, "d_high": np.nan,
|
|
273
|
+
"msg": "全域脆性(σ_y > σ_f everywhere)"}
|
|
274
|
+
else:
|
|
275
|
+
return {"mode": "ductile", "d_low": np.nan, "d_high": np.nan,
|
|
276
|
+
"msg": "全域延性(σ_y < σ_f everywhere)"}
|
|
277
|
+
|
|
278
|
+
if len(crossings) == 1:
|
|
279
|
+
d_star = crossings[0]
|
|
280
|
+
if g_min > 0:
|
|
281
|
+
return {"mode": "transition", "d_low": np.nan, "d_high": d_star,
|
|
282
|
+
"msg": f"単一DBT: d* = {d_star*1e6:.2f} μm(小d側が脆性)"}
|
|
283
|
+
else:
|
|
284
|
+
return {"mode": "transition", "d_low": d_star, "d_high": np.nan,
|
|
285
|
+
"msg": f"単一DBT: d* = {d_star*1e6:.2f} μm(小d側が延性)"}
|
|
286
|
+
|
|
287
|
+
# 交差2つ以上: Re-entrant(延性窓)
|
|
288
|
+
d_low = crossings[0]
|
|
289
|
+
d_high = crossings[-1]
|
|
290
|
+
return {"mode": "window", "d_low": d_low, "d_high": d_high,
|
|
291
|
+
"msg": f"延性窓: {d_low*1e6:.3f} ~ {d_high*1e6:.2f} μm"}
|
|
292
|
+
|
|
293
|
+
def find_c_crit(
|
|
294
|
+
self,
|
|
295
|
+
T: float,
|
|
296
|
+
d_min: float = 1e-8,
|
|
297
|
+
d_max: float = 1e-3,
|
|
298
|
+
c_max: float = 0.1,
|
|
299
|
+
tol: float = 1e-5,
|
|
300
|
+
) -> Dict:
|
|
301
|
+
"""延性窓が消失する臨界濃度 c_crit を二分法で求める"""
|
|
302
|
+
|
|
303
|
+
def has_ductile_region(c: float) -> bool:
|
|
304
|
+
d_arr = np.logspace(np.log10(d_min), np.log10(d_max), 200)
|
|
305
|
+
g_arr = np.array([self.core.g_diff(d, c, T) for d in d_arr])
|
|
306
|
+
return np.any(g_arr < 0)
|
|
307
|
+
|
|
308
|
+
# 境界チェック
|
|
309
|
+
if not has_ductile_region(0):
|
|
310
|
+
return {"c_crit": 0.0, "msg": "c=0で既に全域脆性"}
|
|
311
|
+
if has_ductile_region(c_max):
|
|
312
|
+
return {"c_crit": c_max, "msg": f"c={c_max*100:.1f}%でも延性領域あり"}
|
|
313
|
+
|
|
314
|
+
# 二分法
|
|
315
|
+
c_lo, c_hi = 0.0, c_max
|
|
316
|
+
while (c_hi - c_lo) > tol:
|
|
317
|
+
c_mid = (c_lo + c_hi) / 2
|
|
318
|
+
if has_ductile_region(c_mid):
|
|
319
|
+
c_lo = c_mid
|
|
320
|
+
else:
|
|
321
|
+
c_hi = c_mid
|
|
322
|
+
|
|
323
|
+
c_crit = (c_lo + c_hi) / 2
|
|
324
|
+
theta_crit = self.core.theta_mclean(c_crit, T)
|
|
325
|
+
return {
|
|
326
|
+
"c_crit": c_crit,
|
|
327
|
+
"theta_crit": theta_crit,
|
|
328
|
+
"msg": f"c_crit = {c_crit*100:.4f} at%, θ_crit = {theta_crit:.4f}"
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ==============================================================================
|
|
333
|
+
# View 2: 温度軸(d固定 → T* を求める)
|
|
334
|
+
# ==============================================================================
|
|
335
|
+
|
|
336
|
+
class TemperatureView:
|
|
337
|
+
"""温度軸からのDBTT解析(v5.2相当)"""
|
|
338
|
+
|
|
339
|
+
def __init__(self, core: DBTCore):
|
|
340
|
+
self.core = core
|
|
341
|
+
|
|
342
|
+
def find_DBTT(
|
|
343
|
+
self,
|
|
344
|
+
d: float,
|
|
345
|
+
c: float,
|
|
346
|
+
T_lo: float = 50.0,
|
|
347
|
+
T_hi: float = 1500.0,
|
|
348
|
+
n_scan: int = 500,
|
|
349
|
+
) -> Dict:
|
|
350
|
+
"""固定粒径 d における DBTT(脆性遷移温度)を求める
|
|
351
|
+
|
|
352
|
+
g(T) = σ_y(d,T) - σ_f(d,c,T) の符号変化点を探索
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
T_star: DBTT [K]
|
|
356
|
+
status: "crossing" / "always_brittle" / "always_ductile"
|
|
357
|
+
"""
|
|
358
|
+
T_arr = np.linspace(T_lo, T_hi, n_scan)
|
|
359
|
+
g_arr = np.array([self.core.g_diff(d, c, T) for T in T_arr])
|
|
360
|
+
|
|
361
|
+
# 全域チェック
|
|
362
|
+
if np.all(g_arr > 0):
|
|
363
|
+
return {"T_star": np.nan, "status": "always_brittle",
|
|
364
|
+
"msg": "全温度域で脆性"}
|
|
365
|
+
if np.all(g_arr < 0):
|
|
366
|
+
return {"T_star": np.nan, "status": "always_ductile",
|
|
367
|
+
"msg": "全温度域で延性"}
|
|
368
|
+
|
|
369
|
+
# 符号変化点を探す
|
|
370
|
+
sign_changes = np.where(np.diff(np.sign(g_arr)) != 0)[0]
|
|
371
|
+
if len(sign_changes) == 0:
|
|
372
|
+
return {"T_star": np.nan, "status": "no_crossing",
|
|
373
|
+
"msg": "交差点なし(数値問題?)"}
|
|
374
|
+
|
|
375
|
+
# 最初の交差点(低温側のDBTT)
|
|
376
|
+
idx = sign_changes[0]
|
|
377
|
+
a, b = T_arr[idx], T_arr[idx + 1]
|
|
378
|
+
|
|
379
|
+
# 二分法で高精度化
|
|
380
|
+
for _ in range(60):
|
|
381
|
+
m = 0.5 * (a + b)
|
|
382
|
+
gm = self.core.g_diff(d, c, m)
|
|
383
|
+
ga = self.core.g_diff(d, c, a)
|
|
384
|
+
if np.sign(gm) == np.sign(ga):
|
|
385
|
+
a = m
|
|
386
|
+
else:
|
|
387
|
+
b = m
|
|
388
|
+
|
|
389
|
+
T_star = 0.5 * (a + b)
|
|
390
|
+
|
|
391
|
+
# 交差の向き
|
|
392
|
+
g_low = self.core.g_diff(d, c, T_lo)
|
|
393
|
+
if g_low > 0:
|
|
394
|
+
status = "brittle_to_ductile"
|
|
395
|
+
msg = f"DBTT = {T_star:.1f} K(低温側が脆性)"
|
|
396
|
+
else:
|
|
397
|
+
status = "ductile_to_brittle"
|
|
398
|
+
msg = f"DBTT = {T_star:.1f} K(低温側が延性)"
|
|
399
|
+
|
|
400
|
+
return {"T_star": T_star, "status": status, "msg": msg}
|
|
401
|
+
|
|
402
|
+
def generate_DBTT_table(
|
|
403
|
+
self,
|
|
404
|
+
d_list: List[float],
|
|
405
|
+
c_list: List[float],
|
|
406
|
+
) -> np.ndarray:
|
|
407
|
+
"""DBTT(d, c) テーブルを生成"""
|
|
408
|
+
table = np.zeros((len(d_list), len(c_list)))
|
|
409
|
+
for i, d in enumerate(d_list):
|
|
410
|
+
for j, c in enumerate(c_list):
|
|
411
|
+
result = self.find_DBTT(d, c)
|
|
412
|
+
table[i, j] = result["T_star"]
|
|
413
|
+
return table
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# ==============================================================================
|
|
417
|
+
# View 3: 時間軸(偏析発展)
|
|
418
|
+
# ==============================================================================
|
|
419
|
+
|
|
420
|
+
class SegregationView:
|
|
421
|
+
"""偏析発展からのDBTT解析(Fe-P相当)"""
|
|
422
|
+
|
|
423
|
+
def __init__(self, core: DBTCore):
|
|
424
|
+
self.core = core
|
|
425
|
+
|
|
426
|
+
def theta_from_phr(self, phr: float, S: float, phr_bg: float = 0.0) -> float:
|
|
427
|
+
"""AES PHR → θ 変換(プロキシ)
|
|
428
|
+
|
|
429
|
+
θ = S × max(PHR - PHR_bg, 0) / (1 + S × max(PHR - PHR_bg, 0))
|
|
430
|
+
"""
|
|
431
|
+
r = max(0.0, float(phr) - float(phr_bg))
|
|
432
|
+
return (S * r) / (1.0 + S * r)
|
|
433
|
+
|
|
434
|
+
def fit_S_from_equilibrium(
|
|
435
|
+
self,
|
|
436
|
+
c_bulk: float,
|
|
437
|
+
eq_points: List[Tuple[float, float]], # [(T_age, phr), ...]
|
|
438
|
+
E_seg_eV: Optional[float] = None,
|
|
439
|
+
) -> float:
|
|
440
|
+
"""McLean平衡点からS(PHR→θ変換係数)をフィット
|
|
441
|
+
|
|
442
|
+
条件: θ_McLean(c, T_age) = θ_from_phr(phr, S)
|
|
443
|
+
"""
|
|
444
|
+
from scipy.optimize import minimize_scalar
|
|
445
|
+
|
|
446
|
+
def loss(S):
|
|
447
|
+
err = 0.0
|
|
448
|
+
for T_age, phr in eq_points:
|
|
449
|
+
theta_eq = self.core.theta_mclean(c_bulk, T_age, E_seg_eV)
|
|
450
|
+
theta_phr = self.theta_from_phr(phr, S)
|
|
451
|
+
err += (theta_eq - theta_phr) ** 2
|
|
452
|
+
return err
|
|
453
|
+
|
|
454
|
+
result = minimize_scalar(loss, bounds=(0.1, 100.0), method='bounded')
|
|
455
|
+
return result.x
|
|
456
|
+
|
|
457
|
+
def DBTT_from_theta(
|
|
458
|
+
self,
|
|
459
|
+
d: float,
|
|
460
|
+
theta: float,
|
|
461
|
+
DBTT_base_K: float,
|
|
462
|
+
) -> float:
|
|
463
|
+
"""θ から DBTT を予測(閾値モデル)
|
|
464
|
+
|
|
465
|
+
DBTT = max(DBTT_base, T_GB(θ))
|
|
466
|
+
"""
|
|
467
|
+
# Q0: 基準状態でのHP
|
|
468
|
+
HP_base = self.core.HP(DBTT_base_K)
|
|
469
|
+
R = self.core.R_block(d)
|
|
470
|
+
|
|
471
|
+
# σ_f = σ_y の条件から T_GB を導出
|
|
472
|
+
# HP_req = Q0 / g_seg(θ)
|
|
473
|
+
Q0 = HP_base # 基準状態
|
|
474
|
+
HP_req = Q0 / self.core.g_seg(theta)
|
|
475
|
+
HP_req = np.clip(HP_req, 1e-9, 1.0 - 1e-9)
|
|
476
|
+
|
|
477
|
+
T_GB = self.core.mat.T_m * (1.0 - HP_req)
|
|
478
|
+
|
|
479
|
+
return max(DBTT_base_K, T_GB)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
# ==============================================================================
|
|
483
|
+
# 統合インターフェース
|
|
484
|
+
# ==============================================================================
|
|
485
|
+
|
|
486
|
+
class DBTUnified:
|
|
487
|
+
"""統一DBT/DBTTモデル"""
|
|
488
|
+
|
|
489
|
+
def __init__(
|
|
490
|
+
self,
|
|
491
|
+
mat: Material = MATERIAL_FE,
|
|
492
|
+
beta_hp: float = 3.5e-3,
|
|
493
|
+
C_f: float = 5.0,
|
|
494
|
+
K_emb: float = 3.0,
|
|
495
|
+
n_emb: float = 1.0,
|
|
496
|
+
):
|
|
497
|
+
self.core = DBTCore(mat, beta_hp, C_f, K_emb, n_emb)
|
|
498
|
+
self.grain_view = GrainSizeView(self.core)
|
|
499
|
+
self.temp_view = TemperatureView(self.core)
|
|
500
|
+
self.seg_view = SegregationView(self.core)
|
|
501
|
+
|
|
502
|
+
def summary(self, d: float, c: float, T: float) -> Dict:
|
|
503
|
+
"""状態サマリー"""
|
|
504
|
+
sigma_y = self.core.sigma_y(d, T)
|
|
505
|
+
sigma_f = self.core.sigma_f(d, c, T)
|
|
506
|
+
theta = self.core.theta_mclean(c, T)
|
|
507
|
+
g = self.core.g_diff(d, c, T)
|
|
508
|
+
|
|
509
|
+
mode = "brittle" if g > 0 else "ductile"
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
"d_um": d * 1e6,
|
|
513
|
+
"c_at_pct": c * 100,
|
|
514
|
+
"T_K": T,
|
|
515
|
+
"sigma_y_MPa": sigma_y / 1e6,
|
|
516
|
+
"sigma_f_MPa": sigma_f / 1e6,
|
|
517
|
+
"theta": theta,
|
|
518
|
+
"g_diff_MPa": g / 1e6,
|
|
519
|
+
"mode": mode,
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# ==============================================================================
|
|
524
|
+
# CLI
|
|
525
|
+
# ==============================================================================
|
|
526
|
+
|
|
527
|
+
def cmd_point(args):
|
|
528
|
+
"""単点計算"""
|
|
529
|
+
model = DBTUnified(
|
|
530
|
+
beta_hp=args.beta_hp,
|
|
531
|
+
C_f=args.C_f,
|
|
532
|
+
K_emb=args.K_emb,
|
|
533
|
+
n_emb=args.n_emb,
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
result = model.summary(args.d * 1e-6, args.c / 100, args.T)
|
|
537
|
+
|
|
538
|
+
print("=" * 70)
|
|
539
|
+
print("DBT Unified - 単点計算")
|
|
540
|
+
print("=" * 70)
|
|
541
|
+
print(f"入力: d = {args.d} μm, c = {args.c} at%, T = {args.T} K")
|
|
542
|
+
print("-" * 70)
|
|
543
|
+
print(f"σ_y = {result['sigma_y_MPa']:.1f} MPa")
|
|
544
|
+
print(f"σ_f = {result['sigma_f_MPa']:.1f} MPa")
|
|
545
|
+
print(f"θ = {result['theta']:.4f}")
|
|
546
|
+
print(f"Δσ = {result['g_diff_MPa']:.1f} MPa")
|
|
547
|
+
print(f"モード: {result['mode'].upper()}")
|
|
548
|
+
print("=" * 70)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def cmd_d_axis(args):
|
|
552
|
+
"""粒径軸解析"""
|
|
553
|
+
model = DBTUnified(
|
|
554
|
+
beta_hp=args.beta_hp,
|
|
555
|
+
C_f=args.C_f,
|
|
556
|
+
K_emb=args.K_emb,
|
|
557
|
+
n_emb=args.n_emb,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
result = model.grain_view.classify_mode(args.T, args.c / 100)
|
|
561
|
+
|
|
562
|
+
print("=" * 70)
|
|
563
|
+
print("DBT Unified - 粒径軸解析(View 1)")
|
|
564
|
+
print("=" * 70)
|
|
565
|
+
print(f"条件: T = {args.T} K, c = {args.c} at%")
|
|
566
|
+
print("-" * 70)
|
|
567
|
+
print(f"モード: {result['mode']}")
|
|
568
|
+
print(f"メッセージ: {result['msg']}")
|
|
569
|
+
if np.isfinite(result['d_low']):
|
|
570
|
+
print(f"d_low = {result['d_low']*1e6:.3f} μm")
|
|
571
|
+
if np.isfinite(result['d_high']):
|
|
572
|
+
print(f"d_high = {result['d_high']*1e6:.2f} μm")
|
|
573
|
+
|
|
574
|
+
if args.find_c_crit:
|
|
575
|
+
c_result = model.grain_view.find_c_crit(args.T)
|
|
576
|
+
print("-" * 70)
|
|
577
|
+
print(f"臨界濃度: {c_result['msg']}")
|
|
578
|
+
|
|
579
|
+
print("=" * 70)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def cmd_T_axis(args):
|
|
583
|
+
"""温度軸解析"""
|
|
584
|
+
model = DBTUnified(
|
|
585
|
+
beta_hp=args.beta_hp,
|
|
586
|
+
C_f=args.C_f,
|
|
587
|
+
K_emb=args.K_emb,
|
|
588
|
+
n_emb=args.n_emb,
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
result = model.temp_view.find_DBTT(args.d * 1e-6, args.c / 100)
|
|
592
|
+
|
|
593
|
+
print("=" * 70)
|
|
594
|
+
print("DBT Unified - 温度軸解析(View 2)")
|
|
595
|
+
print("=" * 70)
|
|
596
|
+
print(f"条件: d = {args.d} μm, c = {args.c} at%")
|
|
597
|
+
print("-" * 70)
|
|
598
|
+
print(f"ステータス: {result['status']}")
|
|
599
|
+
print(f"メッセージ: {result['msg']}")
|
|
600
|
+
if np.isfinite(result['T_star']):
|
|
601
|
+
print(f"DBTT = {result['T_star']:.1f} K = {result['T_star']-273.15:.1f} °C")
|
|
602
|
+
print("=" * 70)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def cmd_table(args):
|
|
606
|
+
"""DBTT テーブル生成"""
|
|
607
|
+
model = DBTUnified(
|
|
608
|
+
beta_hp=args.beta_hp,
|
|
609
|
+
C_f=args.C_f,
|
|
610
|
+
K_emb=args.K_emb,
|
|
611
|
+
n_emb=args.n_emb,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
d_list = [float(x) * 1e-6 for x in args.d_list.split(",")]
|
|
615
|
+
c_list = [float(x) / 100 for x in args.c_list.split(",")]
|
|
616
|
+
|
|
617
|
+
print("=" * 70)
|
|
618
|
+
print("DBT Unified - DBTTテーブル")
|
|
619
|
+
print("=" * 70)
|
|
620
|
+
|
|
621
|
+
# ヘッダー
|
|
622
|
+
header = f"{'d [μm]':<10}" + "".join([f"c={c*100:.2f}%".center(12) for c in c_list])
|
|
623
|
+
print(header)
|
|
624
|
+
print("-" * (10 + 12 * len(c_list)))
|
|
625
|
+
|
|
626
|
+
for d in d_list:
|
|
627
|
+
row = f"{d*1e6:<10.0f}"
|
|
628
|
+
for c in c_list:
|
|
629
|
+
result = model.temp_view.find_DBTT(d, c)
|
|
630
|
+
if np.isfinite(result['T_star']):
|
|
631
|
+
row += f"{result['T_star']:^12.0f}"
|
|
632
|
+
else:
|
|
633
|
+
row += f"{result['status'][:8]:^12}"
|
|
634
|
+
print(row)
|
|
635
|
+
|
|
636
|
+
print("=" * 70)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def build_parser():
|
|
640
|
+
ap = argparse.ArgumentParser(description="DBT Unified - 統一DBT/DBTTモデル")
|
|
641
|
+
|
|
642
|
+
# 共通引数
|
|
643
|
+
ap.add_argument("--beta_hp", type=float, default=3.5e-3, help="Hall-Petch係数")
|
|
644
|
+
ap.add_argument("--C_f", type=float, default=5.0, help="幾何係数")
|
|
645
|
+
ap.add_argument("--K_emb", type=float, default=3.0, help="脆化強度")
|
|
646
|
+
ap.add_argument("--n_emb", type=float, default=1.0, help="脆化指数(>1で閾値的)")
|
|
647
|
+
|
|
648
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
649
|
+
|
|
650
|
+
# point
|
|
651
|
+
p1 = sub.add_parser("point", help="単点計算")
|
|
652
|
+
p1.add_argument("--d", type=float, required=True, help="粒径 [μm]")
|
|
653
|
+
p1.add_argument("--c", type=float, required=True, help="濃度 [at%]")
|
|
654
|
+
p1.add_argument("--T", type=float, required=True, help="温度 [K]")
|
|
655
|
+
|
|
656
|
+
# d_axis
|
|
657
|
+
p2 = sub.add_parser("d_axis", help="粒径軸解析(延性窓)")
|
|
658
|
+
p2.add_argument("--T", type=float, required=True, help="温度 [K]")
|
|
659
|
+
p2.add_argument("--c", type=float, required=True, help="濃度 [at%]")
|
|
660
|
+
p2.add_argument("--find_c_crit", action="store_true", help="c_critも計算")
|
|
661
|
+
|
|
662
|
+
# T_axis
|
|
663
|
+
p3 = sub.add_parser("T_axis", help="温度軸解析(DBTT)")
|
|
664
|
+
p3.add_argument("--d", type=float, required=True, help="粒径 [μm]")
|
|
665
|
+
p3.add_argument("--c", type=float, required=True, help="濃度 [at%]")
|
|
666
|
+
|
|
667
|
+
# table
|
|
668
|
+
p4 = sub.add_parser("table", help="DBTTテーブル生成")
|
|
669
|
+
p4.add_argument("--d_list", type=str, default="5,10,20,50,100", help="粒径リスト [μm]")
|
|
670
|
+
p4.add_argument("--c_list", type=str, default="0,0.2,0.5,0.8,1.0", help="濃度リスト [at%]")
|
|
671
|
+
|
|
672
|
+
return ap
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def main():
|
|
676
|
+
ap = build_parser()
|
|
677
|
+
args = ap.parse_args()
|
|
678
|
+
|
|
679
|
+
if args.cmd == "point":
|
|
680
|
+
cmd_point(args)
|
|
681
|
+
elif args.cmd == "d_axis":
|
|
682
|
+
cmd_d_axis(args)
|
|
683
|
+
elif args.cmd == "T_axis":
|
|
684
|
+
cmd_T_axis(args)
|
|
685
|
+
elif args.cmd == "table":
|
|
686
|
+
cmd_table(args)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
if __name__ == "__main__":
|
|
690
|
+
main()
|