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.
@@ -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
+ ]