pyroll-core 2.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.
- pyroll/core/__init__.py +38 -0
- pyroll/core/config.py +155 -0
- pyroll/core/disk_elements/__init__.py +2 -0
- pyroll/core/disk_elements/disk_element_unit.py +73 -0
- pyroll/core/disk_elements/hookimpls.py +26 -0
- pyroll/core/grooves/__init__.py +10 -0
- pyroll/core/grooves/base.py +59 -0
- pyroll/core/grooves/boxes/__init__.py +3 -0
- pyroll/core/grooves/boxes/box.py +57 -0
- pyroll/core/grooves/boxes/constricted_box.py +63 -0
- pyroll/core/grooves/boxes/upset_box.py +18 -0
- pyroll/core/grooves/diamonds/__init__.py +3 -0
- pyroll/core/grooves/diamonds/diamond.py +60 -0
- pyroll/core/grooves/diamonds/gothic.py +41 -0
- pyroll/core/grooves/diamonds/square.py +48 -0
- pyroll/core/grooves/flat.py +31 -0
- pyroll/core/grooves/generic_elongation.py +258 -0
- pyroll/core/grooves/generic_elongation_solvers.py +422 -0
- pyroll/core/grooves/hexagonal.py +57 -0
- pyroll/core/grooves/ovals/__init__.py +8 -0
- pyroll/core/grooves/ovals/circular_oval.py +43 -0
- pyroll/core/grooves/ovals/constricted_circular_oval.py +53 -0
- pyroll/core/grooves/ovals/constricted_swedish_oval.py +61 -0
- pyroll/core/grooves/ovals/flat_oval.py +56 -0
- pyroll/core/grooves/ovals/oval_3radii.py +47 -0
- pyroll/core/grooves/ovals/oval_3radii_flanked.py +65 -0
- pyroll/core/grooves/ovals/swedish_oval.py +57 -0
- pyroll/core/grooves/ovals/upset_oval.py +47 -0
- pyroll/core/grooves/rounds/__init__.py +2 -0
- pyroll/core/grooves/rounds/false_round.py +65 -0
- pyroll/core/grooves/rounds/round.py +44 -0
- pyroll/core/grooves/spline.py +92 -0
- pyroll/core/hooks.py +420 -0
- pyroll/core/log.py +7 -0
- pyroll/core/profile/__init__.py +3 -0
- pyroll/core/profile/hookimpls.py +58 -0
- pyroll/core/profile/profile.py +380 -0
- pyroll/core/repr.py +49 -0
- pyroll/core/roll/__init__.py +3 -0
- pyroll/core/roll/hookimpls.py +65 -0
- pyroll/core/roll/roll.py +145 -0
- pyroll/core/roll_pass/__init__.py +8 -0
- pyroll/core/roll_pass/deformation_unit.py +61 -0
- pyroll/core/roll_pass/hookimpls/__init__.py +5 -0
- pyroll/core/roll_pass/hookimpls/deformation_unit.py +78 -0
- pyroll/core/roll_pass/hookimpls/disk_element.py +16 -0
- pyroll/core/roll_pass/hookimpls/profile.py +94 -0
- pyroll/core/roll_pass/hookimpls/roll.py +43 -0
- pyroll/core/roll_pass/hookimpls/roll_pass.py +107 -0
- pyroll/core/roll_pass/roll_pass.py +168 -0
- pyroll/core/roll_pass/three_roll_pass.py +84 -0
- pyroll/core/rotator/__init__.py +3 -0
- pyroll/core/rotator/hookimpls.py +71 -0
- pyroll/core/rotator/rotator.py +65 -0
- pyroll/core/sequence/__init__.py +3 -0
- pyroll/core/sequence/hookimpls.py +18 -0
- pyroll/core/sequence/sequence.py +68 -0
- pyroll/core/shapes.py +104 -0
- pyroll/core/transport/__init__.py +3 -0
- pyroll/core/transport/hookimpls.py +17 -0
- pyroll/core/transport/transport.py +57 -0
- pyroll/core/unit/__init__.py +3 -0
- pyroll/core/unit/hookimpls.py +67 -0
- pyroll/core/unit/unit.py +271 -0
- pyroll_core-2.0.0.dist-info/METADATA +109 -0
- pyroll_core-2.0.0.dist-info/RECORD +68 -0
- pyroll_core-2.0.0.dist-info/WHEEL +4 -0
- pyroll_core-2.0.0.dist-info/licenses/LICENSE +11 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from .rotator import Rotator
|
|
2
|
+
from shapely.affinity import rotate
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@Rotator.OutProfile.cross_section
|
|
6
|
+
def rotated_cross_section(self: Rotator.OutProfile):
|
|
7
|
+
return rotate(self.rotator.in_profile.cross_section, angle=self.rotator.rotation, origin=(0, 0))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@Rotator.OutProfile.classifiers
|
|
11
|
+
def classifiers(self: Rotator.OutProfile):
|
|
12
|
+
r = self.rotator
|
|
13
|
+
t: set = r.in_profile.classifiers | {"rotated"}
|
|
14
|
+
|
|
15
|
+
if r.rotation == 45:
|
|
16
|
+
t.add("edged")
|
|
17
|
+
|
|
18
|
+
elif r.rotation == 90:
|
|
19
|
+
t.add("vertical")
|
|
20
|
+
|
|
21
|
+
elif r.rotation == 180:
|
|
22
|
+
t.add("mirrored")
|
|
23
|
+
|
|
24
|
+
return t
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@Rotator.rotation
|
|
28
|
+
def default_90(self: Rotator):
|
|
29
|
+
return 90
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@Rotator.rotation
|
|
33
|
+
def square_oval_45(self: Rotator):
|
|
34
|
+
if "square" in self.in_profile.classifiers and "oval" in self.next_roll_pass.classifiers:
|
|
35
|
+
return 45
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@Rotator.rotation
|
|
39
|
+
def square_box_45(self: Rotator):
|
|
40
|
+
if "square" in self.in_profile.classifiers and "box" in self.next_roll_pass.classifiers:
|
|
41
|
+
return 45
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@Rotator.rotation
|
|
45
|
+
def box_diamond_45(self: Rotator):
|
|
46
|
+
if "box" in self.in_profile.classifiers and "diamond" in self.next_roll_pass.classifiers:
|
|
47
|
+
return 45
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@Rotator.rotation
|
|
51
|
+
def square_flat_45(self: Rotator):
|
|
52
|
+
if "square" in self.in_profile.classifiers and "flat" in self.next_roll_pass.classifiers:
|
|
53
|
+
return 45
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@Rotator.rotation
|
|
57
|
+
def oval_flat_0(self: Rotator):
|
|
58
|
+
if "oval" in self.in_profile.classifiers and "flat" in self.next_roll_pass.classifiers:
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@Rotator.rotation
|
|
63
|
+
def upset_in_0(self: Rotator):
|
|
64
|
+
if "upset" in self.in_profile.classifiers:
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@Rotator.rotation
|
|
69
|
+
def three_roll_pass_180(self: Rotator):
|
|
70
|
+
if "3fold" in self.next_roll_pass.classifiers:
|
|
71
|
+
return 180
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
3
|
+
from ..hooks import Hook
|
|
4
|
+
from ..unit import Unit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Rotator(Unit):
|
|
8
|
+
"""Represents a unit rotating a profile around the rolling axis (mostly for feeding into next roll pass)."""
|
|
9
|
+
|
|
10
|
+
rotation = Hook[float]()
|
|
11
|
+
"""Rotation applied to the profile in ° (degree)."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def prev_roll_pass(self):
|
|
15
|
+
"""
|
|
16
|
+
Returns a reference to the first preceding roll pass of this unit in the sequence.
|
|
17
|
+
If the parent of this unit is a roll pass,
|
|
18
|
+
it is considered as the next roll pass, so the preceding of this is searched.
|
|
19
|
+
|
|
20
|
+
:raises ValueError: if this unit has no parent unit
|
|
21
|
+
"""
|
|
22
|
+
from ..roll_pass import RollPass
|
|
23
|
+
|
|
24
|
+
if isinstance(self.parent, RollPass):
|
|
25
|
+
prev = self.parent.prev
|
|
26
|
+
else:
|
|
27
|
+
prev = self.prev
|
|
28
|
+
|
|
29
|
+
while True:
|
|
30
|
+
if isinstance(prev, RollPass):
|
|
31
|
+
return prev
|
|
32
|
+
prev = prev.prev
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def next_roll_pass(self):
|
|
36
|
+
"""
|
|
37
|
+
Returns a reference to the first succeeding roll pass of this unit in the sequence.
|
|
38
|
+
If the parent of this unit is a roll pass, it is considered as the next roll pass.
|
|
39
|
+
|
|
40
|
+
:raises ValueError: if this unit has no parent unit
|
|
41
|
+
"""
|
|
42
|
+
from ..roll_pass import RollPass
|
|
43
|
+
|
|
44
|
+
if isinstance(self.parent, RollPass):
|
|
45
|
+
return self.parent
|
|
46
|
+
|
|
47
|
+
next_ = self.next
|
|
48
|
+
while True:
|
|
49
|
+
if isinstance(next_, RollPass):
|
|
50
|
+
return next_
|
|
51
|
+
next_ = next_.next
|
|
52
|
+
|
|
53
|
+
class Profile(Unit.Profile):
|
|
54
|
+
"""Represents a profile in context of a rotator."""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def rotator(self) -> 'Rotator':
|
|
58
|
+
"""Reference to the rotator. Alias for ``self.unit``."""
|
|
59
|
+
return cast(Rotator, self.unit)
|
|
60
|
+
|
|
61
|
+
class InProfile(Profile, Unit.InProfile):
|
|
62
|
+
"""Represents an incoming profile of a rotator."""
|
|
63
|
+
|
|
64
|
+
class OutProfile(Profile, Unit.OutProfile):
|
|
65
|
+
"""Represents an outgoing profile of a rotator."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .sequence import PassSequence
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@PassSequence.total_elongation
|
|
7
|
+
def total_elongation(self: PassSequence):
|
|
8
|
+
return self.in_profile.cross_section.area / self.out_profile.cross_section.area
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@PassSequence.duration
|
|
12
|
+
def duration(self: PassSequence):
|
|
13
|
+
return np.sum([u.duration for u in self.units])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@PassSequence.length
|
|
17
|
+
def length(self: PassSequence):
|
|
18
|
+
return np.sum([u.length for u in self.units])
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import overload, List, cast
|
|
3
|
+
|
|
4
|
+
from ..unit import Unit
|
|
5
|
+
from ..hooks import Hook
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PassSequence(Unit, Sequence[Unit]):
|
|
9
|
+
"""
|
|
10
|
+
Represents a sequence of other units.
|
|
11
|
+
Main container for defining rolling processes.
|
|
12
|
+
Can be nested to define distinct rolling lines or similar.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
total_elongation = Hook[float]()
|
|
16
|
+
"""Total elongation of the workpiece within the sequence."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, units: Sequence[Unit], label: str = "", **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
:param units: sequence of unit objects
|
|
21
|
+
:param label: label for human identification
|
|
22
|
+
:param kwargs: additional hook values as keyword arguments to set explicitly
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
super().__init__(label=label)
|
|
26
|
+
self.__dict__.update(kwargs)
|
|
27
|
+
self._subunits = self._SubUnitsList(self, units)
|
|
28
|
+
|
|
29
|
+
class Profile(Unit.Profile):
|
|
30
|
+
"""Represents a profile in context of a pass sequence unit."""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def pass_sequence(self) -> 'PassSequence':
|
|
34
|
+
"""Reference to the pass sequence. Alias for ``self.unit``."""
|
|
35
|
+
return cast(PassSequence, self.unit)
|
|
36
|
+
|
|
37
|
+
class InProfile(Profile, Unit.InProfile):
|
|
38
|
+
"""Represents an incoming profile of a pass sequence unit."""
|
|
39
|
+
|
|
40
|
+
class OutProfile(Profile, Unit.OutProfile):
|
|
41
|
+
"""Represents an outgoing profile of a pass sequence unit."""
|
|
42
|
+
|
|
43
|
+
def __len__(self) -> int:
|
|
44
|
+
return self._subunits.__len__()
|
|
45
|
+
|
|
46
|
+
def __iter__(self):
|
|
47
|
+
return self._subunits.__iter__()
|
|
48
|
+
|
|
49
|
+
@overload
|
|
50
|
+
def __getitem__(self, index: int) -> Unit:
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
@overload
|
|
54
|
+
def __getitem__(self, index: slice) -> list[Unit]:
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def __getitem__(self, index: int) -> Unit:
|
|
58
|
+
return self._subunits.__getitem__(index)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def units(self) -> List[Unit]:
|
|
62
|
+
return self._subunits
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def __attrs__(self):
|
|
66
|
+
return super().__attrs__ | {
|
|
67
|
+
"units": self.units
|
|
68
|
+
}
|
pyroll/core/shapes.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pyroll.core.repr import ReprMixin
|
|
4
|
+
from shapely import Polygon, LineString
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# noinspection PyAbstractClass
|
|
8
|
+
class PatchedPolygon(Polygon):
|
|
9
|
+
@property
|
|
10
|
+
def height(self) -> float:
|
|
11
|
+
"""Computes the height of the bounding box."""
|
|
12
|
+
return self.bounds[3] - self.bounds[1]
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def width(self) -> float:
|
|
16
|
+
"""Computes the width of the bounding box."""
|
|
17
|
+
return self.bounds[2] - self.bounds[0]
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def perimeter(self) -> float:
|
|
21
|
+
"""Get the perimeter of the Polygon (alias of ``Polygon.length``)."""
|
|
22
|
+
return self.length
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def __attrs__(self):
|
|
26
|
+
return {
|
|
27
|
+
"width": self.width,
|
|
28
|
+
"height": self.height,
|
|
29
|
+
"perimeter": self.perimeter,
|
|
30
|
+
"area": self.area,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Polygon.height = PatchedPolygon.height
|
|
35
|
+
Polygon.width = PatchedPolygon.width
|
|
36
|
+
Polygon.perimeter = PatchedPolygon.perimeter
|
|
37
|
+
Polygon.__attrs__ = PatchedPolygon.__attrs__
|
|
38
|
+
Polygon.__str__ = ReprMixin.__str__
|
|
39
|
+
Polygon.__repr__ = ReprMixin.__repr__
|
|
40
|
+
# noinspection PyProtectedMember
|
|
41
|
+
Polygon._repr_html_ = ReprMixin._repr_html_
|
|
42
|
+
# noinspection PyProtectedMember
|
|
43
|
+
Polygon._repr_pretty_ = ReprMixin._repr_pretty_
|
|
44
|
+
|
|
45
|
+
_RECTANGLE_CORNERS = np.asarray(
|
|
46
|
+
[
|
|
47
|
+
(-0.5, -0.5),
|
|
48
|
+
(0.5, -0.5),
|
|
49
|
+
(0.5, 0.5),
|
|
50
|
+
(-0.5, 0.5)
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def rectangle(width: float, height: float):
|
|
56
|
+
"""
|
|
57
|
+
Creates an instance of :py:class:`Polygon` with rectangular shape from height and width aligned to the axes.
|
|
58
|
+
|
|
59
|
+
:param width: the width of the rectangle
|
|
60
|
+
:param height: the height of the rectangle
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
width = float(width)
|
|
65
|
+
height = float(height)
|
|
66
|
+
|
|
67
|
+
except TypeError as e:
|
|
68
|
+
raise TypeError("width and height must be convertible to float") from e
|
|
69
|
+
|
|
70
|
+
points = _RECTANGLE_CORNERS * (width, height)
|
|
71
|
+
rect = Polygon(points)
|
|
72
|
+
|
|
73
|
+
return rect
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PatchedLineString(LineString):
|
|
77
|
+
@property
|
|
78
|
+
def depth(self) -> float:
|
|
79
|
+
"""Computes the height of the bounding box."""
|
|
80
|
+
return self.bounds[3] - self.bounds[1]
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def width(self) -> float:
|
|
84
|
+
"""Computes the width of the bounding box."""
|
|
85
|
+
return self.bounds[2] - self.bounds[0]
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def __attrs__(self):
|
|
89
|
+
return {
|
|
90
|
+
"width": self.width,
|
|
91
|
+
"depth": self.depth,
|
|
92
|
+
"length": self.length,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
LineString.depth = PatchedLineString.depth
|
|
97
|
+
LineString.width = PatchedLineString.width
|
|
98
|
+
LineString.__attrs__ = PatchedLineString.__attrs__
|
|
99
|
+
LineString.__str__ = ReprMixin.__str__
|
|
100
|
+
LineString.__repr__ = ReprMixin.__repr__
|
|
101
|
+
# noinspection PyProtectedMember
|
|
102
|
+
LineString._repr_html_ = ReprMixin._repr_html_
|
|
103
|
+
# noinspection PyProtectedMember
|
|
104
|
+
LineString._repr_pretty_ = ReprMixin._repr_pretty_
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .transport import Transport
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@Transport.OutProfile.strain
|
|
5
|
+
def out_strain(self: Transport.OutProfile):
|
|
6
|
+
"""Assume total recrystallization during transport."""
|
|
7
|
+
return 0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@Transport.duration
|
|
11
|
+
def duration(self: Transport):
|
|
12
|
+
return self.length / self.velocity
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@Transport.environment_temperature
|
|
16
|
+
def environment_temperature(self):
|
|
17
|
+
return 293
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import List, cast
|
|
2
|
+
|
|
3
|
+
from ..hooks import Hook
|
|
4
|
+
from ..disk_elements import DiskElementUnit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Transport(DiskElementUnit):
|
|
8
|
+
"""Represents a transport unit, e.g. an inter-rolling-stand gap, a furnace or cooling range."""
|
|
9
|
+
|
|
10
|
+
environment_temperature = Hook[float]()
|
|
11
|
+
"""Temperature of the surrounding atmosphere."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def disk_elements(self) -> List['Transport.DiskElement']:
|
|
15
|
+
"""A list of disk elements used to subdivide this unit."""
|
|
16
|
+
return self._subunits
|
|
17
|
+
|
|
18
|
+
class Profile(DiskElementUnit.Profile):
|
|
19
|
+
"""Represents a profile in context of a transport unit."""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def transport(self) -> 'Transport':
|
|
23
|
+
"""Reference to the transport. Alias for ``self.unit``."""
|
|
24
|
+
return cast(Transport, self.unit)
|
|
25
|
+
|
|
26
|
+
class InProfile(Profile, DiskElementUnit.InProfile):
|
|
27
|
+
"""Represents an incoming profile of a transport unit."""
|
|
28
|
+
|
|
29
|
+
class OutProfile(Profile, DiskElementUnit.OutProfile):
|
|
30
|
+
"""Represents an outgoing profile of a transport unit."""
|
|
31
|
+
|
|
32
|
+
class DiskElement(DiskElementUnit.DiskElement):
|
|
33
|
+
"""Represents a disk element in a roll pass."""
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def transport(self) -> 'Transport':
|
|
37
|
+
"""Reference to the transport. Alias for ``self.parent``."""
|
|
38
|
+
return cast(Transport, self.parent)
|
|
39
|
+
|
|
40
|
+
class Profile(DiskElementUnit.DiskElement.Profile):
|
|
41
|
+
"""Represents a profile in context of a disk element unit."""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def disk_element(self) -> 'Transport.DiskElement':
|
|
45
|
+
"""Reference to the disk element. Alias for ``self.unit``"""
|
|
46
|
+
return cast(Transport.DiskElement, self.unit)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def transport(self) -> 'Transport':
|
|
50
|
+
"""Reference to the transport. Alias for ``self.unit.parent``"""
|
|
51
|
+
return cast(Transport, self.unit.parent)
|
|
52
|
+
|
|
53
|
+
class InProfile(Profile, DiskElementUnit.DiskElement.InProfile):
|
|
54
|
+
"""Represents an incoming profile of a disk element unit."""
|
|
55
|
+
|
|
56
|
+
class OutProfile(Profile, DiskElementUnit.DiskElement.OutProfile):
|
|
57
|
+
"""Represents an outgoing profile of a disk element unit."""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from ..config import Config
|
|
2
|
+
from .unit import Unit
|
|
3
|
+
|
|
4
|
+
for h in Unit.OutProfile.__hooks__:
|
|
5
|
+
@getattr(Unit.OutProfile, h)(trylast=True)
|
|
6
|
+
def copy_from_in_profile(self: Unit.OutProfile, hook=h):
|
|
7
|
+
return getattr(self.unit.in_profile, hook, None)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@getattr(Unit.OutProfile, h)
|
|
11
|
+
def copy_from_last_subunit(self: Unit.OutProfile, hook=h):
|
|
12
|
+
if self.unit.subunits:
|
|
13
|
+
return getattr(self.unit.subunits[-1].out_profile, hook, None)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@Unit.iteration_precision
|
|
17
|
+
def default_iteration_precision(self: Unit):
|
|
18
|
+
return Config.DEFAULT_ITERATION_PRECISION
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@Unit.max_iteration_count
|
|
22
|
+
def default_max_iteration_count(self: Unit):
|
|
23
|
+
return Config.DEFAULT_MAX_ITERATION_COUNT
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@Unit.volume
|
|
27
|
+
def volume(self: Unit):
|
|
28
|
+
return (self.in_profile.cross_section.area + self.out_profile.cross_section.area) / 2 * self.length
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@Unit.surface_area
|
|
32
|
+
def surface_area(self: Unit):
|
|
33
|
+
return (self.in_profile.cross_section.perimeter + self.out_profile.cross_section.perimeter) / 2 * self.length
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@Unit.length
|
|
37
|
+
def length(self: Unit):
|
|
38
|
+
if self.has_set_or_cached("duration"):
|
|
39
|
+
return self.velocity * self.duration
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@Unit.duration
|
|
43
|
+
def duration(self: Unit):
|
|
44
|
+
if self.has_set_or_cached("length"):
|
|
45
|
+
return self.length / self.velocity
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@Unit.velocity
|
|
49
|
+
def velocity(self: Unit):
|
|
50
|
+
if self.in_profile.has_set_or_cached("velocity"):
|
|
51
|
+
return self.in_profile.velocity
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@Unit.OutProfile.velocity
|
|
55
|
+
def out_velocity(self: Unit.OutProfile):
|
|
56
|
+
if self.unit.in_profile.has_set_or_cached("velocity"):
|
|
57
|
+
return self.unit.in_profile.velocity * self.unit.in_profile.cross_section.area / self.cross_section.area
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@Unit.OutProfile.x
|
|
61
|
+
def out_x(self: Unit.OutProfile):
|
|
62
|
+
return self.unit.in_profile.x + self.unit.length
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@Unit.OutProfile.t
|
|
66
|
+
def out_t(self: Unit.OutProfile):
|
|
67
|
+
return self.unit.in_profile.t + self.unit.duration
|