patme 0.4.4__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.

Potentially problematic release.


This version of patme might be problematic. Click here for more details.

Files changed (46) hide show
  1. patme/__init__.py +52 -0
  2. patme/buildtools/__init__.py +7 -0
  3. patme/buildtools/rce_releasecreator.py +336 -0
  4. patme/buildtools/release.py +26 -0
  5. patme/femtools/__init__.py +5 -0
  6. patme/femtools/abqmsgfilechecker.py +137 -0
  7. patme/femtools/fecall.py +1092 -0
  8. patme/geometry/__init__.py +0 -0
  9. patme/geometry/area.py +124 -0
  10. patme/geometry/coordinatesystem.py +635 -0
  11. patme/geometry/intersect.py +284 -0
  12. patme/geometry/line.py +183 -0
  13. patme/geometry/misc.py +420 -0
  14. patme/geometry/plane.py +464 -0
  15. patme/geometry/rotate.py +244 -0
  16. patme/geometry/scale.py +152 -0
  17. patme/geometry/shape2d.py +50 -0
  18. patme/geometry/transformations.py +1831 -0
  19. patme/geometry/translate.py +139 -0
  20. patme/mechanics/__init__.py +4 -0
  21. patme/mechanics/loads.py +435 -0
  22. patme/mechanics/material.py +1260 -0
  23. patme/service/__init__.py +7 -0
  24. patme/service/decorators.py +85 -0
  25. patme/service/duration.py +96 -0
  26. patme/service/exceptionhook.py +104 -0
  27. patme/service/exceptions.py +36 -0
  28. patme/service/io/__init__.py +3 -0
  29. patme/service/io/basewriter.py +122 -0
  30. patme/service/logger.py +375 -0
  31. patme/service/mathutils.py +108 -0
  32. patme/service/misc.py +71 -0
  33. patme/service/moveimports.py +217 -0
  34. patme/service/stringutils.py +419 -0
  35. patme/service/systemutils.py +290 -0
  36. patme/sshtools/__init__.py +3 -0
  37. patme/sshtools/cara.py +435 -0
  38. patme/sshtools/clustercaller.py +420 -0
  39. patme/sshtools/facluster.py +350 -0
  40. patme/sshtools/sshcall.py +168 -0
  41. patme-0.4.4.dist-info/LICENSE +21 -0
  42. patme-0.4.4.dist-info/LICENSES/MIT.txt +9 -0
  43. patme-0.4.4.dist-info/METADATA +168 -0
  44. patme-0.4.4.dist-info/RECORD +46 -0
  45. patme-0.4.4.dist-info/WHEEL +4 -0
  46. patme-0.4.4.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,1260 @@
