Skip to content

5.1 Fracture Analysis

Eric Breitbarth edited this page Apr 9, 2024 · 2 revisions

CrackPy's Fracture Analysis module (crackpy.fracture_analysis) features tools for the automatic determination of stress intensity factors (SIFs) like

from full field displacements and strains obtained by digital image correlation (DIC).

Single nodemap

We start with an example of how to use the Fracture Analysis module to calculate stress intensity factors for a single FE-simulated nodemap.

First, we need to import

# imports
import os

from matplotlib import pyplot as plt

from crackpy.fracture_analysis.analysis import FractureAnalysis
from crackpy.fracture_analysis.data_processing import InputData, CrackTipInfo
from crackpy.fracture_analysis.line_integration import IntegralProperties
from crackpy.fracture_analysis.optimization import OptimizationProperties
from crackpy.fracture_analysis.plot import PlotSettings, Plotter
from crackpy.fracture_analysis.write import OutputWriter
from crackpy.fracture_analysis.read import OutputReader
from crackpy.structure_elements.data_files import Nodemap
from crackpy.structure_elements.material import Material

and specify the input data path as well as the output path for the results

# paths
NODEMAP_FILENAME = 'File_F_10000.0_a_0.5_B_200.0_H_200.0.txt'
NODEMAP_FOLDER = os.path.join('..', '..', 'test_data', 'simulations', 'Nodemaps')
OUT_FOLDER = 'Fracture_Analysis_FE_results'

