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.
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ FatigueData-AM2022 Upstash Redis API
4
+ =====================================
5
+
6
+ δ理論検証用 疲労データベースAPI
7
+
8
+ Usage:
9
+ from fatigue_redis_api import FatigueDB
10
+
11
+ db = FatigueDB()
12
+
13
+ # 材料一覧
14
+ materials = db.list_materials()
15
+
16
+ # S-Nデータ取得
17
+ ti64_sn = db.get_sn('Ti-6Al-4V')
18
+
19
+ # R=-1 のデータのみ
20
+ ti64_r1 = db.get_sn('Ti-6Al-4V', R=-1.0)
21
+
22
+ # σ_y付きデータのみ
23
+ ti64_sy = db.get_sn('Ti-6Al-4V', with_sigma_y=True)
24
+
25
+ # da/dN データ
26
+ dadn = db.get_dadn('Ti-6Al-4V')
27
+
28
+ Author: 環 & ご主人さま
29
+ Date: 2026-02-02
30
+ """
31
+
32
+ import json
33
+ from typing import Dict, List, Optional, Any
34
+ from dataclasses import dataclass
35
+
36
+ try:
37
+ from upstash_redis import Redis
38
+ except ImportError:
39
+ raise ImportError("pip install upstash-redis --break-system-packages")
40
+
41
+
42
+ # =============================================================================
43
+ # Configuration
44
+ # =============================================================================
45
+ UPSTASH_URL = "https://casual-crayfish-21486.upstash.io"
46
+ UPSTASH_TOKEN = "AVPuAAIncDFiNWEyN2ExYmEzZGU0N2I5ODlkNmUxMjRkM2UzNWMwY3AxMjE0ODY"
47
+
48
+
49
+ # =============================================================================
50
+ # Data Classes
51
+ # =============================================================================
52
+ @dataclass
53
+ class SNPoint:
54
+ """S-N データポイント"""
55
+ N: float # 破断サイクル数
56
+ S: float # 応力振幅 [MPa]
57
+ runout: bool # ランアウト(未破断)フラグ
58
+ R: Optional[float] # 応力比
59
+ sigma_y: Optional[float] # 降伏応力 [MPa]
60
+ sigma_uts: Optional[float] # 引張強さ [MPa]
61
+ doi: Optional[str] # 出典DOI
62
+
63
+
64
+ @dataclass
65
+ class ENPoint:
66
+ """ε-N データポイント"""
67
+ N: float # 破断サイクル数
68
+ e: float # ひずみ振幅
69
+ runout: bool # ランアウト
70
+ R: Optional[float]
71
+ sigma_y: Optional[float]
72
+ E: Optional[float] # ヤング率 [GPa]
73
+ doi: Optional[str]
74
+
75
+
76
+ @dataclass
77
+ class DaDNPoint:
78
+ """da/dN-ΔK データポイント"""
79
+ dK: float # 応力拡大係数範囲 [MPa√m]
80
+ dadn: float # き裂進展速度 [m/cycle]
81
+ R: Optional[float]
82
+ doi: Optional[str]
83
+
84
+
85
+ # =============================================================================
86
+ # Main API Class
87
+ # =============================================================================
88
+ class FatigueDB:
89
+ """FatigueData-AM2022 Upstash Redis API"""
90
+
91
+ def __init__(self, url: str = UPSTASH_URL, token: str = UPSTASH_TOKEN):
92
+ """Initialize connection to Upstash Redis"""
93
+ self.redis = Redis(url=url, token=token)
94
+ self._meta = None
95
+ self._materials = None
96
+
97
+ # -------------------------------------------------------------------------
98
+ # Meta & Materials
99
+ # -------------------------------------------------------------------------
100
+
101
+ def get_meta(self) -> Dict[str, Any]:
102
+ """データベースメタ情報"""
103
+ if self._meta is None:
104
+ self._meta = json.loads(self.redis.get('fatigue:meta'))
105
+ return self._meta
106
+
107
+ def list_materials(self, sort_by: str = 'sn_count') -> List[Dict]:
108
+ """材料一覧を取得
109
+
110
+ Args:
111
+ sort_by: ソートキー ('sn_count', 'en_count', 'dadn_count', 'name')
112
+
113
+ Returns:
114
+ 材料リスト [{name, sn_count, en_count, dadn_count, sigma_y_min, sigma_y_max}, ...]
115
+ """
116
+ if self._materials is None:
117
+ self._materials = json.loads(self.redis.get('fatigue:materials'))
118
+
119
+ result = []
120
+ for name, info in self._materials.items():
121
+ result.append({'name': name, **info})
122
+
123
+ if sort_by == 'name':
124
+ result.sort(key=lambda x: x['name'])
125
+ elif sort_by in ['sn_count', 'en_count', 'dadn_count']:
126
+ result.sort(key=lambda x: x.get(sort_by, 0), reverse=True)
127
+
128
+ return result
129
+
130
+ def get_material_info(self, material: str) -> Optional[Dict]:
131
+ """特定材料の情報"""
132
+ if self._materials is None:
133
+ self.list_materials()
134
+ return self._materials.get(material)
135
+
136
+ # -------------------------------------------------------------------------
137
+ # S-N Data
138
+ # -------------------------------------------------------------------------
139
+
140
+ def get_sn(
141
+ self,
142
+ material: str,
143
+ R: Optional[float] = None,
144
+ with_sigma_y: bool = False,
145
+ as_dataclass: bool = False
146
+ ) -> List[Dict | SNPoint]:
147
+ """S-Nデータを取得
148
+
149
+ Args:
150
+ material: 材料名 (e.g., 'Ti-6Al-4V', '316L')
151
+ R: 応力比でフィルタ (e.g., -1.0, 0.1)
152
+ with_sigma_y: σ_yが存在するデータのみ
153
+ as_dataclass: SNPointクラスで返す
154
+
155
+ Returns:
156
+ S-Nデータリスト
157
+ """
158
+ key = f'fatigue:sn:{material}'
159
+ raw = self.redis.get(key)
160
+ if raw is None:
161
+ return []
162
+
163
+ data = json.loads(raw)
164
+
165
+ # フィルタ
166
+ if R is not None:
167
+ data = [d for d in data if d.get('R') == R]
168
+ if with_sigma_y:
169
+ data = [d for d in data if d.get('sigma_y') is not None]
170
+
171
+ if as_dataclass:
172
+ return [SNPoint(
173
+ N=d['N'], S=d['S'], runout=bool(d.get('runout', 0)),
174
+ R=d.get('R'), sigma_y=d.get('sigma_y'),
175
+ sigma_uts=d.get('sigma_uts'), doi=d.get('doi')
176
+ ) for d in data]
177
+
178
+ return data
179
+
180
+ def get_sn_for_delta(
181
+ self,
182
+ material: str,
183
+ R: float = -1.0
184
+ ) -> List[Dict]:
185
+ """δ理論検証用にr = σ_a/σ_y を計算済みのデータを取得
186
+
187
+ Args:
188
+ material: 材料名
189
+ R: 応力比 (default: -1.0 for fully reversed)
190
+
191
+ Returns:
192
+ [{N, S, sigma_y, r, runout}, ...]
193
+ """
194
+ data = self.get_sn(material, R=R, with_sigma_y=True)
195
+
196
+ result = []
197
+ for d in data:
198
+ sigma_y = d['sigma_y']
199
+ # R=-1 の場合、S = σ_max = 2 * σ_a なので σ_a = S/2... ではない
200
+ # 実際は S が応力振幅として記録されていることが多い
201
+ # データセットの定義を確認: S は stress amplitude or max stress?
202
+ # AM2022では Sは応力振幅(amplitude)として扱う
203
+ sigma_a = d['S'] # stress amplitude [MPa]
204
+ r = sigma_a / sigma_y
205
+
206
+ result.append({
207
+ 'N': d['N'],
208
+ 'S': sigma_a,
209
+ 'sigma_y': sigma_y,
210
+ 'r': r,
211
+ 'runout': d.get('runout', 0),
212
+ 'doi': d.get('doi')
213
+ })
214
+
215
+ return result
216
+
217
+ # -------------------------------------------------------------------------
218
+ # ε-N Data
219
+ # -------------------------------------------------------------------------
220
+
221
+ def get_en(
222
+ self,
223
+ material: str,
224
+ R: Optional[float] = None,
225
+ as_dataclass: bool = False
226
+ ) -> List[Dict | ENPoint]:
227
+ """ε-Nデータを取得"""
228
+ key = f'fatigue:en:{material}'
229
+ raw = self.redis.get(key)
230
+ if raw is None:
231
+ return []
232
+
233
+ data = json.loads(raw)
234
+
235
+ if R is not None:
236
+ data = [d for d in data if d.get('R') == R]
237
+
238
+ if as_dataclass:
239
+ return [ENPoint(
240
+ N=d['N'], e=d['e'], runout=bool(d.get('runout', 0)),
241
+ R=d.get('R'), sigma_y=d.get('sigma_y'),
242
+ E=d.get('E'), doi=d.get('doi')
243
+ ) for d in data]
244
+
245
+ return data
246
+
247
+ # -------------------------------------------------------------------------
248
+ # da/dN-ΔK Data
249
+ # -------------------------------------------------------------------------
250
+
251
+ def get_dadn(
252
+ self,
253
+ material: str,
254
+ R: Optional[float] = None,
255
+ as_dataclass: bool = False
256
+ ) -> List[Dict | DaDNPoint]:
257
+ """da/dN-ΔKデータを取得(チャンク対応)"""
258
+ # チャンクがあるか確認
259
+ chunks_key = f'fatigue:dadn:{material}:chunks'
260
+ n_chunks = self.redis.get(chunks_key)
261
+
262
+ if n_chunks:
263
+ # チャンクから読み込み
264
+ data = []
265
+ for i in range(int(n_chunks)):
266
+ chunk = json.loads(self.redis.get(f'fatigue:dadn:{material}:chunk{i}'))
267
+ data.extend(chunk)
268
+ else:
269
+ # 単一キー
270
+ key = f'fatigue:dadn:{material}'
271
+ raw = self.redis.get(key)
272
+ if raw is None:
273
+ return []
274
+ data = json.loads(raw)
275
+
276
+ if R is not None:
277
+ data = [d for d in data if d.get('R') == R]
278
+
279
+ if as_dataclass:
280
+ return [DaDNPoint(
281
+ dK=d['dK'], dadn=d['dadn'],
282
+ R=d.get('R'), doi=d.get('doi')
283
+ ) for d in data]
284
+
285
+ return data
286
+
287
+ # -------------------------------------------------------------------------
288
+ # Utilities
289
+ # -------------------------------------------------------------------------
290
+
291
+ def search_materials(self, query: str) -> List[str]:
292
+ """材料名を検索"""
293
+ if self._materials is None:
294
+ self.list_materials()
295
+
296
+ query_lower = query.lower()
297
+ return [name for name in self._materials.keys()
298
+ if query_lower in name.lower()]
299
+
300
+ def summary(self) -> str:
301
+ """データベースサマリー"""
302
+ meta = self.get_meta()
303
+ return f"""
304
+ FatigueData-AM2022 @ Upstash Redis
305
+ ===================================
306
+ Source: {meta['source']}
307
+ DOI: {meta['doi']}
308
+ License: {meta['license']}
309
+
310
+ Materials: {meta['total_materials']}
311
+ S-N points: {meta['total_sn_points']:,}
312
+ ε-N points: {meta['total_en_points']:,}
313
+ da/dN points: {meta['total_dadn_points']:,}
314
+ TOTAL: {meta['total_sn_points'] + meta['total_en_points'] + meta['total_dadn_points']:,}
315
+
316
+ Updated: {meta['updated']}
317
+ """
318
+
319
+
320
+ # =============================================================================
321
+ # CLI
322
+ # =============================================================================
323
+ if __name__ == '__main__':
324
+ db = FatigueDB()
325
+ print(db.summary())
326
+
327
+ print("\n📦 Top 5 materials by S-N count:")
328
+ for mat in db.list_materials()[:5]:
329
+ print(f" {mat['name']}: {mat['sn_count']} S-N, σ_y={mat.get('sigma_y_min')}-{mat.get('sigma_y_max')} MPa")
330
+
331
+ print("\n🔍 Ti-6Al-4V S-N (R=-1, with σ_y):")
332
+ ti64 = db.get_sn_for_delta('Ti-6Al-4V', R=-1.0)
333
+ print(f" {len(ti64)} points")
334
+ print(f" r range: {min(d['r'] for d in ti64):.4f} - {max(d['r'] for d in ti64):.4f}")