Skip to content

Commit

Permalink
Updated machine learning functions and plotting for support of widget…
Browse files Browse the repository at this point in the history
…s in notebooks
  • Loading branch information
AHartmaier committed Dec 28, 2024
1 parent f3da0cc commit 566cb94
Show file tree
Hide file tree
Showing 6 changed files with 3,045 additions and 74 deletions.
4 changes: 2 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: pylabfea
channels:
- conda-forge
- defaults
dependencies:
- numpy
- matplotlib
- scipy
- scikit-learn
- pytest
- jupyter

- jupyterlab
- ipympl
41 changes: 21 additions & 20 deletions src/pylabfea/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
and microstructures are provided from the data. Materials are defined in
module pylabfea.material based on the analyzed data of this module.
uses NumPy, SciPy, MatPlotLib, Pandas
uses NumPy, SciPy, MatPlotLib
Version: 4.0 (2021-11-27)
Last Update: (24-04-2023)
Expand All @@ -19,7 +19,6 @@
import numpy as np
import matplotlib.pyplot as plt
import warnings
from scipy import interpolate
from scipy.signal import savgol_filter


Expand Down Expand Up @@ -86,14 +85,15 @@ class Data(object):
| sig_ideal : interpolated stress tensors at onset of plastic yielding (epc) for each load case
"""

def __init__(self, source, path_data='./',
name='Dataset', mat_name="Simulanium",
sdim=6,
epl_crit=2.e-3,
epl_start=1.e-3, epl_max=0.03,
plot=False,
wh_data=True):
if sdim!=3 and sdim!=6:
if sdim != 3 and sdim != 6:
raise ValueError('Value of sdim must be either 3 or 6')
self.mat_data = dict()
self.mat_data['epc'] = epl_crit
Expand All @@ -108,7 +108,7 @@ def __init__(self, source, path_data='./',
self.mat_data['texture'] = np.zeros(1)

if type(source) is str:
raw_data = self.read_data(path_data+source)
raw_data = self.read_data(path_data + source)
self.parse_data(raw_data, epl_crit, epl_start, epl_max) # add data to mat_data
else:
raw_data = np.array(source)
Expand Down Expand Up @@ -156,7 +156,7 @@ def read_data(self, Data_File):
peeq_full[i] = FE.eps_eq(E_Total_6D)
Original_Total_Strains[i] = E_Total_6D

Final_Data[key] = {"SEQ": seq_full, "PEEQ": peeq_plastic, "TEEQ": peeq_full, #"Load": Load,
Final_Data[key] = {"SEQ": seq_full, "PEEQ": peeq_plastic, "TEEQ": peeq_full, # "Load": Load,
"Stress": Original_Stresses,
"Plastic_Strain": Original_Plastic_Strains, "Total_Strain": Original_Total_Strains}

Expand All @@ -172,7 +172,7 @@ def read_data(self, Data_File):
Strain_Plastic = [Final_Data[key]["PEEQ"]]
self.SPE_data[key] = {"Stress": Stress, "Strain": Strain_Plastic}

#For having also the elastic data and shift the 0 plastic strain to 0.02% to match the micromechanical data. Can be used in Stress-Strain Reconstruction.
# For having also the elastic data and shift the 0 plastic strain to 0.02% to match the micromechanical data. Can be used in Stress-Strain Reconstruction.
self.Data_Visualization = {}
for key, dat in Final_Data.items():
Stress = dat["Stress"]
Expand Down Expand Up @@ -209,6 +209,7 @@ def parse_data(self, db, epl_crit, epl_start, epl_max):
epl_max : float
Maximum equiv. strain up to which data is considered
"""

