elasticipy 4.2.0__py3-none-any.whl → 5.0.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
@@ -128,9 +128,84 @@ class ElasticityGUI(QMainWindow):
128
128
  self.calculate_button.clicked.connect(self.calculate_and_plot)
129
129
  left_panel_layout.addWidget(self.calculate_button)
130
130
 
131
+ # Add horizontal separator
132
+ separator = QFrame()
133
+ separator.setFrameShape(QFrame.HLine)
134
+ separator.setFrameShadow(QFrame.Sunken)
135
+ left_panel_layout.addWidget(separator)
136
+
137
+ ############################################
138
+ # Numeric results
139
+ ############################################
140
+ self.result_labels = {}
141
+ RESULT_GROUPS = {
142
+ "Young modulus": [
143
+ ("E_mean", "Mean"),
144
+ ("E_voigt", "Voigt"),
145
+ ("E_reuss", "Reuss"),
146
+ ("E_hill", "Hill"),
147
+ ],
148
+ "Shear modulus": [
149
+ ("G_mean", "Mean"),
150
+ ("G_voigt", "Voigt"),
151
+ ("G_reuss", "Reuss"),
152
+ ("G_hill", "Hill"),
153
+ ],
154
+ "Poisson ratio": [
155
+ ("nu_mean", "Mean"),
156
+ ("nu_voigt", "Voigt"),
157
+ ("nu_reuss", "Reuss"),
158
+ ("nu_hill", "Hill"),
159
+ ],
160
+ "Linear compressibility": [
161
+ ("Beta_mean", "Mean"),
162
+ ("Beta_voigt", "Voigt"),
163
+ ("Beta_reuss", "Reuss"),
164
+ ("Beta_hill", "Hill"),
165
+ ],
166
+ "Other": [
167
+ ("K", "Bulk modulus"),
168
+ ("Z", "Zener ratio"),
169
+ ("A", "Univ. anisotropy factor"),
170
+ ]
171
+ }
172
+
173
+ ############################################
174
+ # Numeric results (grouped)
175
+ ############################################
176
+ self.result_labels = {}
177
+ for group_name, items in RESULT_GROUPS.items():
178
+
179
+ # Group title
180
+ group_label = QLabel(group_name + ":")
181
+ group_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
182
+ left_panel_layout.addWidget(group_label)
183
+
184
+ # Indented layout
185
+ indent_layout = QVBoxLayout()
186
+ indent_layout.setContentsMargins(15, 0, 0, 0)
187
+
188
+ for key, label_text in items:
189
+ row = QHBoxLayout()
190
+
191
+ label_name = QLabel(f"{label_text}:")
192
+ label_value = QLabel("—")
193
+ label_value.setMinimumWidth(100)
194
+ label_value.setStyleSheet("font-family: Consolas, Courier;")
195
+
196
+ self.result_labels[key] = label_value
197
+
198
+ row.addWidget(label_name)
199
+ row.addStretch()
200
+ row.addWidget(label_value)
201
+
202
+ indent_layout.addLayout(row)
203
+
204
+ left_panel_layout.addLayout(indent_layout)
205
+
131
206
  # Fill space
132
207
  left_panel_layout.addStretch()
133
- bottom_layout.addLayout(left_panel_layout)
208
+ bottom_layout.addLayout(left_panel_layout,1)
134
209
 
135
210
  ############################################
136
211
  # Plotting area
@@ -143,7 +218,7 @@ class ElasticityGUI(QMainWindow):
143
218
 
144
219
  self.figure = Figure()
145
220
  self.canvas = FigureCanvas(self.figure)
146
- bottom_layout.addWidget(self.canvas)
221
+ bottom_layout.addWidget(self.canvas,4)
147
222
 
148
223
  #######################################################################################
149
224
  # Main widget
@@ -160,6 +235,8 @@ class ElasticityGUI(QMainWindow):
160
235
  self.plotting_selector.setCurrentText('Young modulus')
161
236
  self.which_selector.setEnabled(False)
162
237
 
238
+ self.C_matrix = np.zeros((6, 6))
239
+
163
240
  def update_fields(self):
164
241
  # Deactivate unused fields
165
242
  active_fields = self.selected_symmetry().required
@@ -226,6 +303,25 @@ class ElasticityGUI(QMainWindow):
226
303
  else:
227
304
  value.plot_as_pole_figure(fig=self.figure, **plot_kwargs)
