Example 3 - CT-specimen with elastic-plastic material behaviour

Example 3 shows how ConForce can evaluate configurational forces in an elastic-plastic model with a crack. Small strain plasticity is assumed. Import packages and change to the directory where the *.inp file is located.

>>> import os
>>> import json
>>> import matplotlib.pyplot as plt
>>> HOME_DIR = os.path.abspath(".")
>>> os.chdir(__file__ + "/..")

Problem description

Kolednik [1] suggested the following compact tension (CT) test.

scheme

Geometric dimensions:

>>> w = 50  # mm
>>> a = 27  # mm
>>> b = 25  # mm
>>> h = 60  # mm

Material properties:

Schoengrundner [2] provides material data for an elastic-plastic behaviour with isotropic hardening. Region D is modeled without plasticty to prevent large deformations in the vicinity of constraint nodes.

>>> nu = 0.3
>>> E = 200_000 # MPa
>>> yield_stress_over_plastic_strain = np.array([
...     [    270.,         0.],
...     [ 273.052, 0.00109256],
...     [ 300.462, 0.00837633],
...     [ 329.014,  0.0198685],
...     [ 367.845,  0.0392919],
...     [ 394.113,  0.0580678],
...     [ 415.812,   0.082347],
...     [ 440.557,   0.116176],
...     [ 456.166,   0.145311],
...     [ 472.155,   0.181244],
...     [ 578.368,   0.399433],
...     [   1460.,         3.],
... ])
>>> fig = plot_stress_strain(E, yield_stress_over_plastic_strain)
>>> save_fig(fig, "example_3_images/01_stress_strain.png")
stress strain curve

Applied displacements:

>>> u_max = 0.5  # mm

Literature of crack-driving force

Kolednik [1] investigates this problem. The non-linear elastic J-integrals (j_nl_el_*) define the energy density as sum of elastic and plastic energy densities (e=SENER+PENER). The incremental plasticity J-integrals (j_inc_pl_*) define the energy density only as the elastic energy densities (e=SENER) and ignore the plastic contribution.

>>> literature_data = {
...     "u": [0.10, 0.25, 0.50],  # mm
...     "j_nl_el_contour_tip": [1.513, 8.45, 31.80],  # mJ/mm²
...     "j_inc_pl_contour_tip": [1.736, 10.15, 38.58],  # mJ/mm²
...     "j_nl_el_contour_1": [2.132, 11.92, 44.72],  # mJ/mm²
...     "j_inc_pl_contour_1": [2.271, 13.90, 51.18],  # mJ/mm²
...     "j_nl_el_contour_3": [2.239, 12.88, 46.36],  # mJ/mm²
...     "j_inc_pl_contour_3": [2.239, 13.05, 47.42],  # mJ/mm²
...     "j_nl_el_contour_7": [2.246, 13.67, 47.03],  # mJ/mm²
...     "j_inc_pl_contour_7": [2.246, 13.20, 47.35],  # mJ/mm²
...     "j_nl_el_contour_25": [2.247, 13.56, 48.13],  # mJ/mm²
...     "j_inc_pl_contour_25": [2.247, 13.56, 47.72],  # mJ/mm²
...     "j_nl_el_contour_far": [2.247, 13.56, 48.21],  # mJ/mm²
...     "j_inc_pl_contour_far": [2.247, 13.10, 30.40],  # mJ/mm²
... }

Simulation

The input file is located in the example folder. The model evaluation is scripted and there is no need to open the ConForce-plugin manually. First, call the Abaqus script (example_3_abaqus_script). The script simulates the *.inp file and writes a results.json file.

>>> import subprocess
>>> subprocess.call("abaqus cae noGUI=example_3_abaqus_script.py", shell=True)
0

After the simulation, the result file is loaded to fetch the reaction force load. This force is needed to reach the defined displacement u.

>>> with open("results.json", "r", encoding="UTF-8") as fh:
...     results = json.load(fh)
>>> load = np.array(results["reaction_force"])[:, 1]
>>> u = np.array(results["u"])[:, 1]

With the obtained results the force-displacement curve is plotted.

>>> fig = plot_force_displacement(u, load)
>>> save_fig(fig, "example_3_images/02_force_displacement.png")
force displacement

Abaqus J-Integral

