pyroll-core 2.1.2.post0__tar.gz → 2.1.3__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.
Files changed (73) hide show
  1. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/PKG-INFO +23 -7
  2. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/README.md +21 -6
  3. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyproject.toml +1 -0
  4. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/__init__.py +11 -11
  5. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/equivalent_ripped_groove.py +8 -0
  6. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/spline.py +48 -2
  7. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/hooks.py +19 -2
  8. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/profile/hookimpls.py +9 -4
  9. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/profile/profile.py +120 -0
  10. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll/hookimpls.py +4 -4
  11. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/roll.py +3 -3
  12. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/roll_pass.py +43 -1
  13. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/roll_pass.py +12 -0
  14. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/three_roll_pass.py +3 -0
  15. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/sequence/sequence.py +37 -5
  16. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/transport/__init__.py +1 -0
  17. pyroll_core-2.1.3/pyroll/core/transport/cooling_pipe.py +64 -0
  18. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/transport/transport.py +1 -1
  19. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/.gitignore +0 -0
  20. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/LICENSE +0 -0
  21. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/hatch.toml +0 -0
  22. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/config.py +0 -0
  23. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/disk_elements/__init__.py +0 -0
  24. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/disk_elements/disk_element_unit.py +0 -0
  25. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/disk_elements/hookimpls.py +0 -0
  26. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/__init__.py +0 -0
  27. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/base.py +0 -0
  28. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/boxes/__init__.py +0 -0
  29. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/boxes/box.py +0 -0
  30. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/boxes/constricted_box.py +0 -0
  31. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/boxes/upset_box.py +0 -0
  32. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/diamonds/__init__.py +0 -0
  33. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/diamonds/diamond.py +0 -0
  34. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/diamonds/gothic.py +0 -0
  35. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/diamonds/square.py +0 -0
  36. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/flat.py +0 -0
  37. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/generic_elongation.py +0 -0
  38. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/generic_elongation_solvers.py +0 -0
  39. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/hexagonal.py +0 -0
  40. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/__init__.py +0 -0
  41. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/circular_oval.py +0 -0
  42. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/constricted_circular_oval.py +0 -0
  43. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/constricted_swedish_oval.py +0 -0
  44. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/flat_oval.py +0 -0
  45. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/oval_3radii.py +0 -0
  46. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/oval_3radii_flanked.py +0 -0
  47. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/swedish_oval.py +0 -0
  48. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/ovals/upset_oval.py +0 -0
  49. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/rounds/__init__.py +0 -0
  50. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/rounds/false_round.py +0 -0
  51. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/grooves/rounds/round.py +0 -0
  52. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/log.py +0 -0
  53. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/profile/__init__.py +0 -0
  54. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/repr.py +0 -0
  55. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll/__init__.py +0 -0
  56. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll/roll.py +0 -0
  57. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/__init__.py +0 -0
  58. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/deformation_unit.py +0 -0
  59. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/__init__.py +0 -0
  60. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/deformation_unit.py +0 -0
  61. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/disk_element.py +0 -0
  62. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/helpers.py +0 -0
  63. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/roll_pass/hookimpls/profile.py +0 -0
  64. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/rotator/__init__.py +0 -0
  65. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/rotator/hookimpls.py +0 -0
  66. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/rotator/rotator.py +0 -0
  67. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/sequence/__init__.py +0 -0
  68. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/sequence/hookimpls.py +0 -0
  69. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/shapes.py +0 -0
  70. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/transport/hookimpls.py +0 -0
  71. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/unit/__init__.py +0 -0
  72. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/unit/hookimpls.py +0 -0
  73. {pyroll_core-2.1.2.post0 → pyroll_core-2.1.3}/pyroll/core/unit/unit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyroll-core
3
- Version: 2.1.2.post0
3
+ Version: 2.1.3
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
@@ -25,6 +25,7 @@ Classifier: Programming Language :: Python :: 3.10
25
25
  Classifier: Programming Language :: Python :: 3.11
26
26
  Classifier: Topic :: Scientific/Engineering
27
27
  Requires-Python: >=3.9
28
+ Requires-Dist: ezdxf
28
29
  Requires-Dist: numpy~=1.19
29
30
  Requires-Dist: scipy~=1.9
30
31
  Requires-Dist: shapely~=2.0