228
305
  self.canvas.draw()
306
+ if not np.all(self.C_matrix == Csym):
307
+ self.result_labels["E_mean"].setText(f"{stiff.Young_modulus.mean():.3f}")
308
+ self.result_labels["G_mean"].setText(f"{stiff.shear_modulus.mean():.3f}")
309
+ self.result_labels["nu_mean"].setText(f"{stiff.Poisson_ratio.mean():.3f}")
310
+ self.result_labels["Beta_mean"].setText(f"{stiff.linear_compressibility.mean():.3f}")
311
+ for method in ['voigt', 'reuss', 'hill']:
312
+ C = stiff.average(method=method)
313
+ self.result_labels[f"E_{method}"].setText(f"{C.Young_modulus.eval([1,0,0]):.3f}")
314
+ self.result_labels[f"G_{method}"].setText(f"{C.shear_modulus.eval([1, 0, 0],[0,1,0]):.3f}")
315
+ self.result_labels[f"nu_{method}"].setText(f"{C.Poisson_ratio.eval([1, 0, 0], [0, 1, 0]):.3f}")
316
+ self.result_labels[f"Beta_{method}"].setText(f"{C.linear_compressibility.eval([1, 0, 0]):.3f}")
317
+ self.result_labels["K"].setText(f"{stiff.bulk_modulus:.3f}")
318
+ try:
319
+ Z = stiff.Zener_ratio()
320
+ self.result_labels["Z"].setText(f"{stiff.Zener_ratio():.3f}")
321
+ except ValueError:
322
+ self.result_labels["Z"].setText("—")
323
+ self.result_labels["A"].setText(f"{stiff.universal_anisotropy:.3f}")
324
+ self.C_matrix = Csym
229
325
 
230
326
  except ValueError as inst:
231
327
  QMessageBox.critical(self, "Singular stiffness", inst.__str__(), QMessageBox.Ok)
@@ -261,3 +357,6 @@ def crystal_elastic_plotter():
261
357
  window = ElasticityGUI()
262
358
  window.show()
263
359
  sys.exit(app.exec_())
360
+
361
+ if __name__ == "__main__":
362
+ crystal_elastic_plotter()
@@ -90,14 +90,14 @@ def from_results_folder(folder, dtype=None):
90
90
  - SecondOrderTensor
91
91
  - SymmetricSecondOrderTensor
92
92
  - SkewSymmetricSecondOrderTensor
93
- - stressTensor
94
- - strainTensor
93
+ - StressTensor
94
+ - StrainTensor
95
95
 
96
96
  Returns
97
97
  -------
98
98
  SecondOrderTensor or numpy.ndarray
99
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.
100
+ of time increment n is the number of elements in the mesh.
101
101
  """
102
102
  dir_path = Path(folder)
103
103
  folder_name = dir_path.name
Elasticipy/plasticity.py CHANGED
@@ -37,11 +37,47 @@ class IsotropicHardening:
37
37
  ' current strain: {}'.format(self.plastic_strain))
38
38
 
39
39
  def flow_stress(self, strain, **kwargs):
40
+ """
41
+ Compute the stress from the cumulative plastic strain
42
+
43
+ Parameters
44
+ ----------
45
+ strain : float
46
+ Equivalent Plastic strain
47
+ kwargs
48
+ Additional arguments passed to the function
49
+
50
+ Returns
51
+ -------
52
+ float or np.ndarray
53
+
54
+ Examples
55
+ --------
56
+ As an example, we consider a Jonhson-Cook model:
57
+
58
+ >>> from Elasticipy.plasticity import JohnsonCook
59
+ >>> JC = JohnsonCook(A=792, B=510, n=0.26)
60
+ >>> print(JC)
61
+ Johnson-Cook plasticity model
62
+ type: Isotropic
63
+ criterion: von Mises
64
+ current strain: 0.0
65
+
66
+ >>> JC.flow_stress(0.0) # Check that the yield stress = A
67
+ 792.0
68
+
69
+ In order to get the full tensile curve in 0 to 10% strain range:
70
+
71
+ >>> import numpy as np
72
+ >>> JC.flow_stress(np.linspace(0,0.1,5)) # Check that the yield stress = B
73
+ array([ 792. , 987.44950657, 1026.04662195, 1052.067513 ,
74
+ 1072.26584567])
75
+ """
40
76
  pass
41
77
 
42
78
  def apply_strain(self, strain, **kwargs):
43
79
  """
