Source code for pylife.strength.fkm_linear.fkm_linear_factors
"""Collection of functions for computational proof for machine elements.
FKM-guidelines according to chapter 4 and 5.5 (FKM guideline 2012).
"""
import numpy as np
import pandas as pd
from pylife.strength.fkm_linear.fkm_functions import FkmLinearFunctions as fkm_class
HARDENING_PROCEDURES = [
"Inductive hardening",
"Flame hardening",
"Case hardening",
"Carburizing",
"Nitriding",
"Cyaniding",
"Carbonitriding",
"Deep rolling",
"Shot peening",
"Cold rolling",
]
TEMPERATURE_GROUPS = [
"GS",
"GJL",
"GS",
"GJM",
"Steel",
"Stainless Steel",
"Aluminum",
"fine grain structural steel",
"other kinds of steel",
]
[docs]
def calc_input_parameters_material(experiment_settings, assessment_parameters_):
"""Compute relevant material-dependent factors for FKM guideline.
Calculates factors that depend on the design and material of the component
and not on the applied stress (e.g. mean stress sensitivity or roughness factor).
Parameters
----------
experiment_settings : pd.Series
Pandas Series with following columns:
* fkm_chapter : str
Chapter of the FKM guideline, one of {'chap4', 'chap5.5'}
* MatGroupFKM : str
Material group. One of:
* 'CaseHard_Steel' : Case-hardened steels
* 'Stainless_Steel' : Stainless steels
* 'Forg_Steel' : Forged steels
* 'Steel' : All other steel groups
* 'GS' : Cast steels and tempering cast steels
* 'GJS' : Cast iron with spheroidal graphite (old GGG)
* 'GJM' : Heart fittings (Temperguss, old: GT)
* 'GJL' : Cast iron with lamellar graphite (old GG)
* 'Al_wrought' : Wrought aluminium alloys
* 'Al_cast' : Cast aluminium alloys
* MatGroupFKM_Temp : str
Material temperature group, one of {'fine grain structural steel',
'Stainless Steel', 'other kinds of steel', 'GJL', 'GS', 'GJS',
'GJM', 'Aluminum', 'None'}
* Profile : str
Profile/Geometry of the specimen, one of {'Rod', 'Tube',
'Wide sheet', 'Rectangle', 'Square'}
* Diameter : float
Diameter of the specimen [mm]
* Width : float
Width of the specimen [mm] (only needed if profile is
'Rectangle' or 'Square')
* Thickness : float
Thickness of the specimen [mm] (only needed if profile is
'Rectangle', 'Sheet', or 'Wide sheet')
* Condition : str or None
Heat treatment condition for fictive width b calculation,
one of {'Hardened', 'Annealed'} or None
assessment_parameters_ : pd.DataFrame
DataFrame with following columns:
* Rm : float
Tensile strength [MPa]
* Rz : float
Surface roughness [µm]
* S_Type : {'normal', 'shear'}
Stress type
* Temperature : float
Temperature of the component [°C]
* Finish : {'polished'}, optional
Surface finish polished (if used, no Rz value necessary)
* GJL_mat : str, optional
The GJL material group (if GJL is used)
* HV : float, optional
Vickers hardness (only needed for chap5.5)
* HV_core : float, optional
Core hardness (only needed for chap5.5)
* HardProc : str, optional
Surface hardening method, one of {'Inductive hardening',
'Flame hardening', 'Case hardening', 'Carburizing', 'Nitriding',
'Cyaniding', 'Carbonitriding', 'Deep rolling', 'Shot peening'}
Returns
-------
pd.DataFrame
DataFrame with additional columns including relevant FKM quantities
"""
ap = assessment_parameters_.copy()
# Initialize class object
fkm = fkm_class()
# Set parameters that are equal for all nodes
ap["MatGroupFKM"] = experiment_settings["MatGroupFKM"]
ap["MatGroupFKM_Temp"] = experiment_settings["MatGroupFKM_Temp"]
ap["Profile"] = experiment_settings["Profile"]
ap["Condition"] = experiment_settings["Condition"]
# Adapt data frame such that it matches the desired format
ap = _adapt_data_frame(ap)
# Get material constants
df_consts, df_fw_t = fkm.get_material_constants(ap["MatGroupFKM"], ap["S_Type"])
df_temperature = fkm.get_temperature_constants(ap["MatGroupFKM_Temp"])
# Calculate temperature factor K_D_T
ap["K_D_T"] = fkm.temperature_model(
df_temperature, ap["Temperature"], ap["MatGroupFKM"]
)
if experiment_settings["fkm_chapter"] == "chap4":
# Compute reversed material strength
ap["Sw"] = fkm.reversed_mat_strength_chap4(
ap["Rm"], df_consts, df_fw_t, ap["S_Type"]
)
# Roughness factor
ap["Kr"] = fkm.rough_factor(
ap["Rm"], ap["Rz"], df_consts, df_fw_t, ap["S_Type"], ap["Finish"]
)
# Coating factor KS is set to 1 by default,
# adapt if needed before using calc_input_parameters_stress
ap["Ks"] = 1.0
fictive_width = _fictive_width_b(experiment_settings)
ap["b"] = fictive_width
ap["Knle"] = fkm.GJL_bending_factor(ap["GJL_Mat"])
# Compute characteristic size
characteristic_size = _characteristic_size(experiment_settings)
ap["Deff"] = characteristic_size
# Mean stress sensitivity M
ap["M"] = fkm.sm_sensitivity_chap4(
ap["Rm"], df_consts, df_fw_t, ap["S_Type"]
)
elif experiment_settings["fkm_chapter"] == "chap5.5":
df_proc = fkm.get_material_constants_chap5_5(ap["HardProc"])
ap["Rm"] = 3.3 * ap["HV"]
ap["SW_RS"] = fkm.reversed_mat_strength_chap5_5(
ap["Rm"],
df_consts,
df_fw_t,
ap["S_Type"],
ap["HV"],
df_proc,
ap["HardProc"],
)
ap["Rm_RS"] = 3.3 * ap["HV"]
# Roughness factor
ap["Kr"] = fkm.rough_factor(
ap["Rm"], ap["Rz"], df_consts, df_fw_t, ap["S_Type"], ap["Finish"]
)
# Notch strength reduction factor (Kerbwirkungszahl) (FKM Chapter 4.3.1.2)
ap["Kf"] = fkm.kf_constant(ap["MatGroupFKM"])
# Surface factor for case-hardened materials are initialized with default values,
# adapt if needed before using calc_input_parameters_stress
ap["Deff"] = None
ap["Kv"] = 1.0
ap["Ks"] = 1.0
ap["Knle"] = 1.0
# Mean stress sensitivity
ap["M_RS"] = df_proc["M_RS"].values
# Eigenstress for surface layer SE
ap["SE_RS"] = fkm.eigenstress_RS(
ap["Rm"],
ap["HV"],
ap["HV_core"],
ap["HardProc"],
)
# SDFKM at transition point from hardened surface layer to core
# Reversed material strength
ap["SW_trans"] = fkm.reversed_mat_strength_chap5_5(
ap["Rm"],
df_consts,
df_fw_t,
ap["S_Type"],
ap["HV_core"],
df_proc,
ap["HardProc"],
)
# Calculate mean stress sensitivity M_trans for transition point
ap["Rm_trans"] = fkm.sm_sensitivity_Rm_trans(ap["HV_core"])
ap["M_trans"] = fkm.sm_sensitivity_M_trans(
ap["Rm"], df_consts, df_fw_t, ap["S_Type"], ap["Rm_trans"]
)
ap["SE_trans"] = -0.3 * ap["SE_RS"]
return ap
[docs]
def calc_input_parameters_stress(experiment_settings, assessment_parameters_):
"""Compute relevant stress-dependent factors for FKM guideline.
Calculates factors that depend on the stress applied to the component,
i.e., gradient, amplitude and meanstress (e.g. design factor, support factor).
Note: Requires executing calc_input_parameters_material() before this function.
Parameters
----------
experiment_settings : pd.Series
Pandas Series with following columns:
* fkm_chapter : str
Chapter of the FKM guideline, one of {'chap4', 'chap5.5'}
* sup_method : str, optional
Support factor calculation method. One of {'Stieler', 'V90_Mises', 'A90'}.
Default is Stieler.
assessment_parameters_ : pd.DataFrame
DataFrame with the following columns:
* Rm : float
Tensile strength [MPa]
* G0 : float
Relative stress gradient [1/mm]. Sum of local & global relative
stress gradients
* A90 : float, optional
Maximum loaded surface according to FKM2012 [mm²]
* V90_Mises : float, optional
Maximum loaded volume according to von Mises support factor [mm³]
* MatGroupFKM : str
Material group. One of:
* 'CaseHard_Steel' : Case-hardened steels
* 'Stainless_Steel' : Stainless steels
* 'Forg_Steel' : Forged steels
* 'Steel' : All other steel groups
* 'GS' : Cast steels and tempering cast steels
* 'GJS' : Cast iron with spheroidal graphite (old GGG)
* 'GJM' : Heart fittings (Temperguss, old: GT)
* 'GJL' : Cast iron with lamellar graphite (old GG)
* 'Al_wrought' : Wrought aluminium alloys
* 'Al_cast' : Cast aluminium alloys
* amplitude : float
Stress amplitude [MPa] (can be set to 1.0)
* meanstress : float
Mean stress [MPa]
* S_Type : {'normal', 'shear'}
Stress type
* Kf_method : {'Table', 'Equation'}
Method for estimating the fatigue notch factor (chap. 4.3.1.2)
* FKM_chap : {'chap4', 'chap5.5'}
Chapter of the FKM guideline
* HV : float, optional
Vickers hardness (only needed for chap5.5)
Returns
-------
pd.DataFrame
DataFrame with additional columns including relevant FKM quantities
"""
ap = assessment_parameters_.copy()
fkm = fkm_class()
ap["R_ratio"] = (ap["meanstress"] - ap["amplitude"]) / (
ap["meanstress"] + ap["amplitude"]
)
# Get material constants
df_consts, df_fw_t = fkm.get_material_constants(ap["MatGroupFKM"], ap["S_Type"])
df_proc = fkm.get_material_constants_chap5_5(ap["HardProc"])
if experiment_settings["fkm_chapter"] == "chap4":
# Compute support factor
if experiment_settings["sup_method"] == "Stieler":
ap["n"] = fkm.stieler_support(
df_consts, df_fw_t, ap["S_Type"], ap["G0"], ap["Rm"]
)
(
ap["n_st"],
ap["n_vm"],
ap["n_bm"],
) = (np.nan, np.nan, np.nan)
elif experiment_settings["sup_method"] == "V90_Mises":
# Calculate support factors using V90_Mises (maximum stressed volume)
ap_temp = ap.copy()
res = fkm.support_fkm2012_local_vol_frame(ap_temp)
# Create a new DataFrame from the list of tuples
df_temp = pd.DataFrame(res.tolist(), columns=["n_st", "n_vm", "n_bm", "n"])
# Set support values
ap["n_st"] = df_temp["n_st"].to_numpy()
ap["n_vm"] = df_temp["n_vm"].to_numpy()
ap["n"] = df_temp["n"].to_numpy()
ap["n_bm"] = df_temp["n_bm"].to_numpy()
else:
# Calculate support factors using A90 (maximum stressed surface)
ap_temp = ap.copy()
res = fkm.support_fkm2012_local_surf_frame(ap_temp)
# Create a new DataFrame from the list of tuples
df_temp = pd.DataFrame(res.tolist(), columns=["n_st", "n_vm", "n_bm", "n"])
# Set support values
ap["n_st"] = df_temp["n_st"].to_numpy()
ap["n_vm"] = df_temp["n_vm"].to_numpy()
ap["n"] = df_temp["n"].to_numpy()
ap["n_bm"] = df_temp["n_bm"].to_numpy()
# Notch strength reduction factor (Kerbwirkungszahl) (FKM Chapter 4.3.1.2)
ap["Kf"] = fkm.kf_factor(
ap["Kf_method"], ap["MatGroupFKM"], ap["G0"], ap["b"], ap["n"], ap["S_Type"]
)
# Surface factor for case-hardened materials
ap["Kv"] = fkm.surf_layer_factor(
df_proc, ap["G0"], ap["Deff"], ap["HardProc"]
)
elif experiment_settings["fkm_chapter"] == "chap5.5":
# Support factor for surface layer (chap 5.5 specific)
ap["n_RS"] = fkm.support_chap5(ap["G0"], ap["HV"])
return ap
[docs]
def fatigue_limit_local_chap4(assessment_parameters_):
"""Calculate fatigue limit for non-welded materials using local stress concept.
According to chapter 4 of FKM guideline 2012.
Parameters
----------
assessment_parameters_ : pd.DataFrame
DataFrame with the following columns:
* Rm : float
Tensile strength [MPa]
* G0 : float, optional
Relative stress gradient [1/mm]. Sum of local & global relative
stress gradients
* A90 : float, optional
Maximum loaded surface according to FKM2012 [mm²]
* MatGroupFKM : str
Material group. One of:
* 'CaseHard_Steel' : Case-hardened steels
* 'Stainless_Steel' : Stainless steels
* 'Forg_Steel' : Forged steels
* 'Steel' : All other steel groups
* 'GS' : Cast steels and tempering cast steels
* 'GJS' : Cast iron with spheroidal graphite (old GGG)
* 'GJM' : Heart fittings (Temperguss, old: GT)
* 'GJL' : Cast iron with lamellar graphite (old GG)
* 'Al_wrought' : Wrought aluminium alloys
* 'Al_cast' : Cast aluminium alloys
* R_ratio : float
Stress ratio
* Rz : float
Surface roughness [µm]
* S_Type : {'normal', 'shear'}
Stress type
Returns
-------
pd.DataFrame
DataFrame containing input data and all calculated data for FKM prognoses
with predicted fatigue limit [MPa]
"""
ap = assessment_parameters_.copy()
fkm = fkm_class()
# Compute temperature-adjusted reversed material strength
ap["SW"] = ap["K_D_T"] * ap["Sw"]
# Compute design factor
ap["Kwk"] = fkm.design_factor(
ap["n"],
ap["Kf"],
ap["Kr"],
ap["Kv"],
ap["Ks"],
ap["Knle"],
)
# Reversed strength of design element
ap["Swk"] = ap["SW"] / ap["Kwk"]
SmSa = (1 + ap["R_ratio"]) / (1 - ap["R_ratio"])
# Mean stress factor
ap["Kak"] = fkm.sm_factor(ap["R_ratio"], ap["M"], SmSa)
# Fatigue limit
ap["SDFKM"] = ap["Swk"] * ap["Kak"]
return ap
[docs]
def fatigue_limit_local_chap5(assessment_parameters_):
"""Calculate fatigue limit for surface-hardened materials using local stress concept.
For surface layer according to chapter 5.5 of FKM guideline 2012.
Parameters
----------
assessment_parameters_ : pd.DataFrame
DataFrame with the following columns:
* Rm : float
Tensile strength [MPa]
* G0 : float, optional
Relative stress gradient [1/mm]. Sum of local & global relative
stress gradients
* A90 : float, optional
Maximum loaded surface according to FKM2012 [mm²]
* MatGroupFKM : str
Material group. One of:
* 'CaseHard_Steel' : Case-hardened steels
* 'Stainless_Steel' : Stainless steels
* 'Forg_Steel' : Forged steels
* 'Steel' : All other steel groups
* 'GS' : Cast steels and tempering cast steels
* 'GJS' : Cast iron with spheroidal graphite (old GGG)
* 'GJM' : Heart fittings (Temperguss, old: GT)
* 'GJL' : Cast iron with lamellar graphite (old GG)
* 'Al_wrought' : Wrought aluminium alloys
* 'Al_cast' : Cast aluminium alloys
* R_ratio : float
Stress ratio
* Rz : float
Surface roughness [µm]
* S_Type : {'normal', 'shear'}
Stress type
Returns
-------
pd.DataFrame
DataFrame containing input data and all calculated data for FKM prognoses
with predicted fatigue limits at surface (RS) and core [MPa]
"""
ap = assessment_parameters_.copy()
fkm = fkm_class()
if (ap["HardProc"].isin(fkm.hard_procs)).all():
# Design factor
ap["Kwk_RS"] = fkm.design_factor(
ap["n_RS"],
ap["Kf"],
ap["Kr"],
ap["Kv"],
ap["Ks"],
ap["Knle"],
)
# Reversed strength of design element
ap["Swk_RS"] = ap["SW_RS"] / ap["Kwk_RS"]
# Mean stress factor for overload case F2 and design factor at inner point
SmSa = (1 + ap["R_ratio"]) / (1 - ap["R_ratio"])
ap["Kak_RS"] = fkm.sm_factor_chap5(
ap["Rm_RS"], ap["M_RS"], ap["SE_RS"], ap["Swk_RS"], SmSa, ap["Rm"]
)
# Fatigue limit at surface
ap["SDFKM_RS"] = ap["Swk_RS"] * ap["Kak_RS"]
# Reversed strength of design element in the core
ap["Kwk_trans"] = 1.0 # Design factor Kwk = 1
ap["Swk_trans"] = ap["SW_trans"] * ap["Kwk_trans"]
# Mean stress factor for overload case F2 in the core
ap["Kak_trans"] = fkm.sm_factor_chap5(
ap["Rm_trans"],
ap["M_trans"],
ap["SE_trans"],
ap["Swk_trans"],
SmSa,
ap["Rm"],
)
# Fatigue limit at transition point in the core
ap["SDFKM_trans"] = ap["Swk_trans"] * ap["Kak_trans"]
return ap
def _adapt_data_frame(df):
"""Adapt DataFrame format to match FKM function requirements.
Parameters
----------
df : pd.DataFrame
DataFrame including FKM parameters
Returns
-------
pd.DataFrame
DataFrame with adapted columns matching required format
"""
# Convert all numeric columns to floats
numeric_columns = df.select_dtypes(include=["int", "float"]).columns
df[numeric_columns] = df[numeric_columns].astype(float)
# Replace non-existing names of hardening procedures and temperature groups with 'None'
df["HardProc"] = df["HardProc"].apply(
lambda x: x if x in HARDENING_PROCEDURES else "None"
)
df["MatGroupFKM_Temp"] = df["MatGroupFKM_Temp"].apply(
lambda x: x if x in TEMPERATURE_GROUPS else "None"
)
df["Finish"] = df["Finish"].apply(lambda x: x if x in ["polished"] else "None")
# Replace the parameters Rm, Rm_trans, HV, HV_core with np.nan if they are None
for col in ["Rm", "Rm_trans", "HV", "HV_core"]:
if col in df.columns:
df[col] = df[col].astype(float)
return df
def _fictive_width_b(series):
"""Determine fictive width of the component.
Parameters
----------
series : pd.Series
Series including the geometry of the component with columns:
* Profile : str
One of {'Rod', 'Tube', 'Wide sheet', 'Rectangle', 'Square'}
* Diameter : float
Diameter [mm] (for Rod, Tube)
* Thickness : float
Thickness [mm] (for Tube, Wide sheet, Rectangle)
* Width : float
Width [mm] (for Rectangle, Square)
* Condition : str
Heat treatment condition
* MatGroupFKM : str
Material group
Returns
-------
float
Fictive width of the component [mm]
"""
if series["Profile"] == "Rod":
Deff1 = series["Diameter"]
Deff2 = series["Diameter"]
elif series["Profile"] == "Tube":
Deff1 = 2 * series["Thickness"]
Deff2 = series["Thickness"]
elif series["Profile"] == "Wide sheet":
Deff1 = 2 * series["Thickness"]
Deff2 = series["Thickness"]
elif series["Profile"] == "Rectangle":
Deff1 = (
2
* series["Width"]
* series["Thickness"]
/ (series["Width"] + series["Thickness"])
)
Deff2 = series["Thickness"]
elif series["Profile"] == "Square":
Deff1 = series["Width"]
Deff2 = series["Width"]
else:
raise ValueError(f"Unknown profile type: {series['Profile']}")
if series["Condition"] == "Hardened" or series["MatGroupFKM"] in [
"CaseHard_Steel",
"GJS",
"GJM",
"GJL",
]:
return Deff1 / 2
else:
return Deff2
def _characteristic_size(series):
"""Determine characteristic size of the component.
Parameters
----------
series : pd.Series
Series including the geometry-specific size of the component with columns:
* Profile : str
One of {'Rod', 'Tube', 'Wide sheet', 'Rectangle', 'Square'}
* Diameter : float
Diameter [mm] (for Rod, Tube)
* Thickness : float
Thickness [mm] (for Wide sheet, Rectangle)
* Width : float
Width [mm] (for Square)
Returns
-------
float
Characteristic size of the component [mm]
"""
if series["Profile"] == "Rod":
return series["Diameter"]
elif series["Profile"] == "Tube":
return series["Diameter"]
elif series["Profile"] == "Wide sheet":
return series["Thickness"]
elif series["Profile"] == "Rectangle":
return series["Thickness"]
elif series["Profile"] == "Square":
return series["Width"]
else:
raise ValueError(f"Unknown profile type: {series['Profile']}")