Skip to content

Commit 6c8caef

Browse files
committed
impl pass line finding for asym RPs
1 parent 2187712 commit 6c8caef

12 files changed

+178
-50
lines changed

pyroll/core/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .grooves import *
44
from .transport import Transport, CoolingPipe
5-
from .roll_pass import BaseRollPass, RollPass, DeformationUnit, ThreeRollPass, AsymmetricRollPass
5+
from .roll_pass import BaseRollPass, RollPass, DeformationUnit, ThreeRollPass, AsymmetricTwoRollPass
66
from .unit import Unit
77
from .roll import Roll
88
from .profile import *
@@ -16,6 +16,8 @@
1616

1717
root_hooks.extend(
1818
[
19+
AsymmetricTwoRollPass.InProfile.pass_line,
20+
AsymmetricTwoRollPass.InProfile.cross_section,
1921
BaseRollPass.roll_force,
2022
BaseRollPass.Roll.roll_torque,
2123
BaseRollPass.elongation_efficiency,

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/asymmetric_two_roll_pass.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import List, cast
22

3+
import numpy as np
34
from shapely.affinity import translate, scale
45
from shapely.geometry import LineString
56

@@ -33,7 +34,7 @@ def contour_lines(self) -> List[LineString]:
3334

3435
upper = translate(self.upper_roll.contour_line, yoff=self.gap / 2)
3536
lower = scale(
36-
translate(self.lower_roll.contour_line, yoff=self.gap / 2),
37+
translate(self.lower_roll.contour_line.reverse(), yoff=self.gap / 2),
3738
xfact=1, yfact=-1, origin=(0, 0)
3839
)
3940

@@ -51,6 +52,18 @@ def disk_elements(self) -> List['AsymmetricTwoRollPass.DiskElement']:
5152
"""A list of disk elements used to subdivide this unit."""
5253
return list(self._subunits)
5354

55+
def get_root_hook_results(self):
56+
super_results = super().get_root_hook_results()
57+
upper_roll_results = self.upper_roll.evaluate_and_set_hooks()
58+
lower_roll_results = self.lower_roll.evaluate_and_set_hooks()
59+
return np.concatenate([super_results, upper_roll_results, lower_roll_results], axis=0)
60+
61+
def reevaluate_cache(self):
62+
super().reevaluate_cache()
63+
self.upper_roll.reevaluate_cache()
64+
self.lower_roll.reevaluate_cache()
65+
self._contour_lines = None
66+
5467
class Profile(BaseRollPass.Profile):
5568
"""Represents a profile in context of a roll pass."""
5669

@@ -62,7 +75,8 @@ def roll_pass(self) -> 'AsymmetricTwoRollPass':
6275
class InProfile(Profile, BaseRollPass.InProfile):
6376
"""Represents an incoming profile of a roll pass."""
6477

65-
vertical_displacement = Hook[float]
78+
pass_line = Hook[tuple[float, float, float]]()
79+
"""Point (x, y, z) where the incoming profile centroid enters the roll pass."""
6680

6781
class OutProfile(Profile, BaseRollPass.OutProfile):
6882
"""Represents an outgoing profile of a roll pass."""

pyroll/core/roll_pass/base.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ def __init__(
109109

110110
self._contour_lines = None
111111

112+
self.given_in_profile: BaseProfile | None = None
113+
"""The incoming profile as was given to the ``solve`` method."""
114+
115+
self.rotated_in_profile: BaseProfile | None = None
116+
"""The incoming profile after rotation."""
117+
112118
@property
113119
@abstractmethod
114120
def contour_lines(self):
@@ -128,6 +134,8 @@ def disk_elements(self) -> List['BaseRollPass.DiskElement']:
128134
return list(self._subunits)
129135

130136
def init_solve(self, in_profile: BaseProfile):
137+
self.given_in_profile = in_profile
138+
131139
if self.rotation:
132140
rotator = Rotator(
133141
# make True determining from hook functions
@@ -136,14 +144,15 @@ def init_solve(self, in_profile: BaseProfile):
136144
duration=0, length=0, parent=self
137145
)
138146
rotator.solve(in_profile)
139-
in_profile = rotator.out_profile
147+
self.rotated_in_profile = rotator.out_profile
148+
else:
149+
self.rotated_in_profile = in_profile
140150

141-
super().init_solve(in_profile)
151+
super().init_solve(self.rotated_in_profile)
142152
self.out_profile.cross_section = self.usable_cross_section
143153

144154
def reevaluate_cache(self):
145155
super().reevaluate_cache()
146-
self.roll.reevaluate_cache()
147156
self._contour_lines = None
148157

149158
class Profile(DiskElementUnit.Profile, DeformationUnit.Profile):

pyroll/core/roll_pass/hookimpls/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from . import base_roll_pass
22
from . import two_roll_pass
3+
from . import asymmetric_two_roll_pass
34
from . import three_roll_pass
45
from . import profile
56
from . import roll

pyroll/core/roll_pass/hookimpls/asymmetric_two_roll_pass.py

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import numpy as np
2+
import scipy.optimize
3+
import shapely.affinity
24
from shapely import Polygon
35

46
from . import helpers
@@ -61,37 +63,6 @@ def roll_power(self: AsymmetricTwoRollPass):
6163
return self.upper_roll.roll_power + self.lower_roll.roll_power
6264

6365

64-
@AsymmetricTwoRollPass.entry_point
65-
def entry_point(self: AsymmetricTwoRollPass):
66-
height_change = self.in_profile.height - self.height
67-
return (
68-
np.sqrt(height_change)
69-
* np.sqrt(
70-
(2 * self.upper_roll.min_radius - height_change)
71-
* (2 * self.lower_roll.min_radius - height_change)
72-
* (2 * self.upper_roll.min_radius + 2 * self.lower_roll.min_radius - height_change)
73-
)
74-
) / (2 * (self.upper_roll.min_radius + self.lower_roll.min_radius - height_change))
75-
76-
77-
@AsymmetricTwoRollPass.entry_point
78-
def entry_point_square_oval(self: AsymmetricTwoRollPass):
79-
if "square" in self.in_profile.classifiers and "oval" in self.classifiers:
80-
upper_depth = self.upper_roll.groove.local_depth(self.in_profile.width / 2)
81-
lower_depth = self.lower_roll.groove.local_depth(self.in_profile.width / 2)
82-
height_change = self.in_profile.height - self.gap - upper_depth - lower_depth
83-
upper_radius = self.upper_roll.max_radius - upper_depth
84-
lower_radius = self.lower_roll.max_radius - lower_depth
85-
return (
86-
np.sqrt(height_change)
87-
* np.sqrt(
88-
(2 * upper_radius - height_change)
89-
* (2 * lower_radius - height_change)
90-
* (2 * upper_radius + 2 * lower_radius - height_change)
91-
)
92-
) / (2 * (upper_radius + lower_radius - height_change))
93-
94-
9566
@AsymmetricTwoRollPass.velocity
9667
def velocity(self: AsymmetricTwoRollPass):
9768
if self.upper_roll.has_value("neutral_angle") and self.lower_roll.has_value("neutral_angle"):
@@ -111,3 +82,59 @@ def roll_force(self: AsymmetricTwoRollPass):
11182
* (self.upper_roll.contact_area + self.lower_roll.contact_area)
11283
/ 2
11384
)
85+
86+
87+
@AsymmetricTwoRollPass.InProfile.pass_line
88+
def pass_line(self: AsymmetricTwoRollPass.InProfile) -> tuple[float, float, float]:
89+
rp = self.roll_pass
90+
91+
if not self.has_set("pass_line"):
92+
height_change = self.height - rp.height
93+
x_guess = -(
94+
np.sqrt(height_change)
95+
* np.sqrt(
96+
(2 * rp.upper_roll.min_radius - height_change)
97+
* (2 * rp.lower_roll.min_radius - height_change)
98+
* (2 * rp.upper_roll.min_radius + 2 * rp.lower_roll.min_radius - height_change)
99+
)
100+
) / (2 * (rp.upper_roll.min_radius + rp.lower_roll.min_radius - height_change))
101+
y_guess = 0
102+
else:
103+
x_guess, y_guess, _ = self.pass_line
104+
105+
def contact_objective(xy):
106+
shifted_cross_section = shapely.affinity.translate(rp.rotated_in_profile.cross_section, yoff=xy[1])
107+
108+
upper_contour = shapely.geometry.LineString(np.stack([
109+
rp.upper_roll.surface_z,
110+
rp.upper_roll.surface_interpolation(xy[0], rp.upper_roll.surface_z).squeeze(axis=1)
111+
], axis=1))
112+
upper_contour = shapely.affinity.translate(upper_contour,yoff=self.roll_pass.gap / 2)
113+
lower_contour = shapely.geometry.LineString(np.stack([
114+
rp.lower_roll.surface_z,
115+
rp.lower_roll.surface_interpolation(xy[0], rp.lower_roll.surface_z).squeeze(axis=1)
116+
], axis=1))
117+
lower_contour = shapely.affinity.scale(shapely.affinity.translate(lower_contour, yoff=self.roll_pass.gap / 2), xfact=1, yfact=-1, origin=(0,0))
118+
119+
upper_intersection = shapely.intersection(upper_contour, shifted_cross_section)
120+
lower_intersection = shapely.intersection(lower_contour, shifted_cross_section)
121+
122+
upper_value = upper_intersection.length if not upper_intersection.is_empty else shapely.shortest_line(upper_contour, shifted_cross_section).length
123+
lower_value = lower_intersection.length if not lower_intersection.is_empty else shapely.shortest_line(lower_contour, shifted_cross_section).length
124+
125+
return upper_value ** 2 + lower_value ** 2
126+
127+
sol = scipy.optimize.minimize(contact_objective, (x_guess, y_guess), method="BFGS", options=dict(xrtol=1e-2))
128+
129+
return sol.x[0], sol.x[1], 0
130+
131+
132+
@AsymmetricTwoRollPass.InProfile.cross_section
133+
def in_cross_section(self:AsymmetricTwoRollPass.InProfile):
134+
return shapely.affinity.translate(self.roll_pass.rotated_in_profile.cross_section, xoff=self.pass_line[2], yoff=self.pass_line[1])
135+
136+
137+
@AsymmetricTwoRollPass.entry_point
138+
def entry_point(self: AsymmetricTwoRollPass):
139+
return self.in_profile.pass_line[0]
140+

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: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
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
9-
from ..asymmetric_roll_pass import AsymmetricRollPass
10+
from ..asymmetric_two_roll_pass import AsymmetricTwoRollPass
1011
from ...profile.profile import refine_cross_section
1112

1213

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

1719

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

pyroll/core/roll_pass/hookimpls/roll.py

Lines changed: 3 additions & 3 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

@@ -15,8 +15,8 @@ def contact_length(self: BaseRollPass.Roll):
1515
return self.roll_pass.exit_point - self.roll_pass.entry_point
1616

1717

18-
@RollPass.Roll.contact_area
19-
def contact_area(self: RollPass.Roll):
18+
@BaseRollPass.Roll.contact_area
19+
def contact_area(self: TwoRollPass.Roll):
2020
return (self.roll_pass.in_profile.width + self.roll_pass.out_profile.width) / 2 * self.contact_length
2121

2222

pyroll/core/roll_pass/hookimpls/symmetric_roll_pass.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@SymmetricRollPass.entry_point
77
def entry_point(self: SymmetricRollPass):
88
height_change = self.in_profile.height - self.height
9-
return np.sqrt(self.roll.min_radius * height_change - height_change ** 2 / 4)
9+
return -np.sqrt(self.roll.min_radius * height_change - height_change ** 2 / 4)
1010

1111

1212
@SymmetricRollPass.entry_point
@@ -15,7 +15,7 @@ def entry_point_square_oval(self: SymmetricRollPass):
1515
depth = self.roll.groove.local_depth(self.in_profile.width / 2)
1616
height_change = self.in_profile.height - self.gap - 2 * depth
1717
radius = self.roll.max_radius - depth
18-
return np.sqrt(radius * height_change - height_change ** 2 / 4)
18+
return -np.sqrt(radius * height_change - height_change ** 2 / 4)
1919

2020

2121
@SymmetricRollPass.velocity

0 commit comments

Comments
 (0)