@@ -38,6 +39,8 @@ Description-Content-Type: text/markdown
38
39
  ![Python Versions](https://img.shields.io/pypi/pyversions/pyroll-core)
39
40
  [![License](https://img.shields.io/pypi/l/pyroll-core)](LICENSE)
40
41
  ![Downloads](https://img.shields.io/pypi/dm/pyroll-core)
42
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7971/badge)](https://www.bestpractices.dev/projects/7971)
43
+
41
44
 
42
45
  Welcome to The PyRolL Project!
43
46
 
@@ -46,6 +49,25 @@ The current focus lies on groove rolling in elongation grooves.
46
49
  The core package includes the basic data structures and algorithms.
47
50
  Further functionality is provided via [extension packages](https://pyroll.readthedocs.io/en/latest/extensions/index.html) and model approaches are provided via [plugin packages](https://pyroll.readthedocs.io/en/latest/plugins/index.html).
48
51
 
52
+ ## Aim and Scope
53
+
54
+ Publications in the field of groove rolling simulation are, to the authors knowledge, characterized by lack of reproducibility, mainly because often details are missing regarding implementation or numerical solution procedure or the provided equations are erroneous.
55
+ Very few authors provide their source code alongside their publications.
56
+ PyRolL aims to change this by providing an open basis for model and methodology development.
57
+ The PyRolL Core package hosted in this repository is the common base dependency to refer to.
58
+ Plugins to the core provide model approaches for the partial problems to be described in a rolling simulation, which include mechanical, thermal, material behavior and more.
59
+ The library of available models is constantly growing and can be extended by everyone.
60
+ Extensions provide application logic and numerical procedures helping to analyse the subject rolling process regarding its technical feasibility, optimization potential, identity error sources, or, to design a new process.
61
+ PyRolL is therefore a tool for model developers, either with broad focus or specialists, wishing to try out for example a new physical material model within a real process simulation, as well as for technologists wishing to investigate a concrete process.
62
+ Currently, there is no click'n'run graphical user interface (GUI) provided, at least basic knowledge of programming in general and Python in particular is needed to use the software.
63
+ Nevertheless, this is also recommended to leverage the full power of PyRolL.
64
+
65
+ ## Documentation
66
+
67
+ See the [documentation](https://pyroll.readthedocs.io/en/latest) to learn about basic concepts and
68
+ usage.
69
+ The documentation of version 1.0 can be found [here](https://pyroll.readthedocs.io/en/stable).
70
+
49
71
  ## Versions
50
72
 
51
73
  The main branch of this repository contains the code of the version 2.0 of PyRolL.
@@ -68,12 +90,6 @@ It is recommended to develop new plugins and extensions only for 2.0.
68
90
  - Introduction of disk elements for incremental modelling in rolling direction.
69
91
  - Nestable units (esp. pass sequences and disk elements).
70
92
 
71
- ## Documentation
72
-
73
- See the [documentation](https://pyroll.readthedocs.io/en/latest) to learn about basic concepts and
74
- usage.
75
- The documentation of version 1.0 can be found [here](https://pyroll.readthedocs.io/en/stable).
76
-
77
93
  ## License
78
94
 
79
95
  The project is licensed under the [BSD 3-Clause license](LICENSE).
@@ -4,6 +4,8 @@
4
4
  ![Python Versions](https://img.shields.io/pypi/pyversions/pyroll-core)
5
5
  [![License](https://img.shields.io/pypi/l/pyroll-core)](LICENSE)
6
6
  ![Downloads](https://img.shields.io/pypi/dm/pyroll-core)
7
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7971/badge)](https://www.bestpractices.dev/projects/7971)
8
+
7
9
 
8
10
  Welcome to The PyRolL Project!
9
11
 
@@ -12,6 +14,25 @@ The current focus lies on groove rolling in elongation grooves.
12
14
  The core package includes the basic data structures and algorithms.
13
15
  Further functionality is provided via [extension packages](https://pyroll.readthedocs.io/en/latest/extensions/index.html) and model approaches are provided via [plugin packages](https://pyroll.readthedocs.io/en/latest/plugins/index.html).
14
16
 
17
+ ## Aim and Scope
18
+
19
+ Publications in the field of groove rolling simulation are, to the authors knowledge, characterized by lack of reproducibility, mainly because often details are missing regarding implementation or numerical solution procedure or the provided equations are erroneous.
20
+ Very few authors provide their source code alongside their publications.
21
+ PyRolL aims to change this by providing an open basis for model and methodology development.
22
+ The PyRolL Core package hosted in this repository is the common base dependency to refer to.
23
+ Plugins to the core provide model approaches for the partial problems to be described in a rolling simulation, which include mechanical, thermal, material behavior and more.
24
+ The library of available models is constantly growing and can be extended by everyone.
25
+ Extensions provide application logic and numerical procedures helping to analyse the subject rolling process regarding its technical feasibility, optimization potential, identity error sources, or, to design a new process.
26
+ PyRolL is therefore a tool for model developers, either with broad focus or specialists, wishing to try out for example a new physical material model within a real process simulation, as well as for technologists wishing to investigate a concrete process.
27
+ Currently, there is no click'n'run graphical user interface (GUI) provided, at least basic knowledge of programming in general and Python in particular is needed to use the software.
28
+ Nevertheless, this is also recommended to leverage the full power of PyRolL.
29
+
30
+ ## Documentation
31
+
32
+ See the [documentation](https://pyroll.readthedocs.io/en/latest) to learn about basic concepts and
33
+ usage.
34
+ The documentation of version 1.0 can be found [here](https://pyroll.readthedocs.io/en/stable).
35
+
15
36
  ## Versions
16
37
 
17
38
  The main branch of this repository contains the code of the version 2.0 of PyRolL.
@@ -34,12 +55,6 @@ It is recommended to develop new plugins and extensions only for 2.0.
34
55
  - Introduction of disk elements for incremental modelling in rolling direction.
35
56
  - Nestable units (esp. pass sequences and disk elements).
36
57
 
37
- ## Documentation
38
-
39
- See the [documentation](https://pyroll.readthedocs.io/en/latest) to learn about basic concepts and
40
- usage.
41
- The documentation of version 1.0 can be found [here](https://pyroll.readthedocs.io/en/stable).
42
-
43
58
  ## License
44
59
 
45
60
  The project is licensed under the [BSD 3-Clause license](LICENSE).
@@ -37,6 +37,7 @@ dependencies = [
37
37
  "numpy ~= 1.19",
38
38
  "scipy ~= 1.9",
39
39
  "shapely ~= 2.0",
40
+ "ezdxf"
40
41
  ]
41
42
 
42
43
  dynamic = ["version"]
@@ -1,7 +1,7 @@
1
1
  from . import shapes as _
2
2
 
3
3
  from .grooves import *
4
- from .transport import Transport
4
+ from .transport import Transport, CoolingPipe
5
5
  from .roll_pass import RollPass, DeformationUnit, ThreeRollPass
6
6
  from .unit import Unit
7
7
  from .roll import Roll
@@ -12,23 +12,23 @@ from .hooks import Hook, HookHost, HookFunction, root_hooks
12
12
  from .disk_elements import DiskElementUnit
13
13
  from .config import Config, config
14
14
 
15
- VERSION = "2.1.2.post0"
15
+ VERSION = "2.1.3"
16
16
 
17
- root_hooks.update(
18
- {
17
+ root_hooks.extend(
18
+ [
19
19
  RollPass.roll_force,
20
20
  RollPass.Roll.roll_torque,
21
21
  RollPass.elongation_efficiency,
22
- RollPass.OutProfile.filling_ratio,
23
- RollPass.OutProfile.cross_section_filling_ratio,
24
- RollPass.OutProfile.filling_error,
25
- RollPass.OutProfile.cross_section_error,
22
+ Unit.power,
26
23
  Unit.OutProfile.cross_section,
24
+ Unit.OutProfile.classifiers,
27
25
  Unit.OutProfile.strain,
28
26
  Unit.OutProfile.length,
29
- Unit.OutProfile.classifiers,
30
27
  Unit.OutProfile.t,
28
+ RollPass.OutProfile.filling_ratio,
29
+ RollPass.OutProfile.cross_section_filling_ratio,
30
+ RollPass.OutProfile.filling_error,
31
+ RollPass.OutProfile.cross_section_error,
31
32
  PassSequence.log_elongation,
32
- Unit.power,
33
- }
33
+ ]
34
34
  )
@@ -20,6 +20,7 @@ class EquivalentRibbedGroove(GenericElongationGroove):
20
20
  nominal_outer_diameter: float,
21
21
  usable_width: float,
22
22
  depth: float,
23
+ rib_flank_angle: Optional[float] = None,
23
24
 
24
25
  pad_angle: float = 0,
25
26
  **kwargs
@@ -42,6 +43,13 @@ class EquivalentRibbedGroove(GenericElongationGroove):
42
43
  :param kwargs: more keyword arguments passed to the GenericElongationGroove constructor
43
44
  """
44
45
 
46
+ self.nominal_outer_diameter = nominal_outer_diameter
47
+ self.base_body_height = base_body_height
48
+ self.rib_distance = rib_distance
49
+ self.rib_width = rib_width
50
+ self.rib_flank_angle = rib_flank_angle
51
+
52
+
45
53
  pad_angle = np.deg2rad(pad_angle)
46
54
  rib_angle = np.deg2rad(rib_angle)
47
55
 
@@ -2,7 +2,8 @@ from typing import Union, Tuple, Iterable, Optional
2
2
 
3
3
  import numpy as np
4
4
  import scipy.interpolate
5
- from shapely.geometry import Polygon, LineString
5
+ from ezdxf import readfile
6
+ from shapely.geometry import Polygon, LineString, Point
6
7
 
7
8
  from pyroll.core.grooves import GrooveBase
8
9
 
@@ -18,7 +19,7 @@ class SplineGroove(GrooveBase):
18
19
  ):
19
20
  """
20
21
  :param contour_points: an iterable of contour points to be used for the spline
21
- :param classifiers: an interable of string keys used as type classifiers
22
+ :param classifiers: an iterable of string keys used as type classifiers
22
23
  :param usable_width: the usable width to assume for this instance, if None, the maximum width will be used
23
24
  """
24
25
  contour_points = np.asarray(contour_points, dtype="float64")
@@ -90,3 +91,48 @@ class SplineGroove(GrooveBase):
90
91
 
91
92
  def local_depth(self, z) -> Union[float, np.ndarray]:
92
93
  return self._local_depth(z)
94
+
95
+ @classmethod
96
+ def from_dxf_drawing(cls, filepath: str, classifiers: Iterable[str], usable_width: Optional[float] = None,
97
+ arc_discretization: int = 1000) -> 'SplineGroove':
98
+ """
99
+ Creates a spline groove with a given contour line as defined inside the .dxf drawing.
100
+
101
+ :param filepath: the filepath to the drawing
102
+ :param classifiers: an iterable of string keys used as type classifiers
103
+ :param usable_width: the usable width to assume for this instance, if None, the maximum width will be used
104
+ :param arc_discretization: discretization of arcs of the dxf drawing, default is 1000.
105
+ """
106
+
107
+ doc = readfile(filepath)
108
+ msp = doc.modelspace()
109
+
110
+ points = []
111
+ for entity in msp:
112
+ if entity.dxftype() == 'LINE':
113
+ points.append((entity.dxf.start[0], entity.dxf.start[1]))
114
+ points.append((entity.dxf.end[0], entity.dxf.end[1]))
115
+
116
+ elif entity.dxftype() == 'ARC':
117
+ center = (entity.dxf.center[0], entity.dxf.center[1])
118
+ radius = entity.dxf.radius
119
+ start_angle = np.radians(entity.dxf.start_angle)
120
+ end_angle = np.radians(entity.dxf.end_angle)
121
+
122
+ arc_points = [
123
+ (
124
+ center[0] + radius * np.cos(start_angle + (end_angle - start_angle) * i / arc_discretization),
125
+ center[1] + radius * np.sin(start_angle + (end_angle - start_angle) * i / arc_discretization),
126
+ )
127
+ for i in range(arc_discretization + 1)
128
+ ]
129
+ points.extend(arc_points)
130
+
131
+ points.sort(key=lambda p: p[0])
132
+
133
+ distance_to_y_0 = -points[0][1]
134
+
135
+ shifted_points = [(x, y + distance_to_y_0) for x, y in points]
136
+ shifted_points.sort(key=lambda p: p[0])
137
+
138
+ return SplineGroove(contour_points=shifted_points, classifiers=classifiers, usable_width=usable_width)
@@ -491,9 +491,26 @@ class HookHost(ReprMixin, LogMixin, metaclass=_HookHostMeta):
491
491
  return result
492
492
 
493
493
 
494
- root_hooks = set() # filled in __init__.py due to circular imports
494
+ class _RootHooksList(list):
495
+ def add(self, item):
496
+ self.append(item)
497
+
498
+ def insert_before(self, position, item):
499
+ i = self.index(position)
500
+ self.insert(i, item)
501
+
502
+ def insert_after(self, position, item):
503
+ i = self.index(position) + 1
504
+ self.insert(i, item)
505
+
506
+ def remove_last(self, item):
507
+ i = list(reversed(self)).index(item)
508
+ del self[-1 - i]
509
+
510
+
511
+ root_hooks = _RootHooksList() # filled in __init__.py due to circular imports
495
512
  """
496
- Set of hooks to call explicitly in each solution iteration.
513
+ List of hooks to call explicitly in each solution iteration.
497
514
  Their values will be treated as explicitly set but reevaluated in every iteration.
498
515
  They will not be deleted during cache clearing.
499
516
  They serve as root for the calling tree and persistent iterational variables.
@@ -38,6 +38,11 @@ def equivalent_width(self: Profile):
38
38
  return np.sqrt(self.cross_section.area * self.width / self.height)
39
39
 
40
40
 
41
+ @Profile.equivalent_radius
42
+ def equivalent_radius(self: Profile):
43
+ return np.sqrt(self.cross_section.area / np.pi)
44
+
45
+
41
46
  @Profile.equivalent_rectangle
42
47
  def equivalent_rectangle(self: Profile):
43
48
  return rectangle(self.equivalent_width, self.equivalent_height)
@@ -55,11 +60,11 @@ def core_temperature(self: Profile):
55
60
 
56
61
  @Profile.heat_penetration_number
57
62
  def heat_penetration_number(self: Profile):
58
- if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "thermal_capacity"):
59
- return np.sqrt(self.thermal_conductivity * self.density * self.thermal_capacity)
63
+ if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "specific_heat_capacity"):
64
+ return np.sqrt(self.thermal_conductivity * self.density * self.specific_heat_capacity)
60
65
 
61
66
 
62
67
  @Profile.thermal_diffusivity
63
68
  def thermal_diffusivity(self: Profile):
64
- if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "thermal_capacity"):
65
- return self.thermal_conductivity / (self.density * self.thermal_capacity)
69
+ if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "specific_heat_capacity"):
70
+ return self.thermal_conductivity / (self.density * self.specific_heat_capacity)
@@ -47,6 +47,9 @@ class Profile(HookHost):
47
47
  equivalent_width = Hook[float]()
48
48
  """Width of the equivalent rectangle."""
49
49
 
50
+ equivalent_radius = Hook[float]()
51
+ """Radius of a equivalent round."""
52
+
50
53
  temperature = Hook[float]()
51
54
  """Mean temperature of the profile cross-section."""
52
55
 
@@ -314,6 +317,32 @@ class Profile(HookHost):
314
317
 
315
318
  return DiamondProfile(height, width, corner_radius, **kwargs)
316
319
 
320
+ @classmethod
321
+ def hexagon(
322
+ cls,
323
+ side: Optional[float] = None,
324
+ height: Optional[float] = None,
325
+ diagonal: Optional[float] = None,
326
+ corner_radius: float = 0,
327
+ **kwargs
328
+ ) -> 'HexagonProfile':
329
+ """
330
+ Creates a hexagonal shaped profile (a real hexagonal shape with rounded corners,
331
+ without imperfections of hexagonal grooves). A hexagon is oriented to stand on its side.
332
+ Give exactly one of ``side``, ``height`` and ``diagonal``.
333
+
334
+ :param side: the side length of the hexagonal profile, must be > 0
335
+ :param height: the height of the hexagonal profile when standing on the flat base, must be > 0
336
+ :param diagonal: the diagonal length of the hexagonal profile (corner-to-corner distance)
337
+ :param corner_radius: the radius of the hexagon's corners, must be >= 0, <= diagonal / 2
338
+ :param kwargs: additional keyword arguments to be passed to the Profile constructor
339
+ :raises TypeError: on invalid argument combinations
340
+ :raises ValueError: if arguments are out of range
341
+ """
342
+
343
+
344
+ return HexagonProfile(side, height, diagonal, corner_radius, **kwargs)
345
+
317
346
  def local_height(self, z: float) -> float:
318
347
  coords = np.array([(1, -1), (1, 1)]) * (z, self.height)
319
348
 
@@ -619,3 +648,94 @@ class SquareProfile(Profile):
619
648
  diagonal=self._diagonal,
620
649
  corner_radius=self._corner_radius
621
650
  )
651
+
652
+
653
+ class HexagonProfile(Profile):
654
+ def __init__(
655
+ self,
656
+ side: Optional[float] = None,
657
+ height: Optional[float] = None,
658
+ diagonal: Optional[float] = None,
659
+ corner_radius: float = 0,
660
+ **kwargs
661
+ ):
662
+ """
663
+ Creates a hexagonal shaped profile (a real hexagonal shape with rounded corners,
664
+ without imperfections of hexagonal grooves). A hexagon is oriented to stand on its side.
665
+ Give exactly one of ``side``, ``height`` and ``diagonal``.
666
+
667
+ :param side: the side length of the hexagonal profile, must be > 0
668
+ :param height: the height of the hexagonal profile when standing on the flat base, must be > 0
669
+ :param diagonal: the diagonal length of the hexagonal profile (corner-to-corner distance)
670
+ :param corner_radius: the radius of the hexagon's corners, must be >= 0, <= diagonal / 2
671
+ :param kwargs: additional keyword arguments to be passed to the Profile constructor
672
+ :raises TypeError: on invalid argument combinations
673
+ :raises ValueError: if arguments are out of range
674
+ """
675
+
676
+ if side is not None and diagonal is None and height is None:
677
+ height = side * np.sqrt(3)
678
+ diagonal = side * 2
679
+ elif diagonal is not None and side is None and height is None:
680
+ side = diagonal / 2
681
+ height = side / np.sqrt(3)
682
+ elif height is not None and side is None and diagonal is None:
683
+ side = height / np.sqrt(3)
684
+ diagonal = side * 2
685
+ else:
686
+ raise TypeError("either 'side', 'height' or 'diagonal' must be given")
687
+
688
+ if (
689
+ side <= 0
690
+ or height <= 0
691
+ or diagonal <= 0
692
+ or corner_radius < 0
693
+ or corner_radius > side / 2
694
+ ):
695
+ raise ValueError("argument value(s) out of range")
696
+
697
+ self._corner_radius = corner_radius
698
+ self._side = side
699
+ self._diagonal = diagonal
700
+
701
+ line = LinearRing(
702
+ np.array([
703
+ (-1, 0),
704
+ (-1 / 2, np.sqrt(3) / 2),
705
+ (1 / 2, np.sqrt(3) / 2),
706
+ (1, 0),
707
+ (1 / 2, -np.sqrt(3) / 2),
708
+ (-1 / 2, -np.sqrt(3) / 2),
709
+ ])
710
+ * (side, side)
711
+ )
712
+ polygon = Polygon(line)
713
+ polygon = polygon.buffer(corner_radius)
714
+
715
+ super().__init__(
716
+ cross_section=polygon,
717
+ classifiers={"hexagon"},
718
+ **kwargs
719
+ )
720
+
721
+ @property
722
+ def side(self):
723
+ """The side length of the hexagon profile."""
724
+ return self._side
725
+
726
+ @property
727
+ def diagonal(self):
728
+ """The diagonal between two flat sides of the hexagon profile."""
729
+ return self._diagonal
730
+
731
+ @property
732
+ def corner_radius(self):
733
+ """The radius of the profile's corners resp. edges."""
734
+ return self._corner_radius
735
+
736
+ @property
737
+ def __attrs__(self):
738
+ return super().__attrs__ | dict(
739
+ side=self._side,
740
+ diagonal=self._diagonal,
741
+ corner_radius=self._corner_radius)
@@ -67,11 +67,11 @@ def surface_y(self: Roll):
67
67
 
68
68
  @Roll.heat_penetration_number
69
69
  def heat_penetration_number(self: Roll):
70
- if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "thermal_capacity"):
71
- return np.sqrt(self.thermal_conductivity * self.density * self.thermal_capacity)
70
+ if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "specific_heat_capacity"):
71
+ return np.sqrt(self.thermal_conductivity * self.density * self.specific_heat_capacity)
72
72
 
73
73
 
74
74
  @Roll.thermal_diffusivity
75
75
  def thermal_diffusivity(self: Roll):
76
- if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "thermal_capacity"):
77
- return self.thermal_conductivity / (self.density * self.thermal_capacity)
76
+ if hasattr(self, "thermal_conductivity") and hasattr(self, "density") and hasattr(self, "specific_heat_capacity"):
77
+ return self.thermal_conductivity / (self.density * self.specific_heat_capacity)
@@ -36,13 +36,13 @@ def center(self: RollPass.Roll):
36
36
  @RollPass.Roll.neutral_angle
37
37
  def neutral_angle(self: RollPass.Roll):
38
38
  if self.has_value("neutral_point"):
39
- return -np.arcsin(self.neutral_point / self.working_radius)
39
+ return np.arcsin(self.neutral_point / self.working_radius)
40
40
 
41
41
 
42
42
  @RollPass.Roll.neutral_point
43
43
  def neutral_point(self: RollPass.Roll):
44
44
  if self.has_set_or_cached("neutral_angle"):
45
- return -np.sin(self.neutral_angle) * self.working_radius
45
+ return np.sin(self.neutral_angle) * self.working_radius
46
46
 
47
47
 
48
48
  @RollPass.Roll.surface_velocity
@@ -51,4 +51,4 @@ def surface_velocity(self: RollPass.Roll):
51
51
  if self.has_value("neutral_angle"):
52
52
  return self.roll_pass.velocity / np.cos(self.neutral_angle)
53
53
  else:
54
- return self.roll_pass.velocity / np.cos(0)
54
+ return self.roll_pass.velocity / np.cos(self.roll_pass.exit_point)
@@ -97,6 +97,33 @@ def gap(self: RollPass):
97
97
  return self.height - 2 * self.roll.groove.depth
98
98
 
99
99
 
100
+ @ThreeRollPass.inscribed_circle_diameter
101
+ def inscribed_circle_diameter_from_gap(self: ThreeRollPass):
102
+ if self.has_set_or_cached("gap"):
103
+ half = self.roll.groove.usable_width / 2 / np.sqrt(3) + self.gap / np.sqrt(3) + self.roll.groove.depth
104
+ return half * 2
105
+
106
+
107
+ @ThreeRollPass.gap
108
+ def gap3_from_height(self: ThreeRollPass):
109
+ if self.has_set_or_cached("height"):
110
+ return (
111
+ self.height / 2
112
+ - self.roll.groove.usable_width / 2 / np.sqrt(3)
113
+ - self.roll.groove.depth
114
+ ) * np.sqrt(3)
115
+
116
+
117
+ @ThreeRollPass.gap
118
+ def gap3_from_icd(self: ThreeRollPass):
119
+ if self.has_set_or_cached("inscribed_circle_diameter"):
120
+ return (
121
+ self.inscribed_circle_diameter / 2
122
+ - self.roll.groove.usable_width / 2 / np.sqrt(3)
123
+ - self.roll.groove.depth
124
+ ) * np.sqrt(3)
125
+
126
+
100
127
  @RollPass.height
101
128
  def height(self):
102
129
  if self.has_set_or_cached("gap"):
@@ -223,6 +250,21 @@ def roll_power_3(self: ThreeRollPass):
223
250
  return 3 * self.roll.roll_power
224
251
 
225
252
 
253
+ @RollPass.entry_point
254
+ def entry_point(self: RollPass):
255
+ return - self.roll.contact_length
256
+
257
+
226
258
  @RollPass.entry_angle
227
259
  def entry_angle(self: RollPass):
228
- return -np.arcsin(self.roll.contact_length / self.roll.working_radius)
260
+ return np.arcsin(self.entry_point / self.roll.working_radius)
261
+
262
+
263
+ @RollPass.exit_point
264
+ def exit_point(self: RollPass):
265
+ return 0
266
+
267
+
268
+ @RollPass.exit_angle
269
+ def exit_angle(self: RollPass):
270
+ return np.arcsin(self.exit_point / self.roll.working_radius)
@@ -60,9 +60,18 @@ class RollPass(DiskElementUnit, DeformationUnit):
60
60
  roll_force = Hook[float]()
61
61
  """Vertical roll force."""
62
62
 
63
+ entry_point = Hook[float]()
64
+ """Point where the material enters the roll gap."""
65
+
63
66
  entry_angle = Hook[float]()
64
67
  """Angle at which the material enters the roll gap."""
65
68
 
69
+ exit_point = Hook[float]()
70
+ """Point where the material exits the roll gap."""
71
+
72
+ exit_angle = Hook[float]()
73
+ """Angle at which the material exits the roll gap."""
74
+
66
75
  front_tension = Hook[float]()
67
76
  """Front tension acting on the current roll pass."""
68
77
 
@@ -84,6 +93,9 @@ class RollPass(DiskElementUnit, DeformationUnit):
84
93
  target_cross_section_filling_ratio = Hook[float]()
85
94
  """Target cross-section filling ratio of the out profile."""
86
95
 
96
+ forward_slip_ratio = Hook[float]()
97
+ """Ratio of forward slip of the roll gap."""
98
+
87
99
  def __init__(
88
100
  self,
89
101
  roll: BaseRoll,
@@ -12,6 +12,9 @@ from ..roll import Roll as BaseRoll
12
12
  class ThreeRollPass(RollPass):
13
13
  """Represents a roll pass with three working rolls and 3-fold symmetry."""
14
14
 
15
+ inscribed_circle_diameter = Hook[float]()
16
+ """Diameter of inscribed circle between roll barrels as alternative to roll gap definition."""
17
+
15
18
  @property
16
19
  def contour_lines(self) -> List[LineString]:
17
20
  if self._contour_lines:
@@ -2,6 +2,8 @@ from collections.abc import Sequence
2
2
  from typing import overload, List, cast
3
3
 
4
4
  from ..unit import Unit
5
+ from ..roll_pass import RollPass
6
+ from ..transport import Transport
5
7
  from ..hooks import Hook
6
8
 
7
9
 
@@ -56,22 +58,52 @@ class PassSequence(Unit, Sequence[Unit]):
56
58
  return self._subunits.__iter__()
57
59
 
58
60
  @overload
59
- def __getitem__(self, index: int) -> Unit:
61
+ def __getitem__(self, key: int) -> Unit:
62
+ """Gets unit item by index."""
60
63
  ...
61
64
 
62
65
  @overload
63
- def __getitem__(self, index: slice) -> list[Unit]:
66
+ def __getitem__(self, key: str) -> Unit:
67
+ """Gets unit item by label."""
64
68
  ...
65
69
 
66
- def __getitem__(self, index: int) -> Unit:
67
- return self._subunits.__getitem__(index)
70
+ @overload
71
+ def __getitem__(self, key: slice) -> list[Unit]:
72
+ """Gets a slice of units."""
73
+ ...
74
+
75
+ def __getitem__(self, key):
76
+ if isinstance(key, str):
77
+ try:
78
+ return next(u for u in self._subunits if u.label == key)
79
+ except StopIteration:
80
+ raise KeyError(f"No unit with label '{key}' found.")
81
+
82
+ if isinstance(key, int) or isinstance(key, slice):
83
+ return self._subunits.__getitem__(key)
84
+
85
+ raise TypeError("Key must be int, slice or str")
68
86
 
69
87
  @property
70
88
  def units(self) -> List[Unit]:
71
- return self._subunits
89
+ """Returns a list of all units in this sequence."""
90
+ return list(self._subunits)
91
+
92
+ @property
93
+ def roll_passes(self) -> List[RollPass]:
94
+ """Returns a list of all roll passes in this sequence."""
95
+ return list(u for u in self._subunits if isinstance(u, RollPass))
96
+
97
+ @property
98
+ def transports(self) -> List[Transport]:
99
+ """Returns a list of all transports in this sequence."""
100
+ return list(u for u in self._subunits if isinstance(u, Transport))
72
101
 
73
102
  @property
74
103
  def __attrs__(self):
75
104
  return super().__attrs__ | {
76
105
  "units": self.units
77
106
  }
107
+
108
+ def _ipython_key_completions_(self):
109
+ return [u.label for u in self._subunits]
@@ -1,3 +1,4 @@
1
1
  from .transport import Transport
2
+ from .cooling_pipe import CoolingPipe
2
3
 
3
4
  from . import hookimpls
@@ -0,0 +1,64 @@
1
+ from typing import List, cast
2
+
3
+ from ..hooks import Hook
4
+ from .transport import Transport
5
+
6
+
7
+ class CoolingPipe(Transport):
8
+ """Represents a smooth cooling pipe."""
9
+
10
+ inner_diameter = Hook[float]()
11
+ """Inner diameter of the cooling pipe"""
12
+
13
+ cooling_water_volume_flux = Hook[float]()
14
+ """Volume flux of the cooling water"""
15
+
16
+ cooling_water_temperature = Hook[float]()
17
+ """Temperature of the cooling water"""
18
+
19
+
20
+ @property
21
+ def disk_elements(self) -> List['CoolingPipe.DiskElement']:
22
+ """A list of disk elements used to subdivide this unit."""
23
+ return list(self._subunits)
24
+
25
+ class Profile(Transport.Profile):
26
+ """Represents a profile in context of a transport unit."""
27
+
28
+ @property
29
+ def cooling_pipe(self) -> 'CoolingPipe':
30
+ """Reference to the transport. Alias for ``self.unit``."""
31
+ return cast(CoolingPipe, self.unit)
32
+
33
+ class InProfile(Profile, Transport.InProfile):
34
+ """Represents an incoming profile of a transport unit."""
35
+
36
+ class OutProfile(Profile, Transport.OutProfile):
37
+ """Represents an outgoing profile of a transport unit."""
38
+
39
+ class DiskElement(Transport.DiskElement):
40
+ """Represents a disk element in a roll pass."""
41
+
42
+ @property
43
+ def cooling_pipe(self) -> 'CoolingPipe':
44
+ """Reference to the transport. Alias for ``self.parent``."""
45
+ return cast(CoolingPipe, self.parent)
46
+
47
+ class Profile(Transport.DiskElement.Profile):
48
+ """Represents a profile in context of a disk element unit."""
49
+
50
+ @property
51
+ def disk_element(self) -> 'CoolingPipe.DiskElement':
52
+ """Reference to the disk element. Alias for ``self.unit``"""
53
+ return cast(CoolingPipe.DiskElement, self.unit)
54
+
55
+ @property
56
+ def cooling_pipe(self) -> 'CoolingPipe':
57
+ """Reference to the transport. Alias for ``self.unit.parent``"""
58
+ return cast(CoolingPipe, self.unit.parent)
59
+
60
+ class InProfile(Profile, Transport.DiskElement.InProfile):
61
+ """Represents an incoming profile of a disk element unit."""
62
+
63
+ class OutProfile(Profile, Transport.DiskElement.OutProfile):
64
+ """Represents an outgoing profile of a disk element unit."""
@@ -13,7 +13,7 @@ class Transport(DiskElementUnit):
13
13
  @property
14
14
  def disk_elements(self) -> List['Transport.DiskElement']:
15
15
  """A list of disk elements used to subdivide this unit."""
16
- return self._subunits
16
+ return list(self._subunits)
17
17
 
18
18
  class Profile(DiskElementUnit.Profile):
19
19
  """Represents a profile in context of a transport unit."""
File without changes