[docs]
def coupon_csvdata_read(testdata_file_name: str,
force_index: int,
strain_index: int):
"""
Read tensile coupon test data from a CSV file and extract force and strain columns.
Parameters
----------
testdata_file_name : str
Path to the CSV file containing the test data.
force_index : int
1-based column index for the force data.
strain_index : int
1-based column index for the strain data.
Returns
-------
sample_name : str
Name of the sample extracted from the file name.
df_new : pd.DataFrame
DataFrame containing only 'Force' and 'Strain' columns.
Notes
-----
- CSV must have a header in the first row; second row is skipped.
- First column is treated as index.
- Force and strain columns are extracted based on 1-based indices.
"""
import pandas as pd
df = pd.read_csv(testdata_file_name, skiprows=[1])
Force = df[df.columns[force_index - 1]]
Strain = df[df.columns[strain_index - 1]]
df_new = pd.concat([Force, Strain], axis=1)
df_new.columns = ['Force', 'Strain']
sample_name = testdata_file_name[:-4]
return sample_name, df_new
[docs]
def coupon_test_analysis (sample_name: str,
Force: float,
Strain: float,
thickness: float = 2.5,
width: float = 10,
showfig: bool = True,
savefig: bool = False,
low_bound: float = 0.1,
up_bound: float = 0.3,
):
"""
Post-process a tensile coupon test and plot stress-strain curve.
To active with Jupyter Lab, '%matplotlib widget' is required
Args:
Thickness (float): Specimen thickness in mm.
Width (float): Specimen width in mm.
file_name (str): CSV file containing test data.
low_bound (float): Lower bound of elastic region as fraction of UTS.
upper_bound (float): Upper bound of elastic region as fraction of UTS.
Returns:
fig (matplotlib.figure.Figure): Figure object containing the plot.
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#%matplotlib widget
# Constants
area = thickness * width # Calculate the area of the specimen
# Load tensile test data
strain = Strain # Strain in mm/mm
# Calculate stress and strain
force = Force * 1000 # Convert kN to N
stress = (force / area) # N/mm^2 or MPa
uts = stress.max()
#find the data before uts
idx_peak = np.argmax(stress)
strain_up = Strain[:idx_peak+1]
stress_up = stress[:idx_peak+1]
#Boundary for [low bound] - [up bound] of uts
lower_bound = low_bound * uts
upper_bound = up_bound * uts
elastic_reg = (lower_bound <= stress_up) & (stress_up <= upper_bound)
stress_ela = stress_up[elastic_reg]
strain_ela = strain_up[elastic_reg]
E, intercept = np.polyfit(strain_ela, stress_ela, 1)
#print(f"Young's Modulus is: {E} MPa",)
E_GPa = E / 1000 # Convert MPa to GPa
#print(f"Intercept: {intercept} MPa")
# Select over [lower bound] of UTS, as yield stress will over [lower bound] uts
strain_new = strain
stress_new = force / area
mask = (lower_bound <= stress)
strain_mask = strain_new[mask]
stress_mask = stress_new[mask]
offset_decimal = 0.002 # 0.2% in decimal
offset_line = E * (strain_new - offset_decimal) + intercept
#Find the Yield strength
diff = stress_mask - offset_line
cross_index = np.where(diff <= 0)[0][0] +1
x1 = strain_mask[cross_index-1]
x2 = strain_mask[cross_index]
y1 = diff[cross_index-1]
y2 = diff[cross_index]
yield_strain = x1 - y1 * (x2 - x1) / (y2 - y1)
yield_strength = np.interp(yield_strain, strain_new, stress_new)
#Plot
fig, ax = plt.subplots(figsize=(10,6))
ax.plot(strain, stress, label='Stress-Strain Curve', color='blue')
ax.plot(strain_new, offset_line, label='0.2% Offset Strain Line', color='yellow',linestyle = '-.')
ax.axhline(y=yield_strength, label=f'Yield Strength = {yield_strength:.2f} MPa', color='green', linestyle = 'dotted')
ax.axhline(y=uts, color='red', linestyle = '--', label=f'UTS = {uts:.2f} MPa')
ax.plot(yield_strain, yield_strength, 'ro', label='Yield Point')
ax.set_xlabel('Strain (mm/mm)')
ax.set_ylabel('Stress (MPa)')
ax.set_title(sample_name + ' Stress-Strain Curve with Mechanical Properties')
ax.legend()
ax.grid(True)
ax.set_ylim(0, 1.2 *uts)
#Show fig or not
if showfig == True:
plt.show()
else:
plt.close(fig)
# Save fig or not
if savefig == True:
fig.savefig(sample_name, dpi=300, bbox_inches='tight')
# Print results
print(f"Sample: {sample_name}")
print(f"Young's Modulus (E): {E:.2f} MPa")
print(f"Ultimate Tensile Strength (UTS): {uts:.2f} MPa")
print(f"Yield Strength: {yield_strength:.2f} MPa")
print('-' * 40)
# Prepare results dictionary
results = {
"E_MPa": E,
"UTS_MPa": uts,
"Yield_Strength_MPa": yield_strength
}
return sample_name, results
[docs]
def coupon_sample_geodata_read (Excelfile_name: str):
'''
Reads sample geometric properties from an Excel file.
Parameters
----------
Excelfile_name : str
The name of the Excel file to read, located in the current working directory.
Returns
-------
list
A list containing:
- sample_file_name (sample name from cell A2 with ".csv" extension)
- thickness (value from cell C2)
- width (value from cell B2)
'''
from openpyxl import load_workbook
from pathlib import Path
sample_data_file = Path.cwd()/Excelfile_name
wb = load_workbook(sample_data_file)
ws = wb.worksheets[0]
samples = []
for row in ws.iter_rows(min_row=2, values_only=True):
if all(cell is None for cell in row): # skip empty
continue
sample_file_name = str (row[0] + ".csv")
sample_name = coupon_SampleDetails(row[0], row[1], row[2], sample_file_name)
samples.append(sample_name)
return samples
[docs]
def coupon_batch_analysis(Coupon_geodata: str,
force_index: float,
strain_index:float,
showfig: bool = True,
savefig: bool = False):
"""
Perform batch analysis on a list of tensile coupon samples and return their results.
Parameters
----------
Coupon_geodata : list of coupon_SampleDetails
A list of SampleDetails objects, each containing:
- sample_file_name : str
Path to the sample CSV file.
- thickness : float
Sample thickness (mm).
- width : float
Sample width (mm)
force_index : int
Column index of the Force data in the CSV file (1-based)
strain_index : int
Column index of the Strain data in the CSV file (1-based)
showfig : bool, optional
Whether to display stress-strain plots. Default is True
savefig : bool, optional
Whether to save the stress-strain plots. Default is False
Returns
-------
list of coupon_SampleAnalysisResults
A list of SampleAnalysisResults objects, each containing:
- sample_name : str
Name of the processed sample
- modulus_of_elasticity : float
Elastic modulus (E_GPa)
- ultimate_tensile_strength : float
Ultimate tensile strength (UTS_MPa)
- yield_strength : float
Yield strength (Yield_Strength_MPa)
"""
SARS = []
for Coupon_detail in Coupon_geodata:
csvdata = coupon_csvdata_read(Coupon_detail.sample_file_name,force_index,strain_index)
result = coupon_test_analysis(Coupon_detail.sample_file_name[:-4],
csvdata[1]['Force'],
csvdata[1]['Strain'],
Coupon_detail.thickness,
Coupon_detail.width,
showfig,
savefig)
SAR = coupon_SampleAnalysisResults(result[0], result[1]['E_MPa'], result[1]['UTS_MPa'], result[1]['Yield_Strength_MPa'])
SARS.append(SAR)
return SARS
[docs]
def coupon_results_save(Excelfile_name: str, analysis_results: list):
"""
Save a list of sample analysis results into an Excel file, matching by sample name.
Parameters
----------
Excelfile_name: str
Path to the Excel file to save the results.
analysis_results : list
A list of SampleAnalysisResults objects, each containing:
- sample_name
- modulus_of_elasticity
- ultimate_tensile_strength
- yield_Strength
Notes
-----
- Assumes the Excel file has sample names in column A, starting from row 2.
- Data will be written starting from column D (fourth column).
- Overwrites the original Excel file.
"""
from openpyxl import load_workbook
wb = load_workbook(Excelfile_name)
ws = wb.worksheets[0]
# Build a dictionary for faster lookup
result_dict = {res.sample_name: res for res in analysis_results}
for row_idx, row in enumerate(ws.iter_rows(min_row=2, max_col=ws.max_column), start=2):
excel_sample_name = row[0].value
if excel_sample_name in result_dict:
analysis_result = result_dict[excel_sample_name]
values_to_write = [
analysis_result.modulus_of_elasticity,
analysis_result.ultimate_tensile_strength,
analysis_result.yield_Strength
]
for col_idx, value in enumerate(values_to_write, start=4): # D列开始
ws.cell(row=row_idx, column=col_idx, value=float(value))
wb.save(Excelfile_name)
print('The coupon test data analysis is complete.')
from dataclasses import dataclass
@dataclass
class coupon_SampleDetails:
"""
Holds basic information for a sample.
Attributes
----------
sample_name : str
Name or ID of the sample.
width : float
Width of the sample (geometric property, in consistent units).
thickness : float
Thickness of the sample (geometric property, in consistent units).
sample_file_name : str
Name of the associated data file for the sample, e.g., CSV file.
"""
sample_name: str
width: float
thickness: float
sample_file_name: str
@dataclass
class coupon_SampleAnalysisResults:
"""
Stores the analysis results for a sample after mechanical testing.
Attributes
----------
sample_name : str
Name or ID of the sample corresponding to the analysis.
modulus_of_elasticity : float
Elastic modulus of the sample (E, in GPa).
ultimate_tensile_strength : float
Maximum tensile strength of the sample (UTS, in MPa).
yield_Strength : float
Yield strength of the sample (in MPa).
"""
sample_name: str
modulus_of_elasticity: float
ultimate_tensile_strength: float
yield_Strength: float