### ===================================================================================================================
### FUNCTION: Create the files required for the analysis in ABAQUS
### ===================================================================================================================
# Copyright ©VIIA 2024
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from __future__ import annotations
import re
import os
import shutil
import copy
from pathlib import Path
from typing import TYPE_CHECKING, List, Union
# References for functions and classes in the rhdhv_fem package
from rhdhv_fem import fem_ABAQUSutils
from rhdhv_fem.fem_tools import fem_copy_file
from rhdhv_fem.analyses import Analysis
# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
from viiapackage.viiaStatus import ViiaProject
### ===================================================================================================================
### 2. Function viia_create_analysis_files_abaqus
### ===================================================================================================================
[docs]def viia_create_analysis_files_abaqus(
project: ViiaProject, analysis: Analysis, abaqus_inp_location: Union[Path, str] = None) -> List[Path]:
"""
Function to create or collect the files required to perform the analysis. These files are collected in the current
analysis folder.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- analysis (obj): Object reference of the analysis for which the files are created / collected.
- abaqus_inp_location (Path or str): Location of the ABAQUS inp-file. By default the inp-file is retrieved from
the workfolder, based on VIIA naming convention.
Output:
- Returns list of all created files in the 'current analysis folder'.
"""
# Collect the files
files = list()
# Prepare cae-file
files.append(fem_copy_file(
file_to_copy=project.workfolder_location / f'{project.name}.cae',
destination_folder=project.current_analysis_folder))
# Prepare jnl-file
files.append(fem_copy_file(
file_to_copy=project.workfolder_location / f'{project.name}.jnl',
destination_folder=project.current_analysis_folder))
# Prepare envelop_results json-file
files.append(fem_copy_file(
file_to_copy=project.workfolder_location / f'envelop_results.json',
destination_folder=project.current_analysis_folder))
# Prepare inp-file
if abaqus_inp_location:
if isinstance(abaqus_inp_location, str):
abaqus_inp_location = Path(abaqus_inp_location)
files.append(fem_copy_file(
file_to_copy=abaqus_inp_location,
destination_folder=project.current_analysis_folder))
else:
input_name = analysis.name.replace(' - ', '-').replace(' ', '_')
files.append(fem_copy_file(
file_to_copy=project.workfolder_location / f'{input_name}.inp',
destination_folder=project.current_analysis_folder))
# Copy material include file from software-specific module in viiaPackage
software_specific_folder = project.viia_settings.project_specific_package_location / 'software_specific' / 'ABAQUS'
files.append(fem_copy_file(
file_to_copy=software_specific_folder / 'includes' / f'materials.include',
destination_folder=project.current_analysis_folder))
# Copy abaqus_v6 env-file from software-specific module in viiaPackage
files.append(fem_copy_file(
file_to_copy=software_specific_folder / f'abaqus_v6.env',
destination_folder=project.current_analysis_folder))
# Copy subroutines from software-specific module in viiaPackage
# ABAQUS version needs to be checked, by default, it is using abaqus_version = 2020
try:
with open(Path(shutil.which('abaqus')), 'r') as file:
lines = file.readlines()
for line in lines:
if '2020' in line:
abaqus_version = 2020
elif '2022' in line:
abaqus_version = 2022
except:
abaqus_version = 2020
files.append(shutil.copytree(
src=software_specific_folder / 'subroutines' / 'element_deletion' / f'abq{abaqus_version}',
dst=project.current_analysis_folder / 'subroutines'))
# Copy postprocessing scripts from rhdhv_fem/abaqus_utils
postprocessing_path = project.current_analysis_folder / 'postprocessing'
if not os.path.exists(postprocessing_path):
os.makedirs(postprocessing_path)
fem_path = Path(fem_ABAQUSutils.__file__).parent
for py_file in ['write_abaqus_tb_file.py', 'output_storage.py', 'postprocessor.py', 'read_section_forces.py']:
files.append(fem_copy_file(
file_to_copy=fem_path / 'abaqus_utils' / 'abaqus_specific' / py_file,
destination_folder=postprocessing_path))
# Check required files for running analysis on server
folder = project.current_analysis_folder
file_lst = os.listdir(folder)
required_files = ['materials.include', 'abaqus_v6.env', 'envelop_results.json', 'postprocessing', 'subroutines']
missing_files = []
required_ok = True
inp_file_name = None
for required_file in required_files:
if required_file in file_lst:
continue
else:
required_ok = False
missing_files.append(required_file)
# If analysis is explicit analysis, edit inp-file
if analysis.name.split(' - ')[0] in ['A10', 'A12', 'A13', 'A15']:
file_lst = os.listdir(project.current_analysis_folder)
# If INP file exist
inp_file_exist = False
for file_name in file_lst:
if file_name.lower().endswith('.inp'):
inp_file_exist = True
inp_file_name = file_name
if not inp_file_exist:
project.write_log(f"WARNING: No .inp file is found")
# read materials.include
with open(project.current_analysis_folder / 'materials.include', 'r') as file:
lines_incl = file.read()
# find all materials that are included in the materials.include file
material_incl_string = r'\*Material\,\sname\=\"(.*)\"'
material_incl = re.findall(material_incl_string, lines_incl)
# read .inp file
with open(project.current_analysis_folder / inp_file_name, 'r') as file:
lines_inp = file.read()
# The following loop is using Regex to search materials in the .inp file for which materials provided
# in the materials.include file. The function also consider the case that multiple materials could
# match from inp-file.
# For one to easily understand, the idea is to delete these materials first, and later on adding the
# lines to refer/call the materials.include file.
for material_name in material_incl:
match_string = fr'(\*Material, name=\"{material_name}\"[\s|\S]*?)((\*Material)|(\*\*))'
if re.search(match_string, lines_inp) is not None:
lines_inp = re.sub(match_string, r'\2', lines_inp)
# The matching is finished, to keep the folder clean, results will be copied and
# the original inp-file will be removed
lines_inp_new = copy.deepcopy(lines_inp)
os.remove(project.current_analysis_folder / inp_file_name)
# Write new inp-file
with open(project.current_analysis_folder / inp_file_name, 'w') as file:
# As mentioned above, after clean the materials, here the lines are added to refer/call the
# materials.include file at the beginning of the MATERIAL session
include = re.findall(r'\*\*\sMATERIALS\s\*\*\s', lines_inp_new)[0] + \
r'\n*include, input=materials.include'
lines_inp_new = re.sub(r'\*\*\sMATERIALS\s\*\*\s', include, lines_inp_new)
# New .inp file is written
file.write(lines_inp_new)
# Create batch files
if required_ok:
# Create one general run.bat file under the foldr A12
run_path = project.current_analysis_folder.parent / 'run.txt'
if not os.path.exists(run_path) \
and not os.path.exists(project.current_analysis_folder.parent / 'run.bat'):
with open(run_path, 'w') as file:
inp_name_batch = inp_file_name.split('.')[0]
lines = f'::Replace [your_path] with your current path of the input file folder\n' \
f'cd [your_path]\n' \
f'::Replace [core_number] with integer of number of cores you want to use \n' \
f'call abaqus -j {inp_name_batch} cpus=[core_number] double=both -seq\n' \
f'start postprocessing.bat\n' \
f'pause\n'
commented_lines = f'::You can copy the lines above to queue up all signals. \n' \
f'::Note that pause should be deleted and only needed as the last line in this case.'
file.writelines(lines + commented_lines)
base = os.path.splitext(run_path)[0]
os.rename(run_path, base + '.bat')
# Creating post_processing.bat
inp_name_batch = inp_file_name.split('.')[0]
post_processing_path = folder / 'postprocessing.txt'
with open(post_processing_path, 'w') as file:
lines = f'call abaqus python postprocessing/postprocessor.py --odb_path {inp_name_batch}.odb ' \
f'--spec_path envelop_results.json\n' \
f'pause\n'
file.writelines(lines)
base = os.path.splitext(post_processing_path)[0]
os.rename(post_processing_path, base + '.bat')
else:
raise ValueError(
f"ERROR: {missing_files} is/are not found, please prepare it/them in your current working folder.")
# Collect all signal information
all_signals = []
all_signals_length = []
for timeseries_combination in project.collections.timeseries_combinations:
all_signals.append(timeseries_combination.name.split('_')[-1])
all_signals_length.append(timeseries_combination.x_direction.time_series[-1])
# Prepare the rest of the signals folders except current one, which is already created,
# by copying the latest folder of current signal
signals_folder_path_lst = [project.current_analysis_folder]
current_signal = project.project_specific['signal']['current signal']
for signal in all_signals:
if signal != current_signal:
# Copy the entire folder of the latest signal created by mainscript
folder = project.current_analysis_folder.parent / project.project_specific['signal'][signal]
shutil.copytree(
src=project.current_analysis_folder,
dst=folder)
# Modify the input file based on different signal
# The items need to be modified include: base motion signal, corresponding signal length
current_path = folder
signals_folder_path_lst.append(current_path)
with open(current_path / inp_file_name, 'r') as file:
lines_inp = file.read()
# Since only Structural nonlinear 2 session needs to be modified, to avoid possible mistake,
# the session is abstract separately, called sn_2 .
part_str = r'(\*\*\sSTEP: Structural nonlinear 2[\s|\S]*?)\*\* OUTPUT REQUESTS'
sn_2 = re.findall(part_str, lines_inp)[0]
# Get and replace the signal length
signal_length = all_signals_length[all_signals.index(signal)] + 0.5
pattern_signa_length = fr"(\*Dynamic, Explicit, direct user control\n)([\s|\S]*?,\s)(\d*\.\d+?)"
sn_2_new = re.sub(pattern_signa_length, fr"\g<1>\g<2>{signal_length}", sn_2)
# Replace the base motion signal
pattern_signal = fr"(\*Boundary, amplitude=Amp-base_signal_)(\d*)(_[x,y,z])"
sn_2_new = re.sub(pattern_signal, fr"\g<1>{signal[-1]}\g<3>", sn_2_new)
# To keep folder clean, the results are copied and original file is removed
lines_inp_new = copy.deepcopy(lines_inp.replace(sn_2, sn_2_new))
os.remove(current_path / inp_file_name)
# New inp-file is written
with open(current_path / inp_file_name, 'w') as file:
file.write(lines_inp_new)
files.append(current_path / inp_file_name)
# Return the files that are created for the analysis in the analysis folder
return files
### ===================================================================================================================
### 3. End of script
### ===================================================================================================================