44
- Apply strain to the current JC model.
80
+ Apply strain to the current plasticity model.
45
81
 
46
82
  This function updates the internal variable to store hardening state.
47
83
 
@@ -59,6 +95,38 @@ class IsotropicHardening:
59
95
  See Also
60
96
  --------
61
97
  flow_stress : compute the flow stress, given a cumulative equivalent strain
98
+
99
+ Examples
100
+ --------
101
+ As an example, we consider the Johnson-Cook plasticity model:
102
+
103
+ >>> from Elasticipy.plasticity import JohnsonCook
104
+ >>> JC = JohnsonCook(A=792, B=510, n=0.26)
105
+ >>> print(JC)
106
+ Johnson-Cook plasticity model
107
+ type: Isotropic
108
+ criterion: von Mises
109
+ current strain: 0.0
110
+
111
+ >>> stress = JC.apply_strain(0.1)
112
+ >>> print(stress)
113
+ 1072.2658456673885
114
+ >>> print(JC)
115
+ Johnson-Cook plasticity model
116
+ type: Isotropic
117
+ criterion: von Mises
118
+ current strain: 0.1
119
+
120
+ Obvisously, the applied strain is cumulative:
121
+
122
+ >>> stress = JC.apply_strain(0.1)
123
+ >>> print(stress)
124
+ 1127.612381818713
125
+ >>> print(JC)
126
+ Johnson-Cook plasticity model
127
+ type: Isotropic
128
+ criterion: von Mises
129
+ current strain: 0.2
62
130
  """
63
131
  if isinstance(strain, float):
64
132
  self.plastic_strain += np.abs(strain)
@@ -68,10 +136,112 @@ class IsotropicHardening:
68
136
  raise ValueError('The applied strain must be float of StrainTensor')
69
137
  return self.flow_stress(self.plastic_strain, **kwargs)
70
138
 
71
- def compute_strain_increment(self, stress, **kwargs):
72
- pass
139
+ def compute_strain_increment(self, stress, criterion='von Mises', apply_strain=True, **kwargs):
140
+ """
141
+ Given the equivalent stress, compute the strain increment with respect to the normality rule.
142
+
143
+ Parameters
144
+ ----------
145
+ stress : float or StressTensor
146
+ Equivalent stress to compute the stress from, or full stress tensor.
147
+ apply_strain : bool, optional
148
+ If true, the plasticity model will be updated to account for the applied strain (hardening)
149
+ criterion : str, optional
150
+ Plasticity criterion to consider to compute the equivalent stress and apply the normality rule.
151
+ It can be 'von Mises', 'Tresca' or 'J2'. 'J2' is equivalent to 'von Mises'.
152
+ kwargs
153
+ Keyword arguments passed to the model
154
+
155
+ Returns
156
+ -------
157
+ StrainTensor or float
158
+ Increment of plastic strain. If the input stress is float, only the magnitude of the increment will be
159
+ returned (float value). If the stress is of type StressTensor, the returned value will be a full
160
+ StrainTensor.
161
+
162
+ See Also
163
+ --------
164
+ apply_strain : apply strain to the JC model and updates its hardening value
165
+
166
+ Examples
167
+ --------
168
+ As an example, we consider the Johnson-Cook plasticity model:
169
+
170
+ >>> from Elasticipy.plasticity import JohnsonCook
171
+ >>> JC = JohnsonCook(A=792, B=510, n=0.26)
172
+
173
+ The yield stress is equal to A here. So consider a tensile stress whose magnitude below A:
174
+
175
+ >>> from Elasticipy.tensors.stress_strain import StressTensor
176
+ >>> sigma = StressTensor.tensile([1,0,0], 700)
177
+ >>> strain_inc = JC.compute_strain_increment(sigma)
178
+ >>> print(strain_inc)
179
+ Strain tensor
180
+ [[ 0. 0. 0.]
181
+ [ 0. -0. 0.]
182
+ [ 0. 0. -0.]]
183
+
184
+ whereas if the stress is larger than A:
185
+
186
+ >>> sigma = StressTensor.tensile([1,0,0], 800)
187
+ >>> strain_inc = JC.compute_strain_increment(sigma)
188
+ >>> print(strain_inc)
189
+ Strain tensor
190
+ [[ 1.14733854e-07 0.00000000e+00 0.00000000e+00]
191
+ [ 0.00000000e+00 -5.73669268e-08 0.00000000e+00]
192
+ [ 0.00000000e+00 0.00000000e+00 -5.73669268e-08]]
193
+
194
+ Check out that the JC model has been updated:
195
+
196
+ >>> print(JC)
197
+ Johnson-Cook plasticity model
198
+ type: Isotropic
199
+ criterion: von Mises
200
+ current strain: 1.1473385353505149e-07
201
+
202
+ Therefore, the yield stress has increased because of hardening. For instance, if we apply the same stress has
203
+ before, we get:
204
+
205
+ >>> JC.compute_strain_increment(sigma)
206
+ Strain tensor
207
+ [[ 0. 0. 0.]
208
+ [ 0. -0. 0.]
209
+ [ 0. 0. -0.]]
210
+ """
73
211
 
74
212
  def reset_strain(self):
213
+ """
214
+ Update the internal variable so that the plastic strain is reset to zero.
215
+
216
+ Returns
217
+ -------
218
+ None
219
+
220
+ Examples
221
+ --------
222
+ As an example, we consider the Johnson-Cook plasticity model:
223
+
224
+ >>> from Elasticipy.plasticity import JohnsonCook
225
+ >>> JC = JohnsonCook(A=792, B=510, n=0.26)
226
+
227
+ First apply a strain increment:
228
+
229
+ >>> stress = JC.apply_strain(0.1)
230
+ >>> print(JC)
231
+ Johnson-Cook plasticity model
232
+ type: Isotropic
233
+ criterion: von Mises
234
+ current strain: 0.1
235
+
236
+ If one wants to reset the JC, without recreating it:
237
+
238
+ >>> stress = JC.reset_strain()
239
+ >>> print(JC)
240
+ Johnson-Cook plasticity model
241
+ type: Isotropic
242
+ criterion: von Mises
243
+ current strain: 0.0
244
+ """
75
245
  self.plastic_strain = 0.0
76
246
 
77
247
 
@@ -153,6 +323,7 @@ class JohnsonCook(IsotropicHardening):
153
323
  eps_p.
154
324
  T : float or list or tuple or np.ndarray
155
325
  Temperature. If float, the temperature is supposed to be homogeneous for every value of eps_p.
326
+
156
327
  Returns
157
328
  -------
158
329
  float or numpy.ndarray
@@ -177,35 +348,7 @@ class JohnsonCook(IsotropicHardening):
177
348
 
178
349
  return stress
179
350
 
180
-
181
-
182
351
  def compute_strain_increment(self, stress, T=None, apply_strain=True, criterion='von Mises'):
183
- """
184
- Given the equivalent stress, compute the strain increment with respect to the normality rule.
185
-
186
- Parameters
187
- ----------
188
- stress : float or StressTensor
189
- Equivalent stress to compute the stress from, or full stress tensor.
190
- T : float
191
- Temperature
192
- apply_strain : bool, optional
193
- If true, the JC model will be updated to account for the applied strain (hardening)
194
- criterion : str, optional
195
- Plasticity criterion to consider to compute the equivalent stress and apply the normality rule.
196
- It can be 'von Mises', 'Tresca' or 'J2'. 'J2' is equivalent to 'von Mises'.
197
-
198
- Returns
199
- -------
200
- StrainTensor or float
201
- Increment of plastic strain. If the input stress is float, only the magnitude of the increment will be
202
- returned (float value). If the stress is of type StressTensor, the returned value will be a full
203
- StrainTensor.
204
-
205
- See Also
206
- --------
207
- apply_strain : apply strain to the JC model and updates its hardening value
208
- """
209
352
  if isinstance(stress, StressTensor):
210
353
  eq_stress = self.criterion.eq_stress(stress)
211
354
  else:
@@ -242,9 +385,6 @@ class JohnsonCook(IsotropicHardening):
242
385
  return strain_increment
243
386
 
244
387
  def reset_strain(self):
245
- """
246
- Reinitialize the plastic strain to 0
247
- """
248
388
  self.plastic_strain = 0.0
249
389
 
250
390
 
@@ -351,7 +491,12 @@ class DruckerPrager(PlasticityCriterion):
351
491
  -----
352
492
  The pressure-dependent DG plasticity criterion assumes that the equivalent stress is defined as:
353
493
 
494
+ .. math::
495
+
496
+ \\alpha I_1 + \\sqrt{J_2}
354
497
 
498
+ where :math:`I_1` is the first invariant of the stress tensor, and :math:`J_2` is the second invariant of the
499
+ deviatoric stress tensor.
355
500
  """
