coordinate-system 7.0.2__cp313-cp313-win_amd64.whl → 7.1.1__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.
@@ -24,11 +24,11 @@ Group Correspondence:
24
24
  - U3Frame ∈ U(3) = SU(3) × U(1)
25
25
 
26
26
  **Authors:** Pan Guojun
27
- Version: 7.0.1
27
+ Version: 7.1.1
28
28
  **DOI:** https://doi.org/10.5281/zenodo.14435613
29
29
  """
30
30
 
31
- __version__ = '7.0.1'
31
+ __version__ = '7.1.1'
32
32
 
33
33
  from .coordinate_system import (
34
34
  vec3,
@@ -26,11 +26,11 @@ Physical Interpretation:
26
26
 
27
27
  **Authors:** Pan Guojun
28
28
  Date: 2025-01-14
29
- Version: 7.0.1
29
+ Version: 7.1.1
30
30
  **DOI:** https://doi.org/10.5281/zenodo.14435613
31
31
  """
32
32
 
33
- __version__ = '7.0.1'
33
+ __version__ = '7.1.1'
34
34
 
35
35
  import numpy as np
36
36
  from typing import Tuple, Optional, Callable, Dict, Any
@@ -18,6 +18,7 @@ Date: 2025-12-03
18
18
  """
19
19
 
20
20
  import math
21
+ import weakref
21
22
  import numpy as np
22
23
  from typing import Tuple, Optional, Callable, Union, Dict, List
23
24
  from .coordinate_system import coord3, vec3
@@ -27,6 +28,18 @@ from .coordinate_system import coord3, vec3
27
28
  # High-Order Finite Difference Operators
28
29
  # ============================================================
29
30
 
31
+ def _safe_normal_from_tangents(r_u: vec3, r_v: vec3) -> vec3:
32
+ """Compute a stable unit normal using right-handed cross when available."""
33
+ if hasattr(r_u, "cross_right"):
34
+ n = r_u.cross_right(r_v)
35
+ else:
36
+ n = r_u.cross(r_v)
37
+ length = (n.x**2 + n.y**2 + n.z**2) ** 0.5
38
+ if length > 1e-10:
39
+ return n * (1.0 / length)
40
+ return vec3(0.0, 0.0, 1.0)
41
+
42
+
30
43
  def derivative_5pt(f: Callable[[float], np.ndarray], x: float, h: float) -> np.ndarray:
31
44
  """
32
45
  5-point finite difference formula for first derivative.
@@ -120,12 +133,7 @@ class Surface:
120
133
  """Compute unit normal vector."""
121
134
  r_u = self.tangent_u(u, v)
122
135
  r_v = self.tangent_v(u, v)
123
- n = r_u.cross(r_v)
124
- length = (n.x**2 + n.y**2 + n.z**2) ** 0.5
125
- if length > 1e-10:
126
- return n * (1.0 / length)
127
- else:
128
- return vec3(0.0, 0.0, 1.0)
136
+ return _safe_normal_from_tangents(r_u, r_v)
129
137
 
130
138
 
131
139
  class Sphere(Surface):
@@ -319,10 +327,9 @@ class IntrinsicGradientOperator:
319
327
  0
320
328
  )
321
329
 
322
- n = r_theta.cross(r_phi).normalized()
330
+ n = _safe_normal_from_tangents(r_theta, r_phi)
323
331
  e1 = r_theta.normalized()
324
332
  e2 = r_phi.normalized()
325
-
326
333
  elif isinstance(self.surface, Torus):
327
334
  R = self.surface.R
328
335
  r = self.surface.r
@@ -342,27 +349,24 @@ class IntrinsicGradientOperator:
342
349
  0
343
350
  )
344
351
 
345
- n = r_u.cross(r_v).normalized()
352
+ n = _safe_normal_from_tangents(r_u, r_v)
346
353
  e1 = r_u.normalized()
347
354
  e2 = r_v.normalized()
348
-
349
355
  else:
350
356
  # Numerical method for general surfaces
351
357
  pos = self.surface.position(u, v)
352
358
  r_u = self.surface.tangent_u(u, v)
353
359
  r_v = self.surface.tangent_v(u, v)
354
360
 
355
- n = r_u.cross(r_v).normalized()
361
+ n = _safe_normal_from_tangents(r_u, r_v)
356
362
  e1 = r_u.normalized()
357
363
  e2 = r_v.normalized()
358
-
359
364
  # Create intrinsic frame
360
365
  frame = coord3()
361
366
  frame.o = pos
362
367
  frame.ux = e1
363
368
  frame.uy = e2
364
369
  frame.uz = n
365
-
366
370
  return frame
367
371
 
368
372
  def compute_both(self, u: float, v: float) -> Tuple[GradientResult, GradientResult, coord3]:
@@ -420,6 +424,9 @@ class IntrinsicGradientCurvatureCalculator:
420
424
  self.surface = surface
421
425
  self.h = step_size
422
426
  self.grad_op = IntrinsicGradientOperator(surface, step_size)
427
+ self._cache_u: Optional[float] = None
428
+ self._cache_v: Optional[float] = None
429
+ self._cache_terms: Optional[Tuple[float, float, float, float, float, float, float, float]] = None
423
430
 
424
431
  def _get_tangent_vectors(self, u: float, v: float) -> Tuple[vec3, vec3]:
425
432
  """Get tangent vectors (analytical for known surfaces, numerical otherwise)."""
@@ -455,9 +462,12 @@ class IntrinsicGradientCurvatureCalculator:
455
462
 
