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 \))