elasticipy 4.0.0__py3-none-any.whl → 4.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Elasticipy/gui.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
 
3
3
  import numpy as np
4
- from PyQt5.QtWidgets import (
4
+ from qtpy.QtWidgets import (
5
5
  QApplication, QMainWindow, QComboBox, QGridLayout, QLabel,
6
6
  QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFrame, QMessageBox
7
7
  )
@@ -0,0 +1,119 @@
1
+ from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor, SecondOrderTensor, \
2
+ SkewSymmetricSecondOrderTensor
3
+ from Elasticipy.tensors.stress_strain import StressTensor, StrainTensor
4
+ import pandas as pd
5
+ import numpy as np
6
+ import os
7
+ import re
8
+ from pathlib import Path
9
+
10
+ DTYPES={'stress':StressTensor,
11
+ 'strain':StrainTensor,
12
+ 'strain_el':StrainTensor,
13
+ 'strain_pl':StrainTensor,
14
+ 'velgrad':SecondOrderTensor,
15
+ 'defrate':SymmetricSecondOrderTensor,
16
+ 'defrate_pl':SymmetricSecondOrderTensor,
17
+ 'spinrate':SkewSymmetricSecondOrderTensor,
18
+ }
19
+
20
+
21
+ def _list_valid_filenames(folder, startswith='strain'):
22
+ file_list = os.listdir(folder)
23
+ pattern = r'{}\.step\d+'.format(startswith)
24
+ return [f for f in file_list if re.fullmatch(pattern, f)]
25
+
26
+ def from_step_file(file, dtype=None):
27
+ """
28
+ Import data from a single step file given by FEPX.
29
+
30
+ The type of returns is inferred from the data one wants to parse
31
+ (according the `FEPX documentation <https://fepx.info/doc/output.html>`_ ).
32
+
33
+ Parameters
34
+ ----------
35
+ file : str
36
+ Path to the file to read
37
+ dtype : type, optional
38
+ If provided, sets the type of returned array. It can be:
39
+ - float
40
+ - SecondOrderTensor
41
+ - SymmetricSecondOrderTensor
42
+ - SkewSymmetricSecondOrderTensor
43
+ - stressTensor
44
+ - strainTensor
45
+
46
+ Returns
47
+ -------
48
+ SecondOrderTensor or numpy.ndarray
49
+ Array of second-order tensors built from the read data. The array will be of shape (n,), where n is the number
50
+ of elements in the mesh.
51
+ """
52
+ data = pd.read_csv(file, header=None, sep=' ')
53
+ array = data.to_numpy()
54
+ base_name = os.path.splitext(os.path.basename(file))[0]
55
+ if dtype is None:
56
+ if base_name in DTYPES:
57
+ dtype = DTYPES[base_name]
58
+ else:
59
+ dtype = float
60
+ if issubclass(dtype,SymmetricSecondOrderTensor):
61
+ return dtype.from_Voigt(array, voigt_map=[1,1,1,1,1,1])
62
+ elif dtype == SkewSymmetricSecondOrderTensor:
63
+ zeros = np.zeros(array.shape[0])
64
+ mat = np.array([[ zeros, array[:, 0], array[:, 1]],
65
+ [-array[:, 0], zeros, array[:, 2]],
66
+ [-array[:, 1], -array[:, 2], zeros ]]).transpose((2, 0, 1))
67
+ return SkewSymmetricSecondOrderTensor(mat)
68
+ elif dtype == SecondOrderTensor:
69
+ length = array.shape[0]
70
+ return SecondOrderTensor(array.reshape((length,3,3)))
71
+ elif dtype == float:
72
+ return array
73
+
74
+
75
+
76
+ def from_results_folder(folder, dtype=None):
77
+ """
78
+ Import all data of a given field from FEPX results folder.
79
+
80
+ The type of returns is inferred from the data one wants to parse
81
+ (according the `FEPX documentation <https://fepx.info/doc/output.html>`_ ).
82
+
83
+ Parameters
84
+ ----------
85
+ folder : str
86
+ Path to the folder to read the results from
87
+ dtype : type, optional
88
+ If provided, sets the type of returned array. It can be:
89
+ - float
90
+ - SecondOrderTensor
91
+ - SymmetricSecondOrderTensor
92
+ - SkewSymmetricSecondOrderTensor
93
+ - stressTensor
94
+ - strainTensor
95
+
96
+ Returns
97
+ -------
98
+ SecondOrderTensor or numpy.ndarray
99
+ Array of second-order tensors built from the read data. The array will be of shape (m, n), where m is the number
100
+ of time increment n is the number of elements in the mesh.
101
+ """
102
+ dir_path = Path(folder)
103
+ folder_name = dir_path.name
104
+ if not dir_path.is_dir():
105
+ raise ValueError(f"{folder} is not a valid directory.")
106
+ constructor = None
107
+ array = []
108
+ for file in dir_path.iterdir():
109
+ if file.is_file() and file.name.startswith(folder_name):
110
+ data_file = from_step_file(str(file), dtype=dtype)
111
+ if constructor is None:
112
+ constructor = type(data_file)
113
+ elif constructor != type(data_file):
114
+ raise ValueError('The types of data contained in {} seem to be inconsistent.'.format(folder))
115
+ array.append(data_file)
116
+ if constructor == np.ndarray:
117
+ return np.stack(array, axis=0)
118
+ else:
119
+ return constructor.stack(array)
@@ -0,0 +1,103 @@
1
+ from Elasticipy.tensors.second_order import SecondOrderTensor, SymmetricSecondOrderTensor
2
+ from Elasticipy.tensors.stress_strain import StressTensor
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ def _rebuild_tensor(F11, F22, F33, F12, F13, F21, F23, F31, F32):
7
+ return np.array([[F11, F12, F13], [F21, F22, F23], [F31, F32, F33]]).transpose((2,0,1))
8
+
9
+ def from_quadrature_file(file, returns='stress'):
10
+ """
11
+ Read data from quadrature output file generated by PRISMS plasticity.
12
+
13
+ These files, usually named QuadratureOutputsXXX.csv (where XXX denotes the time increment), contain large amount of
14
+ data, such as gradient components, stress values, grain ID etc.
15
+
16
+ Parameters
17
+ ----------
18
+ file : str
19
+ Path to quadrature file
20
+ returns : str or list of str or tuple of str
21
+ name(s) of requested field(s). They can be:
22
+ - grain ID
23
+ - phase ID
24
+ - det(J)
25
+ - twin
26
+ - coordinates
27
+ - orientation
28
+ - elastic gradient
29
+ - plastic gradient
30
+ - stress
31
+ Returns
32
+ -------
33
+ ndarray or SecondOrderTensor or StressTensor
34
+ The number of returned values depends on the requested fields.
35
+ """
36
+ data=pd.read_csv(file, header=None, dtype=float, usecols=range(0,37))
37
+ grainID, phaseID, detJ, twin, x, y, z, rot1, rot2, rot3, *other = data.T.to_numpy()
38
+ Fe11, Fe22, Fe33, Fe12, Fe13, Fe21, Fe23, Fe31, Fe32, *FpStress = other
39
+ Fp11, Fp22, Fp33, Fp12, Fp13, Fp21, Fp23, Fp31, Fp32, *stress_compo = FpStress
40
+ if isinstance(returns, str):
41
+ returns_list = (returns,)
42
+ else:
43
+ returns_list = returns
44
+ returned_values = []
45
+ for r in returns_list:
46
+ rl = r.lower()
47
+ if (rl == 'grain id') or (rl == 'grainid'):
48
+ returned_values.append(grainID)
49
+ elif (rl == 'phase id') or (rl == 'phaseid'):
50
+ returned_values.append(phaseID)
51
+ elif rl == 'det(j)':
52
+ returned_values.append(detJ)
53
+ elif rl == 'twin':
54
+ returned_values.append(twin)
55
+ elif rl == 'coordinates':
56
+ returned_values.append(np.array([x, y, z]).T)
57
+ elif rl == 'orientation':
58
+ returned_values.append(np.array([rot1, rot2, rot3]).T)
59
+ elif rl == 'elastic gradient':
60
+ Fe = _rebuild_tensor(Fe11, Fe22, Fe33, Fe12, Fe13, Fe21, Fe23, Fe31, Fe32)
61
+ returned_values.append(SecondOrderTensor(Fe))
62
+ elif rl == 'plastic gradient':
63
+ Fp = _rebuild_tensor(Fp11, Fp22, Fp33, Fp12, Fp13, Fp21, Fp23, Fp31, Fp32)
64
+ returned_values.append(SecondOrderTensor(Fp))
65
+ elif rl == 'stress':
66
+ stress = _rebuild_tensor(*stress_compo)
67
+ returned_values.append(StressTensor(stress))
68
+ else:
69
+ raise ValueError('Unknown return type')
70
+ if isinstance(returns, str):
71
+ return returned_values[0]
72
+ else:
73
+ return tuple(returned_values)
74
+
75
+ def from_stressstrain_file(file):
76
+ """
77
+ Read data from stress-strain file generated by PRISMS plasticity.
78
+
79
+ This file is usually named stressstrain.txt
80
+
81
+ Parameters
82
+ ----------
83
+ file : str
84
+ Path to stress-strain file
85
+
86
+ Returns
87
+ -------
88
+ SymmetricSecondOrderTensor
89
+ Average Green-Lagrange strain tensor
90
+ StressTensor
91
+ Average stress tensor
92
+ """
93
+ data = pd.read_csv(file, sep='\t')
94
+ Exx, Exy, Exz = data['Exx'], data['Exy'], data['Exz']
95
+ Eyy, Eyz = data['Eyy'], data['Eyz']
96
+ Ezz = data['Ezz']
97
+ E = np.array([[Exx, Exy, Exz], [Exy, Eyy, Eyz], [Exz, Eyz, Ezz]]).transpose((2,0,1))
98
+ Txx, Txy, Txz = data['Txx'], data['Txy'], data['Txz']
99
+ Tyy, Tyz = data['Tyy'], data['Tyz']
100
+ Tzz = data['Tzz']
101
+ T = np.array([[Txx, Txy, Txz], [Txy, Tyy, Tyz], [Txz, Tyz, Tzz]]).transpose((2, 0, 1))
102
+ return SymmetricSecondOrderTensor(E), StressTensor(T)
103
+
Elasticipy/plasticity.py CHANGED
@@ -6,6 +6,9 @@ class IsotropicHardening:
6
6
  """
7
7
  Template class for isotropic hardening plasticity models
8
8
  """
9
+ type = "Isotropic"
10
+ name = 'Generic'
11
+
9
12
  def __init__(self, criterion='von Mises'):
10
13
  """
11
14
  Create an instance of a plastic model, assuming isotropic hardening
@@ -27,6 +30,12 @@ class IsotropicHardening:
27
30
  self.criterion = criterion
28
31
  self.plastic_strain = 0.0
29
32
 
33
+ def __repr__(self):
34
+ return (('{} plasticity model\n'.format(self.name) +
35
+ ' type: {}\n'.format(self.type)) +
36
+ ' criterion: {}\n'.format(self.criterion.name) +
37
+ ' current strain: {}'.format(self.plastic_strain))
38
+
30
39
  def flow_stress(self, strain, **kwargs):
31
40
  pass
32
41
 
@@ -67,9 +76,14 @@ class IsotropicHardening:
67
76
 
68
77
 
69
78
  class JohnsonCook(IsotropicHardening):
79
+ """
80
+ Special case of isotropic hardening with an underlying Johnson Cook hardening evolution rule
81
+ """
82
+ name = "Johnson-Cook"
83
+
70
84
  def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None, criterion='von Mises'):
71
85
  """
72
- Constructor for a Jonhson-Cook (JC) model.
86
+ Constructor for a Johnson-Cook (JC) model.
73
87
 
74
88
  The JC model is an exponential-law strain hardening model, which can take into account strain-rate sensibility
75
89
  and temperature-dependence (although they are not mandatory). See notes for details.
@@ -235,6 +249,11 @@ class JohnsonCook(IsotropicHardening):
235
249
 
236
250
 
237
251
  class PlasticityCriterion:
252
+ """
253
+ Template class for plasticity criteria
254
+ """
255
+ name = 'generic'
256
+
238
257
  @staticmethod
239
258
  def eq_stress(stress, **kwargs):
240
259
  """
@@ -271,6 +290,10 @@ class PlasticityCriterion:
271
290
  pass
272
291
 
273
292
  class VonMisesPlasticity(PlasticityCriterion):
293
+ """
294
+ von Mises plasticity criterion, with associated normality rule
295
+ """
296
+ name = 'von Mises'
274
297
  @staticmethod
275
298
  def eq_stress(stress, **kwargs):
276
299
  return stress.vonMises()
@@ -283,6 +306,11 @@ class VonMisesPlasticity(PlasticityCriterion):
283
306
  return StrainTensor(3 / 2 * gradient_tensor.matrix)
284
307
 
285
308
  class TrescaPlasticity(PlasticityCriterion):
309
+ """
310
+ Tresca plasticity criterion, with associated normality rule
311
+ """
312
+ name = 'Tresca'
313
+
286
314
  @staticmethod
287
315
  def eq_stress(stress, **kwargs):
288
316
  return stress.Tresca()
@@ -305,6 +333,11 @@ class TrescaPlasticity(PlasticityCriterion):
305
333
  return strain / strain.eq_strain()
306
334
 
307
335
  class DruckerPrager(PlasticityCriterion):
336
+ """
337
+ Drucker-Prager pressure-dependent plasticity criterion, with associated normality rule
338
+ """
339
+ name = 'Drucker-Prager'
340
+
308
341
  def __init__(self, alpha):
309
342
  """
310
343
  Create a Drucker-Prager (DG) plasticity criterion.
Elasticipy/polefigure.py CHANGED
@@ -101,6 +101,27 @@ class LambertScale(mscale.ScaleBase):
101
101
  mscale.register_scale(LambertScale)
102
102
 
103
103
  def add_polefigure(fig, *args, projection='stereographic', **kwargs):
104
+ """
105
+ Add a pole figure to the figure object.
106
+
107
+ Parameters
108
+ ----------
109
+ fig : matplotlib.figure.Figure
110
+ Handle to an existing figure
111
+ args : list
112
+ Positional arguments to pass to the subplot constructor
113
+ projection : str, optional
114
+ Projection to use. I can be 'stereographic' (default), 'equal area' or 'lambert' (which is equivalent to equal
115
+ area).
116
+
117
+ kwargs : dict
118
+ Keyword arguments to pass to the subplot constructor
119
+
120
+ Returns
121
+ -------
122
+ matplotlib.axes._subplots.AxesSubplot
123
+ Handle to the added axes
124
+ """
104
125
  if projection.lower() == 'equal area':
105
126
  projection = 'lambert'
106
127
  ax = fig.add_subplot(*args, projection='polar', **kwargs)
@@ -541,8 +541,6 @@ class SphericalFunction:
541
541
  u, evals = self.evaluate_on_spherical_grid((n_phi, n_theta), return_in_spherical=False, use_symmetry=False)
542
542
  ax = _plot3D(new_fig, u, evals, **kwargs)
543
543
  ax.axis('equal')
544
- if fig is None:
545
- plt.show()
546
544
  return new_fig, ax
547
545
 
548
546
  def plot_xyz_sections(self, n_theta=500, fig=None, axs=None, **kwargs):
@@ -590,12 +588,10 @@ class SphericalFunction:
590
588
  r = self.eval_spherical(angles)
591
589
  ax.plot(theta_polar, r, **kwargs)
592
590
  axs_new.append(ax)
593
- if fig is None:
594
- new_fig.show()
595
591
  return new_fig, axs_new
596
592
 
597
593
  def plot_as_pole_figure(self, n_theta=50, n_phi=200, projection='lambert',
598
- fig=None, plot_type='imshow', show=True, title=None,
594
+ fig=None, plot_type='imshow', title=None,
599
595
  subplot_args=(), subplot_kwargs=None, **kwargs):
600
596
  """
601
597
  Plots a pole figure visualization of spherical data using specified parameters and plot types.
@@ -617,7 +613,7 @@ class SphericalFunction:
617
613
  plot_type : str, optional
618
614
  The type of plot to generate: 'imshow', 'contourf', or 'contour', by default 'imshow'.
619
615
  title : str, optional
620
- Title to add to the current axis. Defaut is None.
616
+ Title to add to the current axis. Default is None.
621
617
  subplot_args : tuple
622
618
  List of arguments to pass to the subplot function, by default ()
623
619
  subplot_kwargs : dict
@@ -656,8 +652,6 @@ class SphericalFunction:
656
652
  ax.set_rlim(*self.domain[1])
657
653
  ax.set_title(title)
658
654
  new_fig.colorbar(sc)
659
- if show:
660
- plt.show()
661
655
  return new_fig, ax
662
656
 
663
657
 
@@ -886,8 +880,6 @@ class HyperSphericalFunction(SphericalFunction):
886
880
  else:
887
881
  r_grid = np.mean(values, axis=2)
888
882
  ax = _plot3D(new_fig, u[:, :, 0, :], r_grid, **kwargs)
889
- if fig is None:
890
- plt.show()
891
883
  return new_fig, ax
892
884
 
893
885
  def plot_xyz_sections(self, n_theta=500, n_psi=100, color_minmax='blue', alpha_minmax=0.2, color_mean='red',
@@ -950,8 +942,6 @@ class HyperSphericalFunction(SphericalFunction):
950
942
  handles.extend([line, area])
951
943
  labels.extend([line.get_label(), area.get_label()])
952
944
  new_fig.legend(handles, labels, loc='upper center', ncol=2, bbox_to_anchor=(0.5, 0.95))
953
- if fig is None:
954
- new_fig.show()
955
945
  return new_fig, axs
956
946
 
957
947
  def plot_as_pole_figure(self, n_theta=50, n_phi=200, n_psi=50, which='mean', projection='lambert', fig=None,
@@ -1033,6 +1023,4 @@ class HyperSphericalFunction(SphericalFunction):
1033
1023
  ax.set_rlim(*self.domain[1])
1034
1024
  ax.set_title(title)
1035
1025
  fig.colorbar(sc)
1036
- if show:
1037
- plt.show()
1038
1026
  return fig, ax