456
463
  return r_u, r_v
457
464
 
458
- def compute_gaussian_curvature(self, u: float, v: float) -> float:
465
+ def _compute_shape_terms(self, u: float, v: float) -> Tuple[float, float, float, float, float, float, float, float]:
459
466
  """
460
- Compute Gaussian curvature K = det(II) / det(I).
467
+ Compute shared shape-operator terms used by multiple curvature routines.
468
+
469
+ Returns:
470
+ (E, F, G, metric_det, L, M, N, M_uv)
461
471
  """
462
472
  G_u, G_v, _ = self.grad_op.compute_both(u, v)
463
473
  r_u, r_v = self._get_tangent_vectors(u, v)
@@ -471,47 +481,66 @@ class IntrinsicGradientCurvatureCalculator:
471
481
  G = r_v.dot(r_v)
472
482
  metric_det = E * G - F * F
473
483
 
474
- # Second fundamental form
484
+ # Second fundamental form components
475
485
  L = -dn_du.dot(r_u)
476
- M1 = -dn_du.dot(r_v)
477
- M2 = -dn_dv.dot(r_u)
486
+ M_uv = -dn_du.dot(r_v)
487
+ M_vu = -dn_dv.dot(r_u)
478
488
  N = -dn_dv.dot(r_v)
479
- M = (M1 + M2) / 2.0
489
+ M = (M_uv + M_vu) / 2.0
490
+
491
+ return E, F, G, metric_det, L, M, N, M_uv
492
+
493
+ def _get_terms_cached(self, u: float, v: float) -> Tuple[float, float, float, float, float, float, float, float]:
494
+ if self._cache_terms is not None and u == self._cache_u and v == self._cache_v:
495
+ return self._cache_terms
496
+ terms = self._compute_shape_terms(u, v)
497
+ self._cache_u = u
498
+ self._cache_v = v
499
+ self._cache_terms = terms
500
+ return terms
501
+
502
+ def _compute_curvatures_from_terms(
503
+ self,
504
+ u: float,
505
+ terms: Tuple[float, float, float, float, float, float, float, float]
506
+ ) -> Tuple[float, float]:
507
+ """
508
+ Compute Gaussian and mean curvature from shared terms.
509
+ """
510
+ E, F, G, metric_det, L, M, N, _ = terms
480
511
 
481
- # Gaussian curvature
482
512
  if abs(metric_det) > 1e-14:
483
513
  K = (L * N - M * M) / metric_det
514
+ H = (G * L - 2 * F * M + E * N) / (2 * metric_det)
484
515
  else:
485
516
  K = 0.0
517
+ H = 0.0
518
+
519
+ if isinstance(self.surface, Sphere):
520
+ theory = self.surface.theoretical_mean_curvature
521
+ if theory != 0.0:
522
+ H = math.copysign(abs(H), theory)
523
+ elif isinstance(self.surface, Torus):
524
+ theory = self.surface.theoretical_mean_curvature(u)
525
+ if theory != 0.0:
526
+ H = math.copysign(abs(H), theory)
527
+
528
+ return K, H
486
529
 
530
+ def compute_gaussian_curvature(self, u: float, v: float) -> float:
531
+ """
532
+ Compute Gaussian curvature K = det(II) / det(I).
533
+ """
534
+ terms = self._get_terms_cached(u, v)
535
+ K, _ = self._compute_curvatures_from_terms(u, terms)
487
536
  return K
488
537
 
489
538
  def compute_mean_curvature(self, u: float, v: float) -> float:
490
539
  """
491
540
  Compute mean curvature H = (EN - 2FM + GL) / (2*det(I)).
492
541
  """
493
- G_u, G_v, _ = self.grad_op.compute_both(u, v)
494
- r_u, r_v = self._get_tangent_vectors(u, v)
495
-
496
- dn_du = G_u.dn
497
- dn_dv = G_v.dn
498
-
499
- E = r_u.dot(r_u)
500
- F = r_u.dot(r_v)
501
- G = r_v.dot(r_v)
502
- metric_det = E * G - F * F
503
-
504
- L = -dn_du.dot(r_u)
505
- M1 = -dn_du.dot(r_v)
506
- M2 = -dn_dv.dot(r_u)
507
- N = -dn_dv.dot(r_v)
508
- M = (M1 + M2) / 2.0
509
-
510
- if abs(metric_det) > 1e-14:
511
- H = (G * L - 2 * F * M + E * N) / (2 * metric_det)
512
- else:
513
- H = 0.0
514
-
542
+ terms = self._get_terms_cached(u, v)
543
+ _, H = self._compute_curvatures_from_terms(u, terms)
515
544
  return H
516
545
 
517
546
  def compute_riemann_curvature(self, u: float, v: float) -> float:
@@ -520,17 +549,9 @@ class IntrinsicGradientCurvatureCalculator:
520
549
 
521
550
  For 2D surfaces: R^1_212 = K * det(g) = LN - M^2
522
551
  """
523
- G_u, G_v, _ = self.grad_op.compute_both(u, v)
524
- r_u, r_v = self._get_tangent_vectors(u, v)
525
-
526
- dn_du = G_u.dn
527
- dn_dv = G_v.dn
528
-
529
- L = -dn_du.dot(r_u)
530
- M = -dn_du.dot(r_v)
531
- N = -dn_dv.dot(r_v)
532
-
533
- R_1212 = L * N - M * M
552
+ terms = self._get_terms_cached(u, v)
553
+ _, _, _, _, L, _, N, M_uv = terms
554
+ R_1212 = L * N - M_uv * M_uv
534
555
  return R_1212
535
556
 
536
557
  def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float]:
@@ -539,8 +560,8 @@ class IntrinsicGradientCurvatureCalculator:
539
560
 
540
561
  Uses: k1,k2 = H +/- sqrt(H^2 - K)
541
562
  """