We use a linear elastic material law (Hooke's law)

# material properties
material = Material(E=72000, nu_xy=0.33, sig_yield=350)

to calculate the stresses. For this, we use a linear elastic material model (Hooke's law). Per default, the plane stress assumtion is used.

The fracture analysis needs three main input properties:

  1. Crack information: crack tip position and crack angle is needed for the integral evaluation and optimization/fitting methods
  2. Integral properties: specifications about the integration paths and which Williams terms to calculate
  3. Optimization properties: specifications about the fitting domain and which Williams terms to calculate
# integration
int_props = IntegralProperties(
    number_of_paths=10,
    number_of_nodes=100,

    bottom_offset=-0,  # should not be zero for dic results
    top_offset=0,  # should not be zero for dic results

    integral_size_left=-5,
    integral_size_right=5,
    integral_size_top=5,
    integral_size_bottom=-5,

    paths_distance_top=0.5,
    paths_distance_left=0.5,
    paths_distance_right=0.5,
    paths_distance_bottom=0.5,

    mask_tolerance=2,

    buckner_williams_terms=[-1, 1, 2, 3, 4, 5]
)

# optimization
opt_props = OptimizationProperties(
    angle_gap=10,
    min_radius=5,
    max_radius=10,
    tick_size=0.01,
    terms=[-1, 0, 1, 2, 3, 4, 5]
)

# crack tip
ct = CrackTipInfo(50, 0, 0, 'right')

Then, we read and preprocess the data and perform a translation of the coordinate system to the crack tip position, i.e. transforming the displacement data.

# preprocess data
nodemap = Nodemap(name=NODEMAP_FILENAME, folder=NODEMAP_FOLDER)
input_data = InputData(nodemap=nodemap)
input_data.calc_stresses(material)
input_data.transform_data(ct.crack_tip_x, ct.crack_tip_y, ct.crack_tip_angle)

This transformation is necessary to assure that the crack is standardized at coordinate $(x,y)=(0, 0)$ with an angle close to 0. Now, we run the fracture analysis

# fracture analysis
analysis = FractureAnalysis(
    material=Material(),
    nodemap=nodemap,
    data=input_data,
    crack_tip_info=ct,
    integral_properties=int_props,
    optimization_properties=opt_props
)
analysis.run()

In order to visualize the results, we can use the plot-module

# set colormap
plt.rcParams['image.cmap'] = 'coolwarm'
plt.rcParams['figure.dpi'] = 100

# plot
plot_sets = PlotSettings(background='eps_vm')
plotter = Plotter(path=os.path.join(OUT_FOLDER, 'plots'), fracture_analysis=analysis, plot_sets=plot_sets)
plotter.plot()

and the write module to save the results in a text file

# write
writer = OutputWriter(path=os.path.join(OUT_FOLDER, 'results'), fracture_analysis=analysis)
writer.write_header()
writer.write_results()
writer.write_json(path=os.path.join(OUT_FOLDER, 'json'))

This is the results output plot: example output plot

Whole experiment

In the following, we apply the Fracture Analysis module to calculate SIFs for DIC-nodemaps of a whole experiment (e.g. fatigue crack growth experiment). This pipeline consists of three parts

  1. Preparations
  2. Crack detection pipeline (see also Crack detection)
  3. Fracture analysis pipeline

1. Preparations

import os

from matplotlib import pyplot as plt

from crackpy.fracture_analysis.line_integration import IntegralProperties
from crackpy.fracture_analysis.optimization import OptimizationProperties
from crackpy.fracture_analysis.read import OutputReader
from crackpy.crack_detection.model import get_model
from crackpy.crack_detection.pipeline.pipeline import CrackDetectionSetup, CrackDetectionPipeline
from crackpy.fracture_analysis.pipeline import FractureAnalysisPipeline
from crackpy.fracture_analysis.plot import PlotSettings
from crackpy.structure_elements.material import Material

# Paths
DATA_PATH = os.path.join('..', '..',
                         'test_data', 'crack_detection', 'Nodemaps')
OUT_FOLDER = 'Fracture_Analysis_Pipeline_results'

# matplotlib settings
plt.rcParams['image.cmap'] = 'coolwarm'
plt.rcParams['figure.dpi'] = 300

2. Crack detection

# crack detectors
tip_detector = get_model('ParallelNets')
path_detector = get_model('UNetPath')

# Detection setup
det_setup = CrackDetectionSetup(
    specimen_size=160,
    sides=['right'],
    detection_window_size=50,
    detection_boundary=(0, 70, -35, 35),
    start_offset=(0, 0)
)

# pipeline
cd_pipeline = CrackDetectionPipeline(
    data_path=DATA_PATH,
    output_path=OUT_FOLDER,
    tip_detector_model=tip_detector,
    path_detector_model=path_detector,
    setup=det_setup
)
cd_pipeline.filter_detection_stages(max_force=15000)
cd_pipeline.run_detection()
cd_pipeline.assign_remaining_stages()
cd_pipeline.write_results('crack_info_by_nodemap.txt')

The crack detection output is written to a file named "crack_info_by_nodemap.txt". This file is used as input for the fracture analysis pipeline.

3. Fracture analysis pipeline

We first need to specify integral properties and optimization properties. If either of these is set to None, it will be skipped during fracture analysis.

int_props = IntegralProperties(
    number_of_paths=10,
    number_of_nodes=100,

    integral_size_left=-5,
    integral_size_right=5,
    integral_size_top=5,
    integral_size_bottom=-5,

    paths_distance_top=0.5,
    paths_distance_left=0.5,
    paths_distance_right=0.5,
    paths_distance_bottom=0.5,

    mask_tolerance=2,

    buckner_williams_terms=[-1, 1, 2, 3, 4, 5]
)

opt_props = OptimizationProperties(
    angle_gap=20,
    min_radius=5,
    max_radius=10,
    tick_size=0.01,
    terms=[-1, 0, 1, 2, 3, 4, 5]
)

Remark: Notice that the top and bottom offset of the integral properties is not defined yet. It can be set automatically using the method FractureAnalysisPipeline.find_integral_props()!

Then, we define the material properties and plot settings:

material = Material(E=72000, nu_xy=0.33, sig_yield=350)
plot_sets = PlotSettings(background='eps_vm', min_value=0, max_value=0.0068, extend='max')

We initialize the fracture analysis pipeline

fa_pipeline = FractureAnalysisPipeline(
    material=material,
    nodemap_path=DATA_PATH,
    input_file=os.path.join(OUT_FOLDER, 'crack_info_by_nodemap.txt'),
    output_path=OUT_FOLDER,
    optimization_properties=opt_props,
    integral_properties=int_props,
    plot_sets=plot_sets
)

Then, we use the automatic integral path finding algorithm to set the integral paths for each nodemap. Make sure to run the find_max_force_stages first.

fa_pipeline.find_max_force_stages(max_force=15000)
fa_pipeline.find_integral_props()

Remark: The find_integral_props() method is a BETA version. Make sure to check if the integral paths are detected correctly!

Alternatively, the same integral properties for each nodemap can be set manually. For example,

int_props = IntegralProperties(
    number_of_paths=10,
    number_of_nodes=100,

    top_offset=1,
    bottom_offset=-1

    integral_size_left=-5,
    integral_size_right=5,
    integral_size_top=5,
    integral_size_bottom=-5,

    paths_distance_top=0.5,
    paths_distance_left=0.5,
    paths_distance_right=0.5,
    paths_distance_bottom=0.5,

    mask_tolerance=2,

    buckner_williams_terms=[-1, 1, 2, 3, 4, 5]
)
fa_pipeline.set_integral_props_manually(int_props)

The pipeline can now be run on multiple kernels

fa_pipeline.run(num_of_kernels=10)

4. Output reader and CSV writer

The fracture analysis results saved in the txt-files folder can postprocessed into a single CSV file using our OutputReader-class. Here is an example:

# Read results and write into CSV file
reader = OutputReader()
fa_output_path = os.path.join(OUT_FOLDER, 'txt-files')

files = os.listdir(fa_output_path)
list_of_tags = ["CJP_results", "Williams_fit_results", "SIFs_integral", "Bueckner_Chen_integral",
                "Path_SIFs", "Path_Williams_a_n", "Path_Williams_b_n"]
for file in files:
    for tag in list_of_tags:
        reader.read_tag_data(path=fa_output_path, filename=file, tag=tag)

reader.make_csv_from_results(files="all", output_path=OUT_FOLDER, output_filename='results.csv')
reader.make_csv_from_results(files="all", filter_condition={'Force': (14900, 15100)},
                             output_path=OUT_FOLDER, output_filename='results_maxforce.csv')

The final output folder structure looks like this:

Fracture_Analysis_Pipeline_results
└── plots
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_52_right.png
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_53_right.png
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_54_right.png
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_55_right.png
└── txt-files
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_52_right_Output.txt
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_53_right_Output.txt
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_54_right_Output.txt
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_55_right_Output.txt
└── json-files
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_52_right_Output.json
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_53_right_Output.json
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_54_right_Output.json
    └── Dummy2_WPXXX_DummyVersuch_2_dic_results_1_55_right_Output.json
└── FAT_Input.txt
└── results.csv