delta-theory 6.9.0__tar.gz → 6.10.1__tar.gz

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 (22) hide show
  1. {delta_theory-6.9.0 → delta_theory-6.10.1}/PKG-INFO +2 -2
  2. {delta_theory-6.9.0 → delta_theory-6.10.1}/core/__init__.py +26 -2
  3. {delta_theory-6.9.0/validation → delta_theory-6.10.1/core}/fatigue_redis_api.py +158 -12
  4. {delta_theory-6.9.0 → delta_theory-6.10.1}/core/unified_yield_fatigue_v6_9.py +195 -80
  5. {delta_theory-6.9.0 → delta_theory-6.10.1}/delta_theory.egg-info/PKG-INFO +2 -2
  6. {delta_theory-6.9.0 → delta_theory-6.10.1}/delta_theory.egg-info/SOURCES.txt +2 -3
  7. delta_theory-6.10.1/delta_theory.egg-info/top_level.txt +2 -0
  8. {delta_theory-6.9.0 → delta_theory-6.10.1}/pyproject.toml +2 -2
  9. delta_theory-6.9.0/delta_theory.egg-info/top_level.txt +0 -3
  10. delta_theory-6.9.0/validation/__init__.py +0 -10
  11. {delta_theory-6.9.0 → delta_theory-6.10.1}/LICENSE +0 -0
  12. {delta_theory-6.9.0 → delta_theory-6.10.1}/README.md +0 -0
  13. {delta_theory-6.9.0 → delta_theory-6.10.1}/apps/__init__.py +0 -0
  14. {delta_theory-6.9.0 → delta_theory-6.10.1}/apps/delta_fatigue_app.py +0 -0
  15. {delta_theory-6.9.0 → delta_theory-6.10.1}/core/Universal_Lindemann.py +0 -0
  16. {delta_theory-6.9.0 → delta_theory-6.10.1}/core/dbt_unified.py +0 -0
  17. {delta_theory-6.9.0 → delta_theory-6.10.1}/core/materials.py +0 -0
  18. {delta_theory-6.9.0 → delta_theory-6.10.1}/delta_theory.egg-info/dependency_links.txt +0 -0
  19. {delta_theory-6.9.0 → delta_theory-6.10.1}/delta_theory.egg-info/entry_points.txt +0 -0
  20. {delta_theory-6.9.0 → delta_theory-6.10.1}/delta_theory.egg-info/requires.txt +0 -0
  21. {delta_theory-6.9.0 → delta_theory-6.10.1}/setup.cfg +0 -0
  22. {delta_theory-6.9.0 → delta_theory-6.10.1}/tests/test_core.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: delta-theory
3
- Version: 6.9.0
3
+ Version: 6.10.1
4
4
  Summary: Unified materials strength and fatigue prediction based on geometric first principles
5
5
  Author: Tamaki
6
- Author-email: Masamichi Iizumi <masamichi@miosync.com>
6
+ Author-email: Masamichi Iizumi <m.iizumi@miosync.email>
7
7
  Maintainer-email: Masamichi Iizumi <masamichi@miosync.com>
8
8
  License: MIT
9
9
  Project-URL: Homepage, https://github.com/miosync/delta-theory
@@ -1,13 +1,13 @@
1
1
  """
2
2
  δ-Theory Core Library
3
3
  =====================
4
-
5
4
  Unified yield stress and fatigue life prediction based on geometric first principles.
6
5
 
7
6
  Modules:
8
7
  - unified_yield_fatigue_v6_9: Main yield + fatigue model (v6.9b)
9
8
  - dbt_unified: Ductile-Brittle Transition Temperature prediction
10
9
  - materials: Material database
10
+ - fatigue_redis_api: FatigueData-AM2022 Redis API
11
11
  """
12
12
 
