morphis 0.11.0__tar.gz → 0.12.0__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.
- {morphis-0.11.0 → morphis-0.12.0}/PKG-INFO +11 -1
- {morphis-0.11.0 → morphis-0.12.0}/pyproject.toml +15 -1
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/solvers.py +6 -7
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/__init__.py +5 -1
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/base.py +28 -0
- morphis-0.12.0/src/morphis/elements/field.py +102 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/multivector.py +45 -0
- morphis-0.12.0/src/morphis/elements/surface.py +367 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/vector.py +34 -2
- morphis-0.12.0/src/morphis/examples/astronaut_animated.py +113 -0
- morphis-0.12.0/src/morphis/examples/astronaut_view.py +25 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/operators.py +56 -55
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/rotations_3d.py +13 -27
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/rotations_4d.py +16 -30
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/operator.py +3 -3
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/transforms/__init__.py +2 -0
- morphis-0.12.0/src/morphis/transforms/rotations.py +348 -0
- morphis-0.12.0/src/morphis/transforms/tests/test_rotations.py +212 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/utils/docgen.py +1 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/__init__.py +30 -4
- morphis-0.12.0/src/morphis/visuals/backends/__init__.py +39 -0
- morphis-0.12.0/src/morphis/visuals/backends/protocol.py +450 -0
- morphis-0.12.0/src/morphis/visuals/backends/pyvista.py +861 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/canvas.py +99 -12
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/drawing/vectors.py +54 -32
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/loop.py +94 -26
- morphis-0.12.0/src/morphis/visuals/model.py +410 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/renderer.py +59 -8
- morphis-0.12.0/src/morphis/visuals/scene.py +644 -0
- morphis-0.12.0/src/morphis/visuals/tests/__init__.py +0 -0
- morphis-0.12.0/src/morphis/visuals/tests/test_model.py +434 -0
- morphis-0.12.0/src/morphis/visuals/text.py +133 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/theme.py +112 -0
- morphis-0.11.0/src/morphis/transforms/rotations.py +0 -142
- {morphis-0.11.0 → morphis-0.12.0}/README.md +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/_legacy/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/_legacy/coordinates.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/_legacy/rotations.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/_legacy/smoothing.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/_legacy/vectors.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/contraction.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/patterns.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/specs.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/tests/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/tests/test_contraction.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/tests/test_patterns.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/tests/test_solvers.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/algebra/tests/test_specs.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/config.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/frame.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/lot_indexed.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/metric.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/operator.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/protocols.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tensor.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/test_complex_blades.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/test_maxwell_features.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/test_model.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/test_operator.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/test_operators.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/elements/tests/test_tensor.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/clifford.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/duality.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/exterior.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/phasors.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/examples/transforms_pga.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/manifold/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/_helpers.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/duality.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/exponential.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/factorization.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/matrix_rep.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/norms.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/outermorphism.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/products.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/projections.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/spectral.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/structure.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/subspaces.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_complex_operations.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_duality.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_exponential.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_matrix_rep.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_norms.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_operations.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_outermorphism.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_products.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_spectral.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/operations/tests/test_structure.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/topology/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/transforms/actions.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/transforms/projective.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/transforms/tests/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/transforms/tests/test_projective.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/utils/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/utils/easing.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/utils/exceptions.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/utils/observer.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/utils/pretty.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/contexts.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/drawing/__init__.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/effects.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/operations.py +0 -0
- {morphis-0.11.0 → morphis-0.12.0}/src/morphis/visuals/projection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: morphis
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: A unified mathematical framework for geometric computation
|
|
5
5
|
Keywords: geometric-algebra,mathematics,visualization,multivector,pga
|
|
6
6
|
Author: ctl-alt-leist
|
|
@@ -12,14 +12,17 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
15
|
+
Requires-Dist: dracopy>=1.4.0
|
|
15
16
|
Requires-Dist: imageio>=2.37.2
|
|
16
17
|
Requires-Dist: imageio-ffmpeg>=0.6.0
|
|
17
18
|
Requires-Dist: matplotlib>=3.10.8
|
|
18
19
|
Requires-Dist: numpy>=2.3.5
|
|
19
20
|
Requires-Dist: pydantic>=2.12.5
|
|
21
|
+
Requires-Dist: pygltflib>=1.16.5
|
|
20
22
|
Requires-Dist: pyvista>=0.46.4
|
|
21
23
|
Requires-Dist: scipy>=1.16.3
|
|
22
24
|
Requires-Dist: tomli>=2.3.0
|
|
25
|
+
Requires-Dist: trimesh>=4.11.1
|
|
23
26
|
Requires-Dist: build ; extra == 'dev'
|
|
24
27
|
Requires-Dist: flake8 ; extra == 'dev'
|
|
25
28
|
Requires-Dist: ipython ; extra == 'dev'
|
|
@@ -28,11 +31,18 @@ Requires-Dist: pre-commit ; extra == 'dev'
|
|
|
28
31
|
Requires-Dist: pytest ; extra == 'dev'
|
|
29
32
|
Requires-Dist: python-lsp-server ; extra == 'dev'
|
|
30
33
|
Requires-Dist: ruff ; extra == 'dev'
|
|
34
|
+
Requires-Dist: pyvista>=0.46.4 ; extra == 'visuals'
|
|
35
|
+
Requires-Dist: imageio>=2.37.2 ; extra == 'visuals'
|
|
36
|
+
Requires-Dist: imageio-ffmpeg>=0.6.0 ; extra == 'visuals'
|
|
37
|
+
Requires-Dist: trimesh>=4.11.1 ; extra == 'visuals'
|
|
38
|
+
Requires-Dist: pygltflib>=1.16.5 ; extra == 'visuals'
|
|
39
|
+
Requires-Dist: dracopy>=1.4.0 ; extra == 'visuals'
|
|
31
40
|
Requires-Python: >=3.12
|
|
32
41
|
Project-URL: Homepage, https://github.com/ctl-alt-leist/morphis
|
|
33
42
|
Project-URL: Repository, https://github.com/ctl-alt-leist/morphis
|
|
34
43
|
Project-URL: Issues, https://github.com/ctl-alt-leist/morphis/issues
|
|
35
44
|
Provides-Extra: dev
|
|
45
|
+
Provides-Extra: visuals
|
|
36
46
|
Description-Content-Type: text/markdown
|
|
37
47
|
|
|
38
48
|
# Morphis
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "morphis"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.12.0"
|
|
4
4
|
description = "A unified mathematical framework for geometric computation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -18,17 +18,31 @@ classifiers = [
|
|
|
18
18
|
"Topic :: Scientific/Engineering :: Visualization",
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
|
+
"DracoPy>=1.4.0",
|
|
21
22
|
"imageio>=2.37.2",
|
|
22
23
|
"imageio-ffmpeg>=0.6.0",
|
|
23
24
|
"matplotlib>=3.10.8",
|
|
24
25
|
"numpy>=2.3.5",
|
|
25
26
|
"pydantic>=2.12.5",
|
|
27
|
+
"pygltflib>=1.16.5",
|
|
26
28
|
"pyvista>=0.46.4",
|
|
27
29
|
"scipy>=1.16.3",
|
|
28
30
|
"tomli>=2.3.0",
|
|
31
|
+
"trimesh>=4.11.1",
|
|
29
32
|
]
|
|
30
33
|
|
|
31
34
|
[project.optional-dependencies]
|
|
35
|
+
visuals = [
|
|
36
|
+
# These are currently in main dependencies but documented here
|
|
37
|
+
# for explicit dependency declaration. In a future major version,
|
|
38
|
+
# these may be moved out of main dependencies.
|
|
39
|
+
"pyvista>=0.46.4",
|
|
40
|
+
"imageio>=2.37.2",
|
|
41
|
+
"imageio-ffmpeg>=0.6.0",
|
|
42
|
+
"trimesh>=4.11.1",
|
|
43
|
+
"pygltflib>=1.16.5",
|
|
44
|
+
"DracoPy>=1.4.0",
|
|
45
|
+
]
|
|
32
46
|
dev = [
|
|
33
47
|
"build",
|
|
34
48
|
"flake8",
|
|
@@ -8,9 +8,8 @@ structured Operators for the results.
|
|
|
8
8
|
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
from numpy import
|
|
13
|
-
from numpy.linalg import lstsq, svd
|
|
11
|
+
from numpy import diag_indices_from, prod
|
|
12
|
+
from numpy.linalg import lstsq, pinv, solve, svd
|
|
14
13
|
from numpy.typing import NDArray
|
|
15
14
|
|
|
16
15
|
from morphis.algebra.specs import VectorSpec
|
|
@@ -143,9 +142,9 @@ def structured_lstsq(
|
|
|
143
142
|
if alpha > 0:
|
|
144
143
|
# Regularized: (G^H G + alpha*I) x = G^H y
|
|
145
144
|
GhG = G_matrix.conj().T @ G_matrix
|
|
146
|
-
GhG[
|
|
145
|
+
GhG[diag_indices_from(GhG)] += alpha
|
|
147
146
|
Ghy = G_matrix.conj().T @ y_vector
|
|
148
|
-
x_vector =
|
|
147
|
+
x_vector = solve(GhG, Ghy)
|
|
149
148
|
else:
|
|
150
149
|
# Standard least squares
|
|
151
150
|
x_vector, _, _, _ = lstsq(G_matrix, y_vector, rcond=None)
|
|
@@ -211,9 +210,9 @@ def structured_pinv(
|
|
|
211
210
|
|
|
212
211
|
# Compute pseudoinverse
|
|
213
212
|
if r_cond is None:
|
|
214
|
-
G_pinv =
|
|
213
|
+
G_pinv = pinv(G_matrix)
|
|
215
214
|
else:
|
|
216
|
-
G_pinv =
|
|
215
|
+
G_pinv = pinv(G_matrix, rcond=r_cond)
|
|
217
216
|
|
|
218
217
|
# Reconstruct as Operator with swapped specs
|
|
219
218
|
return _from_matrix(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Geometric Algebra - Elements
|
|
3
3
|
|
|
4
|
-
Core geometric algebra objects: Vectors, MultiVectors, Frames, and Metrics.
|
|
4
|
+
Core geometric algebra objects: Vectors, MultiVectors, Frames, Surfaces, and Metrics.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from morphis.elements.base import (
|
|
@@ -9,6 +9,7 @@ from morphis.elements.base import (
|
|
|
9
9
|
Element as Element,
|
|
10
10
|
GradedElement as GradedElement,
|
|
11
11
|
)
|
|
12
|
+
from morphis.elements.field import Field
|
|
12
13
|
from morphis.elements.frame import Frame
|
|
13
14
|
from morphis.elements.lot_indexed import LotIndexed as LotIndexed
|
|
14
15
|
from morphis.elements.metric import (
|
|
@@ -32,6 +33,7 @@ from morphis.elements.protocols import (
|
|
|
32
33
|
Spanning as Spanning,
|
|
33
34
|
Transformable as Transformable,
|
|
34
35
|
)
|
|
36
|
+
from morphis.elements.surface import Surface
|
|
35
37
|
from morphis.elements.tensor import Tensor
|
|
36
38
|
from morphis.elements.vector import (
|
|
37
39
|
Vector,
|
|
@@ -48,3 +50,5 @@ Vector.model_rebuild()
|
|
|
48
50
|
MultiVector.model_rebuild()
|
|
49
51
|
Frame.model_rebuild()
|
|
50
52
|
Tensor.model_rebuild()
|
|
53
|
+
Surface.model_rebuild()
|
|
54
|
+
Field.model_rebuild()
|
|
@@ -30,6 +30,7 @@ from morphis.elements.metric import Metric
|
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from morphis.algebra.contraction import IndexedTensor
|
|
33
|
+
from morphis.elements.multivector import MultiVector
|
|
33
34
|
from morphis.elements.vector import Vector
|
|
34
35
|
|
|
35
36
|
|
|
@@ -124,6 +125,33 @@ class Element(BaseModel):
|
|
|
124
125
|
"""Dimension of the underlying vector space."""
|
|
125
126
|
return self.metric.dim
|
|
126
127
|
|
|
128
|
+
def apply_similarity(
|
|
129
|
+
self,
|
|
130
|
+
S: "MultiVector",
|
|
131
|
+
t: "Vector | None" = None,
|
|
132
|
+
) -> Self:
|
|
133
|
+
"""
|
|
134
|
+
Apply a similarity transformation to this element.
|
|
135
|
+
|
|
136
|
+
Computes: transform(self, S) + t
|
|
137
|
+
|
|
138
|
+
The similarity versor S (from align_vectors or a rotor) is applied via
|
|
139
|
+
sandwich product. The optional translation t is added after.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
S: Similarity versor or rotor (MultiVector with grades {0, 2})
|
|
143
|
+
t: Optional translation vector (grade-1). Added after sandwich product.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
New element with the transformation applied.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
S = align_vectors(u, v) # Similarity versor
|
|
150
|
+
t = Vector([1, 0, 0], grade=1, metric=g) # Translation
|
|
151
|
+
v_transformed = v.apply_similarity(S, t)
|
|
152
|
+
"""
|
|
153
|
+
raise NotImplementedError("Subclasses must implement apply_similarity()")
|
|
154
|
+
|
|
127
155
|
|
|
128
156
|
class GradedElement(Element):
|
|
129
157
|
"""
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Field Element - Positions with Values (Skeleton)
|
|
3
|
+
|
|
4
|
+
Field represents a collection of positions in space, each with an associated
|
|
5
|
+
value (scalar, vector, frame, or multivector). This is a skeleton for future
|
|
6
|
+
implementation.
|
|
7
|
+
|
|
8
|
+
Typical uses:
|
|
9
|
+
- Scalar fields: temperature, pressure, density
|
|
10
|
+
- Vector fields: velocity, electric field, force
|
|
11
|
+
- Frame fields: stress tensors, rotation fields
|
|
12
|
+
- Multivector fields: electromagnetic field (F = E + IcB)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Self
|
|
18
|
+
|
|
19
|
+
from pydantic import ConfigDict, model_validator
|
|
20
|
+
|
|
21
|
+
from morphis.elements.base import Element
|
|
22
|
+
from morphis.elements.vector import Vector
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from morphis.elements.frame import Frame
|
|
27
|
+
from morphis.elements.metric import Metric
|
|
28
|
+
from morphis.elements.multivector import MultiVector
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Field(Element):
|
|
32
|
+
"""
|
|
33
|
+
A field of values at positions in space.
|
|
34
|
+
|
|
35
|
+
SKELETON: This class structure is defined for future implementation.
|
|
36
|
+
Full implementation is deferred.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
positions: Grade-1 Vector with lot=(N,) representing N sample positions
|
|
40
|
+
values: The field values at each position (Vector, Frame, or MultiVector)
|
|
41
|
+
|
|
42
|
+
The values must have lot matching positions.lot, so each position has
|
|
43
|
+
exactly one associated value.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
# Velocity field (3D vectors at 3D positions)
|
|
47
|
+
positions = Vector(sample_points, grade=1, metric=g) # lot=(100,)
|
|
48
|
+
velocities = Vector(velocity_data, grade=1, metric=g) # lot=(100,)
|
|
49
|
+
field = Field(positions=positions, values=velocities)
|
|
50
|
+
|
|
51
|
+
# Scalar field (scalars at positions)
|
|
52
|
+
temperatures = Vector(temp_data, grade=0, metric=g) # lot=(100,)
|
|
53
|
+
field = Field(positions=positions, values=temperatures)
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(
|
|
57
|
+
arbitrary_types_allowed=True,
|
|
58
|
+
frozen=False,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
positions: Vector
|
|
62
|
+
values: "Vector | Frame | MultiVector"
|
|
63
|
+
|
|
64
|
+
@model_validator(mode="after")
|
|
65
|
+
def _validate_field(self):
|
|
66
|
+
"""Validate positions and values are compatible."""
|
|
67
|
+
# Positions must be grade-1 vectors
|
|
68
|
+
if self.positions.grade != 1:
|
|
69
|
+
raise ValueError(f"Positions must be grade-1 Vectors, got grade={self.positions.grade}")
|
|
70
|
+
|
|
71
|
+
# Lots must match
|
|
72
|
+
if self.positions.lot != self.values.lot:
|
|
73
|
+
raise ValueError(f"Positions lot {self.positions.lot} != values lot {self.values.lot}")
|
|
74
|
+
|
|
75
|
+
# Sync metric and lot from positions
|
|
76
|
+
object.__setattr__(self, "metric", self.positions.metric)
|
|
77
|
+
object.__setattr__(self, "lot", self.positions.lot)
|
|
78
|
+
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def n_samples(self) -> int:
|
|
83
|
+
"""Number of sample positions in the field."""
|
|
84
|
+
return self.positions.lot[0] if self.positions.lot else 1
|
|
85
|
+
|
|
86
|
+
def copy(self) -> Self:
|
|
87
|
+
"""Create a deep copy of this field."""
|
|
88
|
+
return Field(
|
|
89
|
+
positions=self.positions.copy(),
|
|
90
|
+
values=self.values.copy(),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def with_metric(self, metric: "Metric") -> Self:
|
|
94
|
+
"""Return a new Field with the specified metric context."""
|
|
95
|
+
return Field(
|
|
96
|
+
positions=self.positions.with_metric(metric),
|
|
97
|
+
values=self.values.with_metric(metric),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
value_type = type(self.values).__name__
|
|
102
|
+
return f"Field(n_samples={self.n_samples}, value_type={value_type})"
|
|
@@ -526,6 +526,51 @@ class MultiVector(CompositeElement):
|
|
|
526
526
|
|
|
527
527
|
return unit(self)
|
|
528
528
|
|
|
529
|
+
def apply_similarity(
|
|
530
|
+
self,
|
|
531
|
+
S: MultiVector,
|
|
532
|
+
t: "Vector | None" = None,
|
|
533
|
+
) -> MultiVector:
|
|
534
|
+
"""
|
|
535
|
+
Apply a similarity transformation to this MultiVector.
|
|
536
|
+
|
|
537
|
+
Computes: S * self * ~S + t (translation on grade-1 component only)
|
|
538
|
+
|
|
539
|
+
The similarity versor S (from align_vectors or a rotor) is applied via
|
|
540
|
+
sandwich product. The optional translation t is added to the grade-1
|
|
541
|
+
component only (translations affect positions, not orientations).
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
S: Similarity versor or rotor (MultiVector with grades {0, 2})
|
|
545
|
+
t: Optional translation vector (grade-1). Added to grade-1 component.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
New MultiVector with the transformation applied.
|
|
549
|
+
|
|
550
|
+
Example:
|
|
551
|
+
S = align_vectors(u, v)
|
|
552
|
+
t = Vector([1, 0, 0], grade=1, metric=g)
|
|
553
|
+
M_transformed = M.apply_similarity(S, t)
|
|
554
|
+
"""
|
|
555
|
+
from morphis.operations.products import geometric, reverse
|
|
556
|
+
|
|
557
|
+
# Sandwich product: S * self * ~S
|
|
558
|
+
S_rev = reverse(S)
|
|
559
|
+
temp = geometric(S, self)
|
|
560
|
+
result = geometric(temp, S_rev)
|
|
561
|
+
|
|
562
|
+
if t is not None:
|
|
563
|
+
# Translation applies to grade-1 component only
|
|
564
|
+
grade1 = result.grade_select(1)
|
|
565
|
+
if grade1 is not None:
|
|
566
|
+
new_grade1 = grade1 + t
|
|
567
|
+
result = MultiVector(
|
|
568
|
+
data={**result.data, 1: new_grade1},
|
|
569
|
+
metric=result.metric,
|
|
570
|
+
lot=result.lot,
|
|
571
|
+
)
|
|
572
|
+
return result
|
|
573
|
+
|
|
529
574
|
# =========================================================================
|
|
530
575
|
# Utility Methods
|
|
531
576
|
# =========================================================================
|