Abaqus computes the J-integral using the virtual crack extension method by Parks [3]. This J-integral is compared to the literature values of Kolednik [1] that are marked by “x”.

>>> fig = compare_abaqus_j_with_literature(results, literature_data)
>>> save_fig(fig, "example_3_images/03_j_integral.png")
comparison of J-Integral with literature

The Abaqus J-integrals agree well with the literature values.

Configurational forces

Furthermore, the x-components of the configurational forces are summed up in regions sorrounded by the contours. This corresponds to the J-integral with a negative sign. There is good agreement between the ConForce and the literature values.

>>> fig = compare_conforce_cfx_with_literature(results, literature_data)
>>> save_fig(fig, "example_3_images/04_negative_cfx.png")
comparison of configurational forces with literature

References

After the evaluation, the directory is changed to the home directory.

>>> os.chdir(HOME_DIR)

Abaqus script

"""
This is an Abaqus script for the example in :py:mod:`example_3_CT_specimen_elastic_plastic`.

To simulated and write a result.json file open a shell and type:

.. code-block:: console

    abaqus cae noGUI="example_3_abaqus_script.py"

"""
from __future__ import print_function

import sys
import os
import json

import numpy as np

import abaqus as abq

# append path to the folder where conforce_abq is located and import conforce_abq afterward
sys.path.append(os.path.abspath("../.."))
from conforce_abq.main import apply


def simulate_and_save_results(inp_file_path="CT_specimen_CPE4.inp"):
    """
    This function:
    - simulate the input file
    - computes the configurational forces in the odb
    - writes results to a file called "results.json"

    :param inp_file_path: str, path to input file to simulate
    :return: updated Odb with configurational forces as field output
    """

    # create a job from the input file, start the simulation and wait until the simulation completed
    job_name = os.path.basename(inp_file_path).split(".")[0]
    job = abq.mdb.JobFromInputFile(
        name=job_name,
        inputFileName=inp_file_path
    )
    job.submit()
    job.waitForCompletion()

    # open odb with **readOnly=False**
    odb = abq.session.openOdb(job_name + ".odb", readOnly=False)

    # apply conforce plugin and request configurational forces as field output
    odb = apply(
        odb,
        request_CF=True,
        CF_name="CONF_FORCE_EL",
        method="mbf",
        e_expression="SENER"
    )
    odb = apply(
        odb,
        request_CF=True,
        CF_name="CONF_FORCE_EL_PL",
        method="mbf",
        e_expression="SENER+PENER"
    )

    # put all results in this dictionary
    results = dict()

    # extract CF field output
    for CF_name in ["CONF_FORCE_EL", "CONF_FORCE_EL_PL"]:
        CF_results = dict()
        results[CF_name + "_at_frame"] = CF_results
        for frame in odb.steps['Loading'].frames:
            # extract CF field output
            fo_CF_EL = frame.fieldOutputs[CF_name]

            for set_name, region in odb.rootAssembly.nodeSets.items():
                fo_CF_in_region = fo_CF_EL.getSubset(
                    region=region
                )

                if set_name in CF_results.keys():
                    CF_result_for_set = CF_results[set_name]
                else:
                    CF_result_for_set = CF_results[set_name] = list()

                CF_result_for_set.append(
                    np.sum([
                        value.data
                        for value in fo_CF_in_region.values
                    ], axis=0).tolist()
                )

    # extract history outputs
    (ho_assembly, ho_J_integral, ho_bc_lower, ho_bc_upper) = [
        history_region.historyOutputs
        for history_region in odb.steps['Loading'].historyRegions.values()
    ]

    # extract J-Integrals at the last frame
    results["J"] = {
        key: np.array(value.data).tolist()
        for key, value in ho_J_integral.items()
    }

    # extract reaction forces/displacements
    results["reaction_force"] = np.array(ho_bc_upper['RF2'].data).tolist()
    results["u"] = np.array(ho_bc_upper['U2'].data).tolist()

    # save results
    with open("results.json", "w") as fh:
        json.dump(results, fh, indent=4)

    return odb


if __name__ == '__main__':
    # delegate output to console
    stdout_default, stderr_default = sys.stdout, sys.stderr
    sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__
    try:
        # simulate
        simulate_and_save_results()

    finally:
        # restore default output streams
        sys.stdout, sys.stderr = stdout_default, stderr_default