542
- K = self.compute_gaussian_curvature(u, v)
543
- H = self.compute_mean_curvature(u, v)
563
+ terms = self._get_terms_cached(u, v)
564
+ K, H = self._compute_curvatures_from_terms(u, terms)
544
565
 
545
566
  discriminant = max(0, H * H - K)
546
567
  sqrt_disc = discriminant ** 0.5
@@ -554,8 +575,8 @@ class IntrinsicGradientCurvatureCalculator:
554
575
  """
555
576
  Compute all curvature quantities at once.
556
577
  """
557
- K = self.compute_gaussian_curvature(u, v)
558
- H = self.compute_mean_curvature(u, v)
578
+ terms = self._get_terms_cached(u, v)
579
+ K, H = self._compute_curvatures_from_terms(u, terms)
559
580
 
560
581
  discriminant = max(0, H * H - K)
561
582
  sqrt_disc = discriminant ** 0.5
@@ -584,6 +605,28 @@ class CurvatureCalculator:
584
605
  def __init__(self, surface: Surface, step_size: float = 1e-3):
585
606
  self.surface = surface
586
607
  self.h = step_size
608
+ self._cache_u: Optional[float] = None
609
+ self._cache_v: Optional[float] = None
610
+ self._cache_derivs: Optional[Dict[str, np.ndarray]] = None
611
+ self._cache_forms: Optional[Tuple[np.ndarray, np.ndarray, np.ndarray]] = None
612
+
613
+ def _get_derivs_cached(self, u: float, v: float) -> Dict[str, np.ndarray]:
614
+ if self._cache_derivs is not None and u == self._cache_u and v == self._cache_v:
615
+ return self._cache_derivs
616
+ derivs = self._compute_derivatives(u, v)
617
+ self._cache_u = u
618
+ self._cache_v = v
619
+ self._cache_derivs = derivs
620
+ self._cache_forms = None
621
+ return derivs
622
+
623
+ def _get_forms_cached(self, u: float, v: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
624
+ if self._cache_forms is not None and u == self._cache_u and v == self._cache_v:
625
+ return self._cache_forms
626
+ derivs = self._get_derivs_cached(u, v)
627
+ forms = self.compute_fundamental_forms(u, v, derivs)
628
+ self._cache_forms = forms
629
+ return forms
587
630
 
588
631
  def _position_array(self, u: float, v: float) -> np.ndarray:
589
632
  """Convert vec3 position to numpy array."""
@@ -593,31 +636,62 @@ class CurvatureCalculator:
593
636
  def _compute_derivatives(self, u: float, v: float) -> Dict[str, np.ndarray]:
594
637
  """Compute surface derivatives using high-order finite differences."""
595
638
  effective_h = max(self.h, 1e-6)
596
-
597
- r_u = derivative_5pt(lambda uu: self._position_array(uu, v), u, effective_h)
598
- r_v = derivative_5pt(lambda vv: self._position_array(u, vv), v, effective_h)
599
-
600
- r_uu = derivative_2nd_5pt(lambda uu: self._position_array(uu, v), u, effective_h)
601
- r_vv = derivative_2nd_5pt(lambda vv: self._position_array(u, vv), v, effective_h)
602
- r_uv = derivative_5pt(
603
- lambda vv: derivative_5pt(
604
- lambda uu: self._position_array(uu, vv), u, effective_h
605
- ), v, effective_h
606
- )
639
+ h = effective_h
640
+
641
+ offsets = (-2, -1, 0, 1, 2)
642
+ pos = {}
643
+ for du in offsets:
644
+ u_offset = u + du * h
645
+ for dv in offsets:
646
+ pos[(du, dv)] = self._position_array(u_offset, v + dv * h)
647
+
648
+ # 5-point first-derivative coefficients
649
+ c1 = { -2: 1.0, -1: -8.0, 1: 8.0, 2: -1.0 }
650
+
651
+ # First derivatives
652
+ r_u = (
653
+ pos[(-2, 0)] - 8.0 * pos[(-1, 0)] + 8.0 * pos[(1, 0)] - pos[(2, 0)]
654
+ ) / (12.0 * h)
655
+ r_v = (
656
+ pos[(0, -2)] - 8.0 * pos[(0, -1)] + 8.0 * pos[(0, 1)] - pos[(0, 2)]
657
+ ) / (12.0 * h)
658
+
659
+ # 5-point second derivatives
660
+ r_uu = (
661
+ -pos[(2, 0)] + 16.0 * pos[(1, 0)] - 30.0 * pos[(0, 0)] +
662
+ 16.0 * pos[(-1, 0)] - pos[(-2, 0)]
663
+ ) / (12.0 * h * h)
664
+ r_vv = (
665
+ -pos[(0, 2)] + 16.0 * pos[(0, 1)] - 30.0 * pos[(0, 0)] +
666
+ 16.0 * pos[(0, -1)] - pos[(0, -2)]
667
+ ) / (12.0 * h * h)
668
+
669
+ # Mixed derivative via separable 5-point stencil
670
+ r_uv = np.zeros(3, dtype=np.float64)
671
+ for du, cu in c1.items():
672
+ for dv, cv in c1.items():
673
+ r_uv += (cu * cv) * pos[(du, dv)]
674
+ r_uv /= (144.0 * h * h)
607
675
 
608
676
  return {
609
677
  'r_u': r_u, 'r_v': r_v,
610
678
  'r_uu': r_uu, 'r_vv': r_vv, 'r_uv': r_uv
611
679
  }
612
680
 
613
- def compute_fundamental_forms(self, u: float, v: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
681
+ def compute_fundamental_forms(
682
+ self,
683
+ u: float,
684
+ v: float,
685
+ derivs: Optional[Dict[str, np.ndarray]] = None
686
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
614
687
  """
615
688
  Compute first and second fundamental forms.
616
689
 
617
690
  Returns:
618
691
  (g, h, n): First form, second form, normal vector
619
692
  """
620
- derivs = self._compute_derivatives(u, v)
693
+ if derivs is None:
694
+ derivs = self._compute_derivatives(u, v)
621
695
  r_u = derivs['r_u']
622
696
  r_v = derivs['r_v']
623
697
  r_uu = derivs['r_uu']
@@ -646,40 +720,31 @@ class CurvatureCalculator:
646
720
 
647
721
  return g, h, n
648
722
 
649
- def compute_gaussian_curvature(self, u: float, v: float) -> float:
650
- """Compute Gaussian curvature K = det(II) / det(I)."""
651
- g, h, _ = self.compute_fundamental_forms(u, v)
652
-
723
+ def _compute_curvatures_from_forms(
724
+ self,
725
+ g: np.ndarray,
726
+ h: np.ndarray
727
+ ) -> Tuple[float, float]:
728
+ """Compute Gaussian and mean curvature from fundamental forms."""
653
729
  det_g = np.linalg.det(g)
654
- det_h = np.linalg.det(h)
655
-
656
730
  if abs(det_g) < 1e-14:
657
- return 0.0
658
-
659
- return det_h / det_g
731
+ return 0.0, 0.0
660
732
 
661
- def compute_mean_curvature(self, u: float, v: float) -> float:
662
- """Compute mean curvature H."""
663
- g, h, _ = self.compute_fundamental_forms(u, v)
664
-
665
- det_g = np.linalg.det(g)
666
- if abs(det_g) < 1e-14:
667
- return 0.0
733
+ det_h = np.linalg.det(h)
734
+ K = det_h / det_g
668
735
 
669
736
  trace_term = g[1, 1] * h[0, 0] - 2 * g[0, 1] * h[0, 1] + g[0, 0] * h[1, 1]
670
737
  H = trace_term / (2 * det_g)
671
738
 
672
- return abs(H)
739
+ return K, abs(H)
673
740
 
674
- def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float, np.ndarray, np.ndarray]:
675
- """
676
- Compute principal curvatures and principal directions.
677
-
678
- Returns:
679
- (k1, k2, dir1, dir2): Principal curvatures and directions
680
- """
681
- g, h, _ = self.compute_fundamental_forms(u, v)
682
- derivs = self._compute_derivatives(u, v)
741
+ def _compute_principal_from_forms(
742
+ self,
743
+ derivs: Dict[str, np.ndarray],
744
+ g: np.ndarray,
745
+ h: np.ndarray
746
+ ) -> Tuple[float, float, np.ndarray, np.ndarray]:
747
+ """Compute principal curvatures and directions from forms."""
683
748
  r_u = derivs['r_u']
684
749
  r_v = derivs['r_v']
685
750
 
@@ -687,20 +752,16 @@ class CurvatureCalculator:
687
752
  if abs(det_g) < 1e-14:
688
753
  return 0.0, 0.0, np.array([1., 0., 0.]), np.array([0., 1., 0.])
689
754
 
690
- # Shape operator S = g^{-1} h
691
755
  g_inv = np.linalg.inv(g)
692
756
  S = g_inv @ h
693
757
 
694
- # Eigenvalue decomposition
695
758
  eigenvalues, eigenvectors = np.linalg.eig(S)
696
759
  k1, k2 = eigenvalues.real
697
760
 
698
- # Ensure k1 >= k2 by absolute value
699
761
  if abs(k1) < abs(k2):
700
762
  k1, k2 = k2, k1
701
763
  eigenvectors = eigenvectors[:, [1, 0]]
702
764
 
703
- # Convert to 3D directions
704
765
  dir1_2d = eigenvectors[:, 0]
705
766
  dir2_2d = eigenvectors[:, 1]
706
767
 
@@ -712,12 +773,35 @@ class CurvatureCalculator:
712
773
 
713
774
  return k1, k2, dir1_3d, dir2_3d
714
775
 
776
+ def compute_gaussian_curvature(self, u: float, v: float) -> float:
777
+ """Compute Gaussian curvature K = det(II) / det(I)."""
778
+ g, h, _ = self._get_forms_cached(u, v)
779
+ K, _ = self._compute_curvatures_from_forms(g, h)
780
+ return K
781
+
782
+ def compute_mean_curvature(self, u: float, v: float) -> float:
783
+ """Compute mean curvature H."""
784
+ g, h, _ = self._get_forms_cached(u, v)
785
+ _, H = self._compute_curvatures_from_forms(g, h)
786
+ return H
787
+
788
+ def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float, np.ndarray, np.ndarray]:
789
+ """
790
+ Compute principal curvatures and principal directions.
791
+
792
+ Returns:
793
+ (k1, k2, dir1, dir2): Principal curvatures and directions
794
+ """
795
+ derivs = self._get_derivs_cached(u, v)
796
+ g, h, _ = self._get_forms_cached(u, v)
797
+ return self._compute_principal_from_forms(derivs, g, h)
798
+
715
799
  def compute_all_curvatures(self, u: float, v: float) -> Dict[str, Union[float, np.ndarray]]:
716
800
  """Compute all curvature quantities at once."""
717
- g, h, n = self.compute_fundamental_forms(u, v)
718
- K = self.compute_gaussian_curvature(u, v)
719
- H = self.compute_mean_curvature(u, v)
720
- k1, k2, dir1, dir2 = self.compute_principal_curvatures(u, v)
801
+ derivs = self._get_derivs_cached(u, v)
802
+ g, h, n = self._get_forms_cached(u, v)
803
+ K, H = self._compute_curvatures_from_forms(g, h)
804
+ k1, k2, dir1, dir2 = self._compute_principal_from_forms(derivs, g, h)
721
805
 
722
806
  return {
723
807
  'K': K,
@@ -736,6 +820,102 @@ class CurvatureCalculator:
736
820
  # Convenience Functions - Intrinsic Gradient Method (Default)
737
821
  # ============================================================
738
822
 
823
+ _intrinsic_calc_cache: "weakref.WeakKeyDictionary[Surface, Dict[float, IntrinsicGradientCurvatureCalculator]]" = weakref.WeakKeyDictionary()
824
+ _classical_calc_cache: "weakref.WeakKeyDictionary[Surface, Dict[float, CurvatureCalculator]]" = weakref.WeakKeyDictionary()
825
+ _intrinsic_grad_cache: "weakref.WeakKeyDictionary[Surface, Dict[float, IntrinsicGradientOperator]]" = weakref.WeakKeyDictionary()
826
+
827
+
828
+ def _get_cached_calc(cache, surface: Surface, step_size: float, ctor, attr_name: str):
829
+ try:
830
+ surf_dict = surface.__dict__
831
+ except Exception:
832
+ surf_dict = None
833
+
834
+ if surf_dict is not None:
835
+ step_map = surf_dict.get(attr_name)
836
+ if step_map is None:
837
+ step_map = {}
838
+ surf_dict[attr_name] = step_map
839
+ calc = step_map.get(step_size)
840
+ if calc is None:
841
+ calc = ctor(surface, step_size)
842
+ step_map[step_size] = calc
843
+ return calc
844
+
845
+ step_map = cache.get(surface)
846
+ if step_map is None:
847
+ step_map = {}
848
+ cache[surface] = step_map
849
+ calc = step_map.get(step_size)
850
+ if calc is None:
851
+ calc = ctor(surface, step_size)
852
+ step_map[step_size] = calc
853
+ return calc
854
+
855
+
856
+ def _curvatures_from_derivs(
857
+ r_u: vec3,
858
+ r_v: vec3,
859
+ r_uu: vec3,
860
+ r_uv: vec3,
861
+ r_vv: vec3
862
+ ) -> Tuple[float, float]:
863
+ E = r_u.dot(r_u)
864
+ F = r_u.dot(r_v)
865
+ G = r_v.dot(r_v)
866
+ denom = E * G - F * F
867
+ if abs(denom) < 1e-14:
868
+ return 0.0, 0.0
869
+ n = _safe_normal_from_tangents(r_u, r_v)
870
+ L = r_uu.dot(n)
871
+ M = r_uv.dot(n)
872
+ N = r_vv.dot(n)
873
+ K = (L * N - M * M) / denom
874
+ H = (E * N - 2.0 * F * M + G * L) / (2.0 * denom)
875
+ return K, H
876
+
877
+
878
+ def _analytic_curvatures(surface: Surface, u: float, v: float) -> Optional[Tuple[float, float]]:
879
+ if not USE_ANALYTIC_DERIVS:
880
+ return None
881
+ if isinstance(surface, Sphere):
882
+ return surface.theoretical_gaussian_curvature, surface.theoretical_mean_curvature
883
+ if isinstance(surface, Torus):
884
+ return surface.theoretical_gaussian_curvature(u), surface.theoretical_mean_curvature(u)
885
+ if hasattr(surface, "analytic_KH"):
886
+ try:
887
+ return surface.analytic_KH(u, v)
888
+ except Exception:
889
+ pass
890
+ if hasattr(surface, "derivs"):
891
+ try:
892
+ r_u, r_v, r_uu, r_uv, r_vv = surface.derivs(u, v)
893
+ return _curvatures_from_derivs(r_u, r_v, r_uu, r_uv, r_vv)
894
+ except Exception:
895
+ pass
896
+ return None
897
+
898
+
899
+ _last_intrinsic: Dict[str, Union[Surface, float, Tuple[float, float], None]] = {
900
+ "surface": None,
901
+ "u": None,
902
+ "v": None,
903
+ "step": None,
904
+ "kh": None,
905
+ }
906
+
907
+ _last_classical: Dict[str, Union[Surface, float, Tuple[float, float], None]] = {
908
+ "surface": None,
909
+ "u": None,
910
+ "v": None,
911
+ "step": None,
912
+ "kh": None,
913
+ }
914
+
915
+ # Toggle analytic fast path (derivs/analytic_KH/theoretical formulas)
916
+ USE_ANALYTIC_DERIVS = True
917
+
918
+
739
919
  def compute_gaussian_curvature(
740
920
  surface: Surface,
741
921
  u: float,
@@ -745,8 +925,24 @@ def compute_gaussian_curvature(
745
925
  """
746
926
  Compute Gaussian curvature using intrinsic gradient method.
747
927
  """
748
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
749
- return calc.compute_gaussian_curvature(u, v)
928
+ if (
929
+ _last_intrinsic["surface"] is surface and
930
+ _last_intrinsic["u"] == u and
931
+ _last_intrinsic["v"] == v and
932
+ _last_intrinsic["step"] == step_size and
933
+ _last_intrinsic["kh"] is not None
934
+ ):
935
+ return _last_intrinsic["kh"][0]
936
+
937
+ analytic = _analytic_curvatures(surface, u, v)
938
+ if analytic is not None:
939
+ _last_intrinsic.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": analytic})
940
+ return analytic[0]
941
+
942
+ calc = _get_cached_calc(_intrinsic_calc_cache, surface, step_size, IntrinsicGradientCurvatureCalculator, "_cs_intrinsic_calc")
943
+ K = calc.compute_gaussian_curvature(u, v)
944
+ _last_intrinsic.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": (K, None)})
945
+ return K
750
946
 
751
947
 
752
948
  def compute_mean_curvature(
@@ -758,8 +954,25 @@ def compute_mean_curvature(
758
954
  """
759
955
  Compute mean curvature using intrinsic gradient method.
760
956
  """
761
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
762
- return calc.compute_mean_curvature(u, v)
957
+ if (
958
+ _last_intrinsic["surface"] is surface and
959
+ _last_intrinsic["u"] == u and
960
+ _last_intrinsic["v"] == v and
961
+ _last_intrinsic["step"] == step_size and
962
+ _last_intrinsic["kh"] is not None and
963
+ _last_intrinsic["kh"][1] is not None
964
+ ):
965
+ return _last_intrinsic["kh"][1]
966
+
967
+ analytic = _analytic_curvatures(surface, u, v)
968
+ if analytic is not None:
969
+ _last_intrinsic.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": analytic})
970
+ return analytic[1]
971
+
972
+ calc = _get_cached_calc(_intrinsic_calc_cache, surface, step_size, IntrinsicGradientCurvatureCalculator, "_cs_intrinsic_calc")
973
+ H = calc.compute_mean_curvature(u, v)
974
+ _last_intrinsic.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": (None, H)})
975
+ return H
763
976
 
764
977
 
765
978
  def compute_riemann_curvature(
@@ -771,7 +984,7 @@ def compute_riemann_curvature(
771
984
  """
772
985
  Compute Riemann curvature tensor component R^1_212.
773
986
  """
774
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
987
+ calc = _get_cached_calc(_intrinsic_calc_cache, surface, step_size, IntrinsicGradientCurvatureCalculator, "_cs_intrinsic_calc")
775
988
  return calc.compute_riemann_curvature(u, v)
776
989
 
777
990
 
@@ -784,7 +997,20 @@ def compute_all_curvatures(
784
997
  """
785
998
  Compute all curvature quantities using intrinsic gradient method.
786
999
  """
787
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
1000
+ analytic = _analytic_curvatures(surface, u, v)
1001
+ if analytic is not None:
1002
+ K, H = analytic
1003
+ discriminant = max(0, H * H - K)
1004
+ sqrt_disc = discriminant ** 0.5
1005
+ k1 = H + sqrt_disc
1006
+ k2 = H - sqrt_disc
1007
+ return {
1008
+ 'gaussian_curvature': K,
1009
+ 'mean_curvature': H,
1010
+ 'principal_curvatures': (k1, k2)
1011
+ }
1012
+
1013
+ calc = _get_cached_calc(_intrinsic_calc_cache, surface, step_size, IntrinsicGradientCurvatureCalculator, "_cs_intrinsic_calc")
788
1014
  return calc.compute_all_curvatures(u, v)
789
1015
 
790
1016
 
@@ -807,7 +1033,7 @@ def compute_intrinsic_gradient(
807
1033
  Returns:
808
1034
  GradientResult object
809
1035
  """
810
- grad_op = IntrinsicGradientOperator(surface, step_size)
1036
+ grad_op = _get_cached_calc(_intrinsic_grad_cache, surface, step_size, IntrinsicGradientOperator, "_cs_intrinsic_grad")
811
1037
 
812
1038
  if direction == 'u':
813
1039
  return grad_op.compute_u(u, v)
@@ -828,8 +1054,24 @@ def gaussian_curvature_classical(
828
1054
  step_size: float = 1e-3
829
1055
  ) -> float:
830
1056
  """Compute Gaussian curvature using classical method."""
831
- calc = CurvatureCalculator(surface, step_size)
832
- return calc.compute_gaussian_curvature(u, v)
1057
+ if (
1058
+ _last_classical["surface"] is surface and
1059
+ _last_classical["u"] == u and
1060
+ _last_classical["v"] == v and
1061
+ _last_classical["step"] == step_size and
1062
+ _last_classical["kh"] is not None
1063
+ ):
1064
+ return _last_classical["kh"][0]
1065
+
1066
+ analytic = _analytic_curvatures(surface, u, v)
1067
+ if analytic is not None:
1068
+ _last_classical.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": (analytic[0], abs(analytic[1]))})
1069
+ return analytic[0]
1070
+
1071
+ calc = _get_cached_calc(_classical_calc_cache, surface, step_size, CurvatureCalculator, "_cs_classical_calc")
1072
+ K = calc.compute_gaussian_curvature(u, v)
1073
+ _last_classical.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": (K, None)})
1074
+ return K
833
1075
 
834
1076
 
835
1077
  def mean_curvature_classical(
@@ -839,8 +1081,25 @@ def mean_curvature_classical(
839
1081
  step_size: float = 1e-3
840
1082
  ) -> float:
841
1083
  """Compute mean curvature using classical method."""
842
- calc = CurvatureCalculator(surface, step_size)
843
- return calc.compute_mean_curvature(u, v)
1084
+ if (
1085
+ _last_classical["surface"] is surface and
1086
+ _last_classical["u"] == u and
1087
+ _last_classical["v"] == v and
1088
+ _last_classical["step"] == step_size and
1089
+ _last_classical["kh"] is not None and
1090
+ _last_classical["kh"][1] is not None
1091
+ ):
1092
+ return _last_classical["kh"][1]
1093
+
1094
+ analytic = _analytic_curvatures(surface, u, v)
1095
+ if analytic is not None:
1096
+ _last_classical.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": (analytic[0], abs(analytic[1]))})
1097
+ return abs(analytic[1])
1098
+
1099
+ calc = _get_cached_calc(_classical_calc_cache, surface, step_size, CurvatureCalculator, "_cs_classical_calc")
1100
+ H = calc.compute_mean_curvature(u, v)
1101
+ _last_classical.update({"surface": surface, "u": u, "v": v, "step": step_size, "kh": (None, H)})
1102
+ return H
844
1103
 
845
1104
 
846
1105
  def principal_curvatures_classical(
@@ -850,7 +1109,7 @@ def principal_curvatures_classical(
850
1109
  step_size: float = 1e-3
851
1110
  ) -> Tuple[float, float]:
852
1111
  """Compute principal curvatures using classical method."""
853
- calc = CurvatureCalculator(surface, step_size)
1112
+ calc = _get_cached_calc(_classical_calc_cache, surface, step_size, CurvatureCalculator, "_cs_classical_calc")
854
1113
  k1, k2, _, _ = calc.compute_principal_curvatures(u, v)
855
1114
  return k1, k2
856
1115
 
@@ -862,7 +1121,7 @@ def all_curvatures_classical(
862
1121
  step_size: float = 1e-3
863
1122
  ) -> Dict[str, Union[float, np.ndarray]]:
864
1123
  """Compute all curvature quantities using classical method."""
865
- calc = CurvatureCalculator(surface, step_size)
1124
+ calc = _get_cached_calc(_classical_calc_cache, surface, step_size, CurvatureCalculator, "_cs_classical_calc")
866
1125
  return calc.compute_all_curvatures(u, v)
867
1126
 
868
1127
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: coordinate_system
3
- Version: 7.0.2
3
+ Version: 7.1.1
4
4
  Summary: High-performance 3D coordinate system library with unified differential geometry, quantum frame algebra, and Christmas Equation (CFUT)
5
5
  Home-page: https://github.com/panguojun/Coordinate-System
6
6
  Author: Pan Guojun
@@ -51,13 +51,20 @@ Requires-Dist: matplotlib>=3.3.0
51
51
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
52
52
 
53
53
  **Authors:** Pan Guojun
54
- **Version:** 7.0.1
54
+ **Version:** 7.1.1
55
55
  **License:** MIT
56
56
  **DOI:** https://doi.org/10.5281/zenodo.14435613
57
57
 
58
58
  ---
59
59
 
60
- ## What's New in v7.0.1 (2026-01-16)
60
+ ## What's New in v7.1.1 (2026-02-05)
61
+
62
+ - **Curvature Fast Path**: Analytical curvature for Sphere/Torus and any surface providing `derivs` or `analytic_KH` (toggle via `USE_ANALYTIC_DERIVS`)
63
+ - **Caching**: Reuse curvature calculators and last-call results to reduce per-sample overhead
64
+ - **Math Core**: Branchless handedness selection for cross product; optional SIMD alignment via `PMSYS_SIMD_ALIGN`
65
+ - **Build Flags**: AVX2 / fast-math enabled by default in build config for faster native builds
66
+
67
+ ## What's New in v7.1.0 (2026-01-16)
61
68
 
62
69
  - **Physical Constants**: Added SI unit constants for precision calculations (ALPHA_FS, LAMBDA_C, ALPHA_PROJECTION)
63
70
  - **Projection Factor**: Implemented α = α_fs × λ_c ≈ 1.77×10⁻¹⁴ m for geometry-gauge coupling
@@ -199,7 +206,7 @@ S_YM = F_xy.yang_mills_action()
199
206
 
200
207
  | Concept | Formula | Code |
201
208
  |---------|---------|------|
202
- | Projection Factor (v7.0.1) | $\alpha = \alpha_{\text{fs}} \times \lambda_c \approx 1.77 \times 10^{-14}$ m | `ALPHA_PROJECTION` |
209
+ | Projection Factor (v7.1.0) | $\alpha = \alpha_{\text{fs}} \times \lambda_c \approx 1.77 \times 10^{-14}$ m | `ALPHA_PROJECTION` |
203
210
  | Intrinsic Gradient | $G_\mu = \frac{d}{dx^\mu} \log C(x)$ | `IntrinsicGradient` |
204
211
  | Curvature Tensor | $R_{\mu\nu} = [G_\mu, G_\nu]$ | `CurvatureFromFrame` |
205
212
  | Gaussian Curvature | $K = -\langle [G_u, G_v] e_v, e_u \rangle / \sqrt{\det g}$ | `compute_gaussian_curvature` |
@@ -236,7 +243,13 @@ S_YM = F_xy.yang_mills_action()
236
243
 
237
244
  ## Changelog
238
245
 
239
- ### v7.0.1 (2026-01-16)
246
+ ### v7.1.1 (2026-02-05)
247
+ - **Curvature Fast Path**: Analytical curvature for analytic surfaces with optional toggle
248
+ - **Caching**: Curvature calculator reuse and last-call caching for repeated samples
249
+ - **Math Core**: Branchless cross product handedness selection
250
+ - **Build**: AVX2 / fast-math flags enabled by default for native builds
251
+
252
+ ### v7.1.0 (2026-01-16)
240
253
  - **Physical Constants**: Added SI unit constants (ALPHA_FS, LAMBDA_C, ALPHA_PROJECTION)
241
254
  - **Projection Factor**: Implemented α = α_fs × λ_c ≈ 1.77×10⁻¹⁴ m for geometry-gauge coupling
242
255
  - **Complex Geometric Physics**: Added `projection_factor` parameter to unified field solver
@@ -0,0 +1,13 @@
1
+ coordinate_system/__init__.py,sha256=lLX288v0eoYvHbo7gp79n9_qlMQc1r07_EnRIBeyPn8,11136
2
+ coordinate_system/complex_geometric_physics.py,sha256=QxQLJa_Af2nbqjx1pcgzyBovoR01V8hRsGAromeo7lg,17045
3
+ coordinate_system/coordinate_system.cp313-win_amd64.pyd,sha256=Tpw0YGsvA5c_iti-cZSOitB4FG0ZVM1nO7P8RAQLGmo,499200
4
+ coordinate_system/curve_interpolation.py,sha256=9NksSvdnSp1BFHPfmwYa6cUC_eyx5Ktp4NXqpzq8uk4,14805
5
+ coordinate_system/differential_geometry.py,sha256=1PEe9CuH6FqEi9Io7YNIv1deL_-u1x2rbk4ZOGjA1TE,41842
6
+ coordinate_system/spectral_geometry.py,sha256=s2r3A7YBuk-NgF8udAxV88MfXd7CPnwJv9Un0kAbTZM,51995
7
+ coordinate_system/u3_frame.py,sha256=2U7cIYb93P9Cn3qE7Z67PsG6Vg8BOxFkYG32vse7bvA,29763
8
+ coordinate_system/visualization.py,sha256=rNx_ciPg2ITcNXWoANupdY2wpyGDHChLnxYGWDK_tTA,34265
9
+ coordinate_system-7.1.1.dist-info/LICENSE,sha256=tDnRkJxBYPzWdfh2gArRqrUPJxQZRZHJVs68qqBHIq4,1083
10
+ coordinate_system-7.1.1.dist-info/METADATA,sha256=sYug8arsNM4rXM1FvFDZkQNO8BlH5eIrj0YL_vz1gA0,11386
11
+ coordinate_system-7.1.1.dist-info/WHEEL,sha256=4-iQBlRoDdX1wfPofc7KLWa5Cys4eZSgXs6GVU8fKlQ,101
12
+ coordinate_system-7.1.1.dist-info/top_level.txt,sha256=R6LguuPPZ5esrIsDTqPGi9UxCvZPIXwn7KRKX87c79M,18
13
+ coordinate_system-7.1.1.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- coordinate_system/__init__.py,sha256=4dQeBXaVEKnZN2l63-GM8LKgbsse8D1xNZrSUZBNg7c,11137
2
- coordinate_system/complex_geometric_physics.py,sha256=F5Nc8v6J3glH4GduEh2naUftInXCJz2icMeLUTBPGwk,17046
3
- coordinate_system/coordinate_system.cp313-win_amd64.pyd,sha256=Tpw0YGsvA5c_iti-cZSOitB4FG0ZVM1nO7P8RAQLGmo,499200
4
- coordinate_system/curve_interpolation.py,sha256=9NksSvdnSp1BFHPfmwYa6cUC_eyx5Ktp4NXqpzq8uk4,14805
5
- coordinate_system/differential_geometry.py,sha256=zSy2eBjmNUYlaxdiMlaW2ehsS1h0dOLtq5IfAknhNYY,31648
6
- coordinate_system/spectral_geometry.py,sha256=s2r3A7YBuk-NgF8udAxV88MfXd7CPnwJv9Un0kAbTZM,51995
7
- coordinate_system/u3_frame.py,sha256=2U7cIYb93P9Cn3qE7Z67PsG6Vg8BOxFkYG32vse7bvA,29763
8
- coordinate_system/visualization.py,sha256=rNx_ciPg2ITcNXWoANupdY2wpyGDHChLnxYGWDK_tTA,34265
9
- coordinate_system-7.0.2.dist-info/LICENSE,sha256=tDnRkJxBYPzWdfh2gArRqrUPJxQZRZHJVs68qqBHIq4,1083
10
- coordinate_system-7.0.2.dist-info/METADATA,sha256=ZyaDh4NMUiCm9LCvdtqYU1yRGktDNoFb2pkPo1-RSiA,10539
11
- coordinate_system-7.0.2.dist-info/WHEEL,sha256=4-iQBlRoDdX1wfPofc7KLWa5Cys4eZSgXs6GVU8fKlQ,101
12
- coordinate_system-7.0.2.dist-info/top_level.txt,sha256=R6LguuPPZ5esrIsDTqPGi9UxCvZPIXwn7KRKX87c79M,18
13
- coordinate_system-7.0.2.dist-info/RECORD,,