Skip to content

Commit 2d82aed

Browse files
committed
add common symmetric roll passes base class
1 parent 1b5cbba commit 2d82aed

16 files changed

Lines changed: 533 additions & 421 deletions

pyroll/core/roll/hookimpls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def contour_points(self: Roll):
5151

5252
@Roll.surface_x
5353
def surface_x(self: Roll):
54-
padded_contact_angle = np.arcsin(1.1 * self.contact_length / self.min_radius)
54+
padded_contact_angle = np.arcsin(1.1 * self.contact_length / self.min_radius) if self.has_set_or_cached("contact_length") else np.pi / 4
5555
points = np.concatenate([
5656
np.linspace(0, padded_contact_angle, Config.ROLL_SURFACE_DISCRETIZATION_COUNT, endpoint=False),
5757
np.linspace(padded_contact_angle, np.pi / 2, Config.ROLL_SURFACE_DISCRETIZATION_COUNT),

pyroll/core/roll_pass/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from .base import BaseRollPass
2-
from .roll_pass import RollPass
2+
from .two_roll_pass import TwoRollPass
33
from .three_roll_pass import ThreeRollPass
44
from .deformation_unit import DeformationUnit
55

66
from . import hookimpls
77

8+
RollPass = TwoRollPass
9+
810

911

pyroll/core/roll_pass/base.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import weakref
2+
from abc import abstractmethod, ABC
23
from typing import List, Union, cast
34

45
import numpy as np
@@ -13,7 +14,7 @@
1314
from .deformation_unit import DeformationUnit
1415

1516

16-
class BaseRollPass(DiskElementUnit, DeformationUnit):
17+
class BaseRollPass(DiskElementUnit, DeformationUnit, ABC):
1718
"""Represents a roll pass with two symmetric working rolls."""
1819

1920
rotation = Hook[Union[bool, float]]()
@@ -63,15 +64,9 @@ class BaseRollPass(DiskElementUnit, DeformationUnit):
6364
entry_point = Hook[float]()
6465
"""Point where the material enters the roll gap."""
6566

66-
entry_angle = Hook[float]()
67-
"""Angle at which the material enters the roll gap."""
68-
6967
exit_point = Hook[float]()
7068
"""Point where the material exits the roll gap."""
7169

72-
exit_angle = Hook[float]()
73-
"""Angle at which the material exits the roll gap."""
74-
7570
front_tension = Hook[float]()
7671
"""Front tension acting on the current roll pass."""
7772

@@ -101,7 +96,6 @@ class BaseRollPass(DiskElementUnit, DeformationUnit):
10196

10297
def __init__(
10398
self,
104-
roll: BaseRoll,
10599
label: str = "",
106100
**kwargs
107101
):
@@ -113,21 +107,20 @@ def __init__(
113107

114108
super().__init__(label=label, **kwargs)
115109

116-
self.roll = self.Roll(roll, self)
117-
"""The working roll of this pass (equal upper and lower)."""
118-
119110
self._contour_lines = None
120111

121112
@property
113+
@abstractmethod
122114
def contour_lines(self):
123115
"""List of line strings bounding the roll pass at the high point."""
124-
raise NotImplementedError
116+
raise NotImplementedError()
125117

126118
@property
119+
@abstractmethod
127120
def classifiers(self):
128121
"""A tuple of keywords to specify the shape type classifiers of this roll pass.
129122
Shortcut to ``self.groove.classifiers``."""
130-
return set(self.roll.groove.classifiers)
123+
raise NotImplementedError()
131124

132125
@property
133126
def disk_elements(self) -> List['BaseRollPass.DiskElement']:
@@ -148,12 +141,6 @@ def init_solve(self, in_profile: BaseProfile):
148141
super().init_solve(in_profile)
149142
self.out_profile.cross_section = self.usable_cross_section
150143

151-
def get_root_hook_results(self):
152-
super_results = super().get_root_hook_results()
153-
roll_results = self.roll.evaluate_and_set_hooks()
154-
155-
return np.concatenate([super_results, roll_results], axis=0)
156-
157144
def reevaluate_cache(self):
158145
super().reevaluate_cache()
159146
self.roll.reevaluate_cache()
@@ -197,6 +184,12 @@ def __init__(self, template: BaseRoll, roll_pass: 'BaseRollPass'):
197184

198185
self._roll_pass = weakref.ref(roll_pass)
199186

187+
entry_angle = Hook[float]()
188+
"""Angle at which the material enters the roll gap."""
189+
190+
exit_angle = Hook[float]()
191+
"""Angle at which the material exits the roll gap."""
192+
200193
@property
201194
def roll_pass(self):
202195
"""Reference to the roll pass this roll is used in."""

pyroll/core/roll_pass/hookimpls/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from . import roll_pass
1+
from . import base_roll_pass
2+
from . import symmetric_roll_pass
3+
from . import two_roll_pass
4+
from . import three_roll_pass
25
from . import profile
36
from . import roll
47
from . import disk_element
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from shapely import difference
2+
from shapely.ops import linemerge
3+
4+
from ..base import BaseRollPass
5+
from ...config import Config
6+
from ...rotator import Rotator
7+
8+
9+
@BaseRollPass.rotation
10+
def auto_rotation(self: BaseRollPass):
11+
return Config.ROLL_PASS_AUTO_ROTATION
12+
13+
14+
@BaseRollPass.rotation
15+
def detect_already_rotated(self: BaseRollPass):
16+
if Config.ROLL_PASS_AUTO_ROTATION and self.parent is not None:
17+
try:
18+
prev = self.prev
19+
except IndexError:
20+
return True
21+
22+
while True:
23+
if isinstance(prev, BaseRollPass):
24+
return True
25+
if isinstance(prev, Rotator):
26+
return False
27+
try:
28+
prev = prev.prev
29+
except IndexError:
30+
return True
31+
32+
33+
@BaseRollPass.orientation
34+
def default_orientation(self: BaseRollPass):
35+
return 0
36+
37+
38+
@BaseRollPass.volume
39+
def volume(self: BaseRollPass):
40+
return (self.in_profile.cross_section.area + 2 * self.out_profile.cross_section.area
41+
) / 3 * self.length
42+
43+
44+
@BaseRollPass.surface_area
45+
def surface_area(self: BaseRollPass):
46+
return (self.in_profile.cross_section.perimeter + 2 * self.out_profile.cross_section.perimeter
47+
) / 3 * self.length
48+
49+
50+
@BaseRollPass.duration
51+
def duration(self: BaseRollPass):
52+
return self.length / self.velocity
53+
54+
55+
@BaseRollPass.length
56+
def length(self: BaseRollPass):
57+
return -self.entry_point + self.exit_point
58+
59+
60+
@BaseRollPass.displaced_cross_section
61+
def displaced_cross_section(self: BaseRollPass):
62+
return difference(self.in_profile.cross_section, self.usable_cross_section)
63+
64+
65+
@BaseRollPass.reappearing_cross_section
66+
def reappearing_cross_section(self: BaseRollPass):
67+
return difference(self.out_profile.cross_section, self.in_profile.cross_section)
68+
69+
70+
@BaseRollPass.elongation_efficiency
71+
def elongation_efficiency(self: BaseRollPass):
72+
return 1 - self.reappearing_cross_section.area / self.displaced_cross_section.area
73+
74+
75+
@BaseRollPass.target_filling_ratio(trylast=True)
76+
def default_target_filling(self: BaseRollPass):
77+
return 1
78+
79+
80+
@BaseRollPass.target_width
81+
def target_width_from_target_filling_ratio(self: BaseRollPass):
82+
if self.has_value("target_filling_ratio"):
83+
return self.target_filling_ratio * self.usable_width
84+
85+
86+
@BaseRollPass.target_filling_ratio
87+
def target_filling_ratio_from_target_width(self: BaseRollPass):
88+
if self.has_set_or_cached("target_width"):
89+
return self.target_width / self.usable_width
90+
91+
92+
@BaseRollPass.target_cross_section_area
93+
def target_cross_section_area_from_target_cross_section_filling_ratio(self: BaseRollPass):
94+
if self.has_set_or_cached("target_cross_section_filling_ratio"):
95+
return self.target_cross_section_filling_ratio * self.usable_cross_section.area
96+
97+
98+
@BaseRollPass.target_cross_section_filling_ratio
99+
def target_cross_section_filling_ratio_from_target_cross_section_area(self: BaseRollPass):
100+
if self.has_value("target_cross_section_area"): # important has_value for computing from target_width
101+
return self.target_cross_section_area / self.usable_cross_section.area
102+
103+
104+
@BaseRollPass.exit_point
105+
def exit_point(self: BaseRollPass):
106+
return 0
107+
108+
109+
@BaseRollPass.Profile.contact_lines
110+
def contact_contour_lines(self: BaseRollPass.Profile):
111+
rp = self.roll_pass
112+
return [linemerge(cl.intersection(self.cross_section.exterior.buffer(1e-9))) for cl in rp.contour_lines]
113+
114+
115+
@BaseRollPass.front_tension
116+
def default_front_tension(self: BaseRollPass):
117+
return 0
118+
119+
120+
@BaseRollPass.back_tension
121+
def default_back_tension(self: BaseRollPass):
122+
return 0
123+
124+

pyroll/core/roll_pass/hookimpls/deformation_unit.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import numpy as np
22

3-
from shapely.ops import linemerge
43
from shapely.geometry import LineString
5-
from shapely.affinity import translate, rotate
64

75
from ..deformation_unit import DeformationUnit
8-
from ..roll_pass import RollPass
96
from ...config import Config
107

118

pyroll/core/roll_pass/hookimpls/helpers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import math
22

33
import numpy as np
4+
import shapely
45
from shapely import Polygon, clip_by_rect
56
from shapely.affinity import rotate
67

7-
from ..roll_pass import RollPass
8+
from ..two_roll_pass import TwoRollPass
89
from ..three_roll_pass import ThreeRollPass
910
from ...profile.profile import refine_cross_section
1011

1112

12-
def out_cross_section(rp: RollPass, width: float) -> Polygon:
13+
def out_cross_section(rp: TwoRollPass, width: float) -> Polygon:
1314
poly = Polygon(np.concatenate([cl.coords for cl in rp.contour_lines]))
14-
return refine_cross_section(clip_by_rect(poly, -width / 2, -math.inf, width / 2, math.inf))
15+
poly = clip_by_rect(poly, -width / 2, -math.inf, width / 2, math.inf)
16+
return refine_cross_section(poly)
1517

1618

1719
def out_cross_section3(rp: ThreeRollPass, width: float) -> Polygon:

pyroll/core/roll_pass/hookimpls/roll.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22

33
from ..base import BaseRollPass
4-
from ..roll_pass import RollPass
4+
from ..two_roll_pass import TwoRollPass
55
from ..three_roll_pass import ThreeRollPass
66

77

@@ -12,21 +12,11 @@ def roll_torque(self: BaseRollPass.Roll):
1212

1313
@BaseRollPass.Roll.contact_length
1414
def contact_length(self: BaseRollPass.Roll):
15-
height_change = self.roll_pass.in_profile.height - self.roll_pass.height
16-
return np.sqrt(self.min_radius * height_change - height_change ** 2 / 4)
15+
return self.roll_pass.exit_point - self.roll_pass.entry_point
1716

1817

19-
@BaseRollPass.Roll.contact_length
20-
def contact_length_square_oval(self: BaseRollPass.Roll):
21-
if "square" in self.roll_pass.in_profile.classifiers and "oval" in self.roll_pass.classifiers:
22-
depth = self.groove.local_depth(self.roll_pass.in_profile.width / 2)
23-
height_change = self.roll_pass.in_profile.height - self.roll_pass.gap - 2 * depth
24-
radius = self.max_radius - depth
25-
return np.sqrt(radius * height_change - height_change ** 2 / 4)
26-
27-
28-
@RollPass.Roll.contact_area
29-
def contact_area(self: RollPass.Roll):
18+
@BaseRollPass.Roll.contact_area
19+
def contact_area(self: TwoRollPass.Roll):
3020
return (self.roll_pass.in_profile.width + self.roll_pass.out_profile.width) / 2 * self.contact_length
3121

3222

@@ -60,3 +50,13 @@ def surface_velocity(self: BaseRollPass.Roll):
6050
return self.roll_pass.velocity / np.cos(self.neutral_angle)
6151
else:
6252
return self.roll_pass.velocity / np.cos(self.roll_pass.exit_angle)
53+
54+
55+
@BaseRollPass.Roll.entry_angle
56+
def entry_angle(self: BaseRollPass.Roll):
57+
return np.arcsin(self.roll_pass.entry_point / self.working_radius)
58+
59+
60+
@BaseRollPass.Roll.exit_angle
61+
def exit_angle(self: BaseRollPass.Roll):
62+
return np.arcsin(self.roll_pass.exit_point / self.working_radius)

0 commit comments

Comments
 (0)