pyroll-core 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyroll/core/__init__.py +38 -0
- pyroll/core/config.py +155 -0
- pyroll/core/disk_elements/__init__.py +2 -0
- pyroll/core/disk_elements/disk_element_unit.py +73 -0
- pyroll/core/disk_elements/hookimpls.py +26 -0
- pyroll/core/grooves/__init__.py +10 -0
- pyroll/core/grooves/base.py +59 -0
- pyroll/core/grooves/boxes/__init__.py +3 -0
- pyroll/core/grooves/boxes/box.py +57 -0
- pyroll/core/grooves/boxes/constricted_box.py +63 -0
- pyroll/core/grooves/boxes/upset_box.py +18 -0
- pyroll/core/grooves/diamonds/__init__.py +3 -0
- pyroll/core/grooves/diamonds/diamond.py +60 -0
- pyroll/core/grooves/diamonds/gothic.py +41 -0
- pyroll/core/grooves/diamonds/square.py +48 -0
- pyroll/core/grooves/flat.py +31 -0
- pyroll/core/grooves/generic_elongation.py +258 -0
- pyroll/core/grooves/generic_elongation_solvers.py +422 -0
- pyroll/core/grooves/hexagonal.py +57 -0
- pyroll/core/grooves/ovals/__init__.py +8 -0
- pyroll/core/grooves/ovals/circular_oval.py +43 -0
- pyroll/core/grooves/ovals/constricted_circular_oval.py +53 -0
- pyroll/core/grooves/ovals/constricted_swedish_oval.py +61 -0
- pyroll/core/grooves/ovals/flat_oval.py +56 -0
- pyroll/core/grooves/ovals/oval_3radii.py +47 -0
- pyroll/core/grooves/ovals/oval_3radii_flanked.py +65 -0
- pyroll/core/grooves/ovals/swedish_oval.py +57 -0
- pyroll/core/grooves/ovals/upset_oval.py +47 -0
- pyroll/core/grooves/rounds/__init__.py +2 -0
- pyroll/core/grooves/rounds/false_round.py +65 -0
- pyroll/core/grooves/rounds/round.py +44 -0
- pyroll/core/grooves/spline.py +92 -0
- pyroll/core/hooks.py +420 -0
- pyroll/core/log.py +7 -0
- pyroll/core/profile/__init__.py +3 -0
- pyroll/core/profile/hookimpls.py +58 -0
- pyroll/core/profile/profile.py +380 -0
- pyroll/core/repr.py +49 -0
- pyroll/core/roll/__init__.py +3 -0
- pyroll/core/roll/hookimpls.py +65 -0
- pyroll/core/roll/roll.py +145 -0
- pyroll/core/roll_pass/__init__.py +8 -0
- pyroll/core/roll_pass/deformation_unit.py +61 -0
- pyroll/core/roll_pass/hookimpls/__init__.py +5 -0
- pyroll/core/roll_pass/hookimpls/deformation_unit.py +78 -0
- pyroll/core/roll_pass/hookimpls/disk_element.py +16 -0
- pyroll/core/roll_pass/hookimpls/profile.py +94 -0
- pyroll/core/roll_pass/hookimpls/roll.py +43 -0
- pyroll/core/roll_pass/hookimpls/roll_pass.py +107 -0
- pyroll/core/roll_pass/roll_pass.py +168 -0
- pyroll/core/roll_pass/three_roll_pass.py +84 -0
- pyroll/core/rotator/__init__.py +3 -0
- pyroll/core/rotator/hookimpls.py +71 -0
- pyroll/core/rotator/rotator.py +65 -0
- pyroll/core/sequence/__init__.py +3 -0
- pyroll/core/sequence/hookimpls.py +18 -0
- pyroll/core/sequence/sequence.py +68 -0
- pyroll/core/shapes.py +104 -0
- pyroll/core/transport/__init__.py +3 -0
- pyroll/core/transport/hookimpls.py +17 -0
- pyroll/core/transport/transport.py +57 -0
- pyroll/core/unit/__init__.py +3 -0
- pyroll/core/unit/hookimpls.py +67 -0
- pyroll/core/unit/unit.py +271 -0
- pyroll_core-2.0.0.dist-info/METADATA +109 -0
- pyroll_core-2.0.0.dist-info/RECORD +68 -0
- pyroll_core-2.0.0.dist-info/WHEEL +4 -0
- pyroll_core-2.0.0.dist-info/licenses/LICENSE +11 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from typing import Union, Tuple, Iterable, Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import scipy.interpolate
|
|
5
|
+
from shapely.geometry import Polygon, LineString
|
|
6
|
+
|
|
7
|
+
from pyroll.core.grooves import GrooveBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SplineGroove(GrooveBase):
|
|
11
|
+
"""Represents a groove defined by a linear spline contour."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
contour_points: Union[Iterable[Tuple[float, float]], np.ndarray],
|
|
16
|
+
classifiers: Iterable[str],
|
|
17
|
+
usable_width: Optional[float] = None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
: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 usable_width: the usable width to assume for this instance, if None, the maximum width will be used
|
|
23
|
+
"""
|
|
24
|
+
contour_points = np.asarray(contour_points, dtype="float64")
|
|
25
|
+
|
|
26
|
+
if contour_points.ndim != 2:
|
|
27
|
+
raise ValueError("contour_points must be two-dimensional")
|
|
28
|
+
|
|
29
|
+
if contour_points.shape[1] != 2:
|
|
30
|
+
raise ValueError("contour_points must have length of 2 in second dimension")
|
|
31
|
+
|
|
32
|
+
if not np.isclose(contour_points[0, 1], 0) or not np.isclose(contour_points[-1, 1], 0):
|
|
33
|
+
raise ValueError("first and last element of contour_points should have y coordinate equal to 0")
|
|
34
|
+
|
|
35
|
+
# strip boundary
|
|
36
|
+
contour_points = contour_points[np.logical_not(
|
|
37
|
+
(np.isclose(np.roll(contour_points[:, 1], 1), 0)) & (np.isclose(np.roll(contour_points[:, 1], -1), 0))
|
|
38
|
+
)]
|
|
39
|
+
|
|
40
|
+
# shift center to 0,0
|
|
41
|
+
contour_points[:, 0] -= np.mean(contour_points[:, 0])
|
|
42
|
+
|
|
43
|
+
half_width = contour_points[-1, 0]
|
|
44
|
+
|
|
45
|
+
if usable_width:
|
|
46
|
+
self._usable_width = usable_width
|
|
47
|
+
else:
|
|
48
|
+
self._usable_width = half_width * 2
|
|
49
|
+
|
|
50
|
+
self._contour_points = contour_points
|
|
51
|
+
self._contour_line = LineString(contour_points)
|
|
52
|
+
self._cross_section = Polygon(self._contour_line)
|
|
53
|
+
self._width = half_width * 2
|
|
54
|
+
|
|
55
|
+
self._depth = np.max(contour_points[:, 1])
|
|
56
|
+
|
|
57
|
+
self._local_depth = scipy.interpolate.interp1d(
|
|
58
|
+
contour_points[:, 0], contour_points[:, 1],
|
|
59
|
+
fill_value="extrapolate"
|
|
60
|
+
)
|
|
61
|
+
self._classifiers = tuple(classifiers)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def classifiers(self) -> Tuple[str, ...]:
|
|
65
|
+
return self._classifiers
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def cross_section(self) -> Polygon:
|
|
69
|
+
return self._cross_section
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def usable_width(self) -> float:
|
|
73
|
+
return self._usable_width
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def width(self) -> float:
|
|
77
|
+
return self._width
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def depth(self) -> float:
|
|
81
|
+
return self._depth
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def contour_points(self):
|
|
85
|
+
return self._contour_points
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def contour_line(self) -> LineString:
|
|
89
|
+
return self._contour_line
|
|
90
|
+
|
|
91
|
+
def local_depth(self, z) -> Union[float, np.ndarray]:
|
|
92
|
+
return self._local_depth(z)
|
pyroll/core/hooks.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import inspect
|
|
3
|
+
import weakref
|
|
4
|
+
from abc import ABCMeta
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from functools import partial
|
|
7
|
+
from typing import overload, TypeVar, Generic, List, Generator, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .log import LogMixin
|
|
12
|
+
from .repr import ReprMixin
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HookFunction:
|
|
18
|
+
"""
|
|
19
|
+
Class wrapping a function used to yield the value of hooks.
|
|
20
|
+
Instantiated commonly by use of a `Hook` instance as decorator on a function.
|
|
21
|
+
You should not instantiate it yourself.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, func, hook, tryfirst=False, trylast=False):
|
|
25
|
+
"""
|
|
26
|
+
:param func: the function
|
|
27
|
+
:param hook: the associated hook
|
|
28
|
+
:param tryfirst: whether to use this function with the highest priority
|
|
29
|
+
:param trylast: whether to use this function with the lowest priority
|
|
30
|
+
"""
|
|
31
|
+
self.function = func
|
|
32
|
+
"""The underlying function."""
|
|
33
|
+
|
|
34
|
+
self.module = func.__module__
|
|
35
|
+
"""The module the function originates from."""
|
|
36
|
+
|
|
37
|
+
self.qualname = func.__qualname__
|
|
38
|
+
"""The qualified name of the function."""
|
|
39
|
+
|
|
40
|
+
self.name = func.__name__
|
|
41
|
+
"""The name of the function."""
|
|
42
|
+
|
|
43
|
+
self.hook = hook
|
|
44
|
+
"""The hook the function is defined for."""
|
|
45
|
+
|
|
46
|
+
self.cycle = False
|
|
47
|
+
"""Cycle detection."""
|
|
48
|
+
|
|
49
|
+
self._tryfirst = tryfirst
|
|
50
|
+
self._trylast = trylast
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def tryfirst(self):
|
|
54
|
+
"""Whether to use this function with the highest priority."""
|
|
55
|
+
return self._tryfirst
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def trylast(self):
|
|
59
|
+
"""Whether to use this function with the lowest priority."""
|
|
60
|
+
return self._trylast
|
|
61
|
+
|
|
62
|
+
def __call__(self, instance):
|
|
63
|
+
"""Call the function as it were a method the provided instance."""
|
|
64
|
+
|
|
65
|
+
extra_args = self._determine_extra_args()
|
|
66
|
+
self.cycle = True
|
|
67
|
+
try:
|
|
68
|
+
result = self.function(instance, **extra_args)
|
|
69
|
+
finally:
|
|
70
|
+
self.cycle = False
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return f"<{self.__str__()}>"
|
|
75
|
+
|
|
76
|
+
def __str__(self):
|
|
77
|
+
return f"HookFunction {self.module}.{self.qualname}"
|
|
78
|
+
|
|
79
|
+
def _determine_extra_args(self):
|
|
80
|
+
extra_args = {}
|
|
81
|
+
pars = inspect.signature(self.function).parameters
|
|
82
|
+
if "cycle" in pars:
|
|
83
|
+
extra_args["cycle"] = self.cycle
|
|
84
|
+
|
|
85
|
+
return extra_args
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Hook(Generic[T]):
|
|
89
|
+
"""
|
|
90
|
+
Descriptor yielding the value of a hook attribute if called on instance,
|
|
91
|
+
or itself if called on class.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, name=None, owner=None):
|
|
95
|
+
self.name = name
|
|
96
|
+
"""The name of the hook."""
|
|
97
|
+
|
|
98
|
+
self.owner = owner
|
|
99
|
+
"""The owner class of the hook instance."""
|
|
100
|
+
|
|
101
|
+
self._first_functions: List[HookFunction] = []
|
|
102
|
+
|
|
103
|
+
self._last_functions: List[HookFunction] = []
|
|
104
|
+
|
|
105
|
+
self._functions: List[HookFunction] = []
|
|
106
|
+
|
|
107
|
+
def __set_name__(self, owner, name):
|
|
108
|
+
self.name = name
|
|
109
|
+
self.owner = owner
|
|
110
|
+
|
|
111
|
+
@overload
|
|
112
|
+
def __get__(self, instance: None, owner: type) -> 'Hook[T]':
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
@overload
|
|
116
|
+
def __get__(self, instance: 'HookHost', owner: type) -> T:
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
def __get__(self, instance: 'HookHost', owner: type) -> Union[T, 'Hook[T]']:
|
|
120
|
+
"""
|
|
121
|
+
Get the value of the hook if called on instance or the descriptor itself if called on class.
|
|
122
|
+
|
|
123
|
+
Hook value resolution is done in the following order:
|
|
124
|
+
1. explicit values (in ``__dict__``)
|
|
125
|
+
2. cached values (in ``__cache__``)
|
|
126
|
+
3. from hook functions.
|
|
127
|
+
|
|
128
|
+
Saves the value in ``__cache__`` if the value was determined from functions.
|
|
129
|
+
|
|
130
|
+
:raises AttributeError: if no value could be provided or if a RecursionError occurred during hook function calling
|
|
131
|
+
:raises ValueError: if the resulting value was infinite (only for numeric values)
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
if self.owner != owner:
|
|
135
|
+
# create distinct instance on subclass
|
|
136
|
+
hook = Hook()
|
|
137
|
+
setattr(owner, self.name, hook)
|
|
138
|
+
return hook.__get__(instance, owner)
|
|
139
|
+
|
|
140
|
+
if instance is None:
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
self.owner.logger.debug(f"Hook %s.%s on %s: called.", self.owner.__qualname__, self.name, instance)
|
|
144
|
+
|
|
145
|
+
# try to get value explicitly set by user
|
|
146
|
+
result = instance.__dict__.get(self.name, None)
|
|
147
|
+
if result is not None:
|
|
148
|
+
self.owner.logger.debug(
|
|
149
|
+
f"Hook %s.%s on %s: resolved with explicit value %s.",
|
|
150
|
+
self.owner.__qualname__, self.name, instance, result
|
|
151
|
+
)
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
# try to get cached value
|
|
155
|
+
result = instance.__cache__.get(self.name, None)
|
|
156
|
+
if result is not None:
|
|
157
|
+
self.owner.logger.debug(
|
|
158
|
+
f"Hook %s.%s on %s: resolved with cached value %s.",
|
|
159
|
+
self.owner.__qualname__, self.name, instance, result
|
|
160
|
+
)
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
# try to get value from hook caller
|
|
164
|
+
try:
|
|
165
|
+
result = self.get_result(instance)
|
|
166
|
+
except RecursionError as e:
|
|
167
|
+
raise AttributeError(
|
|
168
|
+
f"Hook call for '{self.name}' on '{instance}' resulted in a RecursionError. "
|
|
169
|
+
f"This may have one of the following reasons: missing data, interference of plugins. "
|
|
170
|
+
f"Double check if you have provided all necessary input data."
|
|
171
|
+
) from e
|
|
172
|
+
|
|
173
|
+
if result is None:
|
|
174
|
+
raise AttributeError(f"Hook call for '{self.name}' on '{instance}' could not provide a value.")
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
if not np.isfinite(result).all():
|
|
178
|
+
raise ValueError(f"Hook call for '{self.name}' on '{instance}' resulted in an infinite value.")
|
|
179
|
+
except TypeError:
|
|
180
|
+
pass # only numeric types can be tested for finiteness, for others it is meaningless
|
|
181
|
+
|
|
182
|
+
instance.__cache__[self.name] = result
|
|
183
|
+
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
def __set__(self, instance: object, value: T) -> None:
|
|
187
|
+
"""Saves a value to the ``__dict__`` of the instance."""
|
|
188
|
+
instance.__dict__[self.name] = value
|
|
189
|
+
|
|
190
|
+
def __delete__(self, instance: object) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Deletes the value from the ``__dict__`` of the instance.
|
|
193
|
+
Does not raise, if the value is not in the ``__dict__``.
|
|
194
|
+
"""
|
|
195
|
+
instance.__dict__.pop(self.name, None)
|
|
196
|
+
|
|
197
|
+
def _yield_functions_from(self, attr: str):
|
|
198
|
+
for s in self.owner.__mro__:
|
|
199
|
+
h = getattr(s, self.name, None)
|
|
200
|
+
funcs = getattr(h, attr, None)
|
|
201
|
+
if funcs:
|
|
202
|
+
yield from reversed(funcs)
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def functions_gen(self) -> Generator[HookFunction, None, None]:
|
|
206
|
+
"""
|
|
207
|
+
Generator listing functions stored in this instance and equally named instances in superclasses of its owner.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
yield from self._yield_functions_from("_first_functions")
|
|
211
|
+
yield from self._yield_functions_from("_functions")
|
|
212
|
+
yield from self._yield_functions_from("_last_functions")
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def functions(self) -> List[HookFunction]:
|
|
216
|
+
"""
|
|
217
|
+
List of functions stored in this instance and equally named instances in superclasses of its owner.
|
|
218
|
+
"""
|
|
219
|
+
return list(self.functions_gen)
|
|
220
|
+
|
|
221
|
+
def get_result(self, instance) -> Union[T, None]:
|
|
222
|
+
"""
|
|
223
|
+
Get the first not ``None`` result of the functions in ``self.functions`` or the cached value.
|
|
224
|
+
"""
|
|
225
|
+
for f in self.functions_gen:
|
|
226
|
+
result = f(instance)
|
|
227
|
+
if result is not None:
|
|
228
|
+
self.owner.logger.debug(
|
|
229
|
+
f"Hook %s.%s on %s: resolved from function %s with value %s.",
|
|
230
|
+
self.owner.__qualname__, self.name, instance, f.qualname, result
|
|
231
|
+
)
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
self.owner.logger.debug(
|
|
235
|
+
f"Hook %s.%s on %s: function %s resulted in None.",
|
|
236
|
+
self.owner.__qualname__, self.name, instance, f.qualname
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def add_function(self, func, tryfirst=False, trylast=False):
|
|
240
|
+
"""
|
|
241
|
+
Add the given function to the internal function store.
|
|
242
|
+
|
|
243
|
+
:return: the created HookFunction object
|
|
244
|
+
"""
|
|
245
|
+
if isinstance(func, HookFunction):
|
|
246
|
+
func = func.function
|
|
247
|
+
|
|
248
|
+
hf = HookFunction(func, self, trylast=trylast, tryfirst=tryfirst)
|
|
249
|
+
|
|
250
|
+
if tryfirst:
|
|
251
|
+
self._first_functions.append(hf)
|
|
252
|
+
elif trylast:
|
|
253
|
+
self._last_functions.append(hf)
|
|
254
|
+
else:
|
|
255
|
+
self._functions.append(hf)
|
|
256
|
+
|
|
257
|
+
return hf
|
|
258
|
+
|
|
259
|
+
def __call__(self, func=None, tryfirst=False, trylast=False):
|
|
260
|
+
if func is None:
|
|
261
|
+
return partial(self.add_function, tryfirst=tryfirst, trylast=trylast)
|
|
262
|
+
return self.add_function(func, tryfirst=tryfirst, trylast=trylast)
|
|
263
|
+
|
|
264
|
+
def remove_function(self, func: HookFunction):
|
|
265
|
+
"""
|
|
266
|
+
Remove a function from the internal function store.
|
|
267
|
+
|
|
268
|
+
:return: the underlying function object
|
|
269
|
+
"""
|
|
270
|
+
self._functions.remove(func)
|
|
271
|
+
return func.function
|
|
272
|
+
|
|
273
|
+
def __repr__(self):
|
|
274
|
+
return f"<{self.__str__()}>"
|
|
275
|
+
|
|
276
|
+
def __str__(self):
|
|
277
|
+
return f"Hook {self.owner.__qualname__}.{self.name}"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class _HookHostMeta(ABCMeta):
|
|
281
|
+
"""
|
|
282
|
+
Metaclass that provides plugin functionality to a class.
|
|
283
|
+
|
|
284
|
+
Not for direct use but through :py:class:`PluginHost` base class.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
def __init__(cls, name, bases, dct):
|
|
288
|
+
super().__init__(name, bases, dct)
|
|
289
|
+
|
|
290
|
+
def __setattr__(self, key, value):
|
|
291
|
+
if isinstance(value, Hook):
|
|
292
|
+
value.__set_name__(self, key)
|
|
293
|
+
super().__setattr__(key, value)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class HookHost(ReprMixin, LogMixin, metaclass=_HookHostMeta):
|
|
297
|
+
"""
|
|
298
|
+
A base class providing plugin functionality and some related convenience methods to a derived class.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
def __init__(self):
|
|
302
|
+
self.__cache__ = dict()
|
|
303
|
+
|
|
304
|
+
def reevaluate_cache(self):
|
|
305
|
+
"""
|
|
306
|
+
Clears the cache of hook function results.
|
|
307
|
+
"""
|
|
308
|
+
for n in list(self.__cache__.keys()):
|
|
309
|
+
hook = getattr(type(self), n)
|
|
310
|
+
self.__cache__[n] = hook.get_result(self)
|
|
311
|
+
|
|
312
|
+
def has_set(self, name: str):
|
|
313
|
+
"""Checks whether a value is explicitly set for the hook `name`."""
|
|
314
|
+
return name in self.__dict__
|
|
315
|
+
|
|
316
|
+
def has_cached(self, name: str):
|
|
317
|
+
"""Checks whether a value is cached for the hook `name`."""
|
|
318
|
+
return name in self.__cache__
|
|
319
|
+
|
|
320
|
+
def has_set_or_cached(self, name: str):
|
|
321
|
+
"""Checks whether a value is explicitly set or cached for the hook `name`."""
|
|
322
|
+
return self.has_set(name) or self.has_cached(name)
|
|
323
|
+
|
|
324
|
+
def has_value(self, name: str):
|
|
325
|
+
"""Checks whether a value is available for the hook `name`."""
|
|
326
|
+
return hasattr(self, name)
|
|
327
|
+
|
|
328
|
+
@classmethod
|
|
329
|
+
@property
|
|
330
|
+
def __hooks__(cls):
|
|
331
|
+
"""Return a set of all hook names defined on this class and all superclasses."""
|
|
332
|
+
|
|
333
|
+
hooks = set()
|
|
334
|
+
for s in cls.__mro__:
|
|
335
|
+
hooks = hooks.union(
|
|
336
|
+
[
|
|
337
|
+
name for name, value in s.__dict__.items()
|
|
338
|
+
if not name.startswith("_")
|
|
339
|
+
if isinstance(value, Hook)
|
|
340
|
+
]
|
|
341
|
+
)
|
|
342
|
+
return hooks
|
|
343
|
+
|
|
344
|
+
@classmethod
|
|
345
|
+
def extension_class(cls, source: type):
|
|
346
|
+
"""
|
|
347
|
+
Class decorator for adding new hook definitions to an existing HookHost.
|
|
348
|
+
|
|
349
|
+
:return: the extended class (the reference to the source class is discarded)
|
|
350
|
+
:raises TypeError: if the source class is itself derived from HookHost (error-prone)
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
for name, value in source.__dict__.items():
|
|
354
|
+
if isinstance(value, Hook) and name not in cls.__dict__:
|
|
355
|
+
setattr(cls, name, value)
|
|
356
|
+
return cls
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def __attrs__(self):
|
|
360
|
+
return {
|
|
361
|
+
n: v
|
|
362
|
+
for n, v in (self.__dict__ | self.__cache__).items()
|
|
363
|
+
if not n.startswith("_") and not isinstance(v, weakref.ref)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
def evaluate_and_set_hooks(self):
|
|
367
|
+
"""Evaluate functions of root hooks and set the results explicitly as attributes."""
|
|
368
|
+
|
|
369
|
+
def _gen():
|
|
370
|
+
for h in root_hooks:
|
|
371
|
+
if issubclass(type(self), h.owner):
|
|
372
|
+
h = getattr(type(self), h.name)
|
|
373
|
+
result = h.get_result(self)
|
|
374
|
+
|
|
375
|
+
if result is None:
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
setattr(self, h.name, result)
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
arr = np.array(result)
|
|
382
|
+
yield from arr[np.isfinite(arr)].flat
|
|
383
|
+
except TypeError: # yield only numeric values
|
|
384
|
+
continue
|
|
385
|
+
|
|
386
|
+
return list(_gen())
|
|
387
|
+
|
|
388
|
+
def __copy__(self):
|
|
389
|
+
cls = self.__class__
|
|
390
|
+
result = cls.__new__(cls)
|
|
391
|
+
result.__dict__.update(self.__dict__)
|
|
392
|
+
return result
|
|
393
|
+
|
|
394
|
+
def __deepcopy__(self, memo):
|
|
395
|
+
cls = self.__class__
|
|
396
|
+
result = cls.__new__(cls)
|
|
397
|
+
memo[id(self)] = result
|
|
398
|
+
|
|
399
|
+
for k, v in self.__dict__.items():
|
|
400
|
+
if isinstance(v, weakref.ref):
|
|
401
|
+
t = v()
|
|
402
|
+
|
|
403
|
+
if id(t) in memo:
|
|
404
|
+
new_v = weakref.ref(memo[id(t)])
|
|
405
|
+
else:
|
|
406
|
+
new_t = copy.deepcopy(t, memo)
|
|
407
|
+
new_v = weakref.ref(new_t)
|
|
408
|
+
else:
|
|
409
|
+
new_v = copy.deepcopy(v, memo)
|
|
410
|
+
setattr(result, k, new_v)
|
|
411
|
+
return result
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
root_hooks = set() # filled in __init__.py due to circular imports
|
|
415
|
+
"""
|
|
416
|
+
Set of hooks to call explicitly in each solution iteration.
|
|
417
|
+
Their values will be treated as explicitly set but reevaluated in every iteration.
|
|
418
|
+
They will not be deleted during cache clearing.
|
|
419
|
+
They serve as root for the calling tree and persistent iterational variables.
|
|
420
|
+
"""
|
pyroll/core/log.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .profile import Profile
|
|
4
|
+
from ..shapes import rectangle
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@Profile.height
|
|
8
|
+
def height(self: Profile):
|
|
9
|
+
if hasattr(self, "cross_section"):
|
|
10
|
+
return np.abs(self.cross_section.bounds[3] - self.cross_section.bounds[1])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@Profile.width
|
|
14
|
+
def width(self: Profile):
|
|
15
|
+
if hasattr(self, "cross_section"):
|
|
16
|
+
return np.abs(self.cross_section.bounds[2] - self.cross_section.bounds[0])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@Profile.height
|
|
20
|
+
def height_3fold(self: Profile):
|
|
21
|
+
if "3fold" in self.classifiers:
|
|
22
|
+
return (self.cross_section.centroid.y - self.cross_section.bounds[1]) * 2
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@Profile.width
|
|
26
|
+
def width_3fold(self: Profile):
|
|
27
|
+
if "3fold" in self.classifiers:
|
|
28
|
+
return (self.cross_section.bounds[3] - self.cross_section.centroid.y) * 2
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@Profile.length
|
|
32
|
+
def length(self: Profile):
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@Profile.equivalent_height
|
|
37
|
+
def equivalent_height(self: Profile):
|
|
38
|
+
return np.sqrt(self.cross_section.area * self.height / self.width)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@Profile.equivalent_width
|
|
42
|
+
def equivalent_width(self: Profile):
|
|
43
|
+
return np.sqrt(self.cross_section.area * self.width / self.height)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@Profile.equivalent_rectangle
|
|
47
|
+
def equivalent_rectangle(self: Profile):
|
|
48
|
+
return rectangle(self.equivalent_width, self.equivalent_height)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@Profile.surface_temperature
|
|
52
|
+
def surface_temperature(self: Profile):
|
|
53
|
+
return self.temperature
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@Profile.core_temperature
|
|
57
|
+
def core_temperature(self: Profile):
|
|
58
|
+
return self.temperature
|