356
501
  self.alpha = alpha
357
502
 
@@ -237,6 +237,23 @@ class SphericalFunction:
237
237
  See Also
238
238
  --------
239
239
  eval_spherical : evaluate the function along a given direction given using the spherical coordinates
240
+
241
+ Examples
242
+ --------
243
+ As an example of spherical function, we consider the Young modulus estimated from a stiffness tensor:
244
+
245
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
246
+ >>> E = StiffnessTensor.cubic(C11=110, C12=54, C44=60).Young_modulus
247
+
248
+ The Young modulus along x direction is:
249
+
250
+ >>> E.eval([1,0,0])
251
+ 74.4390243902439
252
+
253
+ The Young moduli along a set a directions can be evaluated at once. E.g. along x, y and z:
254
+
255
+ >>> E.eval([[1,0,0], [0,1,0], [0,0,1]])
256
+ array([74.43902439, 74.43902439, 74.43902439])
240
257
  """
241
258
  u_vec = np.atleast_2d(u)
242
259
  norm = np.linalg.norm(u_vec, axis=1)
@@ -244,11 +261,20 @@ class SphericalFunction:
244
261
  raise ValueError('The input vector cannot be zeros')
245
262
  u_vec = (u_vec.T / norm).T
246
263
  values = self.fun(u_vec)
247
- if isinstance(u, list) and np.array(u).shape == (3,):
264
+ if isinstance(u, list) and np.array(u).shape == (3,) and isinstance(values, np.ndarray):
248
265
  return values[0]
249
266
  else:
250
267
  return values
251
268
 
269
+ def __eq__(self, other):
270
+ if type(other) is self.__class__:
271
+ n_evals = 10000
272
+ _, a_evals = self.evaluate_on_spherical_grid(n_evals)
273
+ _, b_evals = other.evaluate_on_spherical_grid(n_evals)
274
+ return np.allclose(a_evals, b_evals)
275
+ else:
276
+ return False
277
+
252
278
  def eval_spherical(self, *args, degrees=False):
253
279
  """