def find_transition_index(stress, strain):
"""Calculates the index at which a significant transition in the total stress-strain relationship occurs.
The function applies a Savitzky-Golay filter to smooth the stress data and then calculates the first
Expand Down Expand Up @@ -237,12 +238,12 @@ def find_transition_index(stress, strain):
strain = np.array(strain)
stress = stress.flatten()
strain = strain.flatten()
smoothed_stress = savgol_filter(stress, window_length = 51, polyorder = 3)
smoothed_stress = savgol_filter(stress, window_length=51, polyorder=3)
derivative_smoothed = np.gradient(smoothed_stress, strain)
second_derivative = np.gradient(derivative_smoothed, strain)
transition_index = np.argmax(np.abs(second_derivative))
while val["TEEQ"][transition_index] > 0.0003:
transition_index = transition_index - 5
transition_index = transition_index - 5
return transition_index

Nlc = len(db.keys())
Expand Down Expand Up @@ -316,9 +317,9 @@ def find_transition_index(stress, strain):
ct += 1
it_stress = val["SEQ"]
it_strain = val["TEEQ"]
it = find_transition_index(it_stress, it_strain)
elstrains.append(val['Total_Strain'][it])
elstress.append(val['Stress'][it])
# it = find_transition_index(it_stress, it_strain)
# elstrains.append(val['Total_Strain'][it])
# elstress.append(val['Stress'][it])
E_av /= Nlc
nu_av /= Nlc
sy_av /= Nlc
Expand All @@ -332,15 +333,14 @@ def find_transition_index(stress, strain):
self.mat_data['Nlc'] = Nlc
self.mat_data['sy_list'] = sy_list
self.mat_data['sig_ideal'] = np.array(sig_ideal)
self.mat_data['elstress'] = elstress
self.mat_data['elstrains'] = elstrains
# self.mat_data['elstress'] = elstress
# self.mat_data['elstrains'] = elstrains
print(f'\n### Data set: {self.mat_data["Name"]} ###')
print(f'Type of microstructure: {Key_Translated["Texture_Type"]}')
print('Estimated elastic constants: E=%5.2f GPa, nu=%4.2f' % (E_av / 1000, nu_av))
print('Estimated yield strength: %5.2f MPa at PEEQ = %5.3f' % (sy_av, epl_start))

def convert_data(self, sig):
print("inside where it shoulld not be")
"""
Convert data provided only for stress tensor at yield point into mat_param dictionary
Expand All @@ -349,8 +349,9 @@ def convert_data(self, sig):
sig : ndarray
Stress tensors at yield onset
"""
print("inside where it should not be")
Nlc = len(sig)
sdim = len(sig[0,:])
sdim = len(sig[0, :])
if sdim != self.mat_data['sdim']:
warnings.warn(
'Warning: dimension of stress in data does not agree with parameter sdim. Use value from data.')
Expand Down Expand Up @@ -400,11 +401,11 @@ def plot_set(self, db, mat_param):
syc_1 = []
for i, key in enumerate(db.keys()):
plt.subplot(1, 2, 2) # , projection='polar')
syc = FE.s_cyl(np.array(db[key]["Parsered_Data"]["S_yld"]))
plt.plot(np.array(db[key]["Parsered_Data"]["S_Cyl"])[db[key]["Parsered_Data"]["Plastic_data"], 1],
np.array(db[key]["Parsered_Data"]["S_Cyl"])[db[key]["Parsered_Data"]["Plastic_data"], 0], 'or')
plt.plot(np.array(db[key]["Parsered_Data"]["S_Cyl"])[db[key]["Parsered_Data"]["Elastic_data"], 1],
np.array(db[key]["Parsered_Data"]["S_Cyl"])[db[key]["Parsered_Data"]["Elastic_data"], 0], 'ob')
syc = FE.s_cyl(np.array(db[key]["Parsed_Data"]["S_yld"]))
plt.plot(np.array(db[key]["Parsed_Data"]["S_Cyl"])[db[key]["Parsed_Data"]["Plastic_data"], 1],
np.array(db[key]["Parsed_Data"]["S_Cyl"])[db[key]["Parsed_Data"]["Plastic_data"], 0], 'or')
plt.plot(np.array(db[key]["Parsed_Data"]["S_Cyl"])[db[key]["Parsed_Data"]["Elastic_data"], 1],
np.array(db[key]["Parsed_Data"]["S_Cyl"])[db[key]["Parsed_Data"]["Elastic_data"], 0], 'ob')
syc_0.append(syc[0][0])
syc_1.append(syc[0][1])

Expand Down
193 changes: 193 additions & 0 deletions src/pylabfea/gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import os
import numpy as np
import tkinter as tk
import tkinter.font as tkFont
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from tkinter import ttk, Toplevel, END
from pylabfea import Model, Material


def self_closing_message(message, duration=4000):
"""
Display a self-closing message box.
:param message: The message to be displayed.
:param duration: The time in milliseconds before the message box closes automatically.
:return: A reference to the popup window.
"""
popup = Toplevel()
popup.title("Information")
popup.geometry("300x100")

screen_width = popup.winfo_screenwidth()
screen_height = popup.winfo_screenheight()
window_width = 300
window_height = 100
x_coordinate = int((screen_width / 2) - (window_width / 2))
y_coordinate = int((screen_height / 2) - (window_height / 2))
popup.geometry(f"{window_width}x{window_height}+{x_coordinate}+{y_coordinate}")

label = ttk.Label(popup, text=message, font=("Helvetica", 12), wraplength=250)
label.pack(expand=True)
popup.after(duration, popup.destroy)
popup.update_idletasks()
return


def add_label_and_entry(frame, row, label_text, entry_var, entry_type="entry", bold=True, options=None):
label_font = ("Helvetica", 12, "bold") if bold else ("Helvetica", 12)
ttk.Label(frame, text=label_text, font=label_font).grid(row=row, column=0, sticky='w')
entry = None
if entry_type == "entry":
entry = ttk.Entry(frame, textvariable=entry_var, width=15, font=("Helvetica", 12))
entry.grid(row=row, column=1, sticky='e')
elif entry_type == "checkbox":
ttk.Checkbutton(frame, variable=entry_var).grid(row=row, column=1, sticky='e')
elif entry_type == "combobox" and options is not None:
combobox = ttk.Combobox(frame, textvariable=entry_var, values=options, state='readonly',
width=14)
combobox.grid(row=row, column=1, sticky='e')
combobox.configure(font=("Helvetica", 12))
combobox.current(0)
return entry


class UserInterface(object):
def __init__(self, app, notebook):
self.app = app
# geometry
self.geom = tk.DoubleVar(value=18)
# boundary conditions
self.sides = tk.StringVar(value='force') # free sides, change to 'disp' for fixed lateral sides
self.eps_tot = tk.DoubleVar(value=0.01) # total strain in y-direction
# elastic material parameters
self.E1 = tk.DoubleVar(value=10.e3) # Young's modulus of matrix
self.E2 = tk.DoubleVar(value=300.e3) # Young's modulus of filler phase (fibers or particles)

# plot frames
tab1 = ttk.Frame(notebook)
notebook.add(tab1, text="Composite")

main_frame1 = ttk.Frame(tab1)
main_frame1.grid(row=0, column=0, sticky='nsew', padx=20, pady=20)

plot_frame1 = ttk.Frame(tab1)
plot_frame1.grid(row=0, column=1, sticky='nsew', padx=20, pady=20)
plot_frame1.rowconfigure(0, weight=1)
plot_frame1.columnconfigure(0, weight=1)

self.plot_frame = ttk.Frame(plot_frame1)
self.plot_frame.grid(row=0, column=0, sticky='nsew')

# define labels and entries
line_seq = np.linspace(0, 50, dtype=int)
line = iter(line_seq)
ttk.Label(main_frame1, text="Geometry", font=("Helvetica", 16, "bold")) \
.grid(row=next(line), column=0, columnspan=2, pady=(10, 0), sticky='w')
add_label_and_entry(main_frame1, next(line), "Side length", self.geom, bold=False)
add_label_and_entry(main_frame1, next(line), "Max. strain", self.eps_tot, bold=False)
add_label_and_entry(main_frame1, next(line), "Lateral BC", self.sides, entry_type="combobox",
options=["force", "disp"], bold=False)
add_label_and_entry(main_frame1, next(line), "Matrix Young's modulus", self.E1, bold=False)
add_label_and_entry(main_frame1, next(line), "Filler Young's modulus", self.E2, bold=False)

# create buttons
button_frame1 = ttk.Frame(main_frame1)
button_frame1.grid(row=next(line), column=0, columnspan=2, pady=10, sticky='ew')
button_run = ttk.Button(button_frame1, text="Run", style='TButton',
command=self.run)
button_run.grid(row=1, column=1, padx=(10, 5), pady=5, sticky='ew')
button_exit = ttk.Button(button_frame1, text="Exit", style='TButton', command=self.close)
button_exit.grid(row=1, column=2, padx=(10, 5), pady=5, sticky='ew')

def close(self):
self.app.quit()
self.app.destroy()

def display_plot(self, fig):
""" Show image on canvas. """
self.app.update_idletasks()
width, height = self.app.winfo_reqwidth(), self.app.winfo_reqheight()
self.app.geometry(f"{width}x{height}")

if self.canvas1 is not None:
self.canvas1.get_tk_widget().destroy()
self.canvas1 = FigureCanvasTkAgg(fig, master=self.plot_frame)
self.canvas1.draw()
self.canvas1.get_tk_widget().pack(fill=tk.BOTH, expand=True)

self.app.update_idletasks()
width, height = self.app.winfo_reqwidth(), self.app.winfo_reqheight()
self.app.geometry(f"{width}x{height}")

def run(self):
# setup material definition for regular mesh
NX = NY = int(self.geom.get())
sides = self.sides.get()
E1 = self.E1.get()
E2 = self.E2.get()
NXi1 = int(NX / 3)
NXi2 = 2 * NXi1
NYi1 = int(NY / 3)
NYi2 = 2 * NYi1
el = np.ones((NX, NY), dtype=np.int)
el[NXi1:NXi2, NYi1:NYi2] = 2

# define materials
mat1 = Material(num=1) # call class to generate material object
mat1.elasticity(E=E1, nu=0.27) # define elastic properties
mat1.plasticity(sy=150., khard=500., sdim=6) # isotropic plasticity
mat2 = Material(num=2) # define second material
mat2.elasticity(E=E2, nu=0.3) # material is purely elastic

# setup model for elongation in y-direction
fe = Model(dim=2, planestress=False) # initialize finite element model
fe.geom(sect=2, LX=4., LY=4.) # define geometry with two sections
fe.assign([mat1, mat2]) # assign materials to sections

# boundary conditions: uniaxial stress in longitudinal direction
fe.bcbot(0.) # fix bottom boundary
fe.bcright(0., sides) # boundary condition on lateral edges of model
fe.bcleft(0., sides)
fe.bctop(self.eps_tot.get() * fe.leny, 'disp') # strain applied to top nodes

# meshing and plotting of model
fe.mesh(elmts=el, NX=NX, NY=NY) # create regular mesh with sections as defined in el
if sides == 'force':
# fix lateral displacements of corner node to prevent rigid body motion
hh = [no in fe.nobot for no in fe.noleft]
noc = np.nonzero(hh)[0] # find corner node
fe.bcnode(noc, 0., 'disp', 'x') # fix lateral displacement
fe.plot('mat', mag=1, shownodes=False)

# find solution and plot stress and strain fields
fe.solve() # calculate mechanical equilibrium under boundary conditions
fe.plot('stress1', mag=4, shownodes=False)
fe.plot('stress2', mag=4, shownodes=False)
fe.plot('seq', mag=4, shownodes=False)
fe.plot('peeq', mag=4, shownodes=False)


""" Main code section """
app = tk.Tk()
app.title("pyLabFEA")
screen_width = app.winfo_screenwidth()
screen_height = app.winfo_screenheight()
# plt.rcParams['figure.dpi'] = screen_height / 19 # height stats_plot: 9, height voxel_plot: 6, margin: 4
window_width = int(screen_width * 0.9)
window_height = int(screen_height * 0.8)
x_coordinate = int((screen_width / 2) - (window_width / 2))
y_coordinate = 0 # int((screen_height / 2) - (window_height / 2))
app.geometry(f"{window_width}x{window_height}+{x_coordinate}+{y_coordinate}")

notebook = ttk.Notebook(app)
notebook.pack(fill='both', expand=True)
style = ttk.Style(app)
default_font = tkFont.Font(family="Helvetica", size=12, weight="bold")
style.configure('TNotebook.Tab', font=('Helvetica', '12', "bold"))
style.configure('TButton', font=default_font)

""" Start main loop """
gui = UserInterface(app, notebook)
app.mainloop()
Loading

0 comments on commit 566cb94

Please sign in to comment.