"""Synthetic public-style material and crash datasets for the Crash Intelligence platform.""" from __future__ import annotations from pathlib import Path import numpy as np import pandas as pd RNG = np.random.default_rng(42) MATERIAL_FAMILIES = { "Mild Steel": { "grades": ["AISI 1008", "AISI 1010", "DC04", "DC06", "St14"], "density": (7.80, 7.87), "modulus": (200, 210), "yield": (140, 280), "uts": (270, 420), "elongation": (25, 45), "failure_strain": (0.25, 0.55), "cost": (0.6, 1.2), "co2": (1.8, 2.5), "recyclability": (0.85, 0.98), "strain_rate_sens": (0.01, 0.04), }, "AHSS": { "grades": ["DP600", "DP800", "DP1000", "TRIP780", "CP800", "MS1200"], "density": (7.80, 7.85), "modulus": (200, 210), "yield": (350, 900), "uts": (600, 1200), "elongation": (8, 25), "failure_strain": (0.08, 0.22), "cost": (1.0, 2.2), "co2": (2.0, 2.8), "recyclability": (0.80, 0.95), "strain_rate_sens": (0.02, 0.06), }, "UHSS": { "grades": ["MS1500", "MS1700", "PHS1500", "PHS1800", "QP980", "QP1180"], "density": (7.80, 7.85), "modulus": (200, 210), "yield": (900, 1500), "uts": (1200, 2000), "elongation": (4, 12), "failure_strain": (0.04, 0.12), "cost": (1.8, 3.5), "co2": (2.2, 3.2), "recyclability": (0.75, 0.92), "strain_rate_sens": (0.015, 0.05), }, "Boron Steel": { "grades": ["22MnB5", "30MnB5", "Usibor 1500", "Usibor 2000", "Ductibor 500"], "density": (7.80, 7.85), "modulus": (200, 210), "yield": (1000, 1400), "uts": (1400, 2000), "elongation": (5, 10), "failure_strain": (0.05, 0.10), "cost": (2.0, 3.8), "co2": (2.3, 3.4), "recyclability": (0.78, 0.93), "strain_rate_sens": (0.02, 0.045), }, "Aluminum 5xxx": { "grades": ["AA5052", "AA5083", "AA5182", "AA5754", "AA5454"], "density": (2.66, 2.70), "modulus": (68, 72), "yield": (90, 220), "uts": (190, 320), "elongation": (12, 30), "failure_strain": (0.15, 0.35), "cost": (2.5, 4.0), "co2": (8.0, 12.0), "recyclability": (0.90, 0.98), "strain_rate_sens": (0.01, 0.03), }, "Aluminum 6xxx": { "grades": ["AA6005", "AA6061", "AA6063", "AA6082", "AA6111"], "density": (2.68, 2.71), "modulus": (68, 72), "yield": (150, 280), "uts": (220, 340), "elongation": (8, 18), "failure_strain": (0.10, 0.22), "cost": (2.8, 4.5), "co2": (8.5, 13.0), "recyclability": (0.90, 0.98), "strain_rate_sens": (0.01, 0.035), }, "Aluminum 7xxx": { "grades": ["AA7003", "AA7020", "AA7075", "AA7050", "AA7085"], "density": (2.78, 2.82), "modulus": (70, 73), "yield": (300, 500), "uts": (400, 580), "elongation": (5, 12), "failure_strain": (0.06, 0.14), "cost": (4.0, 7.0), "co2": (10.0, 16.0), "recyclability": (0.85, 0.95), "strain_rate_sens": (0.008, 0.025), }, "Magnesium": { "grades": ["AZ31B", "AZ61", "AZ91", "AM60", "ZK60"], "density": (1.74, 1.82), "modulus": (42, 48), "yield": (120, 220), "uts": (200, 320), "elongation": (8, 18), "failure_strain": (0.08, 0.20), "cost": (4.5, 8.0), "co2": (15.0, 25.0), "recyclability": (0.70, 0.90), "strain_rate_sens": (0.02, 0.05), }, "CFRP": { "grades": ["T700/Epoxy", "T800/Epoxy", "IM7/PEEK", "M55J/Epoxy", "AS4/Epoxy"], "density": (1.50, 1.65), "modulus": (70, 150), "yield": (600, 1200), "uts": (800, 1800), "elongation": (1.2, 2.5), "failure_strain": (0.012, 0.025), "cost": (25, 80), "co2": (20, 45), "recyclability": (0.20, 0.45), "strain_rate_sens": (0.005, 0.02), }, "GFRP": { "grades": ["E-Glass/Epoxy", "S-Glass/Epoxy", "E-Glass/PP", "E-Glass/PA6", "SMC"], "density": (1.80, 2.10), "modulus": (20, 45), "yield": (200, 450), "uts": (300, 700), "elongation": (1.5, 4.0), "failure_strain": (0.015, 0.04), "cost": (5, 18), "co2": (4, 12), "recyclability": (0.25, 0.50), "strain_rate_sens": (0.01, 0.03), }, "Natural Fiber Composite": { "grades": ["Flax/PP", "Hemp/PLA", "Jute/Epoxy", "Kenaf/PP", "Bamboo/Epoxy"], "density": (1.20, 1.50), "modulus": (8, 25), "yield": (60, 150), "uts": (80, 200), "elongation": (2, 6), "failure_strain": (0.02, 0.06), "cost": (3, 10), "co2": (0.5, 3.0), "recyclability": (0.50, 0.80), "strain_rate_sens": (0.015, 0.04), }, "Polymer": { "grades": ["PP-GF30", "PA6-GF35", "ABS", "PC/ABS", "PBT-GF30", "TPU"], "density": (0.95, 1.45), "modulus": (1.5, 12), "yield": (25, 120), "uts": (30, 160), "elongation": (5, 80), "failure_strain": (0.05, 1.5), "cost": (1.5, 6.0), "co2": (2.0, 6.5), "recyclability": (0.40, 0.85), "strain_rate_sens": (0.03, 0.10), }, "Elastomer": { "grades": ["EPDM", "NR", "SBR", "NBR", "Silicone"], "density": (0.90, 1.25), "modulus": (0.005, 0.05), "yield": (2, 15), "uts": (5, 30), "elongation": (200, 600), "failure_strain": (2.0, 6.0), "cost": (2.0, 8.0), "co2": (2.5, 7.0), "recyclability": (0.15, 0.40), "strain_rate_sens": (0.05, 0.15), }, "Structural Foam": { "grades": ["EPS", "EPP", "PUR Foam", "Al Honeycomb", "PET Foam"], "density": (0.03, 0.25), "modulus": (0.01, 2.0), "yield": (0.2, 8), "uts": (0.3, 12), "elongation": (5, 80), "failure_strain": (0.4, 2.0), "cost": (1.0, 15.0), "co2": (1.5, 8.0), "recyclability": (0.20, 0.70), "strain_rate_sens": (0.04, 0.12), }, "Adhesive": { "grades": ["Epoxy Structural", "PU Crash", "Acrylic", "MS Polymer", "Toughened Epoxy"], "density": (1.05, 1.40), "modulus": (0.5, 4.0), "yield": (10, 45), "uts": (15, 60), "elongation": (5, 100), "failure_strain": (0.05, 1.2), "cost": (8, 35), "co2": (3.0, 10.0), "recyclability": (0.05, 0.25), "strain_rate_sens": (0.02, 0.08), }, } COMPONENTS = [ "Bumper Beam", "Crash Box", "Front Rail", "Door Intrusion Beam", "B-Pillar", "A-Pillar", "Roof Rail", "Seat Structure", "Battery Enclosure", "Underbody Shield", "Hood Inner", "Crossmember", ] CRASH_SCENARIOS = [ "Frontal Crash", "Side Impact", "Rear Impact", "Pole Impact", "Pedestrian Impact", "Battery Pack Crash", "Bumper Beam Crash", "Door Intrusion", "Seat Structure Crash", "BIW Crash", "EV Underbody Protection", ] JOINING_METHODS = [ "Spot Weld", "Laser Weld", "MIG Weld", "SPR (Self-Piercing Rivet)", "Structural Adhesive", "Hybrid Weld-Bond", "Bolt Fastened", "FDS (Flow Drill Screw)", ] SOLVERS = ["LS-DYNA", "Abaqus Explicit", "PAM-CRASH", "Radioss"] MATERIAL_CARD_MAP = { "Mild Steel": "MAT_024", "AHSS": "MAT_024", "UHSS": "MAT_024", "Boron Steel": "MAT_024", "Aluminum 5xxx": "MAT_024", "Aluminum 6xxx": "MAT_024", "Aluminum 7xxx": "MAT_024", "Magnesium": "MAT_024", "CFRP": "MAT_054 / Composite Damage", "GFRP": "MAT_054 / Composite Damage", "Natural Fiber Composite": "MAT_054 / Composite Damage", "Polymer": "MAT_187 / SAMP-1", "Elastomer": "MAT_077 / Hyperelastic", "Structural Foam": "MAT_063 / Crushable Foam", "Adhesive": "MAT_240 / Cohesive Zone", } def _sample_range(bounds: tuple[float, float], n: int) -> np.ndarray: lo, hi = bounds return RNG.uniform(lo, hi, n) def generate_materials(n_records: int = 6000) -> pd.DataFrame: """Generate a large public-style automotive crash materials database.""" families = list(MATERIAL_FAMILIES.keys()) rows: list[dict] = [] per_family = max(1, n_records // len(families)) for family in families: spec = MATERIAL_FAMILIES[family] n = per_family grades = spec["grades"] for i in range(n): grade = grades[i % len(grades)] density = float(_sample_range(spec["density"], 1)[0]) modulus = float(_sample_range(spec["modulus"], 1)[0]) yield_s = float(_sample_range(spec["yield"], 1)[0]) uts = float(_sample_range(spec["uts"], 1)[0]) if uts < yield_s: uts = yield_s * RNG.uniform(1.05, 1.35) elong = float(_sample_range(spec["elongation"], 1)[0]) fail = float(_sample_range(spec["failure_strain"], 1)[0]) cost = float(_sample_range(spec["cost"], 1)[0]) co2 = float(_sample_range(spec["co2"], 1)[0]) recycl = float(_sample_range(spec["recyclability"], 1)[0]) srs = float(_sample_range(spec["strain_rate_sens"], 1)[0]) specific_strength = uts / density specific_stiffness = modulus / density energy_abs_potential = 0.5 * (yield_s + uts) * fail / density ductility_index = elong / max(uts / 100.0, 1e-6) crash_index = ( 0.35 * (energy_abs_potential / 50.0) + 0.25 * (specific_strength / 200.0) + 0.20 * min(fail * 5.0, 1.0) + 0.20 * (1.0 / (1.0 + cost / 10.0)) ) crash_index = float(np.clip(crash_index * 100, 5, 98)) strength_weight = specific_strength failure_risk = float(np.clip(1.0 - fail * 2.5 + srs * 2.0, 0.05, 0.95)) cost_perf = float(np.clip(crash_index / (cost + 0.5), 1, 80)) sustain = float( np.clip( 100 * recycl * (1.0 / (1.0 + co2 / 10.0)) * (1.0 / (1.0 + density / 5.0)), 5, 98, ) ) lightweight = float(np.clip(100 * (1.0 - density / 8.0) * (specific_strength / 300.0), 5, 98)) rows.append( { "material_id": f"{family[:3].upper()}-{i:04d}", "material_name": f"{grade}-{i % 100:02d}", "family": family, "grade": grade, "density_g_cm3": round(density, 3), "youngs_modulus_gpa": round(modulus, 2), "yield_strength_mpa": round(yield_s, 1), "uts_mpa": round(uts, 1), "elongation_pct": round(elong, 2), "failure_strain": round(fail, 4), "strain_rate_sensitivity": round(srs, 4), "cost_usd_kg": round(cost, 2), "co2_kg_kg": round(co2, 2), "recyclability": round(recycl, 3), "specific_strength": round(specific_strength, 2), "specific_stiffness": round(specific_stiffness, 2), "energy_absorption_potential": round(energy_abs_potential, 3), "ductility_index": round(ductility_index, 3), "crashworthiness_index": round(crash_index, 2), "strength_to_weight": round(strength_weight, 2), "failure_risk": round(failure_risk, 3), "cost_performance_score": round(cost_perf, 2), "sustainability_score": round(sustain, 2), "lightweighting_score": round(lightweight, 2), "material_card_type": MATERIAL_CARD_MAP[family], "confidence_score": round(float(RNG.uniform(0.55, 0.97)), 3), "source": RNG.choice( ["MatWeb", "NIST MDR", "Literature", "CAE Benchmark", "Public Dataset"] ), } ) df = pd.DataFrame(rows) return df.sample(frac=1.0, random_state=42).reset_index(drop=True) def generate_stress_strain(materials: pd.DataFrame, n_curves: int = 400) -> pd.DataFrame: """Generate plastic stress–strain curves for a subset of materials.""" sample = materials.sample(n=min(n_curves, len(materials)), random_state=7) curve_rows: list[dict] = [] strains = np.linspace(0, 0.25, 40) for _, mat in sample.iterrows(): e = mat["youngs_modulus_gpa"] * 1000 # MPa ys = mat["yield_strength_mpa"] uts = mat["uts_mpa"] n_hard = RNG.uniform(0.08, 0.28) for eps in strains: if eps * e < ys: stress = eps * e else: plastic = max(eps - ys / e, 0) stress = ys + (uts - ys) * (1 - np.exp(-plastic / n_hard)) stress = min(stress, uts * 1.05) rate_factor = 1.0 + mat["strain_rate_sensitivity"] * np.log1p(100) curve_rows.append( { "material_id": mat["material_id"], "material_name": mat["material_name"], "family": mat["family"], "strain": round(float(eps), 5), "stress_mpa": round(float(stress * rate_factor / rate_factor), 2), "stress_high_rate_mpa": round(float(stress * rate_factor), 2), } ) return pd.DataFrame(curve_rows) def generate_recommendations(materials: pd.DataFrame, n: int = 3000) -> pd.DataFrame: """Generate crash-scenario recommendation / prediction records.""" rows: list[dict] = [] sample = materials.sample(n=min(n, len(materials) * 2), replace=True, random_state=11) for i, (_, mat) in enumerate(sample.iterrows()): scenario = CRASH_SCENARIOS[i % len(CRASH_SCENARIOS)] component = COMPONENTS[i % len(COMPONENTS)] thickness = float(RNG.uniform(0.8, 3.5)) joining = JOINING_METHODS[i % len(JOINING_METHODS)] base = mat["crashworthiness_index"] thickness_factor = np.clip(thickness / 2.0, 0.5, 1.6) energy = float(base * thickness_factor * RNG.uniform(0.85, 1.15)) intrusion = float(np.clip(80 - base * 0.5 - thickness * 8 + RNG.normal(0, 5), 5, 120)) peak_force = float(mat["uts_mpa"] * thickness * 0.015 * RNG.uniform(0.8, 1.2)) cfe = float(np.clip(0.45 + base / 300 + RNG.normal(0, 0.05), 0.3, 0.95)) sea = float(mat["energy_absorption_potential"] * thickness_factor * RNG.uniform(0.9, 1.1)) crash_score = float( np.clip( 0.3 * energy + 0.2 * (100 - intrusion) + 0.15 * cfe * 100 + 0.15 * mat["lightweighting_score"] + 0.1 * mat["cost_performance_score"] + 0.1 * mat["sustainability_score"], 10, 98, ) ) weight_reduction = float( np.clip((3.0 - mat["density_g_cm3"]) / 3.0 * 40 + RNG.normal(0, 3), -5, 55) ) sim_risk = float(np.clip(mat["failure_risk"] * 100 + RNG.normal(0, 5), 5, 95)) rows.append( { "rec_id": f"REC-{i:05d}", "material_id": mat["material_id"], "material_name": mat["material_name"], "family": mat["family"], "component": component, "crash_scenario": scenario, "thickness_mm": round(thickness, 2), "joining_method": joining, "energy_absorption_kj": round(energy, 2), "intrusion_mm": round(intrusion, 2), "peak_force_kn": round(peak_force, 2), "crush_force_efficiency": round(cfe, 3), "specific_energy_absorption": round(sea, 3), "crash_score": round(crash_score, 2), "weight_reduction_pct": round(weight_reduction, 2), "cost_score": round(mat["cost_performance_score"], 2), "sustainability_score": round(mat["sustainability_score"], 2), "lightweighting_score": round(mat["lightweighting_score"], 2), "simulation_risk": round(sim_risk, 2), "failure_risk": round(mat["failure_risk"], 3), "solver": SOLVERS[i % len(SOLVERS)], "validation_level": RNG.choice( [ "Coupon Test", "Component Test", "CAE Validation", "Physical Crash", "Certification Align", ] ), "required_test": RNG.choice( [ "Tensile + Strain-Rate", "3-Point Bend", "Drop Tower", "Component Crush", "Side Pole Sled", "Full-Vehicle Barrier", ] ), } ) return pd.DataFrame(rows) def generate_validation(recommendations: pd.DataFrame, n: int = 1500) -> pd.DataFrame: """Generate AI vs CAE / NHTSA-style validation comparison records.""" sample = recommendations.sample(n=min(n, len(recommendations)), random_state=21) rows: list[dict] = [] for i, (_, rec) in enumerate(sample.iterrows()): ai = rec["crash_score"] noise = RNG.normal(0, 4) cae = float(np.clip(ai + noise, 5, 100)) physical = float(np.clip(cae + RNG.normal(0, 3), 5, 100)) error_ai_cae = abs(ai - cae) / max(cae, 1e-6) * 100 error_cae_phys = abs(cae - physical) / max(physical, 1e-6) * 100 rows.append( { "val_id": f"VAL-{i:04d}", "rec_id": rec["rec_id"], "material_name": rec["material_name"], "family": rec["family"], "component": rec["component"], "crash_scenario": rec["crash_scenario"], "ai_crash_score": round(ai, 2), "cae_crash_score": round(cae, 2), "physical_crash_score": round(physical, 2), "ai_cae_error_pct": round(error_ai_cae, 2), "cae_physical_error_pct": round(error_cae_phys, 2), "nhtsa_star_proxy": int(np.clip(round(physical / 20), 1, 5)), "standard": RNG.choice(["Euro NCAP", "FMVSS", "IIHS", "OEM Internal"]), "pass_fail": "Pass" if error_ai_cae < 12 else "Review", } ) return pd.DataFrame(rows) def generate_all(data_dir: Path | str) -> dict[str, pd.DataFrame]: """Generate and persist all datasets.""" data_dir = Path(data_dir) data_dir.mkdir(parents=True, exist_ok=True) materials = generate_materials(6000) stress = generate_stress_strain(materials, 400) recommendations = generate_recommendations(materials, 3500) validation = generate_validation(recommendations, 1800) materials.to_csv(data_dir / "materials.csv", index=False) stress.to_csv(data_dir / "stress_strain.csv", index=False) recommendations.to_csv(data_dir / "recommendations.csv", index=False) validation.to_csv(data_dir / "validation.csv", index=False) return { "materials": materials, "stress_strain": stress, "recommendations": recommendations, "validation": validation, } if __name__ == "__main__": out = Path(__file__).resolve().parent.parent / "data" datasets = generate_all(out) for name, df in datasets.items(): print(f"{name}: {len(df)} rows")