Tensile test analysis#
General scope#
From global point of view, test machines, sensors provide time-stamped data. Most of the time this a data are stored in a tabular shape, i.e. one line for each record with its time-stamp.
A common way to store this data is to use ‘.csv’ file. A ‘.cvs’ file looks like:
time;data1;data2;data3
0.0;12.;.001;'blue'
0.1;11.5;.002;'red'
0.2;14.2;.004;'blue'
0.3;14.2;-.004;'green'
...
...
...
The Pandas
module is a convenient way to manage this file (read and write).
In addition, metadata can be stored in this file, at the beginning of the file. In this metadata global info can be stored, as the date of the test, the name of the operator, the material, the sample type, commentary…
A total file can look like:
date : 01-02-1900
user : bob
material : Steel
length : .5
remarks : This test is awsome
time;data1;data2;data3
0.0;12.;.001;'blue'
0.1;11.5;.002;'red'
0.2;14.2;.004;'blue'
0.3;14.2;-.004;'green'
...
...
...
Session objective#
In this session you are asked to analyze data coming from the tensile test. This analysis aims at extracting the mechanical properties of the material, such as the Young modulus, mechanical strength and yield strength from the load/displacement curve.
At the end of this session, you should have a class that is able to deal with the data coming from a tensile test.
The dataset :#
Required files
It can be downloaded at this link TensileData.
It contains 4 files, the unit are s/N/mm :
Test_1.csv
Test_2.csv
Test_3.csv
Test_4.csv
# Setup
%matplotlib notebook
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
x = np.linspace(0,1,50)
plt.figure()
plt.plot(x, x**2)
plt.show()
Q1 : Load the data and plot its#
code one function that loads one test
call this function 4 times to load all data
plot all the tests on one figure
def a_function(a, b, c = 0.1):
x = a+b+c
return x
print('a_function(1,2) =', a_function(1,2))
print('a_function(1,2, c=5) =', a_function(1,2, c=5))
a_function(1,2) = 3.1
a_function(1,2, c=5) = 8
def read_data(path_to_csv):
df = pd.read_csv(path_to_csv, delimiter=';', skiprows = 2)
return df
#unit test
read_data('Test_1.csv')
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[5], line 2
1 #unit test
----> 2 read_data('Test_1.csv')
Cell In[4], line 2, in read_data(path_to_csv)
1 def read_data(path_to_csv):
----> 2 df = pd.read_csv(path_to_csv, delimiter=';', skiprows = 2)
3 return df
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/util/_decorators.py:211, in deprecate_kwarg.<locals>._deprecate_kwarg.<locals>.wrapper(*args, **kwargs)
209 else:
210 kwargs[new_arg_name] = new_arg_value
--> 211 return func(*args, **kwargs)
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/util/_decorators.py:331, in deprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper(*args, **kwargs)
325 if len(args) > num_allow_args:
326 warnings.warn(
327 msg.format(arguments=_format_argument_list(allow_args)),
328 FutureWarning,
329 stacklevel=find_stack_level(),
330 )
--> 331 return func(*args, **kwargs)
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/io/parsers/readers.py:950, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
935 kwds_defaults = _refine_defaults_read(
936 dialect,
937 delimiter,
(...)
946 defaults={"delimiter": ","},
947 )
948 kwds.update(kwds_defaults)
--> 950 return _read(filepath_or_buffer, kwds)
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/io/parsers/readers.py:605, in _read(filepath_or_buffer, kwds)
602 _validate_names(kwds.get("names", None))
604 # Create the parser.
--> 605 parser = TextFileReader(filepath_or_buffer, **kwds)
607 if chunksize or iterator:
608 return parser
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/io/parsers/readers.py:1442, in TextFileReader.__init__(self, f, engine, **kwds)
1439 self.options["has_index_names"] = kwds["has_index_names"]
1441 self.handles: IOHandles | None = None
-> 1442 self._engine = self._make_engine(f, self.engine)
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/io/parsers/readers.py:1735, in TextFileReader._make_engine(self, f, engine)
1733 if "b" not in mode:
1734 mode += "b"
-> 1735 self.handles = get_handle(
1736 f,
1737 mode,
1738 encoding=self.options.get("encoding", None),
1739 compression=self.options.get("compression", None),
1740 memory_map=self.options.get("memory_map", False),
1741 is_text=is_text,
1742 errors=self.options.get("encoding_errors", "strict"),
1743 storage_options=self.options.get("storage_options", None),
1744 )
1745 assert self.handles is not None
1746 f = self.handles.handle
File /opt/conda/envs/science/lib/python3.10/site-packages/pandas/io/common.py:856, in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)
851 elif isinstance(handle, str):
852 # Check whether the filename is to be opened in binary mode.
853 # Binary mode does not support 'encoding' and 'newline'.
854 if ioargs.encoding and "b" not in ioargs.mode:
855 # Encoding
--> 856 handle = open(
857 handle,
858 ioargs.mode,
859 encoding=ioargs.encoding,
860 errors=errors,
861 newline="",
862 )
863 else:
864 # Binary mode
865 handle = open(handle, ioargs.mode)
FileNotFoundError: [Errno 2] No such file or directory: 'Test_1.csv'
# meta data
f = open('Test_1.csv')
l = f.readline()
l0 = float(l.split('=')[-1])
l = f.readline()
S0 = float(l.split('=')[-1])
print(f'S0={S0} mm²; l0={l0} mm')
S0=11.8 mm²; l0=45.2 mm
def read_metadata(path_to_csv):
f = open(path_to_csv)
l = f.readline()
l0 = float(l.split('=')[-1])
l = f.readline()
S0 = float(l.split('=')[-1])
return S0, l0
# recupérer le nom de tout les fichier xxx.csv du dossier
import glob
cvs_list = glob.glob('*.csv')
for path2csv in cvs_list:
print(path2csv)
Test_1.csv
Test_3.csv
Test_4.csv
Test_2.csv
dfs = []
for path2csv in cvs_list:
df = read_data(path2csv)
dfs.append(df)
class TensileTest:
"""
this class is able to ....
"""
def __init__(self, csv_file):
self.csv_file = csv_file
self.df = read_data(self.csv_file)
self.S0, self.l0 =read_metadata(self.csv_file)
# CODDE HERE
def __repr__(self):
return "<tensileTest: (file: {0}, E = {1:.2} MPa)>".format(
self.csv_file, self.get_YoungModulus()
)
def get_meca_strength(self):
return 0.0
def get_YoungModulus(self):
# CODDE HERE
return 1.0
t1 = TensileTest('Test_1.csv')
t1.S0
11.8
def a_function(a, b, c = 0.1):
x = a+b+c
return x
print('a_function(1,2) =', a_function(1,2))
print('a_function(1,2, c=5) =', a_function(1,2, c=5))
a_function(1,2) = 3.1
a_function(1,2, c=5) = 8
import glob
cvs_list = glob.glob('*.csv')
for path2csv in cvs_list:
print(path2csv)
Test_1.csv
Test_3.csv
Test_4.csv
Test_2.csv
def read_data(path_to_csv="my_csv.csv"):
df = pd.read_csv(path_to_csv, delimiter=';', skiprows=2)
return df
#unit test
read_data('Test_1.csv')
Temps | Force | Course_Traverse | Ext | Largeur | |
---|---|---|---|---|---|
0 | 0.00 | -40.43579 | 0.000000 | 0.000000 | 0.000000 |
1 | 0.01 | -40.43579 | 0.000021 | 0.000000 | 0.000000 |
2 | 0.02 | -40.43579 | 0.000021 | 0.000000 | 0.000000 |
3 | 0.03 | -40.43579 | 0.000021 | 0.000000 | 0.000000 |
4 | 0.04 | -40.43579 | 0.000083 | -0.000015 | 0.000571 |
... | ... | ... | ... | ... | ... |
19154 | 191.54 | 764.01230 | 6.383542 | 5.610752 | 0.331398 |
19155 | 191.55 | 763.47190 | 6.383875 | 5.610752 | 0.331398 |
19156 | 191.56 | 763.31300 | 6.384208 | 5.610752 | 0.331398 |
19157 | 191.57 | 762.64540 | 6.384521 | 5.610752 | 0.331398 |
19158 | 191.58 | 762.56590 | 6.384750 | 5.610752 | 0.331398 |
19159 rows × 5 columns
tests = []
for path2csv in cvs_list:
df = read_data(path2csv)
tests.append(df)
plt.figure()
for df in tests:
plt.plot(df.Ext, df.Force,'.')
plt.xlabel('$\Delta L[mm]$')
plt.ylabel('$F [N]$')
plt.show()
Q2: Load the metadata#
Into the ‘.csv’ file the first 2 line provides \(S_0\) and \(l_0\) of each sample.
code a function that read this data
call this function on each test
Useful function#
Tip: split a string into 2 parts
line = "z = 143.2"
word1, word2 = line.split("=")
f = open('Test_1.csv')
l = f.readline()
l0 = float(l.split('=')[-1])
l = f.readline()
S0 = float(l.split('=')[-1])
print(f'S0={S0} mm²; l0={l0} mm')
S0=11.8 mm²; l0=45.2 mm
def read_metadata(path_to_csv="my_csv.csv"):
f = open(path_to_csv)
l = f.readline()
l0 = float(l.split('=')[-1])
l = f.readline()
S0 = float(l.split('=')[-1])
return S0, l0
#unit test
read_metadata('Test_1.csv')
(11.8, 45.2)
# Class
class TensileTest:
"""
this class is able to ....
"""
def __init__(self, csv_file="xxx"):
print("Init with file " + csv_file)
self.csv_file = csv_file
# read the data
self.df = read_data(self.csv_file)
S0, l0 = read_metadata(self.csv_file)
self.S0 = S0
self.l0 = l0
# compute stress and strain
self.df["stress"] = self.df.Force / S0
self.df["strain"] = self.df.Ext / l0
def __repr__(self):
return "<tensileTest: (file: {0}, E = {1:.2} MPa)>".format(
self.csv_file, self.get_YoungModulus()
)
def plot(self, ax=None):
E, off_set = self.get_YoungModulus()
if ax is None:
fig, ax = plt.subplots()
ax.plot(self.df.strain, self.df.stress,'.', label = self.csv_file + f'E={E/1000:0.2f} GPa')
e_max, s_max = self.get_meca_strength()
ax.plot(e_max, s_max, 'xr')
eps =np.array([0,0.003])
ax.plot(eps, E*eps+off_set, 'k')
#ax.plot(self.df.Ext, self.df.Force,'.', label = self.csv_file)
plt.legend()
return ax
def get_meca_strength(self):
id_max = self.df.stress.argmax()
return self.df.strain[id_max], self.df.stress[id_max]
def get_YoungModulus(self, coef = 3.):
e_max, s_max = self.get_meca_strength()
df_e = self.df[(self.df.stress<s_max/coef) & (self.df.strain<e_max) ]
fit = np.polyfit(df_e.strain, df_e.stress,1)
E = fit[0]
off_set = fit[1]
return E, off_set
tests = []
for path2csv in cvs_list:
t = TensileTest(path2csv)
tests.append(t)
ax= None
for t in tests:
ax = t.plot(ax=ax)
Init with file Test_1.csv
Init with file Test_3.csv
Init with file Test_4.csv
Init with file Test_2.csv
Q3: Stress/Strain curve#
Knowing that: \( \sigma_n = \frac{F}{S0} \) and \( \epsilon_n = \frac{\Delta L}{L_0} \):
Code a function that computes the strain and stress data.
Apply the function to all tests.
Plot this data for all test on a graph.
Q4: Mechanilca strength#
Code a function that computes the mechanical strength.
Apply the function to all tests.
Plot this data for all test on a graph.
Q5: Young modulus measurements#
Code a function that computes the young modulus on each test.
Apply the function to all tests.
Propose a plot to illustrate the young modulus measurements
def get_young_modulus(df):
E = 0.0
return E
Q6: all together in a class#
Include all the function you have coded in a class.
Below is a simple template of the class that you should build :
class TensileTest:
"""
this class is able to ....
"""
def __init__(self, csv_file="xxx"):
self.csv_file = csv_file
# CODDE HERE
def __repr__(self):
return "<tensileTest: (file: {0}, E = {1:.2} MPa)>".format(
self.csv_file, self.get_YoungModulus()
)
def get_meca_strength(self):
return 0.0
def get_YoungModulus(self):
# CODDE HERE
return 1.0
test1 = TensileTest("my_csv.csv") # call the __init__ function
test1 # call the __repr__ function
Q7: The plastic side of the force#
compute the plastique strain (\(\varepsilon_p = \varepsilon - \dfrac{\sigma}{E} \))
fit the stress / plastic strain curve with a power plastic law (\(\sigma = \sigma_0 + K {\varepsilon_p}^n \))