Source code for conforce_gen.one_element_runner
"""
This module contains methods to simulate one element models.
"""
import io
import os
import subprocess
import json
import numpy as np
[docs]def generate_abaqus_input(
element_type, X_at_nodes, U_at_nodes,
E: float = 1000., nu: float = 0.3,
face_count: int = 0
):
r"""
Generate a model containing one element of the given element type
with the given nodal coordinates and nodal displacements.
The element of the given element type has d=2 or d=3 dimensions and n nodes.
:param element_type: str, name of the element type
:param X_at_nodes: array of shape (n, d) containing nodal coordinates
:param U_at_nodes: array of shape (n, d) containing nodal displacements
:param E: Young's modulus
:param nu: Poisson's ratio
:param face_count: number of faces an element has. For each face a surface set is generated.
:return: str, valid abaqus input format
"""
new_line = "\n"
# node block
buffer = io.StringIO()
X_at_nodes = np.asarray(X_at_nodes, dtype=float)
rows, columns = X_at_nodes.shape
node_ids = np.arange(1, rows+1).reshape((-1, 1))
node_set_names = [f"NODE_SET_{node_id}" for node_id in node_ids.flat]
node_data = np.column_stack([node_ids.astype(float), X_at_nodes])
np.savetxt(buffer, node_data, fmt=["%5d"] + ["%10.16g"]*columns, delimiter=",")
buffer.seek(0)
return f"""*Heading
** Generated abaqus input file
*Preprint, echo=NO, model=NO, history=NO, contact=NO
**
** PART
**
*Part, name=PART-1
*Node
{buffer.read()}**
*Element, type={element_type}
1, {', '.join([str(node_id) for node_id in node_ids.flat])}
*Elset, elset=ELEMENT_SET
1,
*Solid Section, elset=ELEMENT_SET, material=MATERIAL-1
,
*End Part
**
** ASSEMBLY
**
*Assembly, name=Assembly
**
*Instance, name=PART-1-1, part=PART-1
*End Instance
**
{new_line.join([
f'*Nset, nset={set_name}, instance=PART-1-1' + new_line +
f'{node_id},'
for set_name, node_id in zip(node_set_names, node_ids.flat)
])}
*Elset, elset=ELEMENT_SET, instance=PART-1-1
1,
**{new_line.join([""] + [
f'*Surface, type=ELEMENT, name=SURFACE-{face_id+1}{new_line}'
f'ELEMENT_SET, S{face_id+1}'
for face_id in range(face_count)
])}
*End Assembly
**
** MATERIAL
**
*Material, name=MATERIAL-1
*Elastic
{E}, {nu}
**
** STEP
**
*Step, name=STEP-1, nlgeom=NO
*Static
1., 1., 1e-05, 1.
**
** BOUNDARY CONDITIONS
**
{
new_line.join([
new_line.join([
f"*Boundary{new_line}"
f"{set_name}, {axis}, {axis}, {U}"
for U, axis in zip(U_at_node, [1, 2, 3])
])
for set_name, U_at_node in zip(node_set_names, U_at_nodes)
])}
**
** OUTPUT REQUESTS
**
** FIELD OUTPUT
**
*Output, field
*Node Output
COORD, RF, U
*Element Output, directions=YES
COORD, S, E, SENER, ESEDEN, EVOL, IVOL
**
** HISTORY OUTPUT
**
*Output, history, variable=PRESELECT
*End Step
"""
[docs]def simulate_one_element(X_at_nodes, U_at_nodes, element_type: str, load_name: str, folder: str,
E: float = 1000., nu: float = 0.3, face_count: int = 0) -> dict:
"""
Generate and simulate a one element model.
The input is generated using the method :py:func`generate_abaqus_input`.
For the simulation the script `one_element_script.py` is executed in Abaqus.
The model is simulated in the given folder.
After the simulation has completed successfully,
a result file in the json format is writen into the folder.
This result file contains:
- model data like the strain energy (ALLSE),
- element data like the element volume (EVOL),
- nodal data like the nodal coordinates (COORD),
- integration point data like the integration point coordinates (COORD),
.. note::
If the result file already exists, no simulation is performed.
Instead, the content of the result file is returned.
**Examples**
The result contains the strain energy (ALLSE),
reaction forces (RF), strain energy densities (SENER) and
the element volume (EVOL)
>>> result = simulate_one_element(
... X_at_nodes=np.array([[0, 0], [1, 0], [0.5, 1]]),
... U_at_nodes=np.array([[0, 0], [0, 0], [0, 1]]),
... element_type="CPE3",
... load_name="triangle_tension",
... folder="res/tests/triangle_tension"
... )
>>> float(np.around(result["model"]["ALLSE"], 3))
336.538
>>> np.around(result["nodes"]["RF"], 3)
array([[-288.462, -336.538],
[ 288.462, -336.538],
[ -0. , 673.077]])
>>> np.around(result["integration_points"]["SENER"], 3)
array([[673.077]])
>>> float(np.around(result["element"]["EVOL"], 3))
0.5
:param X_at_nodes: array of shape (n, d) containing nodal coordinates
:param U_at_nodes: array of shape (n, d) containing nodal displacements
:param element_type: str, name of the element type
:param load_name: str, a valid load name in abaqus
:param folder: str, simulation folder
:param E: Young's modulus
:param nu: Poisson's ratio
:param face_count: number of faces an element has. For each face a surface set is generated.
:return: dict, containing the data of the result file
"""
job_name = f"{element_type}_{load_name}"
folder = os.path.abspath(folder)
file = f"{folder}/{job_name}.inp"
if not os.path.exists(folder):
os.makedirs(folder)
result_file_path = f"{folder}/{job_name}_result.json"
if not os.path.exists(result_file_path):
with open(file, "w", encoding="utf-8") as f:
f.write(generate_abaqus_input(
element_type=element_type,
X_at_nodes=X_at_nodes,
U_at_nodes=U_at_nodes,
E=E,
nu=nu,
face_count=face_count
))
subprocess.call([
"abaqus", "cae", "noGUI=conforce_abq/one_element_script.py", "--", file
], shell=True if os.name == 'nt' else False)
for file in {"abaqus.rpy"} | {
f"{folder}/{job_name}.{extension}"
for extension
in ["com", "dat", "inp", "log", "msg", "odb", "prt", "sim", "sta"]
}:
if os.path.exists(file):
pass # os.remove(file)
with open(result_file_path, "r", encoding="utf-8") as fh:
return json.load(fh)