13
13
  from .unified_yield_fatigue_v6_9 import (
@@ -31,5 +31,29 @@ from .dbt_unified import (
31
31
 
32
32
  from .materials import MaterialGPU
33
33
 
34
- __version__ = "6.9.0"
34
+ from .fatigue_redis_api import FatigueDB # ← 追加!
35
+
36
+ __version__ = "6.10.1" # ← バージョンも上げる!
35
37
  __author__ = "Masamichi Iizumi & Tamaki"
38
+
39
+ __all__ = [
40
+ # v6.9
41
+ "Material",
42
+ "MATERIALS",
43
+ "calc_sigma_y",
44
+ "fatigue_life_const_amp",
45
+ "generate_sn_curve",
46
+ "yield_by_mode",
47
+ "FATIGUE_CLASS_PRESET",
48
+ # DBT
49
+ "DBTUnified",
50
+ "DBTCore",
51
+ "GrainSizeView",
52
+ "TemperatureView",
53
+ "SegregationView",
54
+ "MATERIAL_FE",
55
+ # Materials
56
+ "MaterialGPU",
57
+ # FatigueDB
58
+ "FatigueDB", # ← 追加!
59
+ ]
@@ -32,6 +32,7 @@ Date: 2026-02-02
32
32
  import json
33
33
  from typing import Dict, List, Optional, Any
34
34
  from dataclasses import dataclass
35
+ import os
35
36
 
36
37
  try:
37
38
  from upstash_redis import Redis
@@ -42,10 +43,17 @@ except ImportError:
42
43
  # =============================================================================
43
44
  # Configuration
44
45
  # =============================================================================
45
- UPSTASH_URL = "https://casual-crayfish-21486.upstash.io"
46
- UPSTASH_TOKEN = "AVPuAAIncDFiNWEyN2ExYmEzZGU0N2I5ODlkNmUxMjRkM2UzNWMwY3AxMjE0ODY"
47
-
46
+ UPSTASH_URL = os.environ.get("UPSTASH_URL")
47
+ UPSTASH_TOKEN = os.environ.get("UPSTASH_TOKEN")
48
48
 
49
+ class FatigueDB:
50
+ def __init__(self, url: str = None, token: str = None):
51
+ _url = url or UPSTASH_URL
52
+ _token = token or UPSTASH_TOKEN
53
+ if not _url or not _token:
54
+ raise ValueError("UPSTASH_URL and UPSTASH_TOKEN required! Set env vars or pass directly.")
55
+ self.redis = Redis(url=_url, token=_token)
56
+
49
57
  # =============================================================================
50
58
  # Data Classes
51
59
  # =============================================================================
@@ -320,15 +328,153 @@ Updated: {meta['updated']}
320
328
  # =============================================================================
321
329
  # CLI
322
330
  # =============================================================================
331
+ def build_cli():
332
+ import argparse
333
+
334
+ p = argparse.ArgumentParser(
335
+ description='FatigueData-AM2022 Redis API - δ理論検証用'
336
+ )
337
+ sub = p.add_subparsers(dest='cmd', required=True)
338
+
339
+ # list
340
+ sp_list = sub.add_parser('list', help='材料一覧')
341
+ sp_list.add_argument('--top', type=int, default=20)
342
+ sp_list.add_argument('--sort', choices=['sn_count', 'name'], default='sn_count')
343
+
344
+ # search
345
+ sp_search = sub.add_parser('search', help='材料検索')
346
+ sp_search.add_argument('query', help='検索クエリ (例: Ti, 316, Al)')
347
+
348
+ # info
349
+ sp_info = sub.add_parser('info', help='材料詳細')
350
+ sp_info.add_argument('material', help='材料名')
351
+
352
+ # get-sn
353
+ sp_sn = sub.add_parser('get-sn', help='S-Nデータ取得')
354
+ sp_sn.add_argument('material', help='材料名')
355
+ sp_sn.add_argument('--R', type=float, default=None, help='応力比フィルタ')
356
+ sp_sn.add_argument('--with-sigma-y', action='store_true', help='σ_yありのみ')
357
+ sp_sn.add_argument('--output', '-o', help='CSV出力ファイル')
358
+ sp_sn.add_argument('--limit', type=int, default=20, help='表示件数')
359
+
360
+ # delta
361
+ sp_delta = sub.add_parser('delta', help='δ理論検証用データ (r計算済み)')
362
+ sp_delta.add_argument('material', help='材料名')
363
+ sp_delta.add_argument('--R', type=float, default=-1.0)
364
+ sp_delta.add_argument('--output', '-o', help='CSV出力ファイル')
365
+
366
+ # summary
367
+ sub.add_parser('summary', help='DB全体サマリー')
368
+
369
+ return p
370
+
371
+
372
+ def cmd_list(db, args):
373
+ print(f"\n📦 材料一覧 (top {args.top}, sort={args.sort}):")
374
+ print("-" * 70)
375
+ print(f"{'Material':<25} {'S-N':>8} {'ε-N':>8} {'σ_y range':>20}")
376
+ print("-" * 70)
377
+ for mat in db.list_materials(sort_by=args.sort)[:args.top]:
378
+ sy_min = mat.get('sigma_y_min', '-')
379
+ sy_max = mat.get('sigma_y_max', '-')
380
+ sy_range = f"{sy_min}-{sy_max}" if sy_min != '-' else '-'
381
+ print(f"{mat['name']:<25} {mat['sn_count']:>8} {mat['en_count']:>8} {sy_range:>20}")
382
+
383
+
384
+ def cmd_search(db, args):
385
+ results = db.search_materials(args.query)
386
+ print(f"\n🔍 検索: '{args.query}' → {len(results)}件")
387
+ for name in results:
388
+ info = db.get_material_info(name)
389
+ print(f" {name}: S-N={info['sn_count']}, σ_y={info.get('sigma_y_min')}-{info.get('sigma_y_max')} MPa")
390
+
391
+
392
+ def cmd_info(db, args):
393
+ info = db.get_material_info(args.material)
394
+ if not info:
395
+ print(f"❌ Material '{args.material}' not found")
396
+ return
397
+
398
+ print(f"\n📋 {args.material}")
399
+ print("=" * 50)
400
+ print(f" S-N points: {info['sn_count']}")
401
+ print(f" ε-N points: {info['en_count']}")
402
+ print(f" da/dN points: {info['dadn_count']}")
403
+ print(f" σ_y range: {info.get('sigma_y_min')} - {info.get('sigma_y_max')} MPa")
404
+
405
+ # R値の分布
406
+ sn_data = db.get_sn(args.material)
407
+ R_values = set(d.get('R') for d in sn_data if d.get('R') is not None)
408
+ print(f" R values: {sorted(R_values)}")
409
+
410
+
411
+ def cmd_get_sn(db, args):
412
+ data = db.get_sn(args.material, R=args.R, with_sigma_y=args.with_sigma_y)
413
+
414
+ print(f"\n📊 {args.material} S-N data")
415
+ if args.R is not None:
416
+ print(f" R = {args.R}")
417
+ if args.with_sigma_y:
418
+ print(f" (σ_y required)")
419
+ print(f" Total: {len(data)} points")
420
+ print("-" * 70)
421
+
422
+ if args.output:
423
+ import csv
424
+ with open(args.output, 'w', newline='') as f:
425
+ w = csv.DictWriter(f, fieldnames=['N', 'S', 'R', 'sigma_y', 'sigma_uts', 'runout', 'doi'])
426
+ w.writeheader()
427
+ w.writerows(data)
428
+ print(f"✅ Saved to {args.output}")
429
+ else:
430
+ print(f"{'N':>12} {'S [MPa]':>10} {'R':>6} {'σ_y':>10} {'runout':>8}")
431
+ print("-" * 50)
432
+ for d in data[:args.limit]:
433
+ print(f"{d['N']:>12.0f} {d['S']:>10.1f} {d.get('R', '-'):>6} {d.get('sigma_y', '-'):>10} {d.get('runout', 0):>8}")
434
+ if len(data) > args.limit:
435
+ print(f" ... and {len(data) - args.limit} more (use --output for full data)")
436
+
437
+
438
+ def cmd_delta(db, args):
439
+ data = db.get_sn_for_delta(args.material, R=args.R)
440
+
441
+ print(f"\n🔬 {args.material} δ-theory data (R={args.R})")
442
+ print(f" Total: {len(data)} points with σ_y")
443
+ if data:
444
+ print(f" r range: {min(d['r'] for d in data):.4f} - {max(d['r'] for d in data):.4f}")
445
+ print("-" * 70)
446
+
447
+ if args.output:
448
+ import csv
449
+ with open(args.output, 'w', newline='') as f:
450
+ w = csv.DictWriter(f, fieldnames=['N', 'S', 'sigma_y', 'r', 'runout', 'doi'])
451
+ w.writeheader()
452
+ w.writerows(data)
453
+ print(f"✅ Saved to {args.output}")
454
+ else:
455
+ print(f"{'N':>12} {'S [MPa]':>10} {'σ_y [MPa]':>12} {'r':>10} {'runout':>8}")
456
+ print("-" * 60)
457
+ for d in data[:20]:
458
+ print(f"{d['N']:>12.0f} {d['S']:>10.1f} {d['sigma_y']:>12.1f} {d['r']:>10.4f} {d.get('runout', 0):>8}")
459
+ if len(data) > 20:
460
+ print(f" ... and {len(data) - 20} more")
461
+
462
+
323
463
  if __name__ == '__main__':
324
- db = FatigueDB()
325
- print(db.summary())
464
+ parser = build_cli()
465
+ args = parser.parse_args()
326
466
 
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")
467
+ db = FatigueDB()
330
468
 
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}")
469
+ if args.cmd == 'summary':
470
+ print(db.summary())
471
+ elif args.cmd == 'list':
472
+ cmd_list(db, args)
473
+ elif args.cmd == 'search':
474
+ cmd_search(db, args)
475
+ elif args.cmd == 'info':
476
+ cmd_info(db, args)
477
+ elif args.cmd == 'get-sn':
478
+ cmd_get_sn(db, args)
479
+ elif args.cmd == 'delta':
480
+ cmd_delta(db, args)
@@ -25,6 +25,24 @@ from typing import Dict, Literal, Optional, Tuple
25
25
 
26
26
  import numpy as np
27
27
 
28
+ # ==============================================================================
29
+ # Optional import: DBT/DBTT unified model (separate module)
30
+ # ------------------------------------------------------------------------------
31
+ # Place dbt_unified.py in the same folder as this file.
32
+ # Then you can do:
33
+ # from unified_yield_fatigue_v6_9b_tau_classes import DBTUnified
34
+ # model = DBTUnified()
35
+ #
36
+ # The import is optional so that this module remains usable even if dbt_unified.py
37
+ # is not present in your runtime environment.
38
+ try:
39
+ from dbt_unified import DBTUnified, DBTCore, Material as DBTMaterial, MATERIAL_FE
40
+ except Exception:
41
+ DBTUnified = None
42
+ DBTCore = None
43
+ DBTMaterial = None
44
+ MATERIAL_FE = None
45
+
28
46
  # ==============================================================================
29
47
  # Physical constants
30
48
  # ==============================================================================
@@ -484,40 +502,74 @@ def generate_sn_curve(
484
502
  Ns.append(out['N_fail'])
485
503
  return np.array(Ns, dtype=float)
486
504
 
505
+ # ==============================================================================
506
+ # Alloy validation helper (for FatigueDB integration)
507
+ # ==============================================================================
508
+
509
+ def get_minimal_material(structure: Literal['BCC', 'FCC', 'HCP']) -> Material:
510
+ """結晶構造から最小限のMaterialを取得(合金検証用ヘルパー)
511
+
512
+ FatigueDB等の実験データ検証時、σ_yは実測値を使うため
513
+ structure (r_th, n_cl) のみが必要なケース用
514
+ """
515
+ base = {'BCC': MATERIALS['Fe'], 'FCC': MATERIALS['Cu'], 'HCP': MATERIALS['Ti']}
516
+ return base[structure]
517
+
518
+
487
519
  # ==============================================================================
488
520
  # CLI
489
521
  # ==============================================================================
490
522
 
491
523
  def cmd_point(args: argparse.Namespace) -> None:
492
- mat0 = MATERIALS[args.metal]
493
- mat = replace(
494
- mat0,
495
- A_texture=float(args.A_texture),
496
- T_twin=(float(args.T_twin) if args.T_twin is not None else mat0.T_twin),
497
- R_comp=(float(args.R_comp) if args.R_comp is not None else mat0.R_comp),
498
- c_a=float(args.c_a) if args.c_a is not None else mat0.c_a,
499
- )
524
+ # material 取得(metal or structure_only)
525
+ if args.metal is None and args.structure_only is None:
526
+ raise SystemExit("Error: --metal or --structure_only required")
527
+
528
+ if args.structure_only:
529
+ mat = get_minimal_material(args.structure_only)
530
+ else:
531
+ mat0 = MATERIALS[args.metal]
532
+ mat = replace(
533
+ mat0,
534
+ A_texture=float(args.A_texture),
535
+ T_twin=(float(args.T_twin) if args.T_twin is not None else mat0.T_twin),
536
+ R_comp=(float(args.R_comp) if args.R_comp is not None else mat0.R_comp),
537
+ c_a=float(args.c_a) if args.c_a is not None else mat0.c_a,
538
+ )
500
539
 
501
- y = calc_sigma_y(
502
- mat,
503
- T_K=args.T_K,
504
- c_wt_percent=args.c_wt,
505
- k_ss=args.k_ss,
506
- solute_type=args.solute_type,
507
- eps=args.eps,
508
- rho_0=args.rho_0,
509
- r_ppt_nm=args.r_ppt_nm,
510
- f_ppt=args.f_ppt,
511
- gamma_apb=args.gamma_apb,
512
- A_ppt=args.A_ppt,
513
- )
540
+ # σ_y 計算 or override
541
+ if args.sigma_y_override is not None:
542
+ sigma_y = args.sigma_y_override
543
+ y = {
544
+ 'sigma_y': sigma_y,
545
+ 'sigma_base': sigma_y,
546
+ 'delta_ss': 0.0,
547
+ 'delta_wh': 0.0,
548
+ 'delta_ppt': 0.0,
549
+ 'ppt_mechanism': 'N/A (override)',
550
+ }
551
+ else:
552
+ y = calc_sigma_y(
553
+ mat,
554
+ T_K=args.T_K,
555
+ c_wt_percent=args.c_wt,
556
+ k_ss=args.k_ss,
557
+ solute_type=args.solute_type,
558
+ eps=args.eps,
559
+ rho_0=args.rho_0,
560
+ r_ppt_nm=args.r_ppt_nm,
561
+ f_ppt=args.f_ppt,
562
+ gamma_apb=args.gamma_apb,
563
+ A_ppt=args.A_ppt,
564
+ )
565
+ sigma_y = y['sigma_y']
514
566
 
515
567
  # Fatigue (optional)
516
568
  if args.sigma_a is not None:
517
569
  out = fatigue_life_const_amp(
518
570
  mat,
519
571
  sigma_a_MPa=float(args.sigma_a),
520
- sigma_y_tension_MPa=float(y['sigma_y']),
572
+ sigma_y_tension_MPa=float(sigma_y),
521
573
  A_ext=float(args.A_ext),
522
574
  mode=args.mode,
523
575
  D_fail=args.D_fail,
@@ -528,14 +580,16 @@ def cmd_point(args: argparse.Namespace) -> None:
528
580
  else:
529
581
  out = None
530
582
 
583
+ # 表示
584
+ label = f"structure={mat.structure}" if args.structure_only else f"metal={mat.name} ({mat.structure})"
531
585
  print("=" * 88)
532
- print(f"v6.9b point | metal={mat.name} ({mat.structure}) | mode={args.mode}")
586
+ print(f"v6.9b point | {label} | mode={args.mode}")
533
587
  print("=" * 88)
534
588
 
535
589
  # Yield summary
536
590
  y_mode, diag = yield_by_mode(
537
591
  mat,
538
- sigma_y_tension_MPa=float(y['sigma_y']),
592
+ sigma_y_tension_MPa=float(sigma_y),
539
593
  mode=args.mode,
540
594
  C_class=args.C_class,
541
595
  bcc_w110=args.bcc_w110,
@@ -543,11 +597,15 @@ def cmd_point(args: argparse.Namespace) -> None:
543
597
  )
544
598
 
545
599
  print("[Yield v5.0]")
546
- print(f" σ_base = {y['sigma_base']:.2f} MPa")
547
- print(f" Δσ_ss = {y['delta_ss']:.2f} MPa")
548
- print(f" Δσ_wh = {y['delta_wh']:.2f} MPa (rho_0={args.rho_0:.2e})")
549
- print(f" Δσ_ppt = {y['delta_ppt']:.2f} MPa ({y['ppt_mechanism']})")
550
- print(f" σ_y(t) = {y['sigma_y']:.2f} MPa")
600
+ if args.sigma_y_override is not None:
601
+ print(f" σ_y(override) = {sigma_y:.2f} MPa")
602
+ else:
603
+ print(f" σ_base = {y['sigma_base']:.2f} MPa")
604
+ print(f" Δσ_ss = {y['delta_ss']:.2f} MPa")
605
+ print(f" Δσ_wh = {y['delta_wh']:.2f} MPa (rho_0={args.rho_0:.2e})")
606
+ print(f" Δσ_ppt = {y['delta_ppt']:.2f} MPa ({y['ppt_mechanism']})")
607
+ print(f" σ_y(t) = {y['sigma_y']:.2f} MPa")
608
+
551
609
  print("[Class factors v4.1]")
552
610
  print(f" C_class = {args.C_class:.4f} (apply to HCP: {args.apply_C_class_hcp})")
553
611
  print(f" bcc_w110 = {args.bcc_w110:.3f}")
@@ -577,35 +635,47 @@ def cmd_point(args: argparse.Namespace) -> None:
577
635
 
578
636
  def cmd_calibrate(args: argparse.Namespace) -> None:
579
637
  """Calibrate A_ext from one (σ_a, N_fail) point."""
580
- mat0 = MATERIALS[args.metal]
581
- mat = replace(
582
- mat0,
583
- A_texture=float(args.A_texture),
584
- T_twin=(float(args.T_twin) if args.T_twin is not None else mat0.T_twin),
585
- R_comp=(float(args.R_comp) if args.R_comp is not None else mat0.R_comp),
586
- c_a=float(args.c_a) if args.c_a is not None else mat0.c_a,
587
- )
638
+ # material 取得
639
+ if args.metal is None and args.structure_only is None:
640
+ raise SystemExit("Error: --metal or --structure_only required")
641
+
642
+ if args.structure_only:
643
+ mat = get_minimal_material(args.structure_only)
644
+ else:
645
+ mat0 = MATERIALS[args.metal]
646
+ mat = replace(
647
+ mat0,
648
+ A_texture=float(args.A_texture),
649
+ T_twin=(float(args.T_twin) if args.T_twin is not None else mat0.T_twin),
650
+ R_comp=(float(args.R_comp) if args.R_comp is not None else mat0.R_comp),
651
+ c_a=float(args.c_a) if args.c_a is not None else mat0.c_a,
652
+ )
588
653
 
589
- y = calc_sigma_y(
590
- mat,
591
- T_K=args.T_K,
592
- c_wt_percent=args.c_wt,
593
- k_ss=args.k_ss,
594
- solute_type=args.solute_type,
595
- eps=args.eps,
596
- rho_0=args.rho_0,
597
- r_ppt_nm=args.r_ppt_nm,
598
- f_ppt=args.f_ppt,
599
- gamma_apb=args.gamma_apb,
600
- A_ppt=args.A_ppt,
601
- )
654
+ # σ_y
655
+ if args.sigma_y_override is not None:
656
+ sigma_y = args.sigma_y_override
657
+ else:
658
+ y = calc_sigma_y(
659
+ mat,
660
+ T_K=args.T_K,
661
+ c_wt_percent=args.c_wt,
662
+ k_ss=args.k_ss,
663
+ solute_type=args.solute_type,
664
+ eps=args.eps,
665
+ rho_0=args.rho_0,
666
+ r_ppt_nm=args.r_ppt_nm,
667
+ f_ppt=args.f_ppt,
668
+ gamma_apb=args.gamma_apb,
669
+ A_ppt=args.A_ppt,
670
+ )
671
+ sigma_y = y['sigma_y']
602
672
 
603
673
  preset = FATIGUE_CLASS_PRESET.get(mat.structure, FATIGUE_CLASS_PRESET['FCC'])
604
674
  r_th, n = preset['r_th'], preset['n']
605
675
 
606
676
  y_mode, _ = yield_by_mode(
607
677
  mat,
608
- sigma_y_tension_MPa=float(y['sigma_y']),
678
+ sigma_y_tension_MPa=float(sigma_y),
609
679
  mode=args.mode,
610
680
  C_class=args.C_class,
611
681
  bcc_w110=args.bcc_w110,
@@ -625,11 +695,12 @@ def cmd_calibrate(args: argparse.Namespace) -> None:
625
695
  A_eff = rate_needed / ((r - r_th) ** n)
626
696
  A_ext = A_eff / A_int
627
697
 
698
+ label = f"structure={mat.structure}" if args.structure_only else f"metal={mat.name} ({mat.structure})"
628
699
  print("=" * 88)
629
700
  print("v6.9b calibrate A_ext")
630
701
  print("=" * 88)
631
- print(f"metal={mat.name} ({mat.structure}), mode={args.mode}")
632
- print(f"σ_y(tension) = {y['sigma_y']:.3f} MPa")
702
+ print(f"{label}, mode={args.mode}")
703
+ print(f"σ_y = {sigma_y:.3f} MPa {'(override)' if args.sigma_y_override else '(calc)'}")
633
704
  print(f"yield(mode) = {y_mode:.3f} MPa")
634
705
  print(f"amp = {args.sigma_a:.3f} MPa {'(τ_a)' if args.mode=='shear' else ''}")
635
706
  print(f"r={r:.6f}, r_th={r_th:.3f}, n={n:.2f}")
@@ -638,33 +709,45 @@ def cmd_calibrate(args: argparse.Namespace) -> None:
638
709
 
639
710
 
640
711
  def cmd_sn(args: argparse.Namespace) -> None:
641
- mat0 = MATERIALS[args.metal]
642
- mat = replace(
643
- mat0,
644
- A_texture=float(args.A_texture),
645
- T_twin=(float(args.T_twin) if args.T_twin is not None else mat0.T_twin),
646
- R_comp=(float(args.R_comp) if args.R_comp is not None else mat0.R_comp),
647
- c_a=float(args.c_a) if args.c_a is not None else mat0.c_a,
648
- )
712
+ # material 取得
713
+ if args.metal is None and args.structure_only is None:
714
+ raise SystemExit("Error: --metal or --structure_only required")
715
+
716
+ if args.structure_only:
717
+ mat = get_minimal_material(args.structure_only)
718
+ else:
719
+ mat0 = MATERIALS[args.metal]
720
+ mat = replace(
721
+ mat0,
722
+ A_texture=float(args.A_texture),
723
+ T_twin=(float(args.T_twin) if args.T_twin is not None else mat0.T_twin),
724
+ R_comp=(float(args.R_comp) if args.R_comp is not None else mat0.R_comp),
725
+ c_a=float(args.c_a) if args.c_a is not None else mat0.c_a,
726
+ )
649
727
 
650
- y = calc_sigma_y(
651
- mat,
652
- T_K=args.T_K,
653
- c_wt_percent=args.c_wt,
654
- k_ss=args.k_ss,
655
- solute_type=args.solute_type,
656
- eps=args.eps,
657
- rho_0=args.rho_0,
658
- r_ppt_nm=args.r_ppt_nm,
659
- f_ppt=args.f_ppt,
660
- gamma_apb=args.gamma_apb,
661
- A_ppt=args.A_ppt,
662
- )
728
+ # σ_y
729
+ if args.sigma_y_override is not None:
730
+ sigma_y = args.sigma_y_override
731
+ else:
732
+ y = calc_sigma_y(
733
+ mat,
734
+ T_K=args.T_K,
735
+ c_wt_percent=args.c_wt,
736
+ k_ss=args.k_ss,
737
+ solute_type=args.solute_type,
738
+ eps=args.eps,
739
+ rho_0=args.rho_0,
740
+ r_ppt_nm=args.r_ppt_nm,
741
+ f_ppt=args.f_ppt,
742
+ gamma_apb=args.gamma_apb,
743
+ A_ppt=args.A_ppt,
744
+ )
745
+ sigma_y = y['sigma_y']
663
746
 
664
747
  sigmas = np.linspace(args.sigma_min, args.sigma_max, args.num)
665
748
  Ns = generate_sn_curve(
666
749
  mat,
667
- sigma_y_tension_MPa=float(y['sigma_y']),
750
+ sigma_y_tension_MPa=float(sigma_y),
668
751
  A_ext=args.A_ext,
669
752
  sigmas_MPa=sigmas,
670
753
  mode=args.mode,
@@ -674,17 +757,18 @@ def cmd_sn(args: argparse.Namespace) -> None:
674
757
  apply_C_class_hcp=args.apply_C_class_hcp,
675
758
  )
676
759
 
760
+ label = f"structure={mat.structure}" if args.structure_only else f"metal={mat.name} ({mat.structure})"
677
761
  print("=" * 88)
678
- print(f"v6.9b S-N | metal={mat.name} ({mat.structure}) | mode={args.mode}")
762
+ print(f"v6.9b S-N | {label} | mode={args.mode}")
679
763
  print("=" * 88)
680
- print(f"σ_y(tension)={y['sigma_y']:.3f} MPa | A_ext={args.A_ext:.3e} | D_fail={args.D_fail:.3f}")
764
+ print(f"σ_y={sigma_y:.3f} MPa {'(override)' if args.sigma_y_override else '(calc)'} | A_ext={args.A_ext:.3e} | D_fail={args.D_fail:.3f}")
681
765
 
682
766
  header_amp = 'sigma_a_MPa' if args.mode != 'shear' else 'tau_a_MPa'
683
767
  print(f"{header_amp:>12} {'N_fail':>14} {'log10N':>10} {'r':>10} {'note':>10}")
684
768
  for s, N in zip(sigmas, Ns):
685
769
  y_mode, _ = yield_by_mode(
686
770
  mat,
687
- sigma_y_tension_MPa=float(y['sigma_y']),
771
+ sigma_y_tension_MPa=float(sigma_y),
688
772
  mode=args.mode,
689
773
  C_class=args.C_class,
690
774
  bcc_w110=args.bcc_w110,
@@ -702,7 +786,14 @@ def build_parser() -> argparse.ArgumentParser:
702
786
  sub = p.add_subparsers(dest='cmd', required=True)
703
787
 
704
788
  def add_common(sp: argparse.ArgumentParser):
705
- sp.add_argument('--metal', required=True, choices=sorted(MATERIALS.keys()))
789
+ # metal OR structure_only
790
+ sp.add_argument('--metal', choices=sorted(MATERIALS.keys()), default=None,
791
+ help='Material from database')
792
+ sp.add_argument('--structure_only', choices=['BCC', 'FCC', 'HCP'], default=None,
793
+ help='Use structure preset only (for alloy validation)')
794
+ sp.add_argument('--sigma_y_override', type=float, default=None,
795
+ help='Override σ_y with experimental value [MPa]')
796
+
706
797
  sp.add_argument('--T_K', type=float, default=300.0)
707
798
  sp.add_argument('--c_wt', type=float, default=0.0, help='solute wt%% (e.g., 0.10 for 0.10 wt%%)')
708
799
  sp.add_argument('--k_ss', type=float, default=0.0, help='solid-solution k [MPa/(wt%%)^n]')
@@ -751,12 +842,36 @@ def build_parser() -> argparse.ArgumentParser:
751
842
 
752
843
  return p
753
844
 
754
-
755
845
  def main() -> None:
756
846
  parser = build_parser()
757
847
  args = parser.parse_args()
758
848
  args.func(args)
759
849
 
850
+ BANNER = """
851
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
852
+
853
+ ███╗ ███╗██╗ ██████╗ ███████╗██╗ ██╗███╗ ██╗ ██████╗
854
+ ████╗ ████║██║██╔═══██╗██╔════╝╚██╗ ██╔╝████╗ ██║██╔════╝
855
+ ██╔████╔██║██║██║ ██║███████╗ ╚████╔╝ ██╔██╗ ██║██║
856
+ ██║╚██╔╝██║██║██║ ██║╚════██║ ╚██╔╝ ██║╚██╗██║██║
857
+ ██║ ╚═╝ ██║██║╚██████╔╝███████║ ██║ ██║ ╚████║╚██████╗
858
+ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝
859
+
860
+ δ-theory Unified Model v6.9b
861
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
862
+ v5.0 Yield × v6.8 Fatigue × τ/σ Class
863
+
864
+ BCC: r_th=0.65 FCC: r_th=0.02 HCP: r_th=0.20
865
+ Λ(D) = D/(1-D) → Λ=1 : failure
866
+
867
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
868
+ """
869
+
870
+ def main() -> None:
871
+ print(BANNER)
872
+ parser = build_parser()
873
+ args = parser.parse_args()
874
+ args.func(args)
760
875
 
761
876
  if __name__ == '__main__':
762
877
  main()
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: delta-theory
3
- Version: 6.9.0
3
+ Version: 6.10.1
4
4
  Summary: Unified materials strength and fatigue prediction based on geometric first principles
5
5
  Author: Tamaki
6
- Author-email: Masamichi Iizumi <masamichi@miosync.com>
6
+ Author-email: Masamichi Iizumi <m.iizumi@miosync.email>
7
7
  Maintainer-email: Masamichi Iizumi <masamichi@miosync.com>
8
8
  License: MIT
9
9
  Project-URL: Homepage, https://github.com/miosync/delta-theory
@@ -6,6 +6,7 @@ apps/delta_fatigue_app.py
6
6
  core/Universal_Lindemann.py
7
7
  core/__init__.py
8
8
  core/dbt_unified.py
9
+ core/fatigue_redis_api.py
9
10
  core/materials.py
10
11
  core/unified_yield_fatigue_v6_9.py
11
12
  delta_theory.egg-info/PKG-INFO
@@ -14,6 +15,4 @@ delta_theory.egg-info/dependency_links.txt
14
15
  delta_theory.egg-info/entry_points.txt
15
16
  delta_theory.egg-info/requires.txt
16
17
  delta_theory.egg-info/top_level.txt
17
- tests/test_core.py
18
- validation/__init__.py
19
- validation/fatigue_redis_api.py
18
+ tests/test_core.py
@@ -0,0 +1,2 @@
1
+ apps
2
+ core
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "delta-theory"
7
- version = "6.9.0"
7
+ version = "6.10.1"
8
8
  description = "Unified materials strength and fatigue prediction based on geometric first principles"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  authors = [
12
- {name = "Masamichi Iizumi", email = "masamichi@miosync.com"},
12
+ {name = "Masamichi Iizumi", email = "m.iizumi@miosync.email"},
13
13
  {name = "Tamaki"},
14
14
  ]
15
15
  maintainers = [
@@ -1,3 +0,0 @@
1
- apps
2
- core
3
- validation
@@ -1,10 +0,0 @@
1
- """
2
- δ-Theory Validation Tools
3
- =========================
4
-
5
- Tools for validating δ-theory predictions against experimental data.
6
- """
7
-
8
- from .fatigue_redis_api import FatigueDB
9
-
10
- __all__ = ["FatigueDB"]
File without changes
File without changes
File without changes