vcti-derived 1.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.
- vcti/derived/__init__.py +34 -0
- vcti/derived/catalog.py +649 -0
- vcti/derived/dof6.py +100 -0
- vcti/derived/families.py +81 -0
- vcti/derived/masking.py +95 -0
- vcti/derived/order.py +141 -0
- vcti/derived/py.typed +0 -0
- vcti/derived/ranges.py +29 -0
- vcti/derived/scalar.py +66 -0
- vcti/derived/symtensor.py +348 -0
- vcti/derived/vector.py +56 -0
- vcti_derived-1.0.0.dist-info/METADATA +105 -0
- vcti_derived-1.0.0.dist-info/RECORD +16 -0
- vcti_derived-1.0.0.dist-info/WHEEL +5 -0
- vcti_derived-1.0.0.dist-info/licenses/LICENSE +8 -0
- vcti_derived-1.0.0.dist-info/top_level.txt +1 -0
vcti/derived/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""vcti.derived — An extensible, catalog-driven library for computing
|
|
4
|
+
derived fields from multi-component engineering data arrays.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
|
|
9
|
+
from . import dof6, ranges, scalar, symtensor, vector
|
|
10
|
+
from .catalog import CATALOG, Catalog, Context, DerivedField, OutputKind, Parameter
|
|
11
|
+
from .families import CANONICAL_COMPONENTS, DataFamily
|
|
12
|
+
from .order import ComponentOrder, OrderLike, SolverOrder
|
|
13
|
+
|
|
14
|
+
__version__ = version("vcti-derived")
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"CANONICAL_COMPONENTS",
|
|
18
|
+
"CATALOG",
|
|
19
|
+
"Catalog",
|
|
20
|
+
"ComponentOrder",
|
|
21
|
+
"Context",
|
|
22
|
+
"DataFamily",
|
|
23
|
+
"DerivedField",
|
|
24
|
+
"OrderLike",
|
|
25
|
+
"OutputKind",
|
|
26
|
+
"Parameter",
|
|
27
|
+
"SolverOrder",
|
|
28
|
+
"__version__",
|
|
29
|
+
"dof6",
|
|
30
|
+
"ranges",
|
|
31
|
+
"scalar",
|
|
32
|
+
"symtensor",
|
|
33
|
+
"vector",
|
|
34
|
+
]
|
vcti/derived/catalog.py
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""The catalog: which derived fields exist, per data family.
|
|
4
|
+
|
|
5
|
+
Each :class:`DerivedField` entry carries a stable id (append-only once
|
|
6
|
+
shipped — downstream registries build dispatch values from them), display
|
|
7
|
+
metadata, an output kind, a declared parameter spec, and the bound kernel.
|
|
8
|
+
The module-level :data:`CATALOG` is the populated registry; consumers
|
|
9
|
+
enumerate it (``by_family``, ``default``) and compute through entries
|
|
10
|
+
(:meth:`DerivedField.compute`).
|
|
11
|
+
|
|
12
|
+
Entries never contain math: every kernel lives in a family module, and
|
|
13
|
+
conceptual compositions are fused into kernel parameters there (see
|
|
14
|
+
``docs/design.md``, "Parameterized kernels").
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Callable, Iterator
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import StrEnum
|
|
22
|
+
from functools import partial
|
|
23
|
+
from typing import Any, Final
|
|
24
|
+
|
|
25
|
+
from numpy.typing import NDArray
|
|
26
|
+
|
|
27
|
+
from . import dof6, scalar, symtensor, vector
|
|
28
|
+
from .families import CANONICAL_COMPONENTS, DataFamily
|
|
29
|
+
from .order import OrderLike
|
|
30
|
+
|
|
31
|
+
type Array = NDArray[Any]
|
|
32
|
+
type Kernel = Callable[..., Array]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OutputKind(StrEnum):
|
|
36
|
+
"""The shape of a derived field's output."""
|
|
37
|
+
|
|
38
|
+
SCALAR = "scalar" # (N,)
|
|
39
|
+
VECTOR = "vector" # (N, 2) or (N, 3)
|
|
40
|
+
MATRIX = "matrix" # (N, k, k)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Context(StrEnum):
|
|
44
|
+
"""The physical vocabulary an entry belongs to, for consumer filtering."""
|
|
45
|
+
|
|
46
|
+
GENERAL = "general"
|
|
47
|
+
STRESS = "stress"
|
|
48
|
+
STRAIN = "strain"
|
|
49
|
+
ENGINEERING_STRAIN = "engineering_strain"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class Parameter:
|
|
54
|
+
"""A declared kernel parameter: name, kind, and default.
|
|
55
|
+
|
|
56
|
+
``default=None`` marks the parameter as required (e.g. the plane
|
|
57
|
+
normal of a traction entry).
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
name: str
|
|
61
|
+
kind: str # "float" | "vector"
|
|
62
|
+
default: float | tuple[float, ...] | None = None
|
|
63
|
+
description: str = ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class DerivedField:
|
|
68
|
+
"""One derived field: a named computation on one data family."""
|
|
69
|
+
|
|
70
|
+
id: str
|
|
71
|
+
family: DataFamily
|
|
72
|
+
name: str
|
|
73
|
+
display_name: str
|
|
74
|
+
description: str
|
|
75
|
+
output_kind: OutputKind
|
|
76
|
+
kernel: Kernel
|
|
77
|
+
parameters: tuple[Parameter, ...] = ()
|
|
78
|
+
contexts: tuple[Context, ...] = (Context.GENERAL,)
|
|
79
|
+
|
|
80
|
+
def compute(self, a: Array, *, order: OrderLike = None, **params: object) -> Array:
|
|
81
|
+
"""Run the kernel on ``a`` with validated parameters.
|
|
82
|
+
|
|
83
|
+
``order`` maps the caller's component layout onto canonical order
|
|
84
|
+
(scalar-family entries reject it — scalars have no components).
|
|
85
|
+
"""
|
|
86
|
+
declared = {p.name: p for p in self.parameters}
|
|
87
|
+
unknown = set(params) - set(declared)
|
|
88
|
+
if unknown:
|
|
89
|
+
raise TypeError(
|
|
90
|
+
f"{self.id} got unknown parameter(s) {sorted(unknown)}; "
|
|
91
|
+
f"declared: {sorted(declared)}"
|
|
92
|
+
)
|
|
93
|
+
merged: dict[str, object] = {}
|
|
94
|
+
for name, spec in declared.items():
|
|
95
|
+
if name in params:
|
|
96
|
+
merged[name] = params[name]
|
|
97
|
+
elif spec.default is not None:
|
|
98
|
+
merged[name] = spec.default
|
|
99
|
+
else:
|
|
100
|
+
raise TypeError(f"{self.id} requires parameter {name!r}")
|
|
101
|
+
if self.family is DataFamily.SCALAR:
|
|
102
|
+
if order is not None:
|
|
103
|
+
raise TypeError("scalar fields have no component order")
|
|
104
|
+
return self.kernel(a, **merged)
|
|
105
|
+
return self.kernel(a, order=order, **merged)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Catalog:
|
|
109
|
+
"""The registry of derived fields, queryable by family and context."""
|
|
110
|
+
|
|
111
|
+
def __init__(self) -> None:
|
|
112
|
+
self._entries: dict[str, DerivedField] = {}
|
|
113
|
+
self._defaults: dict[DataFamily, str] = {}
|
|
114
|
+
|
|
115
|
+
def register(self, entry: DerivedField, *, default: bool = False) -> None:
|
|
116
|
+
"""Add an entry; ids are unique and, once shipped, append-only."""
|
|
117
|
+
if entry.id in self._entries:
|
|
118
|
+
raise ValueError(f"duplicate derived-field id {entry.id!r}")
|
|
119
|
+
self._entries[entry.id] = entry
|
|
120
|
+
if default:
|
|
121
|
+
self._defaults[entry.family] = entry.id
|
|
122
|
+
|
|
123
|
+
def get(self, field_id: str) -> DerivedField:
|
|
124
|
+
"""The entry with this id."""
|
|
125
|
+
try:
|
|
126
|
+
return self._entries[field_id]
|
|
127
|
+
except KeyError:
|
|
128
|
+
raise KeyError(f"unknown derived field {field_id!r}") from None
|
|
129
|
+
|
|
130
|
+
def ids(self) -> tuple[str, ...]:
|
|
131
|
+
"""All entry ids, in registration order."""
|
|
132
|
+
return tuple(self._entries)
|
|
133
|
+
|
|
134
|
+
def by_family(
|
|
135
|
+
self, family: DataFamily, *, context: Context | None = None
|
|
136
|
+
) -> tuple[DerivedField, ...]:
|
|
137
|
+
"""The family's entries, optionally filtered to one physical context.
|
|
138
|
+
|
|
139
|
+
With a ``context``, entries tagged :attr:`Context.GENERAL` are
|
|
140
|
+
always included alongside the context-specific ones.
|
|
141
|
+
"""
|
|
142
|
+
entries = (e for e in self._entries.values() if e.family is family)
|
|
143
|
+
if context is None:
|
|
144
|
+
return tuple(entries)
|
|
145
|
+
return tuple(e for e in entries if context in e.contexts or Context.GENERAL in e.contexts)
|
|
146
|
+
|
|
147
|
+
def default(self, family: DataFamily) -> DerivedField:
|
|
148
|
+
"""The family's default entry (e.g. von Mises for symtensor3d)."""
|
|
149
|
+
try:
|
|
150
|
+
return self._entries[self._defaults[family]]
|
|
151
|
+
except KeyError:
|
|
152
|
+
raise KeyError(f"no default derived field for {family.value}") from None
|
|
153
|
+
|
|
154
|
+
def __contains__(self, field_id: str) -> bool:
|
|
155
|
+
return field_id in self._entries
|
|
156
|
+
|
|
157
|
+
def __iter__(self) -> Iterator[DerivedField]:
|
|
158
|
+
return iter(self._entries.values())
|
|
159
|
+
|
|
160
|
+
def __len__(self) -> int:
|
|
161
|
+
return len(self._entries)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
_NU: Final = Parameter("nu", "float", 0.5, "Effective Poisson's ratio (0.5 = fully plastic).")
|
|
165
|
+
_NORMAL3: Final = Parameter("normal", "vector", None, "Unit plane normal, (3,) or (N, 3).")
|
|
166
|
+
|
|
167
|
+
_COMPONENT_DISPLAY: Final[dict[str, str]] = {
|
|
168
|
+
"tx": "Translational X",
|
|
169
|
+
"ty": "Translational Y",
|
|
170
|
+
"tz": "Translational Z",
|
|
171
|
+
"rx": "Rotational X",
|
|
172
|
+
"ry": "Rotational Y",
|
|
173
|
+
"rz": "Rotational Z",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _principal(
|
|
178
|
+
a: Array,
|
|
179
|
+
*,
|
|
180
|
+
index: int,
|
|
181
|
+
shear_factor: float = 1.0,
|
|
182
|
+
order: OrderLike = None,
|
|
183
|
+
) -> Array:
|
|
184
|
+
return symtensor.principals(a, shear_factor=shear_factor, order=order)[:, index]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _principal_direction(a: Array, *, index: int, order: OrderLike = None) -> Array:
|
|
188
|
+
return symtensor.principal_directions(a, order=order)[:, :, index]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _deviatoric_principal(a: Array, *, index: int, order: OrderLike = None) -> Array:
|
|
192
|
+
return symtensor.deviatoric_principals(a, order=order)[:, index]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _register_components(
|
|
196
|
+
catalog: Catalog,
|
|
197
|
+
family: DataFamily,
|
|
198
|
+
kernel: Kernel,
|
|
199
|
+
*,
|
|
200
|
+
contexts: tuple[Context, ...] = (Context.GENERAL,),
|
|
201
|
+
) -> None:
|
|
202
|
+
for label in CANONICAL_COMPONENTS[family]:
|
|
203
|
+
catalog.register(
|
|
204
|
+
DerivedField(
|
|
205
|
+
id=f"{family.value}.{label}",
|
|
206
|
+
family=family,
|
|
207
|
+
name=label,
|
|
208
|
+
display_name=_COMPONENT_DISPLAY.get(label, label.upper()),
|
|
209
|
+
description=f"The {label.upper()} component.",
|
|
210
|
+
output_kind=OutputKind.SCALAR,
|
|
211
|
+
kernel=partial(kernel, comp=label),
|
|
212
|
+
contexts=contexts,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _register_scalar(catalog: Catalog) -> None:
|
|
218
|
+
family = DataFamily.SCALAR
|
|
219
|
+
|
|
220
|
+
def entry(
|
|
221
|
+
name: str,
|
|
222
|
+
display: str,
|
|
223
|
+
description: str,
|
|
224
|
+
kernel: Kernel,
|
|
225
|
+
parameters: tuple[Parameter, ...] = (),
|
|
226
|
+
) -> DerivedField:
|
|
227
|
+
return DerivedField(
|
|
228
|
+
id=f"{family.value}.{name}",
|
|
229
|
+
family=family,
|
|
230
|
+
name=name,
|
|
231
|
+
display_name=display,
|
|
232
|
+
description=description,
|
|
233
|
+
output_kind=OutputKind.SCALAR,
|
|
234
|
+
kernel=kernel,
|
|
235
|
+
parameters=parameters,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
catalog.register(
|
|
239
|
+
entry("identity", "Value", "The values unchanged.", scalar.identity),
|
|
240
|
+
default=True,
|
|
241
|
+
)
|
|
242
|
+
catalog.register(
|
|
243
|
+
entry("absolute", "Absolute Value", "Element-wise absolute value.", scalar.absolute)
|
|
244
|
+
)
|
|
245
|
+
catalog.register(
|
|
246
|
+
entry(
|
|
247
|
+
"affine",
|
|
248
|
+
"Affine Conversion",
|
|
249
|
+
"scale · x + offset — the unit-conversion primitive.",
|
|
250
|
+
scalar.affine,
|
|
251
|
+
(
|
|
252
|
+
Parameter("scale", "float", 1.0, "Multiplier."),
|
|
253
|
+
Parameter("offset", "float", 0.0, "Additive offset."),
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
for name in scalar.TEMPERATURE_CONVERSIONS:
|
|
258
|
+
source, _, target = name.partition("_to_")
|
|
259
|
+
catalog.register(
|
|
260
|
+
entry(
|
|
261
|
+
name,
|
|
262
|
+
f"{source.capitalize()} → {target.capitalize()}",
|
|
263
|
+
f"Temperature conversion, {source} to {target}.",
|
|
264
|
+
partial(scalar.convert, conversion=name),
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _register_vectors(catalog: Catalog) -> None:
|
|
270
|
+
for family in (DataFamily.VECTOR2, DataFamily.VECTOR3):
|
|
271
|
+
_register_components(catalog, family, vector.component)
|
|
272
|
+
catalog.register(
|
|
273
|
+
DerivedField(
|
|
274
|
+
id=f"{family.value}.magnitude",
|
|
275
|
+
family=family,
|
|
276
|
+
name="magnitude",
|
|
277
|
+
display_name="Resultant",
|
|
278
|
+
description="Euclidean magnitude of the vector.",
|
|
279
|
+
output_kind=OutputKind.SCALAR,
|
|
280
|
+
kernel=vector.magnitude,
|
|
281
|
+
),
|
|
282
|
+
default=True,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _register_dof6(catalog: Catalog) -> None:
|
|
287
|
+
for family in (DataFamily.DOF6_2D, DataFamily.DOF6_3D):
|
|
288
|
+
_register_components(catalog, family, dof6.component)
|
|
289
|
+
k = len(CANONICAL_COMPONENTS[family]) // 2
|
|
290
|
+
|
|
291
|
+
def entry(
|
|
292
|
+
name: str,
|
|
293
|
+
display: str,
|
|
294
|
+
description: str,
|
|
295
|
+
kind: OutputKind,
|
|
296
|
+
kernel: Kernel,
|
|
297
|
+
*,
|
|
298
|
+
fam: DataFamily = family,
|
|
299
|
+
) -> DerivedField:
|
|
300
|
+
return DerivedField(
|
|
301
|
+
id=f"{fam.value}.{name}",
|
|
302
|
+
family=fam,
|
|
303
|
+
name=name,
|
|
304
|
+
display_name=display,
|
|
305
|
+
description=description,
|
|
306
|
+
output_kind=kind,
|
|
307
|
+
kernel=kernel,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
catalog.register(
|
|
311
|
+
entry(
|
|
312
|
+
"translation",
|
|
313
|
+
"Translation Vector",
|
|
314
|
+
f"The translational part, (N, {k}).",
|
|
315
|
+
OutputKind.VECTOR,
|
|
316
|
+
dof6.translation,
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
catalog.register(
|
|
320
|
+
entry(
|
|
321
|
+
"rotation",
|
|
322
|
+
"Rotation Vector",
|
|
323
|
+
f"The rotational part, (N, {k}).",
|
|
324
|
+
OutputKind.VECTOR,
|
|
325
|
+
dof6.rotation,
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
catalog.register(
|
|
329
|
+
entry(
|
|
330
|
+
"translation_magnitude",
|
|
331
|
+
"Translational Magnitude",
|
|
332
|
+
"Magnitude of the translational part.",
|
|
333
|
+
OutputKind.SCALAR,
|
|
334
|
+
dof6.translation_magnitude,
|
|
335
|
+
),
|
|
336
|
+
default=True,
|
|
337
|
+
)
|
|
338
|
+
catalog.register(
|
|
339
|
+
entry(
|
|
340
|
+
"rotation_magnitude",
|
|
341
|
+
"Rotational Magnitude",
|
|
342
|
+
"Magnitude of the rotational part.",
|
|
343
|
+
OutputKind.SCALAR,
|
|
344
|
+
dof6.rotation_magnitude,
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _tensor_entry(
|
|
350
|
+
family: DataFamily,
|
|
351
|
+
name: str,
|
|
352
|
+
display: str,
|
|
353
|
+
description: str,
|
|
354
|
+
kernel: Kernel,
|
|
355
|
+
*,
|
|
356
|
+
kind: OutputKind = OutputKind.SCALAR,
|
|
357
|
+
parameters: tuple[Parameter, ...] = (),
|
|
358
|
+
contexts: tuple[Context, ...] = (Context.GENERAL,),
|
|
359
|
+
) -> DerivedField:
|
|
360
|
+
return DerivedField(
|
|
361
|
+
id=f"{family.value}.{name}",
|
|
362
|
+
family=family,
|
|
363
|
+
name=name,
|
|
364
|
+
display_name=display,
|
|
365
|
+
description=description,
|
|
366
|
+
output_kind=kind,
|
|
367
|
+
kernel=kernel,
|
|
368
|
+
parameters=parameters,
|
|
369
|
+
contexts=contexts,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _register_symtensor2d(catalog: Catalog) -> None:
|
|
374
|
+
family = DataFamily.SYMTENSOR2D
|
|
375
|
+
_register_components(catalog, family, symtensor.component)
|
|
376
|
+
entries = [
|
|
377
|
+
_tensor_entry(family, "mean", "Mean", "In-plane mean: (xx + yy) / 2.", symtensor.mean),
|
|
378
|
+
_tensor_entry(
|
|
379
|
+
family,
|
|
380
|
+
"von_mises",
|
|
381
|
+
"Von Mises",
|
|
382
|
+
"Plane-stress von Mises value.",
|
|
383
|
+
symtensor.von_mises,
|
|
384
|
+
contexts=(Context.STRESS,),
|
|
385
|
+
),
|
|
386
|
+
_tensor_entry(
|
|
387
|
+
family,
|
|
388
|
+
"principals",
|
|
389
|
+
"Principal Values",
|
|
390
|
+
"Both in-plane principal values, ascending, (N, 2).",
|
|
391
|
+
symtensor.principals,
|
|
392
|
+
kind=OutputKind.VECTOR,
|
|
393
|
+
),
|
|
394
|
+
_tensor_entry(
|
|
395
|
+
family,
|
|
396
|
+
"min_principal",
|
|
397
|
+
"Minimum Principal Value",
|
|
398
|
+
"The smaller principal value.",
|
|
399
|
+
partial(_principal, index=0),
|
|
400
|
+
),
|
|
401
|
+
_tensor_entry(
|
|
402
|
+
family,
|
|
403
|
+
"max_principal",
|
|
404
|
+
"Maximum Principal Value",
|
|
405
|
+
"The larger principal value.",
|
|
406
|
+
partial(_principal, index=-1),
|
|
407
|
+
),
|
|
408
|
+
_tensor_entry(
|
|
409
|
+
family,
|
|
410
|
+
"abs_max_principal",
|
|
411
|
+
"Absolute Maximum Principal Value",
|
|
412
|
+
"Signed in-plane principal value of largest magnitude.",
|
|
413
|
+
symtensor.abs_max_principal,
|
|
414
|
+
),
|
|
415
|
+
_tensor_entry(
|
|
416
|
+
family,
|
|
417
|
+
"principal_directions",
|
|
418
|
+
"Principal Directions",
|
|
419
|
+
"Unit eigenvectors, (N, 2, 2); [:, :, i] pairs with principal i.",
|
|
420
|
+
symtensor.principal_directions,
|
|
421
|
+
kind=OutputKind.MATRIX,
|
|
422
|
+
),
|
|
423
|
+
_tensor_entry(
|
|
424
|
+
family,
|
|
425
|
+
"max_shear",
|
|
426
|
+
"Maximum Shear",
|
|
427
|
+
"In-plane maximum shear: (tmax − tmin) / 2 (no out-of-plane term).",
|
|
428
|
+
symtensor.max_shear,
|
|
429
|
+
contexts=(Context.STRESS,),
|
|
430
|
+
),
|
|
431
|
+
_tensor_entry(
|
|
432
|
+
family,
|
|
433
|
+
"max_shear_angle",
|
|
434
|
+
"Maximum Shear Angle",
|
|
435
|
+
"Angle of the max-shear direction with the x-axis, degrees.",
|
|
436
|
+
symtensor.max_shear_angle,
|
|
437
|
+
),
|
|
438
|
+
]
|
|
439
|
+
for e in entries:
|
|
440
|
+
catalog.register(e, default=(e.name == "von_mises"))
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _register_symtensor3d(catalog: Catalog) -> None:
|
|
444
|
+
family = DataFamily.SYMTENSOR3D
|
|
445
|
+
_register_components(catalog, family, symtensor.component)
|
|
446
|
+
principal_names = ("min", "mid", "max")
|
|
447
|
+
entries = [
|
|
448
|
+
_tensor_entry(
|
|
449
|
+
family,
|
|
450
|
+
"mean",
|
|
451
|
+
"Mean (Hydrostatic)",
|
|
452
|
+
"Hydrostatic value: (xx + yy + zz) / 3.",
|
|
453
|
+
symtensor.mean,
|
|
454
|
+
),
|
|
455
|
+
_tensor_entry(
|
|
456
|
+
family,
|
|
457
|
+
"von_mises",
|
|
458
|
+
"Von Mises Stress",
|
|
459
|
+
"The von Mises equivalent value.",
|
|
460
|
+
symtensor.von_mises,
|
|
461
|
+
contexts=(Context.STRESS,),
|
|
462
|
+
),
|
|
463
|
+
_tensor_entry(
|
|
464
|
+
family,
|
|
465
|
+
"equivalent_strain",
|
|
466
|
+
"Von Mises Strain",
|
|
467
|
+
"1/(1+ν) of the von Mises value; tensorial shear storage.",
|
|
468
|
+
symtensor.equivalent_strain,
|
|
469
|
+
parameters=(_NU,),
|
|
470
|
+
contexts=(Context.STRAIN,),
|
|
471
|
+
),
|
|
472
|
+
_tensor_entry(
|
|
473
|
+
family,
|
|
474
|
+
"equivalent_strain_engineering",
|
|
475
|
+
"Von Mises Strain (Engineering)",
|
|
476
|
+
"1/(1+ν) of the von Mises value; engineering-shear storage.",
|
|
477
|
+
partial(symtensor.equivalent_strain, shear_factor=0.5),
|
|
478
|
+
parameters=(_NU,),
|
|
479
|
+
contexts=(Context.ENGINEERING_STRAIN,),
|
|
480
|
+
),
|
|
481
|
+
_tensor_entry(
|
|
482
|
+
family,
|
|
483
|
+
"octahedral",
|
|
484
|
+
"Octahedral Shear Stress",
|
|
485
|
+
"τ_oct = (√2⁄3) · σ_vm.",
|
|
486
|
+
symtensor.octahedral,
|
|
487
|
+
contexts=(Context.STRESS,),
|
|
488
|
+
),
|
|
489
|
+
_tensor_entry(
|
|
490
|
+
family,
|
|
491
|
+
"principals",
|
|
492
|
+
"Principal Values",
|
|
493
|
+
"All three principal values, ascending, (N, 3).",
|
|
494
|
+
symtensor.principals,
|
|
495
|
+
kind=OutputKind.VECTOR,
|
|
496
|
+
),
|
|
497
|
+
_tensor_entry(
|
|
498
|
+
family,
|
|
499
|
+
"principal_directions",
|
|
500
|
+
"Principal Directions",
|
|
501
|
+
"Unit eigenvectors, (N, 3, 3); [:, :, i] pairs with principal i.",
|
|
502
|
+
symtensor.principal_directions,
|
|
503
|
+
kind=OutputKind.MATRIX,
|
|
504
|
+
),
|
|
505
|
+
_tensor_entry(
|
|
506
|
+
family,
|
|
507
|
+
"max_shear",
|
|
508
|
+
"Maximum Shear",
|
|
509
|
+
"(tmax − tmin) / 2.",
|
|
510
|
+
symtensor.max_shear,
|
|
511
|
+
contexts=(Context.STRESS,),
|
|
512
|
+
),
|
|
513
|
+
_tensor_entry(
|
|
514
|
+
family,
|
|
515
|
+
"equal_direct",
|
|
516
|
+
"Equal Direct at Maximum Shear",
|
|
517
|
+
"(tmax + tmin) / 2.",
|
|
518
|
+
symtensor.equal_direct,
|
|
519
|
+
contexts=(Context.STRESS,),
|
|
520
|
+
),
|
|
521
|
+
_tensor_entry(
|
|
522
|
+
family,
|
|
523
|
+
"intensity",
|
|
524
|
+
"Intensity",
|
|
525
|
+
"tmax − tmin (twice the maximum shear).",
|
|
526
|
+
symtensor.intensity,
|
|
527
|
+
),
|
|
528
|
+
_tensor_entry(
|
|
529
|
+
family,
|
|
530
|
+
"abs_max_principal",
|
|
531
|
+
"Absolute Maximum Principal Value",
|
|
532
|
+
"Signed principal value of largest magnitude.",
|
|
533
|
+
symtensor.abs_max_principal,
|
|
534
|
+
),
|
|
535
|
+
_tensor_entry(
|
|
536
|
+
family,
|
|
537
|
+
"abs_max_principal_engineering",
|
|
538
|
+
"Absolute Maximum Principal Strain (Engineering)",
|
|
539
|
+
"Signed principal of largest magnitude, engineering-shear storage.",
|
|
540
|
+
partial(symtensor.abs_max_principal, shear_factor=0.5),
|
|
541
|
+
contexts=(Context.ENGINEERING_STRAIN,),
|
|
542
|
+
),
|
|
543
|
+
_tensor_entry(
|
|
544
|
+
family,
|
|
545
|
+
"determinant",
|
|
546
|
+
"Determinant",
|
|
547
|
+
"The third invariant.",
|
|
548
|
+
symtensor.determinant,
|
|
549
|
+
),
|
|
550
|
+
_tensor_entry(
|
|
551
|
+
family,
|
|
552
|
+
"traction",
|
|
553
|
+
"Traction Vector",
|
|
554
|
+
"t = σ·n on the plane with normal n, (N, 3).",
|
|
555
|
+
symtensor.traction,
|
|
556
|
+
kind=OutputKind.VECTOR,
|
|
557
|
+
parameters=(_NORMAL3,),
|
|
558
|
+
contexts=(Context.STRESS,),
|
|
559
|
+
),
|
|
560
|
+
_tensor_entry(
|
|
561
|
+
family,
|
|
562
|
+
"traction_normal",
|
|
563
|
+
"Normal Stress Vector",
|
|
564
|
+
"Normal component of the traction: (t·n)·n, (N, 3).",
|
|
565
|
+
symtensor.traction_normal,
|
|
566
|
+
kind=OutputKind.VECTOR,
|
|
567
|
+
parameters=(_NORMAL3,),
|
|
568
|
+
contexts=(Context.STRESS,),
|
|
569
|
+
),
|
|
570
|
+
_tensor_entry(
|
|
571
|
+
family,
|
|
572
|
+
"traction_shear",
|
|
573
|
+
"Shear Stress Vector",
|
|
574
|
+
"Shear component of the traction: t − (t·n)·n, (N, 3).",
|
|
575
|
+
symtensor.traction_shear,
|
|
576
|
+
kind=OutputKind.VECTOR,
|
|
577
|
+
parameters=(_NORMAL3,),
|
|
578
|
+
contexts=(Context.STRESS,),
|
|
579
|
+
),
|
|
580
|
+
]
|
|
581
|
+
for i, which in enumerate(principal_names):
|
|
582
|
+
entries.append(
|
|
583
|
+
_tensor_entry(
|
|
584
|
+
family,
|
|
585
|
+
f"{which}_principal",
|
|
586
|
+
f"{which.capitalize()}imum Principal Value"
|
|
587
|
+
if which != "mid"
|
|
588
|
+
else "Middle Principal Value",
|
|
589
|
+
f"The {which} principal value.",
|
|
590
|
+
partial(_principal, index=i),
|
|
591
|
+
)
|
|
592
|
+
)
|
|
593
|
+
entries.append(
|
|
594
|
+
_tensor_entry(
|
|
595
|
+
family,
|
|
596
|
+
f"{which}_principal_engineering",
|
|
597
|
+
(
|
|
598
|
+
f"{which.capitalize()}imum Principal Strain (Engineering)"
|
|
599
|
+
if which != "mid"
|
|
600
|
+
else "Middle Principal Strain (Engineering)"
|
|
601
|
+
),
|
|
602
|
+
f"The {which} principal value with engineering-shear storage.",
|
|
603
|
+
partial(_principal, index=i, shear_factor=0.5),
|
|
604
|
+
contexts=(Context.ENGINEERING_STRAIN,),
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
entries.append(
|
|
608
|
+
_tensor_entry(
|
|
609
|
+
family,
|
|
610
|
+
f"{which}_principal_direction",
|
|
611
|
+
(
|
|
612
|
+
f"{which.capitalize()}imum Principal Direction"
|
|
613
|
+
if which != "mid"
|
|
614
|
+
else "Middle Principal Direction"
|
|
615
|
+
),
|
|
616
|
+
f"Unit direction of the {which} principal value, (N, 3).",
|
|
617
|
+
partial(_principal_direction, index=i),
|
|
618
|
+
kind=OutputKind.VECTOR,
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
entries.append(
|
|
622
|
+
_tensor_entry(
|
|
623
|
+
family,
|
|
624
|
+
f"deviatoric_{which}_principal",
|
|
625
|
+
(
|
|
626
|
+
f"Deviatoric {which.capitalize()}imum Principal Value"
|
|
627
|
+
if which != "mid"
|
|
628
|
+
else "Deviatoric Middle Principal Value"
|
|
629
|
+
),
|
|
630
|
+
f"The {which} principal value of the deviator.",
|
|
631
|
+
partial(_deviatoric_principal, index=i),
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
for e in entries:
|
|
635
|
+
catalog.register(e, default=(e.name == "von_mises"))
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _build_catalog() -> Catalog:
|
|
639
|
+
catalog = Catalog()
|
|
640
|
+
_register_scalar(catalog)
|
|
641
|
+
_register_vectors(catalog)
|
|
642
|
+
_register_dof6(catalog)
|
|
643
|
+
_register_symtensor2d(catalog)
|
|
644
|
+
_register_symtensor3d(catalog)
|
|
645
|
+
return catalog
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
CATALOG: Final[Catalog] = _build_catalog()
|
|
649
|
+
"""The populated derived-field registry."""
|