254
280
  Evaluate value along a given (set of) direction(s) defined by its (their) spherical coordinates.
@@ -269,7 +295,18 @@ class SphericalFunction:
269
295
 
270
296
  See Also
271
297
  --------
272
- eval : evaluate the function along a direction given by its cartesian coordinates
298
+ eval : evaluate the function along a direction given by its cartesian
299
+
300
+ Examples
301
+ --------
302
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
303
+ >>> E = StiffnessTensor.cubic(C11=110, C12=54, C44=60).Young_modulus
304
+
305
+ In spherical coordinates, the x direction is defined by theta=90° and phi=0. Therefore, the Young modulus along
306
+ x direction is:
307
+
308
+ >>> E.eval_spherical([0, 90], degrees=True)
309
+ 74.4390243902439
273
310
  """
274
311
  angles = np.atleast_2d(args)
275
312
  if degrees:
@@ -277,7 +314,9 @@ class SphericalFunction:
277
314
  phi, theta = angles.T
278
315
  u = sph2cart(phi, theta)
279
316
  values = self.eval(u)
280
- if (np.array(args).shape == (2,) or np.array(args).shape == (1, 2)) and not isinstance(args, np.ndarray):
317
+ if ((np.array(args).shape == (2,) or np.array(args).shape == (1, 2))
318
+ and not isinstance(args, np.ndarray)
319
+ and isinstance(values, np.ndarray)):
281
320
  return values[0]
282
321
  else:
283
322
  return values
@@ -735,7 +774,7 @@ class HyperSphericalFunction(SphericalFunction):
735
774
  the latitude angle from Z axis (theta==0 -> u = Z axis), and psi is the angle defining the orientation of
736
775
  the second direction (v) in the plane orthogonal to u, as illustrated below:
737
776
 
738
- .. image:: ../../docs/_static/images/HyperSphericalCoordinates.png
777
+ .. image:: ../../../docs/_static/images/HyperSphericalCoordinates.png
739
778
 
740
779
 
741
780
  degrees : bool, default False