pyroll-core 3.2.0b2__tar.gz → 3.2.0b3__tar.gz
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-3.2.0b3/.gitignore +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/PKG-INFO +1 -1
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/__init__.py +6 -10
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/engine/hookimpls.py +4 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/hooks.py +33 -22
- pyroll_core-3.2.0b3/pyroll/core/roll_pass/hookimpls/asymmetric_two_roll_pass.py +175 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/deformation_unit.py +5 -1
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/engine.py +6 -6
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/helpers.py +18 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/profile.py +3 -2
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/sequence/sequence.py +8 -46
- pyroll_core-3.2.0b3/pyroll/core/transport/__init__.py +9 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/transport/hookimpls/__init__.py +2 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/transport/hookimpls/cooling_pipe.py +2 -1
- pyroll_core-3.2.0b3/pyroll/core/transport/hookimpls/induction_furnace.py +23 -0
- pyroll_core-3.2.0b3/pyroll/core/transport/hookimpls/spooler.py +170 -0
- pyroll_core-3.2.0b3/pyroll/core/transport/hookimpls/stelmor_conveyor.py +11 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/transport/hookimpls/transport.py +10 -2
- pyroll_core-3.2.0b3/pyroll/core/transport/induction_furnace.py +81 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/transport/spooler.py +21 -0
- pyroll_core-3.2.0b3/pyroll/core/transport/stelmor_conveyor.py +71 -0
- pyroll_core-3.2.0b2/.gitignore +0 -432
- pyroll_core-3.2.0b2/pyroll/core/roll_pass/hookimpls/asymmetric_two_roll_pass.py +0 -194
- pyroll_core-3.2.0b2/pyroll/core/transport/__init__.py +0 -7
- pyroll_core-3.2.0b2/pyroll/core/transport/hookimpls/spooler.py +0 -68
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/LICENSE +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/README.md +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/hatch.toml +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyproject.toml +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/config.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/disk_elements/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/disk_elements/disk_element_unit.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/disk_elements/hookimpls.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/engine/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/engine/engine.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/angles/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/angles/_geometry.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/angles/_path.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/angles/butterfly_groove.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/angles/factories.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/base.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/boxes/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/boxes/box.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/boxes/constricted_box.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/boxes/upset_box.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/diamonds/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/diamonds/diamond.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/diamonds/gothic.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/diamonds/square.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/equivalent_ripped_groove.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/flat.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/generic_angle.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/generic_elongation.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/generic_elongation_solvers.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/hexagonal.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/circular_oval.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/constricted_circular_oval.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/constricted_swedish_oval.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/flat_oval.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/oval_3radii.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/oval_3radii_flanked.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/swedish_oval.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/ovals/upset_oval.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/rounds/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/rounds/false_round.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/rounds/round.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/grooves/spline.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/log.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/profile/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/profile/hookimpls.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/profile/profile.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/repr.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll/hookimpls.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll/roll.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/asymmetric_two_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/base.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/deformation_unit.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/base_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/disk_element.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/roll.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/symmetric_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/three_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/two_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/symmetric_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/three_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/two_roll_pass.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/rotator/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/rotator/hookimpls.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/rotator/rotator.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/sequence/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/sequence/hookimpls.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/shapes.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/transport/cooling_pipe.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/transport/transport.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/unit/__init__.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/unit/hookimpls.py +0 -0
- {pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/unit/unit.py +1 -1
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyroll-core
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.0b3
|
|
4
4
|
Summary: PyRoll rolling simulation framework - core library.
|
|
5
5
|
Project-URL: Homepage, https://pyroll-project.github.io
|
|
6
6
|
Project-URL: Repository, https://github.com/pyroll-project/pyroll-core
|
|
@@ -29,15 +29,8 @@ from .grooves import (
|
|
|
29
29
|
butterfly_grooves,
|
|
30
30
|
create_groove_by_type_name,
|
|
31
31
|
)
|
|
32
|
-
from .transport import Transport, CoolingPipe, Spooler
|
|
33
|
-
from .roll_pass import
|
|
34
|
-
BaseRollPass,
|
|
35
|
-
DeformationUnit,
|
|
36
|
-
ThreeRollPass,
|
|
37
|
-
SymmetricRollPass,
|
|
38
|
-
TwoRollPass,
|
|
39
|
-
AsymmetricTwoRollPass,
|
|
40
|
-
)
|
|
32
|
+
from .transport import Transport, CoolingPipe, Spooler, StelmorConveyor, InductionFurnace
|
|
33
|
+
from .roll_pass import BaseRollPass, DeformationUnit, ThreeRollPass, SymmetricRollPass, TwoRollPass, AsymmetricTwoRollPass
|
|
41
34
|
from .roll_pass import TwoRollPass as RollPass
|
|
42
35
|
from .unit import Unit
|
|
43
36
|
from .roll import Roll
|
|
@@ -55,7 +48,7 @@ from .hooks import Hook, HookHost, HookFunction, root_hooks
|
|
|
55
48
|
from .disk_elements import DiskElementUnit
|
|
56
49
|
from .config import Config, config, PlottingBackend, ConfigValue, ConfigMeta
|
|
57
50
|
|
|
58
|
-
VERSION = "3.2.
|
|
51
|
+
VERSION = "3.2.0b3"
|
|
59
52
|
|
|
60
53
|
__all__ = [
|
|
61
54
|
# grooves
|
|
@@ -98,6 +91,8 @@ __all__ = [
|
|
|
98
91
|
"Transport",
|
|
99
92
|
"CoolingPipe",
|
|
100
93
|
"Spooler",
|
|
94
|
+
"StelmorConveyor",
|
|
95
|
+
"InductionFurnace",
|
|
101
96
|
# roll_pass
|
|
102
97
|
"RollPass",
|
|
103
98
|
"BaseRollPass",
|
|
@@ -129,6 +124,7 @@ __all__ = [
|
|
|
129
124
|
|
|
130
125
|
root_hooks.extend(
|
|
131
126
|
[
|
|
127
|
+
AsymmetricTwoRollPass.InProfile.pass_line,
|
|
132
128
|
AsymmetricTwoRollPass.InProfile.cross_section,
|
|
133
129
|
BaseRollPass.roll_force,
|
|
134
130
|
SymmetricRollPass.Roll.roll_torque,
|
|
@@ -448,27 +448,44 @@ class HookHost(ReprMixin, LogMixin, metaclass=_HookHostMeta):
|
|
|
448
448
|
return None
|
|
449
449
|
|
|
450
450
|
def evaluate_and_set_hooks(self):
|
|
451
|
-
"""Evaluate functions of root hooks and set the results explicitly as attributes.
|
|
451
|
+
"""Evaluate functions of root hooks and set the results explicitly as attributes.
|
|
452
|
+
|
|
453
|
+
All applicable root hooks are evaluated first, against the state left by the previous
|
|
454
|
+
call (so their hook functions never observe a partially updated mix of this iteration's
|
|
455
|
+
and the previous iteration's root hook values), and only afterward set as attributes.
|
|
456
|
+
Committing them one by one instead would make the result depend on the arbitrary order
|
|
457
|
+
of ``root_hooks``: a hook function reading another root hook registered later in that
|
|
458
|
+
list would always see last iteration's value, while one reading a hook registered
|
|
459
|
+
earlier would see this iteration's, an inconsistency root hook functions must not have to
|
|
460
|
+
account for.
|
|
461
|
+
"""
|
|
452
462
|
|
|
453
|
-
|
|
454
|
-
for h in root_hooks
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
463
|
+
applicable_hooks = [
|
|
464
|
+
getattr(type(self), h.name) for h in root_hooks if issubclass(type(self), h.owner)
|
|
465
|
+
]
|
|
466
|
+
|
|
467
|
+
results = {}
|
|
468
|
+
for h in applicable_hooks:
|
|
469
|
+
result = h.get_result(self)
|
|
458
470
|
|
|
459
|
-
|
|
460
|
-
|
|
471
|
+
if result is None:
|
|
472
|
+
result = self.root_hook_fallback(h)
|
|
461
473
|
|
|
462
|
-
|
|
463
|
-
|
|
474
|
+
if result is None:
|
|
475
|
+
raise AttributeError(f"Call for root hook '{h.name}' on '{self}' resulted in None.")
|
|
464
476
|
|
|
465
|
-
|
|
477
|
+
results[h.name] = result
|
|
466
478
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
479
|
+
for name, result in results.items():
|
|
480
|
+
setattr(self, name, result)
|
|
481
|
+
|
|
482
|
+
def _gen():
|
|
483
|
+
for result in results.values():
|
|
484
|
+
try:
|
|
485
|
+
arr = np.array(result)
|
|
486
|
+
yield from arr[np.isfinite(arr) | np.isinf(arr) | np.isnan(arr)].flat
|
|
487
|
+
except TypeError: # yield only numeric values
|
|
488
|
+
continue
|
|
472
489
|
|
|
473
490
|
return list(_gen())
|
|
474
491
|
|
|
@@ -516,9 +533,3 @@ class _RootHooksList(list):
|
|
|
516
533
|
|
|
517
534
|
|
|
518
535
|
root_hooks = _RootHooksList() # filled in __init__.py due to circular imports
|
|
519
|
-
"""
|
|
520
|
-
List of hooks to call explicitly in each solution iteration.
|
|
521
|
-
Their values will be treated as explicitly set but reevaluated in every iteration.
|
|
522
|
-
They will not be deleted during cache clearing.
|
|
523
|
-
They serve as root for the calling tree and persistent iterational variables.
|
|
524
|
-
"""
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy.optimize
|
|
3
|
+
import shapely.affinity
|
|
4
|
+
from shapely import Polygon
|
|
5
|
+
|
|
6
|
+
from . import helpers
|
|
7
|
+
from ..asymmetric_two_roll_pass import AsymmetricTwoRollPass
|
|
8
|
+
from ...grooves import GenericElongationGroove
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@AsymmetricTwoRollPass.usable_width
|
|
12
|
+
def usable_width(self: AsymmetricTwoRollPass):
|
|
13
|
+
return min(self.upper_roll.groove.usable_width, self.lower_roll.groove.usable_width)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@AsymmetricTwoRollPass.tip_width
|
|
17
|
+
def tip_width(self: AsymmetricTwoRollPass):
|
|
18
|
+
if isinstance(self.upper_roll.groove, GenericElongationGroove) and isinstance(
|
|
19
|
+
self.lower_roll.groove, GenericElongationGroove
|
|
20
|
+
):
|
|
21
|
+
return min(
|
|
22
|
+
self.upper_roll.groove.usable_width + self.gap / 2 / np.tan(self.upper_roll.groove.flank_angle),
|
|
23
|
+
self.lower_roll.groove.usable_width + self.gap / 2 / np.tan(self.lower_roll.groove.flank_angle),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@AsymmetricTwoRollPass.usable_cross_section
|
|
28
|
+
def usable_cross_section(self: AsymmetricTwoRollPass) -> Polygon:
|
|
29
|
+
return helpers.out_cross_section(self, self.usable_width)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@AsymmetricTwoRollPass.tip_cross_section
|
|
33
|
+
def tip_cross_section(self: AsymmetricTwoRollPass) -> Polygon:
|
|
34
|
+
return helpers.out_cross_section(self, self.tip_width)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@AsymmetricTwoRollPass.gap
|
|
38
|
+
def gap(self: AsymmetricTwoRollPass):
|
|
39
|
+
if self.has_set_or_cached("height"):
|
|
40
|
+
return self.height - self.upper_roll.groove.depth - self.lower_roll.groove.depth
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@AsymmetricTwoRollPass.height
|
|
44
|
+
def height(self: AsymmetricTwoRollPass):
|
|
45
|
+
if self.has_set_or_cached("gap"):
|
|
46
|
+
return self.gap + self.upper_roll.groove.depth + self.lower_roll.groove.depth
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@AsymmetricTwoRollPass.contact_area
|
|
50
|
+
def contact_area(self: AsymmetricTwoRollPass):
|
|
51
|
+
return self.upper_roll.contact_area + self.lower_roll.contact_area
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@AsymmetricTwoRollPass.target_cross_section_area
|
|
55
|
+
def target_cross_section_area_from_target_width(self: AsymmetricTwoRollPass):
|
|
56
|
+
if self.has_value("target_width"):
|
|
57
|
+
target_cross_section = helpers.out_cross_section(self, self.target_width)
|
|
58
|
+
return target_cross_section.area
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@AsymmetricTwoRollPass.power
|
|
62
|
+
def roll_power(self: AsymmetricTwoRollPass):
|
|
63
|
+
return self.upper_roll.roll_power + self.lower_roll.roll_power
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@AsymmetricTwoRollPass.velocity
|
|
67
|
+
def velocity(self: AsymmetricTwoRollPass):
|
|
68
|
+
if self.upper_roll.has_value("neutral_angle") and self.lower_roll.has_value("neutral_angle"):
|
|
69
|
+
return (
|
|
70
|
+
self.upper_roll.working_velocity * np.cos(self.upper_roll.neutral_angle)
|
|
71
|
+
+ self.lower_roll.working_velocity * np.cos(self.lower_roll.neutral_angle)
|
|
72
|
+
) / 2
|
|
73
|
+
else:
|
|
74
|
+
return (self.upper_roll.working_velocity + self.lower_roll.working_velocity) / 2
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@AsymmetricTwoRollPass.roll_force
|
|
78
|
+
def roll_force(self: AsymmetricTwoRollPass):
|
|
79
|
+
return (
|
|
80
|
+
(self.in_profile.flow_stress + 2 * self.out_profile.flow_stress)
|
|
81
|
+
/ 3
|
|
82
|
+
* (self.upper_roll.contact_area + self.lower_roll.contact_area)
|
|
83
|
+
/ 2
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@AsymmetricTwoRollPass.InProfile.pass_line
|
|
88
|
+
def pass_line(self: AsymmetricTwoRollPass.InProfile) -> tuple[float, float, float]:
|
|
89
|
+
"""Places the incoming profile's cross-section within the roll gap: a vertical (``y``) and
|
|
90
|
+
transverse (``z``) shift, evaluated against the nominal roll-barrel contours (``contour_lines``,
|
|
91
|
+
the same fixed throat geometry ``draught``/``entry_point`` already use elsewhere). The rolling
|
|
92
|
+
direction is not a coordinate of a 2-D cross-section and plays no role here.
|
|
93
|
+
|
|
94
|
+
``y`` is where the raw (undeformed) profile overlaps the upper and lower roll by exactly
|
|
95
|
+
*equal* area. If the rolls are large enough for the profile to touch both without overlapping
|
|
96
|
+
either, that zero-overlap placement falls out of this same condition automatically (equal
|
|
97
|
+
*and* zero); if not (e.g. an oversized profile against a strongly depth-asymmetric groove
|
|
98
|
+
pair), it is the placement where the initial squeeze is balanced evenly between both rolls
|
|
99
|
+
rather than concentrated on one side. As ``y`` increases the upper overlap area grows
|
|
100
|
+
monotonically while the lower overlap area shrinks monotonically, so there is always exactly
|
|
101
|
+
one such ``y`` -- a plain 1-D root find, no convergence caveats.
|
|
102
|
+
|
|
103
|
+
``z`` is left at 0: both grooves in the currently supported use cases are left-right symmetric,
|
|
104
|
+
so the balanced placement is trivially centered."""
|
|
105
|
+
rp = self.roll_pass
|
|
106
|
+
|
|
107
|
+
# ``self.roll_pass.in_profile`` *is* ``self`` (this hook lives on the in-profile role of the
|
|
108
|
+
# roll pass), and ``in_cross_section`` (registered below) computes ``self.cross_section`` as
|
|
109
|
+
# this same raw shape translated by ``pass_line`` -- so reading ``self.cross_section`` here
|
|
110
|
+
# would normally be circular. As a root hook, every root hook function is evaluated against
|
|
111
|
+
# the state left by the *previous* iteration before any of this iteration's results are
|
|
112
|
+
# written back (see ``Hook.evaluate_and_set_hooks``), so on the very first call
|
|
113
|
+
# ``self.cross_section`` genuinely is the untranslated base profile -- capture it once, here,
|
|
114
|
+
# and reuse that same cached reference on every later call. Re-deriving it from
|
|
115
|
+
# ``self.cross_section`` on later calls would not work: each iteration's ``in_cross_section``
|
|
116
|
+
# also reads ``self.cross_section`` as its own input (same circularity), so it stacks a new
|
|
117
|
+
# translation on top of all previous ones rather than replacing them -- undoing only the most
|
|
118
|
+
# recent increment leaves the earlier accumulated ones baked in.
|
|
119
|
+
if not self.has_set_or_cached("pass_line"):
|
|
120
|
+
self._raw_in_cross_section = rp.in_profile.cross_section
|
|
121
|
+
raw_cross_section = self._raw_in_cross_section
|
|
122
|
+
raw_height = raw_cross_section.bounds[3] - raw_cross_section.bounds[1]
|
|
123
|
+
|
|
124
|
+
upper_contour, lower_contour = rp.contour_lines.geoms
|
|
125
|
+
|
|
126
|
+
def overlap_areas(y: float) -> tuple[float, float]:
|
|
127
|
+
shifted = shapely.affinity.translate(raw_cross_section, yoff=y)
|
|
128
|
+
a_upper = helpers.overlap_area(shifted, upper_contour.coords, direction=1)
|
|
129
|
+
a_lower = helpers.overlap_area(shifted, lower_contour.coords, direction=-1)
|
|
130
|
+
return a_upper, a_lower
|
|
131
|
+
|
|
132
|
+
# A fixed multiple of ``raw_height`` is not a safe bracket at all profile/roll scales: for a
|
|
133
|
+
# large profile it can end up so wide that ``brentq`` lands on a spurious point near the
|
|
134
|
+
# bracket edge instead of the true (much closer) root. Derive a tight, exactly-scaled bracket
|
|
135
|
+
# instead: shift the profile down until its top is fully below the lower contour's lowest
|
|
136
|
+
# point (guaranteeing full penetration into the lower roll, zero overlap with the upper one),
|
|
137
|
+
# and up until its bottom is fully above the upper contour's highest point (the mirror case).
|
|
138
|
+
# These are the true, guaranteed-sign-change bounds -- not an estimate.
|
|
139
|
+
box_bottom, box_top = raw_cross_section.bounds[1], raw_cross_section.bounds[3]
|
|
140
|
+
upper_contour_y = np.asarray(upper_contour.coords)[:, 1]
|
|
141
|
+
lower_contour_y = np.asarray(lower_contour.coords)[:, 1]
|
|
142
|
+
margin = 0.01 * raw_height + 1e-9
|
|
143
|
+
y_lo = lower_contour_y.min() - box_top - margin
|
|
144
|
+
y_hi = upper_contour_y.max() - box_bottom + margin
|
|
145
|
+
y = scipy.optimize.brentq(
|
|
146
|
+
lambda y: overlap_areas(y)[0] - overlap_areas(y)[1], y_lo, y_hi, xtol=1e-9,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
common_overlap_area = overlap_areas(y)[0]
|
|
150
|
+
if common_overlap_area > 0:
|
|
151
|
+
self.logger.info(
|
|
152
|
+
"Hook %s.pass_line: the profile cannot touch both rolls without overlapping one of "
|
|
153
|
+
"them; settled at the placement with the evenly balanced overlap (%.3g mm^2 on each "
|
|
154
|
+
"side).",
|
|
155
|
+
type(self).__qualname__, common_overlap_area * 1e6,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return 0, y, 0
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@AsymmetricTwoRollPass.InProfile.cross_section
|
|
162
|
+
def in_cross_section(self: AsymmetricTwoRollPass.InProfile):
|
|
163
|
+
# ``self.roll_pass.in_profile`` *is* ``self``, so translating ``self.cross_section`` (this
|
|
164
|
+
# hook's own previous result) would stack a new translation on top of all previous ones every
|
|
165
|
+
# iteration instead of replacing them (the same circularity ``pass_line`` avoids by caching
|
|
166
|
+
# the true raw shape in ``self._raw_in_cross_section`` -- reuse that same stable reference
|
|
167
|
+
# here rather than re-reading ``self.cross_section``). Accessing ``self.pass_line`` first
|
|
168
|
+
# guarantees that cache is populated.
|
|
169
|
+
pass_line = self.pass_line
|
|
170
|
+
return shapely.affinity.translate(self._raw_in_cross_section, xoff=pass_line[2], yoff=pass_line[1])
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@AsymmetricTwoRollPass.entry_point
|
|
174
|
+
def entry_point(self: AsymmetricTwoRollPass):
|
|
175
|
+
return min(self.upper_roll.entry_point, self.lower_roll.entry_point)
|
{pyroll_core-3.2.0b2 → pyroll_core-3.2.0b3}/pyroll/core/roll_pass/hookimpls/deformation_unit.py
RENAMED
|
@@ -20,9 +20,13 @@ def spread(self: DeformationUnit):
|
|
|
20
20
|
def elongation(self: DeformationUnit):
|
|
21
21
|
return self.in_profile.cross_section.area / self.out_profile.cross_section.area
|
|
22
22
|
|
|
23
|
+
|
|
23
24
|
@DeformationUnit.reduction
|
|
24
25
|
def reduction(self: DeformationUnit):
|
|
25
|
-
return (
|
|
26
|
+
return (
|
|
27
|
+
self.in_profile.cross_section.area - self.out_profile.cross_section.area
|
|
28
|
+
) / self.in_profile.cross_section.area
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
@DeformationUnit.log_draught
|
|
28
32
|
def log_draught(self: DeformationUnit):
|
|
@@ -5,14 +5,15 @@ from ..asymmetric_two_roll_pass import AsymmetricTwoRollPass
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@TwoRollPass.Engine.torque
|
|
8
|
-
def
|
|
9
|
-
return 2 * self.roll_pass.roll.roll_torque / (self.
|
|
8
|
+
def engine_torque_gear_corrected(self: TwoRollPass.Engine):
|
|
9
|
+
return 2 * self.roll_pass.roll.roll_torque / (self.gear_ratio * self.spindle_efficiency * self.gear_box_efficiency
|
|
10
|
+
) + self.idle_torque
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@ThreeRollPass.Engine.torque
|
|
13
|
-
def
|
|
14
|
-
return 3 * self.roll_pass.roll.roll_torque / (self.
|
|
15
|
-
|
|
14
|
+
def engine_torque_gear_corrected(self: ThreeRollPass.Engine):
|
|
15
|
+
return 3 * self.roll_pass.roll.roll_torque / ( self.gear_ratio * self.spindle_efficiency * self.gear_box_efficiency
|
|
16
|
+
) + self.idle_torque
|
|
16
17
|
|
|
17
18
|
@AsymmetricTwoRollPass.Engine.torque
|
|
18
19
|
def engine_torque_asymmetric(self: AsymmetricTwoRollPass.Engine):
|
|
@@ -29,7 +30,6 @@ def engine_rotational_frequency(self: BaseRollPass.Engine):
|
|
|
29
30
|
else:
|
|
30
31
|
return self.roll_pass.roll.rotational_frequency
|
|
31
32
|
|
|
32
|
-
|
|
33
33
|
@BaseRollPass.Engine.available_power
|
|
34
34
|
def available_power(self: BaseRollPass.Engine):
|
|
35
35
|
if self.has_set_or_cached("base_rotational_frequency"):
|
|
@@ -55,6 +55,24 @@ def max_vertical_penetration(cross_section: Polygon, contour_coords, direction:
|
|
|
55
55
|
return max(float(penetrations.max(initial=0)), 0)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def overlap_area(cross_section: Polygon, contour_coords, direction: int) -> float:
|
|
59
|
+
"""
|
|
60
|
+
Area of the profile cross-section that overlaps the roll material behind a contour line.
|
|
61
|
+
|
|
62
|
+
:param cross_section: the (already positioned) profile cross-section
|
|
63
|
+
:param contour_coords: coordinate sequence of the (already positioned) contour line
|
|
64
|
+
:param direction: ``1`` for an upper roll (material above the contour), ``-1`` for a lower roll (material below)
|
|
65
|
+
"""
|
|
66
|
+
coords = np.asarray(contour_coords)
|
|
67
|
+
y_far = 10.0 if direction > 0 else -10.0
|
|
68
|
+
forbidden_region = Polygon(
|
|
69
|
+
np.concatenate([coords, [[coords[-1, 0], y_far], [coords[0, 0], y_far]]])
|
|
70
|
+
)
|
|
71
|
+
if not forbidden_region.is_valid:
|
|
72
|
+
forbidden_region = shapely.make_valid(forbidden_region)
|
|
73
|
+
return shapely.intersection(cross_section, forbidden_region).area
|
|
74
|
+
|
|
75
|
+
|
|
58
76
|
def out_cross_section3(rp: ThreeRollPass, width: float) -> Polygon:
|
|
59
77
|
poly = Polygon(np.concatenate([cl.coords for cl in rp.contour_lines.geoms]))
|
|
60
78
|
|
|
@@ -72,7 +72,8 @@ def cross_section(self: BaseRollPass.OutProfile) -> Polygon:
|
|
|
72
72
|
cs = helpers.out_cross_section(self.roll_pass, self.width)
|
|
73
73
|
if cs.width * 1.01 < self.width:
|
|
74
74
|
raise ValueError(
|
|
75
|
-
"Profile's width can not be larger than its contour lines."
|
|
75
|
+
"Profile's width can not be larger than its contour lines."
|
|
76
|
+
"May be caused by critical overfilling."
|
|
76
77
|
)
|
|
77
78
|
return cs
|
|
78
79
|
|
|
@@ -98,7 +99,7 @@ def cross_section(self: BaseRollPass.OutProfile) -> Polygon:
|
|
|
98
99
|
half_cross_section_1 = union(intermediate_section, filling_rectangle)
|
|
99
100
|
half_cross_section_2 = scale(half_cross_section_1, xfact=-1, yfact=-1, origin=(0, 0))
|
|
100
101
|
cs = union(half_cross_section_1, half_cross_section_2)
|
|
101
|
-
except GEOSException:
|
|
102
|
+
except GEOSException as e:
|
|
102
103
|
cs = helpers.out_cross_section(self.roll_pass, self.width)
|
|
103
104
|
|
|
104
105
|
if cs.width * 1.01 < self.width:
|
|
@@ -205,6 +205,10 @@ class PassSequence(Unit, Sequence[Unit]):
|
|
|
205
205
|
)
|
|
206
206
|
set_velocities_to_roll_passes(roll_passes=copied_sequence.roll_passes, velocities=initial_roll_pass_velocities)
|
|
207
207
|
|
|
208
|
+
in_profile.velocity = (
|
|
209
|
+
initial_roll_pass_velocities[0] * usable_cross_section_areas[0] / in_profile.cross_section.area
|
|
210
|
+
)
|
|
211
|
+
|
|
208
212
|
copied_sequence.solve(in_profile)
|
|
209
213
|
|
|
210
214
|
for i in range(self.max_iteration_count):
|
|
@@ -217,6 +221,8 @@ class PassSequence(Unit, Sequence[Unit]):
|
|
|
217
221
|
roll_passes=copied_sequence.roll_passes, velocities=current_roll_pass_velocities
|
|
218
222
|
)
|
|
219
223
|
|
|
224
|
+
in_profile.velocity = current_roll_pass_velocities[0] * profile_areas[0] / in_profile.cross_section.area
|
|
225
|
+
|
|
220
226
|
copied_sequence.solve(in_profile)
|
|
221
227
|
|
|
222
228
|
difference = np.abs(prior_roll_pass_velocities - current_roll_pass_velocities)
|
|
@@ -235,6 +241,8 @@ class PassSequence(Unit, Sequence[Unit]):
|
|
|
235
241
|
:param final_cross_section_area: area of the final cross-section
|
|
236
242
|
"""
|
|
237
243
|
|
|
244
|
+
in_profile.velocity = initial_speed
|
|
245
|
+
|
|
238
246
|
copied_sequence = copy.deepcopy(self)
|
|
239
247
|
|
|
240
248
|
def calculate_velocities_array(velocities: np.ndarray[float], cross_sections_areas: np.ndarray[float]):
|
|
@@ -279,52 +287,6 @@ class PassSequence(Unit, Sequence[Unit]):
|
|
|
279
287
|
self.solve(in_profile)
|
|
280
288
|
break
|
|
281
289
|
|
|
282
|
-
def solve_interstand_tensions_with_given_velocity_ratios(
|
|
283
|
-
self, in_profile: Profile, velocity_ratios: np.ndarray[float], final_speed: float
|
|
284
|
-
):
|
|
285
|
-
"""
|
|
286
|
-
Solve method, that calculates the resulting tensions for given reductions.
|
|
287
|
-
Further, it sets the velocities according to these reductions and the finishing speed.
|
|
288
|
-
|
|
289
|
-
:param in_profile: incoming profile
|
|
290
|
-
:param velocity_ratios: velocity_ratio per stand.
|
|
291
|
-
:param final_speed: speed of the last stand
|
|
292
|
-
"""
|
|
293
|
-
|
|
294
|
-
roll_pass_velocities = [final_speed]
|
|
295
|
-
for ratio in reversed(velocity_ratios):
|
|
296
|
-
previous_roll_pass_velocity = roll_pass_velocities[0] / ratio
|
|
297
|
-
roll_pass_velocities.insert(0, previous_roll_pass_velocity)
|
|
298
|
-
|
|
299
|
-
roll_pass_velocities = np.array(roll_pass_velocities)
|
|
300
|
-
for i, roll_pass in enumerate(self.roll_passes):
|
|
301
|
-
roll_pass.velocity = roll_pass_velocities[i]
|
|
302
|
-
|
|
303
|
-
copied_sequence = copy.deepcopy(self)
|
|
304
|
-
copied_sequence.solve(in_profile)
|
|
305
|
-
|
|
306
|
-
tensions = np.zeros(len(copied_sequence.roll_passes) * 2)
|
|
307
|
-
engineering_strains = (roll_pass_velocities[1:] - roll_pass_velocities[:-1]) / roll_pass_velocities[:-1]
|
|
308
|
-
|
|
309
|
-
for index in range(1, len(self.roll_passes)):
|
|
310
|
-
engineering_strain = engineering_strains[index - 1]
|
|
311
|
-
|
|
312
|
-
tensions[2 * index - 1] = (
|
|
313
|
-
copied_sequence.roll_passes[index - 1].in_profile.elastic_modulus * engineering_strain
|
|
314
|
-
)
|
|
315
|
-
tensions[2 * index] -= (
|
|
316
|
-
copied_sequence.roll_passes[index - 1].out_profile.elastic_modulus * engineering_strain
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
tensions[0] = 0
|
|
320
|
-
tensions[-1] = 0
|
|
321
|
-
|
|
322
|
-
for index, roll_pass in enumerate(self.roll_passes):
|
|
323
|
-
roll_pass.back_tension = tensions[2 * index]
|
|
324
|
-
roll_pass.front_tension = tensions[2 * index + 1]
|
|
325
|
-
|
|
326
|
-
self.solve(in_profile=in_profile)
|
|
327
|
-
|
|
328
290
|
def find_value_by_position_or_time(self, hook_name: str, position: float = None, time: float = None):
|
|
329
291
|
if position is None:
|
|
330
292
|
search_coordinate = "t"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .transport import Transport
|
|
2
|
+
from .cooling_pipe import CoolingPipe
|
|
3
|
+
from .spooler import Spooler
|
|
4
|
+
from .stelmor_conveyor import StelmorConveyor
|
|
5
|
+
from .induction_furnace import InductionFurnace
|
|
6
|
+
|
|
7
|
+
from . import hookimpls # noqa: F401
|
|
8
|
+
|
|
9
|
+
__all__ = ["Transport", "CoolingPipe", "Spooler", "StelmorConveyor", "InductionFurnace"]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
|
|
2
3
|
from ..cooling_pipe import CoolingPipe
|
|
3
4
|
|
|
4
5
|
|
|
@@ -11,7 +12,7 @@ def inner_radius(self: CoolingPipe):
|
|
|
11
12
|
@CoolingPipe.cross_section_area
|
|
12
13
|
def cross_section_area(self: CoolingPipe):
|
|
13
14
|
if self.has_set_or_cached("inner_radius"):
|
|
14
|
-
return self.inner_radius**2 * np.pi
|
|
15
|
+
return self.inner_radius ** 2 * np.pi
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@CoolingPipe.coolant_flow_cross_section
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from ..induction_furnace import InductionFurnace
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@InductionFurnace.electrical_efficiency
|
|
5
|
+
def default_electrical_efficiency(self: InductionFurnace):
|
|
6
|
+
return 0.6
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@InductionFurnace.generator_power
|
|
10
|
+
def generator_power_from_target_temperature(self: InductionFurnace):
|
|
11
|
+
if self.has_value("target_temperature"):
|
|
12
|
+
delta_t = self.target_temperature - self.in_profile.temperature
|
|
13
|
+
return self.mass_flux * self.in_profile.specific_heat_capacity * delta_t / self.electrical_efficiency
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@InductionFurnace.absorbed_power
|
|
17
|
+
def absorbed_power(self: InductionFurnace):
|
|
18
|
+
return self.generator_power * self.electrical_efficiency
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@InductionFurnace.power_density
|
|
22
|
+
def power_density(self: InductionFurnace):
|
|
23
|
+
return self.absorbed_power / self.volume
|