1
+ # Copyright (C) 2020 Deutsches Zentrum fuer Luft- und Raumfahrt(DLR, German Aerospace Center) <www.dlr.de>
2
+ # SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
3
+ #
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ """
7
+ documentation
8
+ """
9
+ import math
10
+ from collections import OrderedDict
11
+ from functools import reduce
12
+
13
+ import numpy as np
14
+ import numpy.linalg as nplin
15
+ from scipy.linalg import block_diag
16
+
17
+ from patme import epsilon
18
+ from patme.geometry.rotate import Rotation
19
+ from patme.service.exceptions import ImproperParameterError, InternalError
20
+ from patme.service.logger import log
21
+
22
+
23
+ def abdOffset(abdMatrix, offset=0.0):
24
+ """Calculates the given ABD matrix offset by offset
25
+
26
+ :param abdMatrix: 6x6 np array
27
+ :param offset: scalar
28
+ """
29
+ a = abdMatrix[:3, :3]
30
+ b = -offset * a + abdMatrix[:3, 3:6]
31
+ d = offset**2 * a - 2 * offset * abdMatrix[:3, 3:6] + abdMatrix[3:6, 3:6]
32
+
33
+ newAbdMatrix = np.zeros_like(abdMatrix)
34
+ newAbdMatrix[:3, :3] = a
35
+ newAbdMatrix[3:6, :3] = newAbdMatrix[:3, 3:6] = b
36
+ newAbdMatrix[3:6, 3:6] = d
37
+
38
+ return newAbdMatrix
39
+
40
+
41
+ def abdByPlyShares(q, r0, r90, r_pm45, laminateThickness):
42
+ """Returns the abd-matrix of a laminate with given thickness and ply shares.
43
+ The laminate is expected to be homogenized by all angles. Thus, the reducedStiffness of each
44
+ angle orientation is reduced by the ply share for calculation of the abd matrix.
45
+ The reference surface is midPlane.
46
+
47
+ :param q: reduced stiffness matrix [3,3] in material coordinate system (0-deg)
48
+ :param r0: relative 0-deg share
49
+ :param r90: relative 90-deg share
50
+ :param r_pm45: relative plus minus 45-deg share
51
+ :param laminateThickness: thickness of the laminate
52
+ """
53
+ plyShares = [r0, r90, r_pm45 / 2.0, r_pm45 / 2.0]
54
+ if not (np.sum(plyShares) - 1) < epsilon:
55
+ raise ImproperParameterError(f"The ply shares sum has to be 1 but got: {plyShares}")
56
+
57
+ abd = np.zeros((6, 6))
58
+ angles = np.radians(np.array([0, 90, 45, -45]))
59
+ for phi, plyShare in zip(angles, plyShares):
60
+ qTrans = MaterialDefinition.rotateReducedStiffnessMatrix(q, phi)
61
+ abd[:3, :3] += qTrans * plyShare * laminateThickness
62
+ abd[3:, 3:] += qTrans * plyShare * laminateThickness**3 / 12 # no excentricity, e=0
63
+
64
+ return abd
65
+
66
+
67
+ def abdByLaminationParameters(laminationParameters, stiffnessInvariants, thickness):
68
+ """calculate 6x6 ABD matrix based on laminaiton parameters and stiffness invariants
69
+
70
+ see [1]C. G. Diaconu und H. Sekine,
71
+ „Layup Optimization for Buckling of Laminated Composite Shells with Restricted Layer Angles“,
72
+ AIAA Journal, Bd. 42, Nr. 10, S. 2153–2163, Okt. 2004.
73
+ """
74
+
75
+ abdComponents = []
76
+ lamParm2D = laminationParameters.reshape((3, 4))
77
+ for lamParmComponent, factor, isBMatrix in zip(
78
+ lamParm2D, [thickness, thickness**2 / 4.0, thickness**3 / 12.0], [False, True, False]
79
+ ):
80
+ xsiMatrix = _getXsiMatrix(lamParmComponent, isBMatrix)
81
+ abdComponentVec = np.dot(xsiMatrix, stiffnessInvariants)
82
+ abdComponent = np.zeros((3, 3))
83
+ abdComponent[0, 0] = abdComponentVec[0]
84
+ abdComponent[1, 1] = abdComponentVec[1]
85
+ abdComponent[0, 1] = abdComponentVec[2]
86
+ abdComponent[2, 2] = abdComponentVec[3]
87
+ abdComponent[0, 2] = abdComponentVec[4]
88
+ abdComponent[1, 2] = abdComponentVec[5]
89
+ # symm
90
+ abdComponent[1, 0] = abdComponentVec[2]
91
+ abdComponent[2, 0] = abdComponentVec[4]
92
+ abdComponent[2, 1] = abdComponentVec[5]
93
+
94
+ abdComponent *= factor
95
+ abdComponents.append(abdComponent)
96
+
97
+ abdMatrix = np.zeros((6, 6))
98
+ abdMatrix[:3, :3] += abdComponents[0]
99
+ abdMatrix[:3, 3:] += abdComponents[1]
100
+ abdMatrix[3:, :3] += abdComponents[1]
101
+ abdMatrix[3:, 3:] += abdComponents[2]
102
+
103
+ return abdMatrix
104
+
105
+
106
+ def _getXsiMatrix(xsi, isBMatrix):
107
+ """calculates the xsi matrix based on xsi(lamination parameters either A,B,D)
108
+
109
+ :return: 6x5 matrix"""
110
+ factor = 0.0 if isBMatrix else 1.0
111
+ xsiMatrix = np.array(
112
+ [
113
+ [factor, xsi[0], xsi[1], 0.0, 0.0],
114
+ [factor, -xsi[0], xsi[1], 0.0, 0.0],
115
+ [0.0, 0.0, -xsi[1], factor, 0.0],
116
+ [0.0, 0.0, -xsi[1], 0.0, factor],
117
+ [0.0, xsi[2] / 2.0, xsi[3], 0.0, 0.0],
118
+ [0.0, xsi[2] / 2.0, -xsi[3], 0.0, 0.0],
119
+ ]
120
+ )
121
+ return xsiMatrix
122
+
123
+
124
+ class MaterialDefinition:
125
+ """classdocs"""
126
+
127
+ def __init__(self, **kwargs):
128
+ """doc"""
129
+ self.id = None
130
+ self.name = None
131
+ self.description = None
132
+ self.rho = None
133
+
134
+ self.isShell = False
135
+ """Flag if shell properties are given. Required for cpacs writing"""
136
+
137
+ self.stiffnessMatrix = np.zeros((6, 6))
138
+ """stiffnesses of material definition"""
139
+
140
+ self.strength = {}
141
+ """Max strength"""
142
+ self.strengthValues = ["sigma11t", "sigma11c", "sigma22t", "sigma22c", "tau"]
143
+
144
+ self.strain = {}
145
+ """Max strain"""
146
+ self.strainValues = ["eps11t", "eps11c", "eps22t", "eps22c", "gamma"]
147
+
148
+ self._savedModuli = {}
149
+
150
+ self._kPlaneStressCondition = None
151
+ """3x3 matrix containing the plane stress stiffnesses.
152
+ calculated according to altenberg page 53. The z-direction is the out of
153
+ plane direction with sigma_z = 0 and so on."""
154
+
155
+ self.number = None
156
+ """id within fe systems"""
157
+
158
+ self.usedAngles = set()
159
+ """Angles as integer in degrees which represent the orientations that
160
+ are used with this materialDefinition.
161
+ It is set within Layer.materialDefinition and is needed to create rotated
162
+ materialDefinitions for complex cross sections"""
163
+
164
+ self.thermalConductivity = np.zeros((6,))
165
+ """Thermal conductivity KXX KXY KXZ KYY KYZ KZZ"""
166
+
167
+ self.thermalExpansionCoeff = np.zeros((6,))
168
+ """Thermal expansion coefficient in the 3 directions XX XY XZ YY YZ ZZ"""
169
+
170
+ self.thermalExpansionCoeffTemp = 20 + 273.15
171
+ """Reference temperature for thermalExpansionCoeff"""
172
+
173
+ self.specificHeats = None
174
+ """Specific heat capacity sample points of the material in [J/(kg*K)].
175
+ Vector with at least len=2"""
176
+
177
+ self.specificHeatTemperatures = None
178
+ """Temperatures to the specific heat capacities
179
+ Vector with at least len=2"""
180
+
181
+ self.specificHeatDefaultTemp = 20 + 273.15
182
+ """Default temperature for specificHeat"""
183
+
184
+ self.setStrength(kwargs.pop("strength", {s: 0.0 for s in self.strengthValues}))
185
+
186
+ for key in kwargs:
187
+ if not hasattr(self, key):
188
+ log.warning(f'Setting unknown key "{key}" in class {self.__class__} with name "{str(self)}"')
189
+ setattr(self, key, kwargs[key])
190
+
191
+ def copy(self):
192
+ """doc"""
193
+ return MaterialDefinition(
194
+ id=self.id,
195
+ name=self.name,
196
+ description=self.description,
197
+ rho=self.rho,
198
+ stiffnessMatrix=self.stiffnessMatrix,
199
+ strength=self.strength,
200
+ number=self.number,
201
+ )
202
+
203
+ def specificHeatFunction(self, temperature):
204
+ """returns specific heat to the given temperature
205
+ :param temperature: scalar or vector with temperatures [K]
206
+ :return: scalar or vector with specific heats
207
+ """
208
+ return np.interp(temperature, self.specificHeatTemperatures, self.specificHeats)
209
+
210
+ def setStiffnessMatrix(
211
+ self,
212
+ # isotrop
213
+ e1,
214
+ g12,
215
+ # transversal isotrop
216
+ e2=None,
217
+ nu12=None,
218
+ nu23=None,
219
+ # orthotrop
220
+ e3=None,
221
+ g23=None,
222
+ g13=None,
223
+ nu31=None,
224
+ ):
225
+ """This method assumes a transverse isotropic material.
226
+
227
+ Altenbach, Holm, Johannes Altenbach, und Rolands Rikards.
228
+ Einführung in die Mechanik der Laminat- und Sandwichtragwerke:
229
+ Modellierung und Berechnung von Balken und Platten aus Verbundwerkstoffen.
230
+ 1. Aufl. Stuttgart: Wiley-VCH, 1996.
231
+
232
+ page 45 (Transversale Isotropie)
233
+
234
+ Accepted parameter combinations:
235
+
236
+ - isotrop
237
+ - e1, g12
238
+ - e1, nu12
239
+ - transversal isotrop
240
+ - e1, g12, e2, nu12
241
+ - e1, g12, e2, nu12, nu23
242
+ - e1, g12, e2, nu12, nu23, g23, g13
243
+ - orthotrop
244
+ - e1, g12, e2, nu12, nu23, e3, g23, g13, nu31
245
+ """
246
+
247
+ if g12 is None:
248
+ # isotrop switch if e and nu are given
249
+ g12 = e1 / (2 * (1 + nu12))
250
+
251
+ if not all(np.array([e2, nu12]) != None):
252
+ log.debug(f"Isotrop behavior material assumed for material with id {self.id}")
253
+ e2 = e3 = e1
254
+ nu12 = e1 / 2 / g12 - 1
255
+ nu31 = nu23 = nu12
256
+ g13 = g23 = g12
257
+ elif not all(np.array([e3, g23, g13, nu31]) != None):
258
+ log.debug(f"Transversal isotrop material behavior assumed for material with id {self.id}")
259
+ e3 = e2
260
+ nu31 = nu12
261
+ g13 = g12 if g13 is None else g13
262
+ nu23 = nu12 if nu23 is None else nu23
263
+ g23 = e2 / (2.0 * (1 + nu23)) if g23 is None else g23
264
+ else:
265
+ log.debug(f"Orthotrop material behavior assumed for material with id {self.id}")
266
+
267
+ self._savedModuli = {}
268
+
269
+ matUpperLeft = np.array(
270
+ [
271
+ [1.0 / e1, -nu12 / e1, -nu31 / e1],
272
+ [-nu12 / e1, 1.0 / e2, -nu23 / e2],
273
+ [
274
+ -nu31 / e1,
275
+ -nu23 / e2,
276
+ 1.0 / e3,
277
+ ],
278
+ ]
279
+ )
280
+
281
+ matLowerRight = np.diag([1.0 / g23, 1.0 / g13, 1.0 / g12])
282
+
283
+ compliance = block_diag(matUpperLeft, matLowerRight)
284
+ self.stiffnessMatrix = np.linalg.inv(compliance)
285
+
286
+ def setStrength(self, strength):
287
+ """This method is intended to set the strength of the material."""
288
+ self.strength.update(strength)
289
+
290
+ @staticmethod
291
+ def getTransformationMatrix(phi):
292
+ """returns the material Transformation matrix: lamCS → matCS
293
+ see eq 2.76 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
294
+
295
+ :param phi: angle [rad]
296
+ """
297
+ cos = np.cos(phi)
298
+ sin = np.sin(phi)
299
+ cs = cos * sin
300
+ cc = cos**2
301
+ ss = sin**2
302
+
303
+ T = np.array([[cc, ss, 2 * cs], [ss, cc, -2 * cs], [-cs, cs, cc - ss]])
304
+ return T
305
+
306
+ @staticmethod
307
+ def getTransformationMatrix(phi):
308
+ """returns the material Transformation matrix: lamCS → matCS
309
+ see eq 2.76 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
310
+
311
+ :param phi: angle [rad]
312
+ """
313
+ cos = np.cos(phi)
314
+ sin = np.sin(phi)
315
+ cs = cos * sin
316
+ cc = cos**2
317
+ ss = sin**2
318
+
319
+ T = np.array([[cc, ss, 2 * cs], [ss, cc, -2 * cs], [-cs, cs, cc - ss]])
320
+ return T
321
+
322
+ @staticmethod
323
+ def getTransformationInverseMatrix(phi):
324
+ """returns the material Transformation matrix: matCS → lamCS
325
+ see eq 2.72 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
326
+
327
+ :param phi: angle [rad]
328
+ """
329
+ cos = np.cos(phi)
330
+ sin = np.sin(phi)
331
+ cs = cos * sin
332
+ cc = cos**2
333
+ ss = sin**2
334
+
335
+ Ti = np.array([[cc, ss, -2 * cs], [ss, cc, 2 * cs], [cs, -cs, cc - ss]])
336
+ return Ti
337
+
338
+ @staticmethod
339
+ def rotateReducedStiffnessMatrix(redStiff, phi):
340
+ """transforms the given reduced stiffnessmatrix according to angle phi
341
+ see eq 2.85 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
342
+
343
+ :param redStif: 3x3 matrix
344
+ :param phi: angle [rad]
345
+ """
346
+ redStiffVec = np.array([[redStiff[0, 0]], [redStiff[0, 1]], [redStiff[1, 1]], [redStiff[2, 2]]])
347
+
348
+ c = np.cos(phi)
349
+ s = np.sin(phi)
350
+
351
+ transM = np.array(
352
+ [
353
+ [c**4, 2 * c**2 * s**2, s**4, 4 * c**2 * s**2],
354
+ [c**2 * s**2, c**4 + s**4, c**2 * s**2, -4 * c**2 * s**2],
355
+ [c**3 * s, c * s * (c**2 - s**2), -c * s**3, -2 * c * s * (c**2 - s**2)],
356
+ [s**4, 2 * c**2 * s**2, c**4, 4 * c**2 * s**2],
357
+ [c * s**3, c * s * (c**2 - s**2), -(c**3) * s, 2 * c * s * (c**2 - s**2)],
358
+ [c**2 * s**2, -2 * c**2 * s**2, c**2 * s**2, (c**2 - s**2) ** 2],
359
+ ]
360
+ )
361
+
362
+ redStiffVecGlo = np.mat(transM) * np.mat(redStiffVec)
363
+ redStiffMatGlo = np.array(
364
+ [
365
+ [redStiffVecGlo[0, 0], redStiffVecGlo[1, 0], redStiffVecGlo[2, 0]],
366
+ [redStiffVecGlo[1, 0], redStiffVecGlo[3, 0], redStiffVecGlo[4, 0]],
367
+ [redStiffVecGlo[2, 0], redStiffVecGlo[4, 0], redStiffVecGlo[5, 0]],
368
+ ]
369
+ )
370
+
371
+ T = MaterialDefinition.getTransformationMatrix(phi)
372
+ Ti = np.linalg.inv(T)
373
+
374
+ Qbar = np.matmul(np.matmul(Ti, redStiff), T.T)
375
+ # return Qbar
376
+
377
+ return redStiffMatGlo
378
+
379
+ def getSpecificHeat(self, T=None):
380
+ if T is None:
381
+ T = self.specificHeatDefaultTemp
382
+ return self.specificHeatFunction(T)
383
+
384
+ @property
385
+ def moduli(self):
386
+ """
387
+ calculates moduli
388
+
389
+ :return: dict with these keys: e11, e22, e33, g12, g23, g13, nu12, nu21, nu31, nu31, nu23
390
+ """
391
+ if self._savedModuli != {}:
392
+ return self._savedModuli
393
+ stiffnessM = self.stiffnessMatrix
394
+ try:
395
+ complianceM = np.linalg.inv(stiffnessM)
396
+ except np.linalg.LinAlgError:
397
+ raise InternalError(
398
+ "Please check your material definition! "
399
+ + f"Could not calculate compliance matrix of material with name {self.name} and id {self.id}."
400
+ )
401
+
402
+ e11 = complianceM[0, 0] ** -1
403
+ e22 = complianceM[1, 1] ** -1
404
+ e33 = complianceM[2, 2] ** -1
405
+
406
+ g23 = complianceM[3, 3] ** -1
407
+ g13 = complianceM[4, 4] ** -1
408
+ g12 = complianceM[5, 5] ** -1
409
+
410
+ nu12 = -complianceM[1, 0] * e11
411
+ nu21 = -complianceM[0, 1] * e22
412
+ nu13 = -complianceM[0, 2] * e11
413
+
414
+ nu31 = -complianceM[2, 0] * e33
415
+ nu23 = -complianceM[2, 1] * e22
416
+ nu32 = -complianceM[1, 2] * e33
417
+
418
+ if any(value < 0 for value in [e11, e22, e33, g12, g23, g13]):
419
+ if not hasattr(self, "xPath"):
420
+ self.xPath = ""
421
+ raise InternalError(
422
+ "Please check your material definition! "
423
+ + "Got negative youngs- or shear modulus at material element at xPath "
424
+ + self.xPath
425
+ )
426
+
427
+ self._savedModuli = OrderedDict(
428
+ [
429
+ ("e11", e11),
430
+ ("e22", e22),
431
+ ("e33", e33),
432
+ ("g12", g12),
433
+ ("g23", g23),
434
+ ("g13", g13),
435
+ ("nu12", nu12),
436
+ ("nu21", nu21),
437
+ ("nu13", nu13),
438
+ ("nu31", nu31),
439
+ ("nu23", nu23),
440
+ ("nu32", nu32),
441
+ ]
442
+ )
443
+
444
+ return self._savedModuli
445
+
446
+ def getRotatedMaterialDefinition(self, orientationAngle):
447
+ r"""The materialDefinitions of this layer with 'orientationAngle' will be calculated.
448
+
449
+ Angle definition:
450
+
451
+ \2 |y /1
452
+ \ | /
453
+ \ | /<--\
454
+ \ | / phi \
455
+ \|/_______\_______x
456
+ 'phi' is defined as the positive angle from the global coordinate system (x,y) to the
457
+ local coordinate system (1,2), which is fiberorientated.
458
+
459
+ validated with eLamX from TU Dresden
460
+
461
+ Reference: VDI 2014 Part 3, 2006, 'Development of fibre-reinforced plastics components'
462
+ """
463
+ newMaterialDefinition = self.copy()
464
+ # reset attributes
465
+ newMaterialDefinition._savedModuli = {}
466
+ newMaterialDefinition._kPlaneStressCondition = None
467
+
468
+ stiff = np.mat(newMaterialDefinition.stiffnessMatrix)
469
+
470
+ c = np.cos(np.radians(orientationAngle))
471
+ s = np.sin(np.radians(orientationAngle))
472
+ trafoMinv = np.mat(
473
+ np.array(
474
+ [
475
+ [c**2.0, s**2.0, 0.0, 0.0, 0.0, -2 * s * c],
476
+ [s**2.0, c**2.0, 0, 0.0, 0.0, 2 * s * c],
477
+ [0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
478
+ [0.0, 0.0, 0.0, c, s, 0.0],
479
+ [0.0, 0.0, 0.0, -s, c, 0.0],
480
+ [s * c, -s * c, 0.0, 0.0, 0.0, c**2 - s**2],
481
+ ]
482
+ )
483
+ )
484
+
485
+ rotStiff = trafoMinv * stiff * trafoMinv.T
486
+
487
+ newMaterialDefinition.stiffnessMatrix = rotStiff
488
+ newMaterialDefinition.id = f"{self.id}_{orientationAngle}"
489
+ newMaterialDefinition.description = f"{self.id}rotated {orientationAngle}deg"
490
+ newMaterialDefinition.name = f"{self.name}rotated {orientationAngle}deg"
491
+
492
+ return newMaterialDefinition
493
+
494
+ @property
495
+ def reducedStiffnessMatrix(self):
496
+ """doc"""
497
+ return self.getReducedStiffnessMatrixStatic(self.stiffnessMatrix)
498
+
499
+ @staticmethod
500
+ def getReducedStiffnessMatrixStatic(stiffnessMatrix):
501
+ """calculates the reduced stiffness matrix
502
+ see eq 2.63 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
503
+
504
+ :param stiffnessMatrix:
505
+ """
506
+ k = stiffnessMatrix
507
+
508
+ redCom = np.zeros((3, 3))
509
+ if k[2, 2] == 0 or k[3, 3] == 0 or k[4, 4] == 0:
510
+ com = nplin.inv(k[:2, :2])
511
+ redCom[:2, :2] = com[:2, :2]
512
+ redCom[2, 2] = k[5, 5] ** -1
513
+ else:
514
+ com = nplin.inv(k)
515
+ redCom[:2, :2] = com[:2, :2]
516
+ redCom[2, 2] = com[5, 5]
517
+
518
+ return nplin.inv(redCom)
519
+
520
+ @property
521
+ def reducedThermalExpansionCoeff(self):
522
+ """returns the thermal expansion coefficients in directions XX YY XY"""
523
+ return np.array([self.thermalExpansionCoeff[0], self.thermalExpansionCoeff[3], self.thermalExpansionCoeff[1]])
524
+
525
+ def _getkPlaneStressCondition(self):
526
+ """doc"""
527
+ if self._kPlaneStressCondition is not None:
528
+ return self._kPlaneStressCondition
529
+
530
+ self._kPlaneStressCondition = np.zeros((3, 3))
531
+
532
+ denom = 1 - self.moduli["nu12"] * self.moduli["nu21"]
533
+ # k11
534
+ self._kPlaneStressCondition[0, 0] = self.moduli["e11"] / denom
535
+ # k22
536
+ self._kPlaneStressCondition[1, 1] = self.moduli["e22"] / denom
537
+ # k66
538
+ self._kPlaneStressCondition[2, 2] = self.moduli["g12"]
539
+ # k12
540
+ self._kPlaneStressCondition[0, 1] = self.moduli["nu12"] * self.moduli["e22"] / denom
541
+ # k21
542
+ self._kPlaneStressCondition[1, 0] = self._kPlaneStressCondition[0, 1]
543
+
544
+ return self._kPlaneStressCondition
545
+
546
+ def _getIsIsotrop(self):
547
+ """:return: True if MaterialDefinition is isotrop. This is calculated by means of the stiffness matrix."""
548
+ return abs((self.stiffnessMatrix[0, 1] / self.stiffnessMatrix[1, 2]) - 1) < epsilon
549
+
550
+ def _getIsOrthotrop(self):
551
+ """:return: True if MaterialDefinition is orthotrop. This is calculated by means of the stiffness matrix."""
552
+ return not np.any(self.stiffnessMatrix[3:, :3])
553
+
554
+ def _getStiffnessInvariants(self):
555
+ """doc"""
556
+
557
+ reducedStiffness = np.array(
558
+ [
559
+ self.kPlaneStressCondition[0, 0],
560
+ self.kPlaneStressCondition[1, 1],
561
+ self.kPlaneStressCondition[0, 1],
562
+ self.kPlaneStressCondition[2, 2],
563
+ ]
564
+ )
565
+
566
+ coefficientMatrix = np.array(
567
+ [
568
+ [3.0 / 8.0, 3.0 / 8.0, 1.0 / 4.0, 1.0 / 2.0],
569
+ [1.0 / 2.0, -1.0 / 2.0, 0.0, 0.0],
570
+ [1.0 / 8.0, 1.0 / 8.0, -1.0 / 4.0, -1.0 / 2.0],
571
+ [1.0 / 8.0, 1.0 / 8.0, 3.0 / 4.0, -1.0 / 2.0],
572
+ [1.0 / 8.0, 1.0 / 8.0, -1.0 / 4.0, 1.0 / 2.0],
573
+ ]
574
+ )
575
+
576
+ return np.dot(coefficientMatrix, reducedStiffness)
577
+
578
+ stiffnessInvariants = property(fget=_getStiffnessInvariants)
579
+ kPlaneStressCondition = property(fget=_getkPlaneStressCondition)
580
+ """see MaterialDefinition._kPlaneStressCondition"""
581
+
582
+ isIsotrop = property(fget=_getIsIsotrop)
583
+ isOrthotrop = property(fget=_getIsOrthotrop)
584
+ specificHeat = property(fget=getSpecificHeat)
585
+
586
+
587
+ class Composite:
588
+ """classdocs"""
589
+
590
+ def __init__(self, **kwargs):
591
+ """doc"""
592
+ self.id = None
593
+ self.name = None
594
+ self.description = None
595
+ self.offset = None
596
+ self.number = None
597
+ self.xPath = None
598
+ self.layers = []
599
+ self._savedModuli = {}
600
+ self._laminationParameters = None
601
+ """vector of length 12 (eq.7a-c from diaconu 2004)
602
+ defines the lamination parameters for the whole composite"""
603
+ self._stiffnessInvariants = None
604
+ """vector of length 5 (eq.5 from diaconu 2004)
605
+ stiffness invariants for the material of the whole composite. Only one material can be used for all layers"""
606
+
607
+ for key in kwargs:
608
+ if not hasattr(self, key):
609
+ log.warning('Setting unknown key "%s" in class %s with name "%s"' % (key, self.__class__, str(self)))
610
+ setattr(self, key, kwargs[key])
611
+
612
+ def copy(self):
613
+ """doc"""
614
+ return self.__class__(
615
+ id=self.id,
616
+ name=self.name,
617
+ description=self.description,
618
+ offset=self.offset,
619
+ layers=[layer.copy() for layer in self.layers],
620
+ _savedModuli=self._savedModuli,
621
+ )
622
+
623
+ @property
624
+ def layerBounds(self):
625
+ """Calculates the bounds of all layers"""
626
+ bounds = np.zeros((self.numberOfLayers + 1,))
627
+ bounds[1:] = np.cumsum(self.thicknesses)
628
+ bounds -= bounds[-1] / 2
629
+ offset = self.offset if self.offset else 0.0
630
+ return bounds + offset
631
+
632
+ def getOffsetForSubLaminate(self, layers):
633
+ """Calculates the actual z-coordinate of the specified layer (which belongs to the current laminate) wrt.
634
+ the mid-plane of the laminate.
635
+
636
+ :param layers: list, containing continuous instances of type Layer() or
637
+ VariableStiffnessLayer() representing the sub-laminate
638
+
639
+ :return: float, specifying the offset of the layer from the mid-plane of the laminate
640
+ """
641
+ layerStartIndex = self.layers.index(layers[0])
642
+ layerEndIndex = self.layers.index(layers[-1])
643
+
644
+ lowerZ = -self.thickness / 2
645
+ distanceToLowerBorder = sum(self.layerThicknesses[:layerStartIndex])
646
+ subLaminateThickness = sum(self.layerThicknesses[layerStartIndex : layerEndIndex + 1])
647
+ return lowerZ + distanceToLowerBorder + subLaminateThickness / 2.0
648
+
649
+ @property
650
+ def thicknesses(self):
651
+ return np.array([layer.thickness for layer in self.layers])
652
+
653
+ def _getThickness(self):
654
+ """doc"""
655
+ thickness = sum(self.thicknesses, 0.0)
656
+
657
+ if thickness < epsilon:
658
+ log.warning(f'thickness of composite with id "{self.id}" is zero. Resetting it to {epsilon}')
659
+ thickness = epsilon
660
+ return thickness
661
+
662
+ def _getLayerOrientations(self):
663
+ """doc"""
664
+ return [layer.phi for layer in self.layers]
665
+
666
+ def _getLayerThicknesses(self):
667
+ """return layer thicknesses as list"""
668
+ return np.array([layer.thickness for layer in self.layers])
669
+
670
+ def _getRho(self):
671
+ """doc"""
672
+ rho = sum((layer.materialDefinition.rho * layer.thickness for layer in self.layers), 0.0)
673
+ return rho / self.thickness
674
+
675
+ def getABD(self, scaling=None, offset=None):
676
+ """calculates ABD-matrix - see a composite book for explanation
677
+
678
+ :param scaling: optional parameter to use a custom scaling of the thickness of the composites layers
679
+ :param offset: optional parameter can set another offset. The reference plane
680
+ is the composites middle plane. If offset is not given, the class property
681
+ offset is used instead. If none of these are given, offset=0 which means the mid-thickness
682
+ surface is the reference surface.
683
+ """
684
+ if self.layers:
685
+ return self.getAbdByLayers(scaling, offset)
686
+
687
+ if self.laminationParameters is not None:
688
+ return self.getAbdByLaminationParameters()
689
+
690
+ raise InternalError("There are no layers or lamination parameters defined")
691
+
692
+ def getAbdByLayers(self, scaling=None, offset=None):
693
+ """calculates ABD-matrix - see a composite book for explanation
694
+
695
+ :param scaling: optional parameter to use a custom scaling of the thickness of the composites layers
696
+ :param offset: optional parameter can set another offset. The reference plane
697
+ is the composites middle plane. If offset is not given, the class property
698
+ offset is used instead. If none of these are given, offset=0 which means the mid-thickness
699
+ surface is the reference surface.
700
+
701
+ abd = ABD matrix
702
+ zk = offset of layer from the middle of the laminate
703
+ 0 -> of previous layer, 1-> of present layer
704
+
705
+ com = compliance matrix
706
+ redCom = reduced compliance matrix
707
+ redStiff = reduced stiffness matrix
708
+ redStiffVec = reduced stiffness matrix in vector form
709
+ transM = transformation matrix
710
+ redStiffVecGlo = reduced stiffness vector in the global coordinate system
711
+ """
712
+
713
+ if scaling is None:
714
+ scaling = 1.0
715
+ if offset is None:
716
+ offset = 0.0 if self.offset is None else self.offset
717
+
718
+ abd = np.zeros((6, 6))
719
+ zk1 = -(self.thickness * scaling) / 2 - offset
720
+ for layer in self.layers:
721
+ redStiff = MaterialDefinition.getReducedStiffnessMatrixStatic(layer.materialDefinition.stiffnessMatrix)
722
+ redStiffMatGlo = MaterialDefinition.rotateReducedStiffnessMatrix(redStiff, np.radians(layer.phi))
723
+
724
+ zk0 = zk1
725
+ zk1 = zk0 + layer.thickness * scaling
726
+
727
+ zkA = np.zeros((3, 3))
728
+ zkA[:, :] = zk1 - zk0
729
+
730
+ zkB = np.zeros((3, 3))
731
+ zkB[:, :] = zk1**2 - zk0**2
732
+
733
+ zkD = np.zeros((3, 3))
734
+ zkD[:, :] = zk1**3 - zk0**3
735
+
736
+ abd[:3, :3] += redStiffMatGlo * zkA
737
+ abd[3:, :3] += redStiffMatGlo * zkB
738
+ abd[:3, 3:] += redStiffMatGlo * zkB
739
+ abd[3:, 3:] += redStiffMatGlo * zkD
740
+
741
+ abd[3:, :3] = abd[3:, :3] * 1 / 2
742
+ abd[:3, 3:] = abd[:3, 3:] * 1 / 2
743
+ abd[3:, 3:] = abd[3:, 3:] * 1 / 3
744
+ # ABD-matrix now fully calculated
745
+
746
+ return abd
747
+
748
+ def setLaminationParametersOfLayers(self, scaling=None, offset=None):
749
+ """This method sets for each layer the lamination parameters according to the actual stacking sequence."""
750
+ if scaling is None:
751
+ scaling = 1.0
752
+ if offset is None:
753
+ offset = 0.0 if self.offset is None else self.offset
754
+
755
+ actualZ = -(self.thickness * scaling) / 2 + offset
756
+ for layer in self.layers:
757
+ xsiAList = np.zeros(4)
758
+ xsiBList = np.zeros(4)
759
+ xsiDList = np.zeros(4)
760
+ actualAngle = layer.phi * np.pi / 180.0
761
+ actualThickness = layer.thickness
762
+
763
+ previousZ = actualZ
764
+ actualZ = previousZ + actualThickness
765
+
766
+ cos2 = np.cos(2.0 * actualAngle)
767
+ cos4 = np.cos(4.0 * actualAngle)
768
+ sin2 = np.sin(2.0 * actualAngle)
769
+ sin4 = np.sin(4.0 * actualAngle)
770
+
771
+ xsiAList[0] += 1.0 / 2.0 * cos2 * (actualZ - previousZ)
772
+ xsiAList[1] += 1.0 / 2.0 * cos4 * (actualZ - previousZ)
773
+ xsiAList[2] += 1.0 / 2.0 * sin2 * (actualZ - previousZ)
774
+ xsiAList[3] += 1.0 / 2.0 * sin4 * (actualZ - previousZ)
775
+
776
+ xsiBList[0] += cos2 * (actualZ**2 - previousZ**2)
777
+ xsiBList[1] += cos4 * (actualZ**2 - previousZ**2)
778
+ xsiBList[2] += sin2 * (actualZ**2 - previousZ**2)
779
+ xsiBList[3] += sin4 * (actualZ**2 - previousZ**2)
780
+
781
+ xsiDList[0] += 3.0 / 2.0 * cos2 * (actualZ**3 - previousZ**3)
782
+ xsiDList[1] += 3.0 / 2.0 * cos4 * (actualZ**3 - previousZ**3)
783
+ xsiDList[2] += 3.0 / 2.0 * sin2 * (actualZ**3 - previousZ**3)
784
+ xsiDList[3] += 3.0 / 2.0 * sin4 * (actualZ**3 - previousZ**3)
785
+
786
+ layer.laminationParameters = [xsiAList, xsiBList, xsiDList]
787
+
788
+ def getAbdByLaminationParameters(self):
789
+ """doc"""
790
+ if self._laminationParameters:
791
+ # there are lamination parameters defined for the whole composite
792
+ if self._stiffnessInvariants is None:
793
+ raise Exception()
794
+ return abdByLaminationParameters(self._laminationParameters, self._stiffnessInvariants, self.thickness)
795
+
796
+ else:
797
+ # calculate layerwise
798
+ abdMatrix = np.zeros((6, 6))
799
+ for layerIndex, layer in enumerate(self.layers):
800
+ stiffnessInvariants = layer.stiffnessInvariants
801
+ laminationParameters = np.array(self._getLamParmsCoeffMatOfLayer(layerIndex + 1)).flatten()
802
+ abd = abdByLaminationParameters(laminationParameters, stiffnessInvariants, layer.thickness)
803
+ abdMatrix += abd
804
+ return abdMatrix
805
+
806
+ def _getLamParmsCoeffMatOfLayer(self, layerNumber):
807
+ """This method returns the lamination parameters of the specifed ply.
808
+
809
+ :param layerNumber: int, the number of the layer within the actual stacking sequence (starting with 1)
810
+
811
+ :return: list, containing three arrays (lamination parameter matrixes for a-Matrix, b-Matrix and d-Matrix)
812
+ """
813
+ laminationParameters = self.layers[layerNumber - 1].laminationParameters
814
+ plyThickness = self.layers[layerNumber - 1].thickness
815
+ ratio = plyThickness / self.thickness
816
+
817
+ xsiAList = laminationParameters[0]
818
+ xsiAMatrix = np.array(
819
+ [
820
+ [1.0 * ratio, xsiAList[0], xsiAList[1], 0.0, 0.0],
821
+ [1.0 * ratio, -xsiAList[0], xsiAList[1], 0.0, 0.0],
822
+ [0.0, 0.0, -xsiAList[1], 1.0 * ratio, 0.0],
823
+ [0.0, 0.0, -xsiAList[1], 0.0, 1.0 * ratio],
824
+ [0.0, xsiAList[2] / 2.0, xsiAList[3], 0.0, 0.0],
825
+ [0.0, xsiAList[2] / 2.0, -xsiAList[3], 0.0, 0.0],
826
+ ]
827
+ )
828
+
829
+ xsiBList = laminationParameters[1]
830
+ xsiBMatrix = np.array(
831
+ [
832
+ [0.0, xsiBList[0], xsiBList[1], 0.0, 0.0],
833
+ [0.0, -xsiBList[0], xsiBList[1], 0.0, 0.0],
834
+ [0.0, 0.0, -xsiBList[1], 0.0, 0.0],
835
+ [0.0, 0.0, -xsiBList[1], 0.0, 0.0],
836
+ [0.0, xsiBList[2] / 2.0, xsiBList[3], 0.0, 0.0],
837
+ [0.0, xsiBList[2] / 2.0, -xsiBList[3], 0.0, 0.0],
838
+ ]
839
+ )
840
+
841
+ xsiDList = laminationParameters[2]
842
+ xsiDMatrix = np.array(
843
+ [
844
+ [1.0 * ratio, xsiDList[0], xsiDList[1], 0.0, 0.0],
845
+ [1.0 * ratio, -xsiDList[0], xsiDList[1], 0.0, 0.0],
846
+ [0.0, 0.0, -xsiDList[1], 1.0 * ratio, 0.0],
847
+ [0.0, 0.0, -xsiDList[1], 0.0, 1.0 * ratio],
848
+ [0.0, xsiDList[2] / 2.0, xsiDList[3], 0.0, 0.0],
849
+ [0.0, xsiDList[2] / 2.0, -xsiDList[3], 0.0, 0.0],
850
+ ]
851
+ )
852
+
853
+ return [xsiAMatrix, xsiBMatrix, xsiDMatrix]
854
+
855
+ @staticmethod
856
+ def getLaminationParameter(thickness, orientation):
857
+ """Calculates and returns the "Lamination Parameter"
858
+ Source: Shutian Liu , Yupin Hou , Xiannian Sun , Yongcun Zhang: A two-step optimization scheme for
859
+ maximum stiffness design of laminated
860
+ plates based on lamination parameters, 2012
861
+
862
+ p. 3531ff.
863
+ """
864
+
865
+ # Determine the normalized coordinate of thickness z
866
+ # (origin: plane of symmetrie , direction: from plane of symmetrie to rim]
867
+
868
+ thicknessList = orientation
869
+ for i in range(0, len(thicknessList)):
870
+ thicknessList[i] = thickness
871
+
872
+ # t = [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001] # test-thicknesses
873
+ h = reduce(lambda x, y: x + y, thicknessList)
874
+
875
+ ori = orientation # list with plyorientations
876
+
877
+ z = [] # list with normalized thickness-coordinates zi
878
+ if len(thicknessList) % 2 == 0:
879
+ for i in range(0, len(thicknessList) // 2):
880
+ p = reduce(lambda k, l: k + l, thicknessList[i : len(thicknessList) / 2], 0)
881
+ z.append(p)
882
+
883
+ # reflect list at the plane of symmetry
884
+ # z.extend(z[::1])
885
+ z.append(0)
886
+ zRezi = [-x for x in z]
887
+ z.extend(zRezi[::-1])
888
+ z.remove(0)
889
+ zges = [x / h for x in z]
890
+
891
+ else:
892
+
893
+ try:
894
+ i = 0
895
+ while i < len(thicknessList) / 2:
896
+ p = (len(thicknessList) / 2 - i) * thickness
897
+
898
+ z.append(p)
899
+ i += 1
900
+
901
+ z.append(0.5 * thickness)
902
+ zRezi = [-x for x in z]
903
+ z.extend(zRezi[::-1])
904
+ zges = [x / h for x in z]
905
+ except:
906
+ z.append(0.5 * thickness)
907
+ zRezi = [-x for x in z]
908
+ z.extend(zRezi[::-1])
909
+ zges = [x / h for x in z]
910
+
911
+ # Calculation of the Lamination Parameter:
912
+
913
+ lpA1 = [] # list of the components of lpA1 calculating to the Lamination Parameter lpA1ges
914
+ lpA2 = [] # list of the components of lpA2 calculating to the Lamination Parameter lpA2ges
915
+ lpA3 = [] # list of the components of lpA3 calculating to the Lamination Parameter lpA3ges
916
+ lpA4 = [] # list of the components of lpA4 calculating to the Lamination Parameter lpA4ges
917
+ lpB1 = [] # list of the components of lpB1 calculating to the Lamination Parameter lpB1ges
918
+ lpB2 = [] # list of the components of lpB2 calculating to the Lamination Parameter lpB2ges
919
+ lpB3 = [] # list of the components of lpB3 calculating to the Lamination Parameter lpB3ges
920
+ lpB4 = [] # list of the components of lpB4 calculating to the Lamination Parameter lpB4ges
921
+ lpD1 = [] # list of the components of lpD1 calculating to the Lamination Parameter lpD1ges
922
+ lpD2 = [] # list of the components of lpD2 calculating to the Lamination Parameter lpD2ges
923
+ lpD3 = [] # list of the components of lpD3 calculating to the Lamination Parameter lpD3ges
924
+ lpD4 = [] # list of the components of lpD4 calculating to the Lamination Parameter lpD4ges
925
+ lenThickness = len(thicknessList) # number of plies of a stack
926
+ # print 'lenThickness:', lenThickness
927
+ for n in range(0, lenThickness - 1):
928
+ # Lamination Parameter for matrix A
929
+
930
+ lpA1n = (zges[n + 1] - zges[n]) * (math.cos(2 * ori[n] * math.pi / 180))
931
+ lpA1.append(lpA1n)
932
+
933
+ lpA2n = (zges[n + 1] - zges[n]) * (math.sin(2 * ori[n] * math.pi / 180))
934
+ lpA2.append(lpA2n)
935
+
936
+ lpA3n = (zges[n + 1] - zges[n]) * (math.cos(4 * ori[n] * math.pi / 180))
937
+ lpA3.append(lpA3n)
938
+
939
+ lpA4n = (zges[n + 1] - zges[n]) * (math.sin(4 * ori[n] * math.pi / 180))
940
+ lpA4.append(lpA4n)
941
+
942
+ # Lamination Parameter for matrix B
943
+
944
+ lpB1n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.cos(2 * ori[n] * math.pi / 180))
945
+ lpB1.append(lpB1n)
946
+
947
+ lpB2n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.sin(2 * ori[n] * math.pi / 180))
948
+ lpB2.append(lpB2n)
949
+
950
+ lpB3n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.cos(4 * ori[n] * math.pi / 180))
951
+ lpB3.append(lpB3n)
952
+
953
+ lpB4n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.sin(4 * ori[n] * math.pi / 180))
954
+ lpB4.append(lpB4n)
955
+
956
+ # Lamination Parameter for matrix D
957
+
958
+ lpD1n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.cos(2 * ori[n] * math.pi / 180))
959
+ lpD1.append(lpD1n)
960
+
961
+ lpD2n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.sin(2 * ori[n] * math.pi / 180))
962
+ lpD2.append(lpD2n)
963
+
964
+ lpD3n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.cos(4 * ori[n] * math.pi / 180))
965
+ lpD3.append(lpD3n)
966
+
967
+ lpD4n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.sin(4 * ori[n] * math.pi / 180))
968
+ lpD4.append(lpD4n)
969
+
970
+ truncToZero = lambda x: 0 if x < epsilon else x
971
+
972
+ # Lamination Parameter for A:
973
+ lpA = []
974
+ for lp_a in [lpA1, lpA2, lpA3, lpA4]:
975
+ res = truncToZero(reduce(lambda i, j: i + j, lp_a, 0))
976
+ lpA.append(res)
977
+
978
+ # Lamination Parameter for B:
979
+ lpB = []
980
+ for lp_b in [lpB1, lpB2, lpB3, lpB4]:
981
+ res = truncToZero(reduce(lambda i, j: i + 2 * j, lp_b, 0))
982
+ lpB.append(res)
983
+
984
+ # Lamination Parameter for D:
985
+ lpD = []
986
+ for lp_d in [lpD1, lpD2, lpD3, lpD4]:
987
+ res = truncToZero(reduce(lambda i, j: i + 4 * j, lp_d, 0))
988
+ lpD.append(res)
989
+
990
+ # =========================================================
991
+
992
+ def getPolarParameter(self, Q11, Q12, Q22, Q16, Q26, Q66):
993
+ """Calculates and returns the "Polar Parameter" -> calculation according to: Optimal Orthotropy for Minimum Elastic Energy
994
+ by the Polar Method by A. Vincenti , B. Desmorat"""
995
+
996
+ # stiffnesses of the A- matrix were needed
997
+
998
+ T0 = (Q11 + Q22 - 2 * Q12 + 4 * Q66) / 8
999
+ T1 = (Q11 + Q22 + 2 * Q12) / 8
1000
+ R0 = (
1001
+ math.sqrt(
1002
+ (Q11 + Q22 - 2 * Q12 - 4 * Q66) * (Q11 + Q22 - 2 * Q12 - 4 * Q66)
1003
+ + (4 * (Q16 - Q26)) * (4 * (Q16 - Q26))
1004
+ )
1005
+ ) / 8
1006
+ R1 = (math.sqrt((Q11 - Q22) * (Q11 - Q22) + (2 * (Q16 + Q26)) * (2 * (Q16 + Q26)))) / 8
1007
+
1008
+ if (Q16 - Q26) == 0:
1009
+ phi0 = 0
1010
+ else:
1011
+ try:
1012
+ phi0 = math.atan(4 * (Q16 - Q26) / (Q11 + Q22 - 2 * Q12 - 4 * Q66)) / 4
1013
+ except:
1014
+ phi0 = math.pi / 8
1015
+
1016
+ if (Q16 + Q26) == 0:
1017
+ phi1 = 0
1018
+ else:
1019
+ try:
1020
+ phi1 = math.atan(2 * (Q16 + Q26) / (Q11 - Q22)) / 2
1021
+ except:
1022
+ phi1 = math.pi / 4
1023
+
1024
+ return {"T0": T0, "T1": T1, "R0": R0, "R1": R1, "phi0": phi0, "phi1": phi1}
1025
+
1026
+ def getThermalForces(self, dT=0, Q=None):
1027
+ """Calculates thermal force due to temperature variations.
1028
+
1029
+ :param dT: delta temperature: scalar or vector with temperatures for top, each interface and bottom.
1030
+ length: number of layers + 1
1031
+ :return: force vector of len 6 in force/length etc.
1032
+ """
1033
+ if not hasattr(dT, "__iter__"):
1034
+ # dT is scalar
1035
+ dT = [dT] * (self.numberOfLayers + 1)
1036
+
1037
+ forceThermalLcs = np.zeros(6)
1038
+ layerBounds = self.layerBounds
1039
+ layBoundsInf = layerBounds[:-1]
1040
+ layBoundsSup = layerBounds[1:]
1041
+
1042
+ for layer, dTPlyInf, dTPlySup, boundInf, boundSup in zip(
1043
+ self.layers, dT[:-1], dT[1:], layBoundsInf, layBoundsSup
1044
+ ):
1045
+ phi = np.radians(layer.phi)
1046
+ materialDef = layer.materialDefinition
1047
+ T = layer.materialDefinition.getTransformationMatrix(phi)
1048
+ # LCS: laminate coordinate system
1049
+ if Q is None:
1050
+ Q = materialDef.reducedStiffnessMatrix
1051
+ stressThermalInfMcs = np.dot(Q, materialDef.reducedThermalExpansionCoeff) * dTPlyInf
1052
+ stressThermalSupMcs = np.dot(Q, materialDef.reducedThermalExpansionCoeff) * dTPlySup
1053
+ stressThermalInfLcs = np.matmul(np.linalg.inv(T), stressThermalInfMcs)
1054
+ stressThermalSupLcs = np.matmul(np.linalg.inv(T), stressThermalSupMcs)
1055
+ stressThermalMeanLcs = np.mean([stressThermalInfLcs, stressThermalSupLcs], axis=0)
1056
+ forceThermalLcs[:3] = forceThermalLcs[:3] + stressThermalMeanLcs * (boundSup - boundInf)
1057
+ forceThermalLcs[3:] = forceThermalLcs[3:] + stressThermalMeanLcs * (1 / 2) * (boundSup**2 - boundInf**2)
1058
+
1059
+ return forceThermalLcs
1060
+
1061
+ def getThermalStrains(self, dT=0):
1062
+ """Calculates thermal strains due to temperature variations.
1063
+ :param dT: delta temperature: scalar or vector with temperatures for top, each interface and bottom.
1064
+ length: number of layers + 1
1065
+
1066
+ :return: strain vector of len 6
1067
+ """
1068
+ forceThermal = self.getThermalForces(dT)
1069
+ strains = np.linalg.solve(self.abd, forceThermal)
1070
+
1071
+ return strains
1072
+
1073
+ def getThermalLayerStresses(self, dT=0, clamped=False, outputCS="mcs"):
1074
+ """Calculates the Stresses in each layer due to CTE
1075
+
1076
+ :param dT: delta temperature: scalar or vector with temperatures for top, each interface and bottom.
1077
+ length: number of layers + 1
1078
+ :param clamped: laminate is clamped or unconstrained
1079
+ :param outputCS: (mcs, lcs)
1080
+ :return: tuple: (stressInferior, stressSuperior) denotes the stresses on the upper and lower border
1081
+ of the composite. Each parameter has shape (len(layerCount), 3)
1082
+ """
1083
+
1084
+ # Lcs = laminate coordinate system
1085
+ # Mcs = material coordinate system
1086
+ allowedCoordSystems = ["mcs", "lcs"]
1087
+ if outputCS not in allowedCoordSystems:
1088
+ raise ImproperParameterError(f"Parameter outputCS must be one of {allowedCoordSystems}")
1089
+ if not hasattr(dT, "__iter__"):
1090
+ # dT is scalar
1091
+ dT = [dT] * (self.numberOfLayers + 1)
1092
+
1093
+ layerBounds = self.layerBounds
1094
+ layBoundsInf = layerBounds[:-1]
1095
+ layBoundsSup = layerBounds[1:]
1096
+
1097
+ strainsLcs = self.getThermalStrains(dT)
1098
+ strains = strainsLcs[:3]
1099
+ curvatures = strainsLcs[3:6]
1100
+
1101
+ strainsInfLcs = []
1102
+ strainsSupLcs = []
1103
+ for layer, boundInf, boundSup in zip(self.layers, layBoundsInf, layBoundsSup):
1104
+ strainsInfLcs.append(strains + curvatures * boundInf)
1105
+ strainsSupLcs.append(strains + curvatures * boundSup)
1106
+
1107
+ stressesInf = []
1108
+ stressesSup = []
1109
+ for layer, dTPlyInf, dTPlySup, strainInfLcs, strainSupLcs in zip(
1110
+ self.layers, dT[:-1], dT[1:], strainsInfLcs, strainsSupLcs
1111
+ ):
1112
+ phi = np.radians(layer.phi)
1113
+ materialDef = layer.materialDefinition
1114
+ T = materialDef.getTransformationMatrix(phi)
1115
+ Ti = materialDef.getTransformationInverseMatrix(phi)
1116
+ Q = materialDef.reducedStiffnessMatrix
1117
+ Qbar = materialDef.rotateReducedStiffnessMatrix(Q, phi)
1118
+
1119
+ stressInfLcs = np.matmul(Qbar, strainInfLcs)
1120
+ stressSupLcs = np.matmul(Qbar, strainSupLcs)
1121
+ stressInfMcs = np.matmul(T, stressInfLcs)
1122
+ stressSupMcs = np.matmul(T, stressSupLcs)
1123
+
1124
+ if not clamped:
1125
+ # subtract thermal layer strains from laminate strains
1126
+ layDeltaTStressInfMcs = np.matmul(Q, materialDef.reducedThermalExpansionCoeff * dTPlyInf)
1127
+ layDeltaTStressSupMcs = np.matmul(Q, materialDef.reducedThermalExpansionCoeff * dTPlySup)
1128
+ stressInfMcs = stressInfMcs - layDeltaTStressInfMcs
1129
+ stressSupMcs = stressSupMcs - layDeltaTStressSupMcs
1130
+
1131
+ if outputCS == "lcs":
1132
+ stressesInf.append(np.dot(Ti, stressInfMcs))
1133
+ stressesSup.append(np.dot(Ti, stressSupMcs))
1134
+ else:
1135
+ stressesInf.append(stressInfMcs)
1136
+ stressesSup.append(stressSupMcs)
1137
+
1138
+ return np.array(stressesInf), np.array(stressesSup)
1139
+
1140
+ def _getModuli(self):
1141
+ """
1142
+ abdCompliance = inverse of (ABD matrix divided by the laminate thickness)
1143
+ """
1144
+ if self._savedModuli != {}:
1145
+ return self._savedModuli
1146
+ abd = self.getABD()
1147
+ self._savedModuli = self.getModuliStatic(abd, self.thickness)
1148
+ return self._savedModuli
1149
+
1150
+ @staticmethod
1151
+ def getModuliStatic(abd, thickness):
1152
+ """doc"""
1153
+ abdCompliance = nplin.inv(abd[:, :] / thickness)
1154
+ e11 = abdCompliance[0, 0] ** -1
1155
+ e22 = abdCompliance[1, 1] ** -1
1156
+ g12 = abdCompliance[2, 2] ** -1
1157
+ nu12 = -abdCompliance[1, 0] * e11
1158
+ nu21 = -abdCompliance[0, 1] * e22
1159
+
1160
+ return {"e11": e11, "e22": e22, "g12": g12, "nu12": nu12, "nu21": nu21}
1161
+
1162
+ def _isComposite(self):
1163
+ """doc"""
1164
+ return self.numberOfLayers != 1 or not self.layers[0].materialDefinition.isIsotrop
1165
+
1166
+ def getInfoString(self):
1167
+ """doc"""
1168
+ retStr = "Composite with materialname, orientation, thickness\n"
1169
+ retStr += "\n".join(layer.getInfoString() for layer in self.layers)
1170
+ return retStr
1171
+
1172
+ def _getNumberOfLayers(self):
1173
+ """doc"""
1174
+ return len(self.layers)
1175
+
1176
+ def _getLaminationParameters(self):
1177
+ """doc"""
1178
+ laminationParameters = []
1179
+ xsiA = np.zeros(4)
1180
+ xsiB = np.zeros(4)
1181
+ xsiD = np.zeros(4)
1182
+
1183
+ for layer in self.layers:
1184
+ # in case that not for all layers lamination parameters are set (e.g. when a layer was changed)
1185
+ if layer.laminationParameters is None:
1186
+ return None
1187
+
1188
+ xsiA += layer.laminationParameters[0]
1189
+ xsiB += layer.laminationParameters[1]
1190
+ xsiD += layer.laminationParameters[2]
1191
+
1192
+ laminationParameters += [xsiA, xsiB, xsiD]
1193
+ return laminationParameters
1194
+
1195
+ rho = property(fget=_getRho)
1196
+ """homogenized density"""
1197
+
1198
+ thickness = property(fget=_getThickness)
1199
+
1200
+ layerorientation = property(fget=_getLayerOrientations)
1201
+
1202
+ layerThicknesses = property(fget=_getLayerThicknesses)
1203
+
1204
+ moduli = property(fget=_getModuli)
1205
+
1206
+ isComposite = property(fget=_isComposite)
1207
+
1208
+ abd = property(fget=getABD)
1209
+
1210
+ numberOfLayers = property(fget=_getNumberOfLayers)
1211
+
1212
+ laminationParameters = property(fget=_getLaminationParameters)
1213
+
1214
+
1215
+ class Layer:
1216
+ """This class describes one layer of a composite"""
1217
+
1218
+ def __init__(self, **kwargs):
1219
+ """doc"""
1220
+ self.id = None
1221
+ self.name = None
1222
+ self.description = None
1223
+ self.phi = None
1224
+ """region between [0,180] degrees"""
1225
+ self.xPath = None
1226
+ self.laminationParameters = None
1227
+ self.thickness = None
1228
+ self._materialDefinition = None
1229
+
1230
+ for key in kwargs:
1231
+ if not hasattr(self, key):
1232
+ log.warning(f'Setting unknown key "{key}" in class {self.__class__} with name "{str(self)}"')
1233
+ setattr(self, key, kwargs[key])
1234
+
1235
+ def copy(self):
1236
+ """Returns a copy of self"""
1237
+ return Layer(
1238
+ id=self.id,
1239
+ name=self.name,
1240
+ description=self.description,
1241
+ phi=self.phi,
1242
+ thickness=self.thickness,
1243
+ _materialDefinition=self._materialDefinition,
1244
+ )
1245
+
1246
+ def _setMaterialDefinition(self, materialDefinition):
1247
+ """doc"""
1248
+ if self.phi:
1249
+ materialDefinition.usedAngles.add(int(self.phi))
1250
+ self._materialDefinition = materialDefinition
1251
+
1252
+ def _getMaterialDefinition(self):
1253
+ """doc"""
1254
+ return self._materialDefinition
1255
+
1256
+ def getInfoString(self):
1257
+ """doc"""
1258
+ return f"{self.materialDefinition.name},\t{self.phi},\t{self.thickness}"
1259
+
1260
+ materialDefinition = property(fset=_setMaterialDefinition, fget=_getMaterialDefinition)