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.
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")
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")
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")
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")
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