# -*- 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)