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.
Files changed (68) hide show
  1. pyroll/core/__init__.py +38 -0
  2. pyroll/core/config.py +155 -0
  3. pyroll/core/disk_elements/__init__.py +2 -0
  4. pyroll/core/disk_elements/disk_element_unit.py +73 -0
  5. pyroll/core/disk_elements/hookimpls.py +26 -0
  6. pyroll/core/grooves/__init__.py +10 -0
  7. pyroll/core/grooves/base.py +59 -0
  8. pyroll/core/grooves/boxes/__init__.py +3 -0
  9. pyroll/core/grooves/boxes/box.py +57 -0
  10. pyroll/core/grooves/boxes/constricted_box.py +63 -0
  11. pyroll/core/grooves/boxes/upset_box.py +18 -0
  12. pyroll/core/grooves/diamonds/__init__.py +3 -0
  13. pyroll/core/grooves/diamonds/diamond.py +60 -0
  14. pyroll/core/grooves/diamonds/gothic.py +41 -0
  15. pyroll/core/grooves/diamonds/square.py +48 -0
  16. pyroll/core/grooves/flat.py +31 -0
  17. pyroll/core/grooves/generic_elongation.py +258 -0
  18. pyroll/core/grooves/generic_elongation_solvers.py +422 -0
  19. pyroll/core/grooves/hexagonal.py +57 -0
  20. pyroll/core/grooves/ovals/__init__.py +8 -0
  21. pyroll/core/grooves/ovals/circular_oval.py +43 -0
  22. pyroll/core/grooves/ovals/constricted_circular_oval.py +53 -0
  23. pyroll/core/grooves/ovals/constricted_swedish_oval.py +61 -0
  24. pyroll/core/grooves/ovals/flat_oval.py +56 -0
  25. pyroll/core/grooves/ovals/oval_3radii.py +47 -0
  26. pyroll/core/grooves/ovals/oval_3radii_flanked.py +65 -0
  27. pyroll/core/grooves/ovals/swedish_oval.py +57 -0
  28. pyroll/core/grooves/ovals/upset_oval.py +47 -0
  29. pyroll/core/grooves/rounds/__init__.py +2 -0
  30. pyroll/core/grooves/rounds/false_round.py +65 -0
  31. pyroll/core/grooves/rounds/round.py +44 -0
  32. pyroll/core/grooves/spline.py +92 -0
  33. pyroll/core/hooks.py +420 -0
  34. pyroll/core/log.py +7 -0
  35. pyroll/core/profile/__init__.py +3 -0
  36. pyroll/core/profile/hookimpls.py +58 -0
  37. pyroll/core/profile/profile.py +380 -0
  38. pyroll/core/repr.py +49 -0
  39. pyroll/core/roll/__init__.py +3 -0
  40. pyroll/core/roll/hookimpls.py +65 -0
  41. pyroll/core/roll/roll.py +145 -0
  42. pyroll/core/roll_pass/__init__.py +8 -0
  43. pyroll/core/roll_pass/deformation_unit.py +61 -0
  44. pyroll/core/roll_pass/hookimpls/__init__.py +5 -0
  45. pyroll/core/roll_pass/hookimpls/deformation_unit.py +78 -0
  46. pyroll/core/roll_pass/hookimpls/disk_element.py +16 -0
  47. pyroll/core/roll_pass/hookimpls/profile.py +94 -0
  48. pyroll/core/roll_pass/hookimpls/roll.py +43 -0
  49. pyroll/core/roll_pass/hookimpls/roll_pass.py +107 -0
  50. pyroll/core/roll_pass/roll_pass.py +168 -0
  51. pyroll/core/roll_pass/three_roll_pass.py +84 -0
  52. pyroll/core/rotator/__init__.py +3 -0
  53. pyroll/core/rotator/hookimpls.py +71 -0
  54. pyroll/core/rotator/rotator.py +65 -0
  55. pyroll/core/sequence/__init__.py +3 -0
  56. pyroll/core/sequence/hookimpls.py +18 -0
  57. pyroll/core/sequence/sequence.py +68 -0
  58. pyroll/core/shapes.py +104 -0
  59. pyroll/core/transport/__init__.py +3 -0
  60. pyroll/core/transport/hookimpls.py +17 -0
  61. pyroll/core/transport/transport.py +57 -0
  62. pyroll/core/unit/__init__.py +3 -0
  63. pyroll/core/unit/hookimpls.py +67 -0
  64. pyroll/core/unit/unit.py +271 -0
  65. pyroll_core-2.0.0.dist-info/METADATA +109 -0
  66. pyroll_core-2.0.0.dist-info/RECORD +68 -0
  67. pyroll_core-2.0.0.dist-info/WHEEL +4 -0
  68. 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,3 @@
1
+ from .sequence import PassSequence
2
+
3
+ from. import hookimpls
@@ -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,3 @@
1
+ from .transport import Transport
2
+
3
+ from . import hookimpls
@@ -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,3 @@
1
+ from .unit import Unit
2
+
3
+ from . import hookimpls
@@ -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