coordinate-system 7.0.0__cp313-cp313-win_amd64.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.
- coordinate_system/__init__.py +324 -0
- coordinate_system/complex_geometric_physics.py +519 -0
- coordinate_system/coordinate_system.cp313-win_amd64.pyd +0 -0
- coordinate_system/curve_interpolation.py +507 -0
- coordinate_system/differential_geometry.py +986 -0
- coordinate_system/spectral_geometry.py +1602 -0
- coordinate_system/u3_frame.py +885 -0
- coordinate_system/visualization.py +1069 -0
- coordinate_system-7.0.0.dist-info/LICENSE +9 -0
- coordinate_system-7.0.0.dist-info/METADATA +312 -0
- coordinate_system-7.0.0.dist-info/RECORD +13 -0
- coordinate_system-7.0.0.dist-info/WHEEL +5 -0
- coordinate_system-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Differential Geometry Module
|
|
3
|
+
============================
|
|
4
|
+
|
|
5
|
+
Comprehensive discrete differential geometry computations on surfaces,
|
|
6
|
+
combining classical methods and intrinsic gradient operator framework.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- Surface parametrization (Sphere, Torus, custom surfaces)
|
|
10
|
+
- Metric tensor computation
|
|
11
|
+
- Intrinsic gradient operator for curvature calculation
|
|
12
|
+
- Classical curvature methods with high-order finite differences
|
|
13
|
+
- Gaussian, mean, principal, and Riemann curvatures
|
|
14
|
+
|
|
15
|
+
Author: Coordinate System Package
|
|
16
|
+
Date: 2025-12-03
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import numpy as np
|
|
21
|
+
from typing import Tuple, Optional, Callable, Union, Dict, List
|
|
22
|
+
from .coordinate_system import coord3, vec3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ============================================================
|
|
26
|
+
# High-Order Finite Difference Operators
|
|
27
|
+
# ============================================================
|
|
28
|
+
|
|
29
|
+
def derivative_5pt(f: Callable[[float], np.ndarray], x: float, h: float) -> np.ndarray:
|
|
30
|
+
"""
|
|
31
|
+
5-point finite difference formula for first derivative.
|
|
32
|
+
|
|
33
|
+
Accuracy: O(h^4)
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
f: Function to differentiate
|
|
37
|
+
x: Point at which to compute derivative
|
|
38
|
+
h: Step size
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
First derivative approximation
|
|
42
|
+
"""
|
|
43
|
+
return (-f(x + 2*h) + 8*f(x + h) - 8*f(x - h) + f(x - 2*h)) / (12*h)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def derivative_2nd_5pt(f: Callable[[float], np.ndarray], x: float, h: float) -> np.ndarray:
|
|
47
|
+
"""
|
|
48
|
+
5-point finite difference formula for second derivative.
|
|
49
|
+
|
|
50
|
+
Accuracy: O(h^4)
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
f: Function to differentiate
|
|
54
|
+
x: Point at which to compute derivative
|
|
55
|
+
h: Step size
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Second derivative approximation
|
|
59
|
+
"""
|
|
60
|
+
return (-f(x + 2*h) + 16*f(x + h) - 30*f(x) + 16*f(x - h) - f(x - 2*h)) / (12*h*h)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def richardson_extrapolation(f_h: float, f_2h: float, order: int = 4) -> float:
|
|
64
|
+
"""
|
|
65
|
+
Richardson extrapolation for accelerating convergence.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
f_h: Value computed with step size h
|
|
69
|
+
f_2h: Value computed with step size 2h
|
|
70
|
+
order: Order of the method
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Extrapolated value with improved accuracy
|
|
74
|
+
"""
|
|
75
|
+
return (2**order * f_h - f_2h) / (2**order - 1)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ============================================================
|
|
79
|
+
# Surface Base Class and Common Surfaces
|
|
80
|
+
# ============================================================
|
|
81
|
+
|
|
82
|
+
class Surface:
|
|
83
|
+
"""
|
|
84
|
+
Base class for parametric surfaces r(u, v).
|
|
85
|
+
|
|
86
|
+
Subclasses must implement the position(u, v) method.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, h: float = 1e-6):
|
|
90
|
+
"""
|
|
91
|
+
Initialize surface.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
h: Step size for numerical differentiation
|
|
95
|
+
"""
|
|
96
|
+
self.h = h
|
|
97
|
+
|
|
98
|
+
def position(self, u: float, v: float) -> vec3:
|
|
99
|
+
"""
|
|
100
|
+
Compute position on surface at parameters (u, v).
|
|
101
|
+
|
|
102
|
+
Must be implemented by subclasses.
|
|
103
|
+
"""
|
|
104
|
+
raise NotImplementedError("Subclass must implement position(u, v)")
|
|
105
|
+
|
|
106
|
+
def tangent_u(self, u: float, v: float) -> vec3:
|
|
107
|
+
"""Compute tangent vector in u direction using central difference."""
|
|
108
|
+
r_plus = self.position(u + self.h, v)
|
|
109
|
+
r_minus = self.position(u - self.h, v)
|
|
110
|
+
return (r_plus - r_minus) * (1.0 / (2.0 * self.h))
|
|
111
|
+
|
|
112
|
+
def tangent_v(self, u: float, v: float) -> vec3:
|
|
113
|
+
"""Compute tangent vector in v direction using central difference."""
|
|
114
|
+
r_plus = self.position(u, v + self.h)
|
|
115
|
+
r_minus = self.position(u, v - self.h)
|
|
116
|
+
return (r_plus - r_minus) * (1.0 / (2.0 * self.h))
|
|
117
|
+
|
|
118
|
+
def normal(self, u: float, v: float) -> vec3:
|
|
119
|
+
"""Compute unit normal vector."""
|
|
120
|
+
r_u = self.tangent_u(u, v)
|
|
121
|
+
r_v = self.tangent_v(u, v)
|
|
122
|
+
n = r_u.cross(r_v)
|
|
123
|
+
length = (n.x**2 + n.y**2 + n.z**2) ** 0.5
|
|
124
|
+
if length > 1e-10:
|
|
125
|
+
return n * (1.0 / length)
|
|
126
|
+
else:
|
|
127
|
+
return vec3(0.0, 0.0, 1.0)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Sphere(Surface):
|
|
131
|
+
"""
|
|
132
|
+
Sphere surface.
|
|
133
|
+
|
|
134
|
+
Parametrization: r(theta, phi) = R(sin(theta)cos(phi), sin(theta)sin(phi), cos(theta))
|
|
135
|
+
where theta in [0, pi] is polar angle, phi in [0, 2*pi] is azimuthal angle.
|
|
136
|
+
|
|
137
|
+
Theoretical curvatures:
|
|
138
|
+
- Gaussian curvature: K = 1/R^2
|
|
139
|
+
- Mean curvature: H = 1/R
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, radius: float = 1.0, h: float = 1e-6):
|
|
143
|
+
super().__init__(h)
|
|
144
|
+
self.R = radius
|
|
145
|
+
|
|
146
|
+
def position(self, theta: float, phi: float) -> vec3:
|
|
147
|
+
"""Position on sphere."""
|
|
148
|
+
x = self.R * math.sin(theta) * math.cos(phi)
|
|
149
|
+
y = self.R * math.sin(theta) * math.sin(phi)
|
|
150
|
+
z = self.R * math.cos(theta)
|
|
151
|
+
return vec3(x, y, z)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def theoretical_gaussian_curvature(self) -> float:
|
|
155
|
+
"""Theoretical Gaussian curvature K = 1/R^2."""
|
|
156
|
+
return 1.0 / (self.R * self.R)
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def theoretical_mean_curvature(self) -> float:
|
|
160
|
+
"""Theoretical mean curvature H = 1/R."""
|
|
161
|
+
return 1.0 / self.R
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Torus(Surface):
|
|
165
|
+
"""
|
|
166
|
+
Torus surface.
|
|
167
|
+
|
|
168
|
+
Parametrization: r(u, v) = ((R + r*cos(u))*cos(v), (R + r*cos(u))*sin(v), r*sin(u))
|
|
169
|
+
where R is major radius, r is minor radius.
|
|
170
|
+
|
|
171
|
+
Theoretical curvatures:
|
|
172
|
+
- Gaussian curvature: K = cos(u) / (r * (R + r*cos(u)))
|
|
173
|
+
- Mean curvature: H = (R + 2*r*cos(u)) / (2*r*(R + r*cos(u)))
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def __init__(self, major_radius: float = 3.0, minor_radius: float = 1.0, h: float = 1e-6):
|
|
177
|
+
super().__init__(h)
|
|
178
|
+
self.R = major_radius
|
|
179
|
+
self.r = minor_radius
|
|
180
|
+
|
|
181
|
+
def position(self, u: float, v: float) -> vec3:
|
|
182
|
+
"""Position on torus."""
|
|
183
|
+
x = (self.R + self.r * math.cos(u)) * math.cos(v)
|
|
184
|
+
y = (self.R + self.r * math.cos(u)) * math.sin(v)
|
|
185
|
+
z = self.r * math.sin(u)
|
|
186
|
+
return vec3(x, y, z)
|
|
187
|
+
|
|
188
|
+
def theoretical_gaussian_curvature(self, u: float) -> float:
|
|
189
|
+
"""Theoretical Gaussian curvature at parameter u."""
|
|
190
|
+
return math.cos(u) / (self.r * (self.R + self.r * math.cos(u)))
|
|
191
|
+
|
|
192
|
+
def theoretical_mean_curvature(self, u: float) -> float:
|
|
193
|
+
"""Theoretical mean curvature at parameter u."""
|
|
194
|
+
return (self.R + 2 * self.r * math.cos(u)) / (2 * self.r * (self.R + self.r * math.cos(u)))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ============================================================
|
|
198
|
+
# Metric Tensor
|
|
199
|
+
# ============================================================
|
|
200
|
+
|
|
201
|
+
class MetricTensor:
|
|
202
|
+
"""
|
|
203
|
+
First fundamental form (metric tensor) of a surface.
|
|
204
|
+
|
|
205
|
+
g_ij = <dr/du^i, dr/du^j>
|
|
206
|
+
|
|
207
|
+
Components:
|
|
208
|
+
- E = g_11 = <r_u, r_u>
|
|
209
|
+
- F = g_12 = <r_u, r_v>
|
|
210
|
+
- G = g_22 = <r_v, r_v>
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def __init__(self, E: float, F: float, G: float):
|
|
214
|
+
"""
|
|
215
|
+
Initialize metric tensor.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
E: g_11 = <r_u, r_u>
|
|
219
|
+
F: g_12 = <r_u, r_v>
|
|
220
|
+
G: g_22 = <r_v, r_v>
|
|
221
|
+
"""
|
|
222
|
+
self.E = E
|
|
223
|
+
self.F = F
|
|
224
|
+
self.G = G
|
|
225
|
+
self.det = E * G - F * F
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_surface(cls, surface: Surface, u: float, v: float) -> 'MetricTensor':
|
|
229
|
+
"""Create metric tensor from surface at point (u, v)."""
|
|
230
|
+
r_u = surface.tangent_u(u, v)
|
|
231
|
+
r_v = surface.tangent_v(u, v)
|
|
232
|
+
E = r_u.dot(r_u)
|
|
233
|
+
F = r_u.dot(r_v)
|
|
234
|
+
G = r_v.dot(r_v)
|
|
235
|
+
return cls(E, F, G)
|
|
236
|
+
|
|
237
|
+
def determinant(self) -> float:
|
|
238
|
+
"""Get determinant of metric tensor."""
|
|
239
|
+
return self.det
|
|
240
|
+
|
|
241
|
+
def inverse(self) -> np.ndarray:
|
|
242
|
+
"""Get inverse metric tensor as numpy array."""
|
|
243
|
+
if abs(self.det) < 1e-14:
|
|
244
|
+
return np.eye(2)
|
|
245
|
+
return np.array([[self.G, -self.F], [-self.F, self.E]]) / self.det
|
|
246
|
+
|
|
247
|
+
def as_matrix(self) -> np.ndarray:
|
|
248
|
+
"""Get metric tensor as numpy array."""
|
|
249
|
+
return np.array([[self.E, self.F], [self.F, self.G]])
|
|
250
|
+
|
|
251
|
+
def __repr__(self) -> str:
|
|
252
|
+
return f"MetricTensor(E={self.E:.6f}, F={self.F:.6f}, G={self.G:.6f}, det={self.det:.6f})"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ============================================================
|
|
256
|
+
# Gradient Result
|
|
257
|
+
# ============================================================
|
|
258
|
+
|
|
259
|
+
class GradientResult:
|
|
260
|
+
"""
|
|
261
|
+
Gradient result containing normal vector derivative.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
def __init__(self, dn: vec3, direction: str):
|
|
265
|
+
"""
|
|
266
|
+
Initialize gradient result.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
dn: Normal vector derivative
|
|
270
|
+
direction: Parameter direction ('u' or 'v')
|
|
271
|
+
"""
|
|
272
|
+
self.dn = dn
|
|
273
|
+
self.direction = direction
|
|
274
|
+
|
|
275
|
+
def __repr__(self) -> str:
|
|
276
|
+
return f"GradientResult({self.direction}: [{self.dn.x:.6f}, {self.dn.y:.6f}, {self.dn.z:.6f}])"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ============================================================
|
|
280
|
+
# Intrinsic Gradient Operator
|
|
281
|
+
# ============================================================
|
|
282
|
+
|
|
283
|
+
class IntrinsicGradientOperator:
|
|
284
|
+
"""
|
|
285
|
+
Intrinsic gradient operator implementation.
|
|
286
|
+
|
|
287
|
+
Key formula:
|
|
288
|
+
G_mu = (c(u+h) - c(u-h)) / (2h) / c(u)
|
|
289
|
+
Then extract normal derivative using .VZ()
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
def __init__(self, surface: Surface, step_size: float = 1e-3):
|
|
293
|
+
self.surface = surface
|
|
294
|
+
self.h = step_size
|
|
295
|
+
|
|
296
|
+
def calc_intrinsic_frame(self, u: float, v: float) -> coord3:
|
|
297
|
+
"""
|
|
298
|
+
Calculate intrinsic frame at point (u, v).
|
|
299
|
+
|
|
300
|
+
For Sphere and Torus, uses analytical expressions.
|
|
301
|
+
For other surfaces, uses numerical derivatives.
|
|
302
|
+
"""
|
|
303
|
+
if isinstance(self.surface, Sphere):
|
|
304
|
+
R = self.surface.R
|
|
305
|
+
theta, phi = u, v
|
|
306
|
+
|
|
307
|
+
pos = self.surface.position(theta, phi)
|
|
308
|
+
|
|
309
|
+
# Analytical tangent vectors
|
|
310
|
+
r_theta = vec3(
|
|
311
|
+
R * math.cos(theta) * math.cos(phi),
|
|
312
|
+
R * math.cos(theta) * math.sin(phi),
|
|
313
|
+
-R * math.sin(theta)
|
|
314
|
+
)
|
|
315
|
+
r_phi = vec3(
|
|
316
|
+
-R * math.sin(theta) * math.sin(phi),
|
|
317
|
+
R * math.sin(theta) * math.cos(phi),
|
|
318
|
+
0
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
n = r_theta.cross(r_phi).normalized()
|
|
322
|
+
e1 = r_theta.normalized()
|
|
323
|
+
e2 = r_phi.normalized()
|
|
324
|
+
|
|
325
|
+
elif isinstance(self.surface, Torus):
|
|
326
|
+
R = self.surface.R
|
|
327
|
+
r = self.surface.r
|
|
328
|
+
u_param, v_param = u, v
|
|
329
|
+
|
|
330
|
+
pos = self.surface.position(u_param, v_param)
|
|
331
|
+
|
|
332
|
+
# Analytical tangent vectors
|
|
333
|
+
r_u = vec3(
|
|
334
|
+
-r * math.sin(u_param) * math.cos(v_param),
|
|
335
|
+
-r * math.sin(u_param) * math.sin(v_param),
|
|
336
|
+
r * math.cos(u_param)
|
|
337
|
+
)
|
|
338
|
+
r_v = vec3(
|
|
339
|
+
-(R + r * math.cos(u_param)) * math.sin(v_param),
|
|
340
|
+
(R + r * math.cos(u_param)) * math.cos(v_param),
|
|
341
|
+
0
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
n = r_u.cross(r_v).normalized()
|
|
345
|
+
e1 = r_u.normalized()
|
|
346
|
+
e2 = r_v.normalized()
|
|
347
|
+
|
|
348
|
+
else:
|
|
349
|
+
# Numerical method for general surfaces
|
|
350
|
+
pos = self.surface.position(u, v)
|
|
351
|
+
r_u = self.surface.tangent_u(u, v)
|
|
352
|
+
r_v = self.surface.tangent_v(u, v)
|
|
353
|
+
|
|
354
|
+
n = r_u.cross(r_v).normalized()
|
|
355
|
+
e1 = r_u.normalized()
|
|
356
|
+
e2 = r_v.normalized()
|
|
357
|
+
|
|
358
|
+
# Create intrinsic frame
|
|
359
|
+
frame = coord3()
|
|
360
|
+
frame.o = pos
|
|
361
|
+
frame.ux = e1
|
|
362
|
+
frame.uy = e2
|
|
363
|
+
frame.uz = n
|
|
364
|
+
|
|
365
|
+
return frame
|
|
366
|
+
|
|
367
|
+
def compute_both(self, u: float, v: float) -> Tuple[GradientResult, GradientResult, coord3]:
|
|
368
|
+
"""
|
|
369
|
+
Compute gradients in both u and v directions using central differences.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Tuple of (G_u, G_v, center_frame)
|
|
373
|
+
"""
|
|
374
|
+
c_center = self.calc_intrinsic_frame(u, v)
|
|
375
|
+
|
|
376
|
+
# u direction: central difference
|
|
377
|
+
c_u_plus = self.calc_intrinsic_frame(u + self.h, v)
|
|
378
|
+
c_u_minus = self.calc_intrinsic_frame(u - self.h, v)
|
|
379
|
+
dn_du = ((c_u_plus - c_u_minus) / (2 * self.h)).VZ()
|
|
380
|
+
|
|
381
|
+
# v direction: central difference
|
|
382
|
+
c_v_plus = self.calc_intrinsic_frame(u, v + self.h)
|
|
383
|
+
c_v_minus = self.calc_intrinsic_frame(u, v - self.h)
|
|
384
|
+
dn_dv = ((c_v_plus - c_v_minus) / (2 * self.h)).VZ()
|
|
385
|
+
|
|
386
|
+
G_u = GradientResult(dn_du, "u")
|
|
387
|
+
G_v = GradientResult(dn_dv, "v")
|
|
388
|
+
|
|
389
|
+
return G_u, G_v, c_center
|
|
390
|
+
|
|
391
|
+
def compute_u(self, u: float, v: float) -> GradientResult:
|
|
392
|
+
"""Compute gradient in u direction only."""
|
|
393
|
+
c_plus = self.calc_intrinsic_frame(u + self.h, v)
|
|
394
|
+
c_minus = self.calc_intrinsic_frame(u - self.h, v)
|
|
395
|
+
dn = ((c_plus - c_minus) / (2 * self.h)).VZ()
|
|
396
|
+
return GradientResult(dn, "u")
|
|
397
|
+
|
|
398
|
+
def compute_v(self, u: float, v: float) -> GradientResult:
|
|
399
|
+
"""Compute gradient in v direction only."""
|
|
400
|
+
c_plus = self.calc_intrinsic_frame(u, v + self.h)
|
|
401
|
+
c_minus = self.calc_intrinsic_frame(u, v - self.h)
|
|
402
|
+
dn = ((c_plus - c_minus) / (2 * self.h)).VZ()
|
|
403
|
+
return GradientResult(dn, "v")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# ============================================================
|
|
407
|
+
# Intrinsic Gradient Curvature Calculator
|
|
408
|
+
# ============================================================
|
|
409
|
+
|
|
410
|
+
class IntrinsicGradientCurvatureCalculator:
|
|
411
|
+
"""
|
|
412
|
+
Curvature calculator using intrinsic gradient method.
|
|
413
|
+
|
|
414
|
+
Computes curvatures from the shape operator derived
|
|
415
|
+
from the intrinsic gradient of the normal field.
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
def __init__(self, surface: Surface, step_size: float = 1e-3):
|
|
419
|
+
self.surface = surface
|
|
420
|
+
self.h = step_size
|
|
421
|
+
self.grad_op = IntrinsicGradientOperator(surface, step_size)
|
|
422
|
+
|
|
423
|
+
def _get_tangent_vectors(self, u: float, v: float) -> Tuple[vec3, vec3]:
|
|
424
|
+
"""Get tangent vectors (analytical for known surfaces, numerical otherwise)."""
|
|
425
|
+
if isinstance(self.surface, Sphere):
|
|
426
|
+
R = self.surface.R
|
|
427
|
+
theta, phi = u, v
|
|
428
|
+
r_u = vec3(
|
|
429
|
+
R * math.cos(theta) * math.cos(phi),
|
|
430
|
+
R * math.cos(theta) * math.sin(phi),
|
|
431
|
+
-R * math.sin(theta)
|
|
432
|
+
)
|
|
433
|
+
r_v = vec3(
|
|
434
|
+
-R * math.sin(theta) * math.sin(phi),
|
|
435
|
+
R * math.sin(theta) * math.cos(phi),
|
|
436
|
+
0
|
|
437
|
+
)
|
|
438
|
+
elif isinstance(self.surface, Torus):
|
|
439
|
+
R = self.surface.R
|
|
440
|
+
r = self.surface.r
|
|
441
|
+
r_u = vec3(
|
|
442
|
+
-r * math.sin(u) * math.cos(v),
|
|
443
|
+
-r * math.sin(u) * math.sin(v),
|
|
444
|
+
r * math.cos(u)
|
|
445
|
+
)
|
|
446
|
+
r_v = vec3(
|
|
447
|
+
-(R + r * math.cos(u)) * math.sin(v),
|
|
448
|
+
(R + r * math.cos(u)) * math.cos(v),
|
|
449
|
+
0
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
r_u = self.surface.tangent_u(u, v)
|
|
453
|
+
r_v = self.surface.tangent_v(u, v)
|
|
454
|
+
|
|
455
|
+
return r_u, r_v
|
|
456
|
+
|
|
457
|
+
def compute_gaussian_curvature(self, u: float, v: float) -> float:
|
|
458
|
+
"""
|
|
459
|
+
Compute Gaussian curvature K = det(II) / det(I).
|
|
460
|
+
"""
|
|
461
|
+
G_u, G_v, _ = self.grad_op.compute_both(u, v)
|
|
462
|
+
r_u, r_v = self._get_tangent_vectors(u, v)
|
|
463
|
+
|
|
464
|
+
dn_du = G_u.dn
|
|
465
|
+
dn_dv = G_v.dn
|
|
466
|
+
|
|
467
|
+
# First fundamental form
|
|
468
|
+
E = r_u.dot(r_u)
|
|
469
|
+
F = r_u.dot(r_v)
|
|
470
|
+
G = r_v.dot(r_v)
|
|
471
|
+
metric_det = E * G - F * F
|
|
472
|
+
|
|
473
|
+
# Second fundamental form
|
|
474
|
+
L = -dn_du.dot(r_u)
|
|
475
|
+
M1 = -dn_du.dot(r_v)
|
|
476
|
+
M2 = -dn_dv.dot(r_u)
|
|
477
|
+
N = -dn_dv.dot(r_v)
|
|
478
|
+
M = (M1 + M2) / 2.0
|
|
479
|
+
|
|
480
|
+
# Gaussian curvature
|
|
481
|
+
if abs(metric_det) > 1e-14:
|
|
482
|
+
K = (L * N - M * M) / metric_det
|
|
483
|
+
else:
|
|
484
|
+
K = 0.0
|
|
485
|
+
|
|
486
|
+
return K
|
|
487
|
+
|
|
488
|
+
def compute_mean_curvature(self, u: float, v: float) -> float:
|
|
489
|
+
"""
|
|
490
|
+
Compute mean curvature H = (EN - 2FM + GL) / (2*det(I)).
|
|
491
|
+
"""
|
|
492
|
+
G_u, G_v, _ = self.grad_op.compute_both(u, v)
|
|
493
|
+
r_u, r_v = self._get_tangent_vectors(u, v)
|
|
494
|
+
|
|
495
|
+
dn_du = G_u.dn
|
|
496
|
+
dn_dv = G_v.dn
|
|
497
|
+
|
|
498
|
+
E = r_u.dot(r_u)
|
|
499
|
+
F = r_u.dot(r_v)
|
|
500
|
+
G = r_v.dot(r_v)
|
|
501
|
+
metric_det = E * G - F * F
|
|
502
|
+
|
|
503
|
+
L = -dn_du.dot(r_u)
|
|
504
|
+
M1 = -dn_du.dot(r_v)
|
|
505
|
+
M2 = -dn_dv.dot(r_u)
|
|
506
|
+
N = -dn_dv.dot(r_v)
|
|
507
|
+
M = (M1 + M2) / 2.0
|
|
508
|
+
|
|
509
|
+
if abs(metric_det) > 1e-14:
|
|
510
|
+
H = (G * L - 2 * F * M + E * N) / (2 * metric_det)
|
|
511
|
+
else:
|
|
512
|
+
H = 0.0
|
|
513
|
+
|
|
514
|
+
return H
|
|
515
|
+
|
|
516
|
+
def compute_riemann_curvature(self, u: float, v: float) -> float:
|
|
517
|
+
"""
|
|
518
|
+
Compute Riemann curvature tensor component R^1_212.
|
|
519
|
+
|
|
520
|
+
For 2D surfaces: R^1_212 = K * det(g) = LN - M^2
|
|
521
|
+
"""
|
|
522
|
+
G_u, G_v, _ = self.grad_op.compute_both(u, v)
|
|
523
|
+
r_u, r_v = self._get_tangent_vectors(u, v)
|
|
524
|
+
|
|
525
|
+
dn_du = G_u.dn
|
|
526
|
+
dn_dv = G_v.dn
|
|
527
|
+
|
|
528
|
+
L = -dn_du.dot(r_u)
|
|
529
|
+
M = -dn_du.dot(r_v)
|
|
530
|
+
N = -dn_dv.dot(r_v)
|
|
531
|
+
|
|
532
|
+
R_1212 = L * N - M * M
|
|
533
|
+
return R_1212
|
|
534
|
+
|
|
535
|
+
def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float]:
|
|
536
|
+
"""
|
|
537
|
+
Compute principal curvatures k1 and k2.
|
|
538
|
+
|
|
539
|
+
Uses: k1,k2 = H +/- sqrt(H^2 - K)
|
|
540
|
+
"""
|
|
541
|
+
K = self.compute_gaussian_curvature(u, v)
|
|
542
|
+
H = self.compute_mean_curvature(u, v)
|
|
543
|
+
|
|
544
|
+
discriminant = max(0, H * H - K)
|
|
545
|
+
sqrt_disc = discriminant ** 0.5
|
|
546
|
+
|
|
547
|
+
k1 = H + sqrt_disc
|
|
548
|
+
k2 = H - sqrt_disc
|
|
549
|
+
|
|
550
|
+
return k1, k2
|
|
551
|
+
|
|
552
|
+
def compute_all_curvatures(self, u: float, v: float) -> Dict[str, Union[float, Tuple[float, float]]]:
|
|
553
|
+
"""
|
|
554
|
+
Compute all curvature quantities at once.
|
|
555
|
+
"""
|
|
556
|
+
K = self.compute_gaussian_curvature(u, v)
|
|
557
|
+
H = self.compute_mean_curvature(u, v)
|
|
558
|
+
|
|
559
|
+
discriminant = max(0, H * H - K)
|
|
560
|
+
sqrt_disc = discriminant ** 0.5
|
|
561
|
+
k1 = H + sqrt_disc
|
|
562
|
+
k2 = H - sqrt_disc
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
'gaussian_curvature': K,
|
|
566
|
+
'mean_curvature': H,
|
|
567
|
+
'principal_curvatures': (k1, k2)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# ============================================================
|
|
572
|
+
# Classical Curvature Calculator
|
|
573
|
+
# ============================================================
|
|
574
|
+
|
|
575
|
+
class CurvatureCalculator:
|
|
576
|
+
"""
|
|
577
|
+
High-precision discrete curvature calculator using classical differential geometry.
|
|
578
|
+
|
|
579
|
+
Uses high-order finite differences for computing derivatives
|
|
580
|
+
and fundamental forms.
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
def __init__(self, surface: Surface, step_size: float = 1e-3):
|
|
584
|
+
self.surface = surface
|
|
585
|
+
self.h = step_size
|
|
586
|
+
|
|
587
|
+
def _position_array(self, u: float, v: float) -> np.ndarray:
|
|
588
|
+
"""Convert vec3 position to numpy array."""
|
|
589
|
+
pos = self.surface.position(u, v)
|
|
590
|
+
return np.array([pos.x, pos.y, pos.z])
|
|
591
|
+
|
|
592
|
+
def _compute_derivatives(self, u: float, v: float) -> Dict[str, np.ndarray]:
|
|
593
|
+
"""Compute surface derivatives using high-order finite differences."""
|
|
594
|
+
effective_h = max(self.h, 1e-6)
|
|
595
|
+
|
|
596
|
+
r_u = derivative_5pt(lambda uu: self._position_array(uu, v), u, effective_h)
|
|
597
|
+
r_v = derivative_5pt(lambda vv: self._position_array(u, vv), v, effective_h)
|
|
598
|
+
|
|
599
|
+
r_uu = derivative_2nd_5pt(lambda uu: self._position_array(uu, v), u, effective_h)
|
|
600
|
+
r_vv = derivative_2nd_5pt(lambda vv: self._position_array(u, vv), v, effective_h)
|
|
601
|
+
r_uv = derivative_5pt(
|
|
602
|
+
lambda vv: derivative_5pt(
|
|
603
|
+
lambda uu: self._position_array(uu, vv), u, effective_h
|
|
604
|
+
), v, effective_h
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
'r_u': r_u, 'r_v': r_v,
|
|
609
|
+
'r_uu': r_uu, 'r_vv': r_vv, 'r_uv': r_uv
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
def compute_fundamental_forms(self, u: float, v: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
613
|
+
"""
|
|
614
|
+
Compute first and second fundamental forms.
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
(g, h, n): First form, second form, normal vector
|
|
618
|
+
"""
|
|
619
|
+
derivs = self._compute_derivatives(u, v)
|
|
620
|
+
r_u = derivs['r_u']
|
|
621
|
+
r_v = derivs['r_v']
|
|
622
|
+
r_uu = derivs['r_uu']
|
|
623
|
+
r_vv = derivs['r_vv']
|
|
624
|
+
r_uv = derivs['r_uv']
|
|
625
|
+
|
|
626
|
+
# First fundamental form
|
|
627
|
+
E = np.dot(r_u, r_u)
|
|
628
|
+
F = np.dot(r_u, r_v)
|
|
629
|
+
G = np.dot(r_v, r_v)
|
|
630
|
+
g = np.array([[E, F], [F, G]])
|
|
631
|
+
|
|
632
|
+
# Normal vector
|
|
633
|
+
n_vec = np.cross(r_u, r_v)
|
|
634
|
+
n_norm = np.linalg.norm(n_vec)
|
|
635
|
+
if n_norm > 1e-14:
|
|
636
|
+
n = n_vec / n_norm
|
|
637
|
+
else:
|
|
638
|
+
n = np.array([0., 0., 1.])
|
|
639
|
+
|
|
640
|
+
# Second fundamental form
|
|
641
|
+
L = np.dot(r_uu, n)
|
|
642
|
+
M = np.dot(r_uv, n)
|
|
643
|
+
N = np.dot(r_vv, n)
|
|
644
|
+
h = np.array([[L, M], [M, N]])
|
|
645
|
+
|
|
646
|
+
return g, h, n
|
|
647
|
+
|
|
648
|
+
def compute_gaussian_curvature(self, u: float, v: float) -> float:
|
|
649
|
+
"""Compute Gaussian curvature K = det(II) / det(I)."""
|
|
650
|
+
g, h, _ = self.compute_fundamental_forms(u, v)
|
|
651
|
+
|
|
652
|
+
det_g = np.linalg.det(g)
|
|
653
|
+
det_h = np.linalg.det(h)
|
|
654
|
+
|
|
655
|
+
if abs(det_g) < 1e-14:
|
|
656
|
+
return 0.0
|
|
657
|
+
|
|
658
|
+
return det_h / det_g
|
|
659
|
+
|
|
660
|
+
def compute_mean_curvature(self, u: float, v: float) -> float:
|
|
661
|
+
"""Compute mean curvature H."""
|
|
662
|
+
g, h, _ = self.compute_fundamental_forms(u, v)
|
|
663
|
+
|
|
664
|
+
det_g = np.linalg.det(g)
|
|
665
|
+
if abs(det_g) < 1e-14:
|
|
666
|
+
return 0.0
|
|
667
|
+
|
|
668
|
+
trace_term = g[1, 1] * h[0, 0] - 2 * g[0, 1] * h[0, 1] + g[0, 0] * h[1, 1]
|
|
669
|
+
H = trace_term / (2 * det_g)
|
|
670
|
+
|
|
671
|
+
return abs(H)
|
|
672
|
+
|
|
673
|
+
def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float, np.ndarray, np.ndarray]:
|
|
674
|
+
"""
|
|
675
|
+
Compute principal curvatures and principal directions.
|
|
676
|
+
|
|
677
|
+
Returns:
|
|
678
|
+
(k1, k2, dir1, dir2): Principal curvatures and directions
|
|
679
|
+
"""
|
|
680
|
+
g, h, _ = self.compute_fundamental_forms(u, v)
|
|
681
|
+
derivs = self._compute_derivatives(u, v)
|
|
682
|
+
r_u = derivs['r_u']
|
|
683
|
+
r_v = derivs['r_v']
|
|
684
|
+
|
|
685
|
+
det_g = np.linalg.det(g)
|
|
686
|
+
if abs(det_g) < 1e-14:
|
|
687
|
+
return 0.0, 0.0, np.array([1., 0., 0.]), np.array([0., 1., 0.])
|
|
688
|
+
|
|
689
|
+
# Shape operator S = g^{-1} h
|
|
690
|
+
g_inv = np.linalg.inv(g)
|
|
691
|
+
S = g_inv @ h
|
|
692
|
+
|
|
693
|
+
# Eigenvalue decomposition
|
|
694
|
+
eigenvalues, eigenvectors = np.linalg.eig(S)
|
|
695
|
+
k1, k2 = eigenvalues.real
|
|
696
|
+
|
|
697
|
+
# Ensure k1 >= k2 by absolute value
|
|
698
|
+
if abs(k1) < abs(k2):
|
|
699
|
+
k1, k2 = k2, k1
|
|
700
|
+
eigenvectors = eigenvectors[:, [1, 0]]
|
|
701
|
+
|
|
702
|
+
# Convert to 3D directions
|
|
703
|
+
dir1_2d = eigenvectors[:, 0]
|
|
704
|
+
dir2_2d = eigenvectors[:, 1]
|
|
705
|
+
|
|
706
|
+
dir1_3d = dir1_2d[0] * r_u + dir1_2d[1] * r_v
|
|
707
|
+
dir2_3d = dir2_2d[0] * r_u + dir2_2d[1] * r_v
|
|
708
|
+
|
|
709
|
+
dir1_3d = dir1_3d / (np.linalg.norm(dir1_3d) + 1e-14)
|
|
710
|
+
dir2_3d = dir2_3d / (np.linalg.norm(dir2_3d) + 1e-14)
|
|
711
|
+
|
|
712
|
+
return k1, k2, dir1_3d, dir2_3d
|
|
713
|
+
|
|
714
|
+
def compute_all_curvatures(self, u: float, v: float) -> Dict[str, Union[float, np.ndarray]]:
|
|
715
|
+
"""Compute all curvature quantities at once."""
|
|
716
|
+
g, h, n = self.compute_fundamental_forms(u, v)
|
|
717
|
+
K = self.compute_gaussian_curvature(u, v)
|
|
718
|
+
H = self.compute_mean_curvature(u, v)
|
|
719
|
+
k1, k2, dir1, dir2 = self.compute_principal_curvatures(u, v)
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
'K': K,
|
|
723
|
+
'H': H,
|
|
724
|
+
'k1': k1,
|
|
725
|
+
'k2': k2,
|
|
726
|
+
'dir1': dir1,
|
|
727
|
+
'dir2': dir2,
|
|
728
|
+
'g': g,
|
|
729
|
+
'h': h,
|
|
730
|
+
'n': n
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
# ============================================================
|
|
735
|
+
# Convenience Functions - Intrinsic Gradient Method (Default)
|
|
736
|
+
# ============================================================
|
|
737
|
+
|
|
738
|
+
def compute_gaussian_curvature(
|
|
739
|
+
surface: Surface,
|
|
740
|
+
u: float,
|
|
741
|
+
v: float,
|
|
742
|
+
step_size: float = 1e-3
|
|
743
|
+
) -> float:
|
|
744
|
+
"""
|
|
745
|
+
Compute Gaussian curvature using intrinsic gradient method.
|
|
746
|
+
"""
|
|
747
|
+
calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
|
|
748
|
+
return calc.compute_gaussian_curvature(u, v)
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def compute_mean_curvature(
|
|
752
|
+
surface: Surface,
|
|
753
|
+
u: float,
|
|
754
|
+
v: float,
|
|
755
|
+
step_size: float = 1e-3
|
|
756
|
+
) -> float:
|
|
757
|
+
"""
|
|
758
|
+
Compute mean curvature using intrinsic gradient method.
|
|
759
|
+
"""
|
|
760
|
+
calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
|
|
761
|
+
return calc.compute_mean_curvature(u, v)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def compute_riemann_curvature(
|
|
765
|
+
surface: Surface,
|
|
766
|
+
u: float,
|
|
767
|
+
v: float,
|
|
768
|
+
step_size: float = 1e-3
|
|
769
|
+
) -> float:
|
|
770
|
+
"""
|
|
771
|
+
Compute Riemann curvature tensor component R^1_212.
|
|
772
|
+
"""
|
|
773
|
+
calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
|
|
774
|
+
return calc.compute_riemann_curvature(u, v)
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def compute_all_curvatures(
|
|
778
|
+
surface: Surface,
|
|
779
|
+
u: float,
|
|
780
|
+
v: float,
|
|
781
|
+
step_size: float = 1e-3
|
|
782
|
+
) -> Dict[str, Union[float, Tuple[float, float]]]:
|
|
783
|
+
"""
|
|
784
|
+
Compute all curvature quantities using intrinsic gradient method.
|
|
785
|
+
"""
|
|
786
|
+
calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
|
|
787
|
+
return calc.compute_all_curvatures(u, v)
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def compute_intrinsic_gradient(
|
|
791
|
+
surface: Surface,
|
|
792
|
+
u: float,
|
|
793
|
+
v: float,
|
|
794
|
+
direction: str = 'u',
|
|
795
|
+
step_size: float = 1e-3
|
|
796
|
+
) -> GradientResult:
|
|
797
|
+
"""
|
|
798
|
+
Compute intrinsic gradient in specified direction.
|
|
799
|
+
|
|
800
|
+
Args:
|
|
801
|
+
surface: Surface object
|
|
802
|
+
u, v: Parameter values
|
|
803
|
+
direction: 'u' or 'v'
|
|
804
|
+
step_size: Step size
|
|
805
|
+
|
|
806
|
+
Returns:
|
|
807
|
+
GradientResult object
|
|
808
|
+
"""
|
|
809
|
+
grad_op = IntrinsicGradientOperator(surface, step_size)
|
|
810
|
+
|
|
811
|
+
if direction == 'u':
|
|
812
|
+
return grad_op.compute_u(u, v)
|
|
813
|
+
elif direction == 'v':
|
|
814
|
+
return grad_op.compute_v(u, v)
|
|
815
|
+
else:
|
|
816
|
+
raise ValueError(f"direction must be 'u' or 'v', got: {direction}")
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
# ============================================================
|
|
820
|
+
# Convenience Functions - Classical Method
|
|
821
|
+
# ============================================================
|
|
822
|
+
|
|
823
|
+
def gaussian_curvature_classical(
|
|
824
|
+
surface: Surface,
|
|
825
|
+
u: float,
|
|
826
|
+
v: float,
|
|
827
|
+
step_size: float = 1e-3
|
|
828
|
+
) -> float:
|
|
829
|
+
"""Compute Gaussian curvature using classical method."""
|
|
830
|
+
calc = CurvatureCalculator(surface, step_size)
|
|
831
|
+
return calc.compute_gaussian_curvature(u, v)
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def mean_curvature_classical(
|
|
835
|
+
surface: Surface,
|
|
836
|
+
u: float,
|
|
837
|
+
v: float,
|
|
838
|
+
step_size: float = 1e-3
|
|
839
|
+
) -> float:
|
|
840
|
+
"""Compute mean curvature using classical method."""
|
|
841
|
+
calc = CurvatureCalculator(surface, step_size)
|
|
842
|
+
return calc.compute_mean_curvature(u, v)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def principal_curvatures_classical(
|
|
846
|
+
surface: Surface,
|
|
847
|
+
u: float,
|
|
848
|
+
v: float,
|
|
849
|
+
step_size: float = 1e-3
|
|
850
|
+
) -> Tuple[float, float]:
|
|
851
|
+
"""Compute principal curvatures using classical method."""
|
|
852
|
+
calc = CurvatureCalculator(surface, step_size)
|
|
853
|
+
k1, k2, _, _ = calc.compute_principal_curvatures(u, v)
|
|
854
|
+
return k1, k2
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def all_curvatures_classical(
|
|
858
|
+
surface: Surface,
|
|
859
|
+
u: float,
|
|
860
|
+
v: float,
|
|
861
|
+
step_size: float = 1e-3
|
|
862
|
+
) -> Dict[str, Union[float, np.ndarray]]:
|
|
863
|
+
"""Compute all curvature quantities using classical method."""
|
|
864
|
+
calc = CurvatureCalculator(surface, step_size)
|
|
865
|
+
return calc.compute_all_curvatures(u, v)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
# ============================================================
|
|
869
|
+
# Backward Compatibility Aliases
|
|
870
|
+
# ============================================================
|
|
871
|
+
|
|
872
|
+
def gaussian_curvature(
|
|
873
|
+
surface: Surface,
|
|
874
|
+
u: float,
|
|
875
|
+
v: float,
|
|
876
|
+
step_size: float = 1e-3
|
|
877
|
+
) -> float:
|
|
878
|
+
"""Compute Gaussian curvature (default: intrinsic gradient method)."""
|
|
879
|
+
return compute_gaussian_curvature(surface, u, v, step_size)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def mean_curvature(
|
|
883
|
+
surface: Surface,
|
|
884
|
+
u: float,
|
|
885
|
+
v: float,
|
|
886
|
+
step_size: float = 1e-3
|
|
887
|
+
) -> float:
|
|
888
|
+
"""Compute mean curvature (default: intrinsic gradient method)."""
|
|
889
|
+
return compute_mean_curvature(surface, u, v, step_size)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def principal_curvatures(
|
|
893
|
+
surface: Surface,
|
|
894
|
+
u: float,
|
|
895
|
+
v: float,
|
|
896
|
+
step_size: float = 1e-3
|
|
897
|
+
) -> Tuple[float, float]:
|
|
898
|
+
"""Compute principal curvatures (default: intrinsic gradient method)."""
|
|
899
|
+
calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
|
|
900
|
+
return calc.compute_principal_curvatures(u, v)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def all_curvatures(
|
|
904
|
+
surface: Surface,
|
|
905
|
+
u: float,
|
|
906
|
+
v: float,
|
|
907
|
+
step_size: float = 1e-3
|
|
908
|
+
) -> Dict[str, Union[float, Tuple[float, float]]]:
|
|
909
|
+
"""Compute all curvature quantities (default: intrinsic gradient method)."""
|
|
910
|
+
return compute_all_curvatures(surface, u, v, step_size)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
# ============================================================
|
|
914
|
+
# Method Comparison
|
|
915
|
+
# ============================================================
|
|
916
|
+
|
|
917
|
+
def compare_methods(
|
|
918
|
+
surface: Surface,
|
|
919
|
+
u: float,
|
|
920
|
+
v: float,
|
|
921
|
+
step_size: float = 1e-3
|
|
922
|
+
) -> Dict[str, float]:
|
|
923
|
+
"""
|
|
924
|
+
Compare classical and intrinsic gradient curvature methods.
|
|
925
|
+
|
|
926
|
+
Returns:
|
|
927
|
+
Dictionary with curvature values from both methods and error metrics
|
|
928
|
+
"""
|
|
929
|
+
K_classical = gaussian_curvature_classical(surface, u, v, step_size)
|
|
930
|
+
K_intrinsic = compute_gaussian_curvature(surface, u, v, step_size)
|
|
931
|
+
|
|
932
|
+
difference = abs(K_classical - K_intrinsic)
|
|
933
|
+
relative_error = difference / abs(K_classical) if abs(K_classical) > 1e-14 else 0.0
|
|
934
|
+
|
|
935
|
+
return {
|
|
936
|
+
'K_classical': K_classical,
|
|
937
|
+
'K_intrinsic': K_intrinsic,
|
|
938
|
+
'difference': difference,
|
|
939
|
+
'relative_error': relative_error
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
# ============================================================
|
|
944
|
+
# Export
|
|
945
|
+
# ============================================================
|
|
946
|
+
|
|
947
|
+
__all__ = [
|
|
948
|
+
# Surface classes
|
|
949
|
+
'Surface',
|
|
950
|
+
'Sphere',
|
|
951
|
+
'Torus',
|
|
952
|
+
|
|
953
|
+
# Core classes
|
|
954
|
+
'MetricTensor',
|
|
955
|
+
'GradientResult',
|
|
956
|
+
'IntrinsicGradientOperator',
|
|
957
|
+
'IntrinsicGradientCurvatureCalculator',
|
|
958
|
+
'CurvatureCalculator',
|
|
959
|
+
|
|
960
|
+
# Intrinsic gradient method functions (default)
|
|
961
|
+
'compute_gaussian_curvature',
|
|
962
|
+
'compute_mean_curvature',
|
|
963
|
+
'compute_riemann_curvature',
|
|
964
|
+
'compute_all_curvatures',
|
|
965
|
+
'compute_intrinsic_gradient',
|
|
966
|
+
|
|
967
|
+
# Classical method functions
|
|
968
|
+
'gaussian_curvature_classical',
|
|
969
|
+
'mean_curvature_classical',
|
|
970
|
+
'principal_curvatures_classical',
|
|
971
|
+
'all_curvatures_classical',
|
|
972
|
+
|
|
973
|
+
# Backward compatibility aliases
|
|
974
|
+
'gaussian_curvature',
|
|
975
|
+
'mean_curvature',
|
|
976
|
+
'principal_curvatures',
|
|
977
|
+
'all_curvatures',
|
|
978
|
+
|
|
979
|
+
# Method comparison
|
|
980
|
+
'compare_methods',
|
|
981
|
+
|
|
982
|
+
# Utility functions
|
|
983
|
+
'derivative_5pt',
|
|
984
|
+
'derivative_2nd_5pt',
|
|
985
|
+
'richardson_extrapolation',
|
|
986
|
+
]
|