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.
Files changed (68) hide show
  1. pyroll/core/__init__.py +38 -0
  2. pyroll/core/config.py +155 -0
  3. pyroll/core/disk_elements/__init__.py +2 -0
  4. pyroll/core/disk_elements/disk_element_unit.py +73 -0
  5. pyroll/core/disk_elements/hookimpls.py +26 -0
  6. pyroll/core/grooves/__init__.py +10 -0
  7. pyroll/core/grooves/base.py +59 -0
  8. pyroll/core/grooves/boxes/__init__.py +3 -0
  9. pyroll/core/grooves/boxes/box.py +57 -0
  10. pyroll/core/grooves/boxes/constricted_box.py +63 -0
  11. pyroll/core/grooves/boxes/upset_box.py +18 -0
  12. pyroll/core/grooves/diamonds/__init__.py +3 -0
  13. pyroll/core/grooves/diamonds/diamond.py +60 -0
  14. pyroll/core/grooves/diamonds/gothic.py +41 -0
  15. pyroll/core/grooves/diamonds/square.py +48 -0
  16. pyroll/core/grooves/flat.py +31 -0
  17. pyroll/core/grooves/generic_elongation.py +258 -0
  18. pyroll/core/grooves/generic_elongation_solvers.py +422 -0
  19. pyroll/core/grooves/hexagonal.py +57 -0
  20. pyroll/core/grooves/ovals/__init__.py +8 -0
  21. pyroll/core/grooves/ovals/circular_oval.py +43 -0
  22. pyroll/core/grooves/ovals/constricted_circular_oval.py +53 -0
  23. pyroll/core/grooves/ovals/constricted_swedish_oval.py +61 -0
  24. pyroll/core/grooves/ovals/flat_oval.py +56 -0
  25. pyroll/core/grooves/ovals/oval_3radii.py +47 -0
  26. pyroll/core/grooves/ovals/oval_3radii_flanked.py +65 -0
  27. pyroll/core/grooves/ovals/swedish_oval.py +57 -0
  28. pyroll/core/grooves/ovals/upset_oval.py +47 -0
  29. pyroll/core/grooves/rounds/__init__.py +2 -0
  30. pyroll/core/grooves/rounds/false_round.py +65 -0
  31. pyroll/core/grooves/rounds/round.py +44 -0
  32. pyroll/core/grooves/spline.py +92 -0
  33. pyroll/core/hooks.py +420 -0
  34. pyroll/core/log.py +7 -0
  35. pyroll/core/profile/__init__.py +3 -0
  36. pyroll/core/profile/hookimpls.py +58 -0
  37. pyroll/core/profile/profile.py +380 -0
  38. pyroll/core/repr.py +49 -0
  39. pyroll/core/roll/__init__.py +3 -0
  40. pyroll/core/roll/hookimpls.py +65 -0
  41. pyroll/core/roll/roll.py +145 -0
  42. pyroll/core/roll_pass/__init__.py +8 -0
  43. pyroll/core/roll_pass/deformation_unit.py +61 -0
  44. pyroll/core/roll_pass/hookimpls/__init__.py +5 -0
  45. pyroll/core/roll_pass/hookimpls/deformation_unit.py +78 -0
  46. pyroll/core/roll_pass/hookimpls/disk_element.py +16 -0
  47. pyroll/core/roll_pass/hookimpls/profile.py +94 -0
  48. pyroll/core/roll_pass/hookimpls/roll.py +43 -0
  49. pyroll/core/roll_pass/hookimpls/roll_pass.py +107 -0
  50. pyroll/core/roll_pass/roll_pass.py +168 -0
  51. pyroll/core/roll_pass/three_roll_pass.py +84 -0
  52. pyroll/core/rotator/__init__.py +3 -0
  53. pyroll/core/rotator/hookimpls.py +71 -0
  54. pyroll/core/rotator/rotator.py +65 -0
  55. pyroll/core/sequence/__init__.py +3 -0
  56. pyroll/core/sequence/hookimpls.py +18 -0
  57. pyroll/core/sequence/sequence.py +68 -0
  58. pyroll/core/shapes.py +104 -0
  59. pyroll/core/transport/__init__.py +3 -0
  60. pyroll/core/transport/hookimpls.py +17 -0
  61. pyroll/core/transport/transport.py +57 -0
  62. pyroll/core/unit/__init__.py +3 -0
  63. pyroll/core/unit/hookimpls.py +67 -0
  64. pyroll/core/unit/unit.py +271 -0
  65. pyroll_core-2.0.0.dist-info/METADATA +109 -0
  66. pyroll_core-2.0.0.dist-info/RECORD +68 -0
  67. pyroll_core-2.0.0.dist-info/WHEEL +4 -0
  68. 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,7 @@
1
+ import logging
2
+
3
+
4
+ class LogMixin:
5
+ def __init_subclass__(cls, logger_prefix: str = "pyroll.core", **kwargs):
6
+ cls.logger = logging.getLogger(f"{logger_prefix}.{cls.__qualname__}")
7
+ super().__init_subclass__(**kwargs)
@@ -0,0 +1,3 @@
1
+ from .profile import Profile
2
+
3
+ from . import hookimpls
@@ -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