dissertation/visualisation/data/stress_50CrMo4/true-flow-convert.py
2025-04-06 19:46:30 +02:00

292 lines
9.9 KiB
Python

# -*- coding: utf-8 -*-
"""Convert Stress-Strain Tables to true values.
This Skript takes a stress-strain table or a collection of such and converts
the technical strains into true strains.
It is also capable of outputting comparison graphs of the converted tables.
"""
import os, sys, re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def parse_args():
import argparse
parser = argparse.ArgumentParser(description='Convert Stress-Strain Tables to true plastic flow values.',
usage='python %(prog)s [options] INPUTFILE [-o OUTPUTFILE]',
epilog='''
''')
parser.add_argument('input',
metavar='INPUTFILE',
help='path to input file containing stress-strain data')
parser.add_argument('-v', '--version',
action='version',
version='%(prog)s 1.1')
parser.add_argument('-e',
action='store_true',
dest='autodetect_emod',
help='enables autodetection of Young\'s modulus. (currently nonfunctional)')
group0 = parser.add_mutually_exclusive_group()
group0.add_argument('-c',
action='store_true',
dest='compressionflag',
help='set computation for compressive test data.')
group0.add_argument('-n',
action='store_false',
dest='cut_at_uts',
help='extend computation of tensile data beyond ultimate tensile strength.')
parser.add_argument('-N',
action='store_false',
dest='cut_neg_slope',
help='allow exported data to have a negative slope (may cause errors in FEM).')
parser.add_argument('-x',
metavar = 'SCALE',
default = 1.0,
type = float,
dest='scale_x',
help='set scaling factor for X data. Defaults to 1.')
parser.add_argument('-y',
metavar = 'SCALE',
default = 1.0,
type = float,
dest='scale_y',
help='set scaling factor for Y data. Defaults to 1.')
parser.add_argument('-p',
action='store_true',
dest='plotflag',
help='plot transformation steps from technical to true flow curves.')
parser.add_argument('-P',
action='store_true',
dest='comparisonflag',
help='plot comparison of true flow curves.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-r',
metavar=('X', 'Y'),
type=float,
nargs=2,
dest='range_man',
help='''set global plot range in X-axis (strain, dimensionless)
and Y-axis (stress, MPa).''')
group.add_argument('-a',
action='store_true',
dest='range_auto',
help='''determine global plot range automatically.''')
parser.add_argument('-o',
metavar='OUTPUTFILE',
type=str,
dest='output',
help='output file path')
parser.add_argument('-s',
action='store_true',
dest='serial',
help='concatinates all curves for import into Abaqus.')
parser.add_argument('-t',
metavar = 'LENGTH',
default = 0,
type = int,
dest='output_length',
help='constrain output to a maximum length.')
return parser.parse_args()
def sanitize_title(s):
"""turns a unicode or string into a purely alphanumeric string."""
return str(s.encode('ascii', 'ignore').decode())
def reduce_lenth(data):
num_split = args.output_length
assert num_split>2
first = data[0]
last = data[-1]
rest = data[1:-1]
subs = np.array_split(rest, num_split-2)
short = np.array([a.mean() for a in subs if a.size!=0])
return np.insert(short, [0,len(short)], [first, last])
if __name__=="__main__":
args = parse_args()
inpath = os.path.abspath(args.input)
datafile = pd.ExcelFile(inpath)
sheets = datafile.sheet_names
frames = {sheet:datafile.parse(sheet) for sheet in sheets}
if args.output:
outpath = os.path.abspath(args.output)
outfilename, ext = os.path.splitext(outpath)
else:
outpath = outfilename = ext = False
if args.range_man:
xmax, ymax = args.range_man
if args.range_auto:
xmax = 0
ymax = 0
for frame in frames.values():
e_data, sig_data = np.transpose(frame.values)[:2] * np.array([args.scale_x, args.scale_y])
xmax = max(xmax, np.max(np.abs(e_data)))
ymax = max(ymax, np.max(sig_data*(e_data + 1)))
xmax = 1.05 * xmax
ymax = 1.05 * ymax
e_max_global = 0
s_max_global = 0
df_dict = {}
for title, frame in frames.items():
print(title)
temp = sanitize_title(title)
e_data, sig_data, Emod = np.transpose(frame.values)[:3]
Emod = Emod[0] * args.scale_y
e_tech = e_data * args.scale_x
sig_tech = sig_data * args.scale_y
assert np.sign(np.median(sig_tech)) == np.sign(np.median(e_tech)), "Stress and strain do not seem to share sign!"
if Emod not in (np.inf, -np.inf, 0):
e_tech_plastic = e_tech - e_tech[0] - sig_tech/Emod
else:
e_tech_plastic = e_tech
try:
idx_plastic = np.where(e_tech_plastic < 0)[0][-1]
except IndexError:
idx_plastic = 0
if args.compressionflag:
e_true = -np.log(1 - e_tech)
e_true_plastic = -np.log(1 - e_tech_plastic)
sig_true = sig_tech * (1 - e_tech)
idx_ultimate = -1
else:
e_true = np.log(1 + e_tech)
e_true_plastic = np.log(1 + e_tech_plastic)
sig_true = sig_tech * (1 + e_tech)
if args.cut_at_uts:
smax = np.max(sig_tech)
idx_ultimate = np.where(sig_tech == smax)[0][0]
else:
idx_ultimate = -1
sig_slice = sig_true[idx_plastic:idx_ultimate]
e_slice = e_true_plastic[idx_plastic:idx_ultimate]
if args.cut_neg_slope:
idx_negative_slope = [i for i in range(len(sig_slice)) if i != np.argmax(sig_slice[0:i+1])]
sig_slice = np.delete(sig_slice, idx_negative_slope)
e_slice = np.delete(e_slice, idx_negative_slope)
e_slice[0] = 0
if args.output_length > 0:
e_slice = reduce_lenth(e_slice)
sig_slice = reduce_lenth(sig_slice)
e_max_global = max(e_max_global, np.max(e_slice))
s_max_global = max(s_max_global, np.max(sig_slice))
df_dict[temp] = pd.DataFrame({'Sig':sig_slice, 'Eps':e_slice})
if args.plotflag:
plt.figure(figsize=[5, 2.8])
plt.plot(e_tech, sig_tech/args.scale_y, 'g')
plt.plot(e_true, sig_true/args.scale_y, 'r')
plt.plot(e_true[idx_plastic:idx_ultimate], sig_true[idx_plastic:idx_ultimate]/args.scale_y, 'k')
plt.plot(e_slice, sig_slice/args.scale_y, 'b')
plt.title(title)
plt.ylabel(r'$\sigma$ [MPa]')
plt.xlabel(r'$\epsilon$ [-]')
if args.range_auto or args.range_man:
plt.ylim(0, ymax)
plt.xlim(0, xmax)
plt.grid(True, which='major', linestyle='--')
plt.grid(True, which='minor', linestyle=':')
# plt.axes().set_aspect('equal')
if outpath:
plotpath = '{}_{}.png'.format(os.path.splitext(outpath)[0],temp)
plt.savefig(plotpath, bbox_inches="tight", dpi=144)
else:
plt.show()
plt.close()
if args.comparisonflag:
plt.figure(figsize=[5, 2.8])
for temp, frame in df_dict.items():
e_frame = frame['Eps']
sig_frame = frame['Sig']
plt.plot(e_frame, sig_frame/args.scale_y, label=temp)
plt.title('True Stress-Strain Curves')
plt.ylabel(r'$\sigma$ [MPa]')
plt.xlabel(r'$\epsilon$ [-]')
if args.range_auto or args.range_man:
plt.ylim(0, ymax)
plt.xlim(0, xmax)
else:
plt.ylim(0, s_max_global/args.scale_y*1.05)
plt.xlim(0, e_max_global*1.05)
plt.grid(True, which='major', linestyle='--')
plt.grid(True, which='minor', linestyle=':')
plt.legend(loc='lower right')
# plt.axes().set_aspect('equal')
if outpath:
plotpath = outfilename + '.png'
plt.savefig(plotpath, bbox_inches="tight", dpi=144)
else:
plt.show()
plt.close()
if args.output and args.serial:
frame_out = pd.DataFrame(columns=['Sig', 'Eps', 'T'])
for temp, frame in df_dict.items():
nums = re.findall(r"[-+]?\d*\.\d+|\d+", temp)
try:
frame['T'] = int(nums[0])
except ValueError:
frame['T'] = float(nums[0])
except IndexError as e:
raise e('Could not extract number from frame title to use as temperature!')
frame_out = frame_out.append(frame, ignore_index=True, sort=False)
frame_out.sort_values(by=['T', 'Eps'], inplace=True)
frame_out = frame_out.reindex(columns=['Sig', 'Eps', 'T'])
if ext and ext.lower() in ['.xls', '.xlsx']:
frame_out.to_excel(outpath,index=False)
else:
frame_out.to_csv(outpath,index=False)
elif args.output:
if ext and ext.lower() in ['.xls', '.xlsx']:
with pd.ExcelWriter(outpath) as writer:
for temp, frame in df_dict.items():
frame.to_excel(writer, sheet_name=temp,
index=False)
else:
for temp, frame in df_dict.items():
newpath = ("_{}".format(temp)).join(os.path.splitext(outpath))
frame.to_csv(newpath ,index=False)
else:
pass #frame_out.to_csv(sys.stdout,index=False)