Basic postprocessing with IndenToolBox

In this example, we’ll look at tests carried out on fused quartz samples and measure their modulus.

Important: run the pre_processing.ipynb file before.

%matplotlib widget
import indentoolbox as itb
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

pd.options.display.float_format = "{:.5e}".format

Setup

root_path = "./FusedQuartz_batch"
batch = itb.core.Batch.load(root_path)
batch
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[2], line 2
      1 root_path = "./FusedQuartz_batch"
----> 2 batch = itb.core.Batch.load(root_path)
      3 batch

File ~/checkouts/readthedocs.org/user_builds/indentoolbox/envs/latest/lib/python3.11/site-packages/indentoolbox/core.py:765, in Batch.load(cls, root_path)
    752 @classmethod
    753 def load(cls, root_path):
    754     """
    755     Load a `Batch` object from the specified root path.
    756 
   (...)
    763     :return: A `Batch` object loaded from the files.
    764     """
--> 765     batch_data = pd.read_csv(root_path + ".csv")
    766     dic = toml.load(open(root_path + ".toml"))
    767     batch_data = pd.read_csv(root_path + ".csv")

File ~/checkouts/readthedocs.org/user_builds/indentoolbox/envs/latest/lib/python3.11/site-packages/pandas/io/parsers/readers.py:912, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, 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, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
    899 kwds_defaults = _refine_defaults_read(
    900     dialect,
    901     delimiter,
   (...)
    908     dtype_backend=dtype_backend,
    909 )
    910 kwds.update(kwds_defaults)
--> 912 return _read(filepath_or_buffer, kwds)

File ~/checkouts/readthedocs.org/user_builds/indentoolbox/envs/latest/lib/python3.11/site-packages/pandas/io/parsers/readers.py:577, in _read(filepath_or_buffer, kwds)
    574 _validate_names(kwds.get("names", None))
    576 # Create the parser.
--> 577 parser = TextFileReader(filepath_or_buffer, **kwds)
    579 if chunksize or iterator:
    580     return parser

File ~/checkouts/readthedocs.org/user_builds/indentoolbox/envs/latest/lib/python3.11/site-packages/pandas/io/parsers/readers.py:1407, in TextFileReader.__init__(self, f, engine, **kwds)
   1404     self.options["has_index_names"] = kwds["has_index_names"]
   1406 self.handles: IOHandles | None = None
-> 1407 self._engine = self._make_engine(f, self.engine)

File ~/checkouts/readthedocs.org/user_builds/indentoolbox/envs/latest/lib/python3.11/site-packages/pandas/io/parsers/readers.py:1661, in TextFileReader._make_engine(self, f, engine)
   1659     if "b" not in mode:
   1660         mode += "b"
-> 1661 self.handles = get_handle(
   1662     f,
   1663     mode,
   1664     encoding=self.options.get("encoding", None),
   1665     compression=self.options.get("compression", None),
   1666     memory_map=self.options.get("memory_map", False),
   1667     is_text=is_text,
   1668     errors=self.options.get("encoding_errors", "strict"),
   1669     storage_options=self.options.get("storage_options", None),
   1670 )
   1671 assert self.handles is not None
   1672 f = self.handles.handle

File ~/checkouts/readthedocs.org/user_builds/indentoolbox/envs/latest/lib/python3.11/site-packages/pandas/io/common.py:859, in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)
    854 elif isinstance(handle, str):
    855     # Check whether the filename is to be opened in binary mode.
    856     # Binary mode does not support 'encoding' and 'newline'.
    857     if ioargs.encoding and "b" not in ioargs.mode:
    858         # Encoding
--> 859         handle = open(
    860             handle,
    861             ioargs.mode,
    862             encoding=ioargs.encoding,
    863             errors=errors,
    864             newline="",
    865         )
    866     else:
    867         # Binary mode
    868         handle = open(handle, ioargs.mode)

FileNotFoundError: [Errno 2] No such file or directory: './FusedQuartz_batch.csv'

First look at the data

# THE TIP
batch.tip
# TEST DATA
print(batch.tests[0].data)
batch.collect_steps(0).data

Basic Plots

plot_reject = False
colors = "rgb"
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
tests = batch.tests
Ntests = len(tests)
for Nt in range(Ntests):
    test = tests[Nt]
    if plot_reject == False and test.reject == False:
        test_data = test.data
        test_structure = [step.kind() for step in test.steps]
        for step_id, step_data in test_data.groupby("step"):
            ax.plot(
                step_data.disp * 1.0e9,
                step_data.force * 1.0e6,
                "-",
                lw=1.0,
                color=colors[step_id%len(colors)],
            )
            

ax.grid()
ax.set_xlabel("Displacement, $h$ [nm]")
ax.set_ylabel("Force, $P$ [µN]")
#plt.savefig("experimental_batch.png")
plt.show()

print(test_structure)

Post-processing

In this section, we post-process our tests to extract basic data from the various steps. Indentoolbox can be used to collectively process one step of data from all trials and export the result as a dataframe. This is a powerful feature for batch processing, as is often the case with indentation.

First, we focus on the parabolic loading step. Please note that:

  • The method collect_steps allows us to collect every occurrence of the same step in every test.

  • The method parabolic_fit performs a parabolic fit on every step.

loading_data = batch.collect_steps(0).parabolic_fit(displim = 50.e-9, htrunc = batch.tip.htrunc)
loading_data.index = loading_data.index.droplevel(1) #
print(loading_data)

We then turn our attention to the unloading step:

unloading_data = batch.collect_steps(2).unloading_fit()
unloading_data.index = unloading_data.index.droplevel(1)
print(unloading_data)

And we apply the method of Oliver and Pharr to deduce the indentation modulus and then the Young’s modulus.

OP = itb.processing.OliverPharr(data = unloading_data, tip = batch.tip)
print(OP)
nu_samp = 0.17
OP["Esamp"] = OP.Eeqsamp * (1. - nu_samp**2)
print(OP)

Reverse analysis

In this section, we use several inverse analysis algorithms to retrieve the mechanical properties of the material under study. As this material is fused quartz, these methods are unsuitable for crystalline metals. See it as a demonstration, but disregard the results.

test_id = 0
C = loading_data.loc[test_id, "C"]
S = unloading_data.loc[test_id, "S"]
hm = unloading_data.loc[test_id, "hm"]
Pm = loading_data.loc[test_id, "Pm"]
hf = unloading_data.loc[test_id, "hf"]
E_ind = batch.tip.young_modulus
nu_ind = batch.tip.poisson_coefficient
Wrev = unloading_data.loc[test_id, "W"]
Wtot = loading_data.loc[test_id, "W"]
Wirr = Wtot + Wrev
Wfrac = Wirr/Wtot
#Wfrac = 0.9
gian99 = pd.DataFrame(itb.processing.GIAN99(hm = hm, hf = hf, S = S, C = C, nu = nu_samp, E_ind = E_ind, nu_ind = nu_ind))
print(gian99)
dao01 = pd.DataFrame(itb.processing.DAO01(S=S,C=C,Pm=Pm,Wfrac=Wfrac,hm=hm,hf=hf))
print(dao01)
casals05 = pd.DataFrame(itb.processing.CASA05(hm=hm, hf=hf, Pm=Pm, S=S, C=C))
print(casals05)