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,139 @@
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
+ documentation
7
+ """
8
+
9
+ import numpy as np
10
+
11
+ from patme.service.exceptions import ImproperParameterError
12
+
13
+
14
+ class Translation(np.ndarray):
15
+ """
16
+ This class describes a point in 3D space which is represented by a vector of length 3.
17
+
18
+ With this class points in 3D space can be created and operations may be performed.
19
+ For that purpose the class inherits from np.ndarray which is a class for storing
20
+ matrix values and performing operations on them. For further details on np.ndarray
21
+ please refer to the numpy/scipy documentation.
22
+
23
+ Usage::
24
+
25
+ >>> from patme.geometry.translate import Translation
26
+ >>> t = Translation([1,2,0])
27
+ >>> t
28
+ Translation([1, 2, 0])
29
+ >>> # getting only one coordinate
30
+ >>> t.x
31
+ 1
32
+ >>> # setting only one coordinate value
33
+ >>> t.x = 5
34
+ >>> t
35
+ Translation([5, 2, 0])
36
+ >>> #adding translations
37
+ >>> t2 = t + t
38
+ >>> t2
39
+ Translation([10, 4, 0])
40
+ >>> # getting the distance between two translations
41
+ >>> t.distance(t2)
42
+ 5.385164807134504
43
+ >>> # checking coordinate equality
44
+ >>> t3=Translation([5,2,0])
45
+ >>> t==t;t==t2;t==t3
46
+ True
47
+ False
48
+ True
49
+
50
+ """
51
+
52
+ __hash__ = object.__hash__
53
+ """hash reimplementation due to definition of __eq__. See __hash__ doc for more details.
54
+ https://docs.python.org/3.4/reference/datamodel.html#object.__hash__"""
55
+
56
+ def __new__(cls, input_array=None, id=None):
57
+ """constructing np.ndarray instance. For more information see
58
+ http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
59
+ # Input array is an already formed ndarray instance
60
+ # We first cast to be our class type
61
+ if input_array is None:
62
+ input_array = np.zeros(3)
63
+ if not hasattr(input_array, "shape"):
64
+ input_array = np.asarray(input_array)
65
+ if not input_array.shape == (3,):
66
+ raise ImproperParameterError(
67
+ "The given translation must be a vector with 3 elements. Got this instead: %s" % input_array
68
+ )
69
+
70
+ obj = input_array.view(cls)
71
+ # cope with numerical inaccuracy
72
+ setZero = abs(obj) < 1e-15
73
+ if np.any(setZero):
74
+ obj[setZero] = 0.0
75
+ # Finally, we must return the newly created object:
76
+ return obj
77
+
78
+ def distance(self, toPoint):
79
+ """doc"""
80
+ return np.linalg.norm(self - toPoint)
81
+
82
+ def __eq__(self, other):
83
+ """doc"""
84
+ try:
85
+ return np.allclose(self, other)
86
+ except TypeError:
87
+ return False
88
+
89
+ def _getX(self):
90
+ """doc"""
91
+ return self[0]
92
+
93
+ def _setX(self, x):
94
+ """doc"""
95
+ self[0] = x
96
+
97
+ def _getY(self):
98
+ """doc"""
99
+ return self[1]
100
+
101
+ def _setY(self, y):
102
+ """doc"""
103
+ self[1] = y
104
+
105
+ def _getZ(self):
106
+ """doc"""
107
+ return self[2]
108
+
109
+ def _setZ(self, z):
110
+ """doc"""
111
+ self[2] = z
112
+
113
+ x = property(fget=_getX, fset=_setX)
114
+ y = property(fget=_getY, fset=_setY)
115
+ z = property(fget=_getZ, fset=_setZ)
116
+
117
+
118
+ def getNearestPointOrPointsIndex(fromPointOrPoints, toPoints):
119
+ """calculates the index of the toPoint that is closest to fromPointOrPoints
120
+
121
+ :param fromPointOrPoints: point or 2d array with points (fromPointNum, (x,y,z))
122
+ :param toPoints: 2d array with points (toPointNum, (x,y,z))
123
+ :return: index of nearest point or list of indices of nearest points (len = fromPointNum)
124
+ """
125
+ points = np.array(fromPointOrPoints)
126
+ if len(points.shape) == 1:
127
+ return np.argmin(np.linalg.norm(points - toPoints, axis=1))
128
+
129
+ # create 3d arrays
130
+ loadPointsExtended = np.array([toPoints])
131
+ pointsExtended = np.array([[point] for point in points])
132
+ distances = np.linalg.norm(pointsExtended - loadPointsExtended, axis=2)
133
+ return np.argmin(distances, axis=1)
134
+
135
+
136
+ if __name__ == "__main__":
137
+ import doctest
138
+
139
+ doctest.testmod()
@@ -0,0 +1,4 @@
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
@@ -0,0 +1,435 @@
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
+ from collections import OrderedDict
7
+ from enum import IntEnum
8
+
9
+ import numpy as np
10
+ from scipy.linalg import block_diag
11
+
12
+ from patme import epsilon
13
+ from patme.geometry.rotate import Rotation
14
+ from patme.geometry.translate import Translation
15
+ from patme.service.exceptions import ImproperParameterError, InternalError
16
+ from patme.service.stringutils import indent
17
+
18
+ loadComponentNames = ["fx", "fy", "fz", "mx", "my", "mz"]
19
+ loadComponentUpperNames = ["Fx", "Fy", "Fz", "Mx", "My", "Mz"]
20
+ loadcomponentToIndex = dict(
21
+ [
22
+ (name, compIndex)
23
+ for compIndex, name in list(enumerate(loadComponentNames)) + list(enumerate(loadComponentUpperNames))
24
+ ]
25
+ )
26
+
27
+
28
+ class PointTypeOfLoad(IntEnum):
29
+
30
+ keypoint = 1
31
+ meshNode = 2
32
+
33
+
34
+ class Loads(list):
35
+ """This class serves as collection of several single loads."""
36
+
37
+ def __init__(self, *args, **kwargs):
38
+ """doc"""
39
+ list.__init__(self, *args, **kwargs)
40
+
41
+ def getLoad(self, point=None, rotation=None):
42
+ """Sums up all loads referencing to a point and rotation.
43
+
44
+ :param point: object of type Translation (defaultes to aircraft coordinate system [0,0,0])
45
+ :param rotation: object of type Rotation (defaultes to aircraft coordinate system )"""
46
+ if point is None:
47
+ point = Translation((0.0, 0.0, 0.0))
48
+
49
+ loadSum = Load(point=point)
50
+ for load in self:
51
+ loadSum += load.getLoad(point)
52
+
53
+ return loadSum
54
+
55
+ def getCutLoads(self):
56
+ """Retruns an instance of Loads with cutloads of self
57
+
58
+ >>> from patme.geometry.translate import Translation
59
+ >>> loads = Loads([Load([1*loadFactor,2*loadFactor,0,0,0,0], point = Translation([loadFactor,0,0])) for loadFactor in [1,2,3]])
60
+ >>> cutLoads = loads.getCutLoads()
61
+ >>> print(cutLoads)
62
+ [Load([ 6., 12., 0., 0., 0., 16.]), Load([ 5., 10., 0., 0., 0., 6.]), Load([3., 6., 0., 0., 0., 0.])]
63
+ """
64
+ cutLoads = Loads()
65
+ if not self:
66
+ return cutLoads
67
+
68
+ cutLoads.append(self[-1])
69
+ for load in self[-2::-1]: # reversed without last original element
70
+ cutLoads.append(load + cutLoads[-1].getLoad(load.point))
71
+
72
+ return cutLoads[::-1]
73
+
74
+ def removeZeroLoads(self):
75
+ """Removes loads from self that are zero"""
76
+ loadIndexesToRemove = [ix for ix, load in enumerate(self) if np.allclose(load, np.zeros(6))]
77
+ for loadIndex in loadIndexesToRemove[::-1]:
78
+ self.pop(loadIndex)
79
+
80
+ def getLoadsMovedToTranslations(self, translations):
81
+ """This method copies the given loads to the respectively nearest
82
+ translation object of the component(wing or fuselage) and
83
+ returns a list of these loads with damPoints as load.point"""
84
+ translation2NewLoadDict = OrderedDict()
85
+
86
+ # create initial load at each damPoint
87
+ cls = self[0].__class__ if self else Load
88
+ for point in translations:
89
+ translation2NewLoadDict[point] = cls(point=point)
90
+
91
+ for load in self:
92
+ nearestIndex = np.argmin(np.linalg.norm(np.array(translations) - [load.point], axis=1))
93
+ newLoad = load.copy()
94
+ # move load
95
+ newLoad.point = translations[nearestIndex]
96
+ # add load to existing loads at dam-axis
97
+ translation2NewLoadDict[translations[nearestIndex]] += newLoad
98
+
99
+ return Loads(list(translation2NewLoadDict.values()))
100
+
101
+ @staticmethod
102
+ def massesToLoads(masses, acceleration=Translation([0.0, 0.0, 1.0])):
103
+ """converts masses to loads with the given acceleration"""
104
+ loads = Loads()
105
+ for mass in masses:
106
+ load = list(acceleration * mass.mass) + [0.0, 0.0, 0.0]
107
+ loads.append(Load(load, point=mass.coG))
108
+ return loads
109
+
110
+ def getInfoString(self):
111
+ """returns a string with the loads and ref points"""
112
+ body = [[load.tolist() + load.point.tolist()] for load in self]
113
+ header = loadComponentNames
114
+ return indent([header] + body, hasHeader=True)
115
+
116
+ def __iadd__(self, otherLoads):
117
+ """Adds the other Loads to the loads in self.
118
+
119
+ Condition: the length and points of both load lists must be equal or one of
120
+ the load lists must be empty."""
121
+ if len(otherLoads) == 0:
122
+ return self
123
+ elif len(self) == 0:
124
+ self.extend(otherLoads)
125
+ return self
126
+ elif len(self) != len(otherLoads):
127
+ raise ImproperParameterError("The given Loads lists are not of equal length!")
128
+
129
+ for load, otherLoad in zip(self, otherLoads):
130
+ if not np.allclose(load.point, otherLoad.point):
131
+ raise ImproperParameterError("The given Loads do not share the same locations!")
132
+ load += otherLoad
133
+ return self
134
+
135
+
136
+ class Load(np.ndarray):
137
+ """This class represents a Load that is defined by a vector of length 6 (fx,fy,fz,mx,my,mz).
138
+ The load vector is included in a numpy array and can be accessed and altered like this::
139
+
140
+ >>> from patme.mechanics.loads import Load
141
+ >>> load=Load([1,0,0,0,0,0])
142
+ >>> load
143
+ Load([1., 0., 0., 0., 0., 0.])
144
+ >>> load.point
145
+ Translation([0., 0., 0.])
146
+ >>> load.rotation
147
+ Rotation([[1., 0., 0.],
148
+ [0., 1., 0.],
149
+ [0., 0., 1.]])
150
+
151
+ The load can be moved and rotated by the ``point`` and ``rotation`` properties::
152
+
153
+ >>> # translate the load
154
+ >>> # getting a copy of the point object, move it 2[m] in y-direction and
155
+ >>> # move the load - the result is a newly added moment
156
+ >>> p=load.point
157
+ >>> p
158
+ Translation([0., 0., 0.])
159
+ >>> p2=p+[0,2,0]
160
+ >>> p2
161
+ Translation([0., 2., 0.])
162
+ >>> load.point = p2
163
+ >>> load
164
+ Load([1., 0., 0., 0., 0., 2.])
165
+
166
+ >>> # rotate the load
167
+ >>> # getting a copy of the rotation, rotate it by 90 degress around the z-axis
168
+ >>> # and rotate the load
169
+ >>> load=Load([1,0,0,0,0,0])
170
+ >>> r=load.rotation
171
+ >>> r
172
+ Rotation([[1., 0., 0.],
173
+ [0., 1., 0.],
174
+ [0., 0., 1.]])
175
+ >>> r2 = Rotation()
176
+ >>> r2.angles = [0.,0.,np.pi/2]
177
+ >>> r2
178
+ Rotation([[ 6.123234e-17, -1.000000e+00, 0.000000e+00],
179
+ [ 1.000000e+00, 6.123234e-17, -0.000000e+00],
180
+ [ 0.000000e+00, 0.000000e+00, 1.000000e+00]])
181
+ >>> load.rotation = r2
182
+ >>> load
183
+ Load([ 6.123234e-17, -1.000000e+00, 0.000000e+00, 0.000000e+00,
184
+ 0.000000e+00, 0.000000e+00])
185
+
186
+ The point and rotation properties always reference to the aircraft coordinate system.
187
+ Thus one can not operate with them directly - a new point or rotation has
188
+ to be calculated outside of the loads class and applied to the load as
189
+ shown in the description above.
190
+
191
+ """
192
+
193
+ def __new__(cls, input_array=None, point=Translation(), rotation=Rotation(), pointTypeOfLoad=None):
194
+ """constructing np.ndarray instance. For more information see
195
+ http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
196
+ # Input array is an already formed ndarray instance
197
+ # We first cast to be our class type
198
+ if input_array is None:
199
+ input_array = np.zeros(6)
200
+
201
+ if not hasattr(input_array, "shape"):
202
+ input_array = np.asarray(input_array, dtype=np.float64)
203
+
204
+ if not input_array.shape == (6,):
205
+ raise ImproperParameterError(
206
+ "The given load must be a vector with 6 elements. " + "Got this instead: " + str(input_array)
207
+ )
208
+
209
+ obj = input_array.view(cls)
210
+ # add the new attribute to the created instance
211
+ if not isinstance(point, Translation):
212
+ point = Translation(point)
213
+ obj._point = point
214
+
215
+ if not isinstance(rotation, Rotation):
216
+ rotation = Rotation(rotation)
217
+ obj._rotation = rotation
218
+
219
+ obj._pointTypeOfLoad = pointTypeOfLoad
220
+ if pointTypeOfLoad is None:
221
+ obj._pointTypeOfLoad = PointTypeOfLoad.keypoint
222
+
223
+ # Finally, we must return the newly created object:
224
+ return obj
225
+
226
+ def getPickleLoadProperties(self):
227
+ """returns the properties that are not pickled automatically."""
228
+ return (
229
+ self._point,
230
+ self._point.id,
231
+ self._point.cutout,
232
+ self._point.refType,
233
+ self._rotation,
234
+ self._pointTypeOfLoad,
235
+ )
236
+
237
+ def setPickleLoadProperties(self, loadProperties):
238
+ """sets the properties that are not pickled automatically."""
239
+ (
240
+ self._point,
241
+ self._point.id,
242
+ self._point.cutout,
243
+ self._point.refType,
244
+ self._rotation,
245
+ self._pointTypeOfLoad,
246
+ ) = loadProperties
247
+
248
+ def __array_finalize__(self, obj):
249
+ """constructing np.ndarray instance. For more information see
250
+ http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
251
+ # see InfoArray.__array_finalize__ for comments
252
+ if obj is None:
253
+ return
254
+ self._point = getattr(obj, "_point", Translation())
255
+ """Reference position of type model.geometry.translation.Translation in reference to the aircraft coordinate system"""
256
+
257
+ self._rotation = getattr(obj, "_rotation", Rotation())
258
+ """Reference rotation of type model.geometry.rotation.Rotation in reference to the aircraft coordinate system"""
259
+
260
+ self._pointTypeOfLoad = getattr(obj, "_pointTypeOfLoad", PointTypeOfLoad.keypoint)
261
+ """Type of point where load is applied, either geometric keypoint or mesh node"""
262
+
263
+ def copy(self):
264
+ """returns a copy of self"""
265
+ return self.getLoad()
266
+
267
+ def getLoad(self, point=None, rotation=None):
268
+ """returns a copy of the load in reference to optionally differing point and rotation"""
269
+ ret = np.ndarray.copy(self)
270
+ if point is None:
271
+ newPoint = self.point.copy()
272
+ else:
273
+ newPoint = Translation(point).copy()
274
+
275
+ if rotation is None:
276
+ newRotation = self.rotation.copy()
277
+ else:
278
+ newRotation = Rotation(rotation).copy()
279
+
280
+ ret.point = newPoint
281
+ ret.rotation = newRotation
282
+ return ret
283
+
284
+ def getLoadMirrored(self, plane="xz"):
285
+ """returns a copy of this load mirrored at the given global plane
286
+
287
+ :param plane: one of 'xz','xy','yz'
288
+ """
289
+ if not self.rotation.isIdentity:
290
+ NotImplementedError("Mirroring a rotated load is actuallay unsupported. Please contact the developer.")
291
+
292
+ mirrorMask = np.ones(6)
293
+ if plane == "xy":
294
+ mirrorMask[2:5] = -1
295
+ elif plane == "xz":
296
+ mirrorMask[[1, 3, 5]] = -1
297
+ elif plane == "yz":
298
+ mirrorMask[[0, 4, 5]] = -1
299
+ else:
300
+ raise InternalError("got wrong parameter for plane: %s" % plane)
301
+
302
+ newLoad = self.copy()
303
+ # mirror load
304
+ newLoad *= mirrorMask
305
+ # mirror the reference point without changing the load itself
306
+ newLoad._point = self.point * mirrorMask[:3]
307
+
308
+ return newLoad
309
+
310
+ def _getMomentsDueToNewPoint(self, point):
311
+ """this method calculates the moments resulting from moving a force vector.
312
+ It is only used with identity rotation!"""
313
+ # moments due to translation
314
+ return np.cross(self[:3], np.array(point) - self.point)
315
+
316
+ def _setPoint(self, newPoint):
317
+ """resets the reference point of the load. Thus the load is rotated back to
318
+ the aircraft coordinate system rotation to calculate the correct moments due to
319
+ the newPoint. Then it is rotated back to the original rotation."""
320
+ if not self.rotation.isIdentity:
321
+ originalRotation = self.rotation
322
+ self.rotation = Rotation()
323
+
324
+ if np.linalg.norm(self._point - newPoint) > epsilon:
325
+ self[3:6] += self._getMomentsDueToNewPoint(newPoint)
326
+ self._point = newPoint
327
+
328
+ if not self.rotation.isIdentity:
329
+ self.rotation = originalRotation
330
+
331
+ def _getPoint(self):
332
+ """returns the reference point
333
+
334
+ Attention: altering this point object will not affect the loads like"""
335
+ return self._point
336
+
337
+ def _getPositionX(self):
338
+ """returns the x-coordinate of the loads point. (Used for sorting loads)"""
339
+ return self._point.x
340
+
341
+ def _setRotation(self, newRotation):
342
+ """rotates the load back to and identity rotation and then to newRotation"""
343
+
344
+ # rotate to identity: inverse(oldRotation)*forces .....
345
+ if not self._rotation.isIdentity:
346
+ rotInv = np.linalg.inv(self._rotation)
347
+ self[:] = self[:] @ block_diag(rotInv, rotInv)
348
+ # @ operator is equivalent to np.dot and represents matrix-vector multiplication
349
+
350
+ # rotate to new rotation: newRotation*forces ....
351
+ if not newRotation.isIdentity:
352
+ self[:] = self[:] @ block_diag(newRotation, newRotation)
353
+
354
+ self._rotation = newRotation
355
+
356
+ def _getRotation(self):
357
+ """returns a copy of the object's rotation attribute"""
358
+ return self._rotation
359
+
360
+ def __str__(self):
361
+ """doc"""
362
+ return ", ".join(self.astype(str))
363
+
364
+ def _getFX(self):
365
+ return self[0]
366
+
367
+ def _getFY(self):
368
+ return self[1]
369
+
370
+ def _getFZ(self):
371
+ return self[2]
372
+
373
+ def _getMX(self):
374
+ return self[3]
375
+
376
+ def _getMY(self):
377
+ return self[4]
378
+
379
+ def _getMZ(self):
380
+ return self[5]
381
+
382
+ def _setFX(self, fx):
383
+ self[0] = fx
384
+
385
+ def _setFY(self, fy):
386
+ self[1] = fy
387
+
388
+ def _setFZ(self, fz):
389
+ self[2] = fz
390
+
391
+ def _setMX(self, mx):
392
+ self[3] = mx
393
+
394
+ def _setMY(self, my):
395
+ self[4] = my
396
+
397
+ def _setMZ(self, mz):
398
+ self[5] = mz
399
+
400
+ fx = property(fget=_getFX, fset=_setFX)
401
+ fy = property(fget=_getFY, fset=_setFY)
402
+ fz = property(fget=_getFZ, fset=_setFZ)
403
+ mx = property(fget=_getMX, fset=_setMX)
404
+ my = property(fget=_getMY, fset=_setMY)
405
+ mz = property(fget=_getMZ, fset=_setMZ)
406
+
407
+ point = property(fset=_setPoint, fget=_getPoint)
408
+ rotation = property(fset=_setRotation, fget=_getRotation)
409
+ positionX = property(fget=_getPositionX)
410
+
411
+
412
+ if __name__ == "__main__":
413
+ if 0:
414
+ f = Flow()
415
+ f.machNumber = 0.0
416
+ heightsFt = np.array([5000, 6000, 7000, 8000, 9000, 10000, 39000])
417
+ heights = heightsFt * 0.3048
418
+ pressures = []
419
+
420
+ for height in heights:
421
+ f.h = height
422
+ f._p = None
423
+ pressures.append(f.staticPressure)
424
+ pressures = np.array(pressures)
425
+ dPs = pressures - pressures[-1]
426
+ print(
427
+ indent(
428
+ [["height[ft]", "height[m]", "pressure[Pa]", "dP[Pa]"]] + list(zip(heightsFt, heights, pressures, dPs))
429
+ )
430
+ )
431
+
432
+ elif 1:
433
+ import doctest
434
+
435
+ doctest.testmod() # verbose=True