#!/usr/bin/env python3
"""Andrade Safe: four independent synthetic educational fatigue models.
No material characterization, component approval, probabilistic life or inspection interval.
Basquin N: cycles, not reversals. Paris stress MPa, crack length metres internally.
"""
import math
B=math.log(200/300)/math.log(10)
A=300/(1e5**B)
C=2e-11  # m/cycle/(MPa sqrt(m))^3
Y=1.12
M=3
def finite(*values):
    if not all(isinstance(v,(int,float)) and math.isfinite(v) for v in values):raise ValueError('Finite numeric inputs required')
def basquin(stress):
    finite(stress)
    if stress<=0:raise ValueError('Positive amplitude required')
    return math.exp(math.log(stress/A)/B)
def goodman(sa,mean):
    finite(sa,mean)
    if sa<=0 or mean<0:raise ValueError('Positive amplitude and tensile/zero mean only')
    U=sa/200+mean/550
    return dict(U=U,nominal_reciprocal=1/U)
def miner(blocks):
    if len(blocks)!=3:raise ValueError('Three pre-counted blocks required')
    rows=[]
    for stress,n in blocks:
        finite(stress,n)
        if n<0:raise ValueError('Nonnegative applied cycles required')
        N=basquin(stress);rows.append(dict(stress_MPa=stress,n=n,N=N,fraction=n/N))
    return dict(rows=rows,D=sum(x['fraction'] for x in rows))
def paris(ds,a0_mm,af_mm):
    finite(ds,a0_mm,af_mm)
    if ds<=0 or a0_mm<=0 or af_mm<=a0_mm:raise ValueError('Positive stress range and increasing positive crack interval required')
    a0=a0_mm/1000;af=af_mm/1000;g=C*(Y*ds*math.sqrt(math.pi))**M
    return 2*(a0**(-.5)-af**(-.5))/g
def crack_at(n,ds,a0_mm):
    finite(n,ds,a0_mm)
    if n<0 or ds<=0 or a0_mm<=0:raise ValueError('Invalid crack-growth domain')
    g=C*(Y*ds*math.sqrt(math.pi))**M;d=(a0_mm/1000)**(-.5)-.5*g*n
    if d<=0:raise ValueError('Outside the finite educational trajectory')
    return 1000/d**2
if __name__=='__main__':
    print('Synthetic Basquin:',dict(A_MPa=A,b=B,N_at_240=basquin(240)))
    print('Synthetic Goodman:',goodman(160,70))
    print('Synthetic Miner:',miner([(220,100000),(240,120000),(270,25000)]))
    print('Synthetic Paris cycles1to5mm:',paris(120,1,5))
