coordinate-system 7.1.0__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.1.0
27
+ Version: 7.1.1
28
28
  **DOI:** https://doi.org/10.5281/zenodo.14435613
29
29
  """
30
30
 
31
- __version__ = '7.1.0'
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.1.0
29
+ Version: 7.1.1
30
30
  **DOI:** https://doi.org/10.5281/zenodo.14435613
31
31
  """
32
32
 
33
- __version__ = '7.1.0'
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
@@ -38,6 +39,7 @@ def _safe_normal_from_tangents(r_u: vec3, r_v: vec3) -> vec3:
38
39
  return n * (1.0 / length)
39
40
  return vec3(0.0, 0.0, 1.0)
40
41
 
42
+
41
43
  def derivative_5pt(f: Callable[[float], np.ndarray], x: float, h: float) -> np.ndarray:
42
44
  """
43
45
  5-point finite difference formula for first derivative.
@@ -328,7 +330,6 @@ class IntrinsicGradientOperator:
328
330
  n = _safe_normal_from_tangents(r_theta, r_phi)
329
331
  e1 = r_theta.normalized()
330
332
  e2 = r_phi.normalized()
331
-
332
333
  elif isinstance(self.surface, Torus):
333
334
  R = self.surface.R
334
335
  r = self.surface.r
@@ -351,7 +352,6 @@ class IntrinsicGradientOperator:
351
352
  n = _safe_normal_from_tangents(r_u, r_v)
352
353
  e1 = r_u.normalized()
353
354
  e2 = r_v.normalized()
354
-
355
355
  else:
356
356
  # Numerical method for general surfaces
357
357
  pos = self.surface.position(u, v)
@@ -361,14 +361,12 @@ class IntrinsicGradientOperator:
361
361
  n = _safe_normal_from_tangents(r_u, r_v)
362
362
  e1 = r_u.normalized()
363
363
  e2 = r_v.normalized()
364
-
365
364
  # Create intrinsic frame
366
365
  frame = coord3()
367
366
  frame.o = pos
368
367
  frame.ux = e1
369
368
  frame.uy = e2
370
369
  frame.uz = n
371
-
372
370
  return frame
373
371
 
374
372
  def compute_both(self, u: float, v: float) -> Tuple[GradientResult, GradientResult, coord3]:
@@ -426,6 +424,9 @@ class IntrinsicGradientCurvatureCalculator:
426
424
  self.surface = surface
427
425
  self.h = step_size
428
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
429
430
 
430
431
  def _get_tangent_vectors(self, u: float, v: float) -> Tuple[vec3, vec3]:
431
432
  """Get tangent vectors (analytical for known surfaces, numerical otherwise)."""
@@ -461,9 +462,12 @@ class IntrinsicGradientCurvatureCalculator:
461
462
 
462
463
  return r_u, r_v
463
464
 
464
- 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]:
465
466
  """
466
- 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)
467
471
  """
468
472
  G_u, G_v, _ = self.grad_op.compute_both(u, v)
469
473
  r_u, r_v = self._get_tangent_vectors(u, v)
@@ -477,45 +481,39 @@ class IntrinsicGradientCurvatureCalculator:
477
481
  G = r_v.dot(r_v)
478
482
  metric_det = E * G - F * F
479
483
 
480
- # Second fundamental form
484
+ # Second fundamental form components
481
485
  L = -dn_du.dot(r_u)
482
- M1 = -dn_du.dot(r_v)
483
- M2 = -dn_dv.dot(r_u)
486
+ M_uv = -dn_du.dot(r_v)
487
+ M_vu = -dn_dv.dot(r_u)
484
488
  N = -dn_dv.dot(r_v)
485
- M = (M1 + M2) / 2.0
486
-
487
- # Gaussian curvature
488
- if abs(metric_det) > 1e-14:
489
- K = (L * N - M * M) / metric_det
490
- else:
491
- K = 0.0
492
-
493
- return K
494
-
495
- def compute_mean_curvature(self, u: float, v: float) -> float:
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]:
496
507
  """
497
- Compute mean curvature H = (EN - 2FM + GL) / (2*det(I)).
508
+ Compute Gaussian and mean curvature from shared terms.
498
509
  """
499
- G_u, G_v, _ = self.grad_op.compute_both(u, v)
500
- r_u, r_v = self._get_tangent_vectors(u, v)
501
-
502
- dn_du = G_u.dn
503
- dn_dv = G_v.dn
504
-
505
- E = r_u.dot(r_u)
506
- F = r_u.dot(r_v)
507
- G = r_v.dot(r_v)
508
- metric_det = E * G - F * F
509
-
510
- L = -dn_du.dot(r_u)
511
- M1 = -dn_du.dot(r_v)
512
- M2 = -dn_dv.dot(r_u)
513
- N = -dn_dv.dot(r_v)
514
- M = (M1 + M2) / 2.0
510
+ E, F, G, metric_det, L, M, N, _ = terms
515
511
 
516
512
  if abs(metric_det) > 1e-14:
513
+ K = (L * N - M * M) / metric_det
517
514
  H = (G * L - 2 * F * M + E * N) / (2 * metric_det)
518
515
  else:
516
+ K = 0.0
519
517
  H = 0.0
520
518
 
521
519
  if isinstance(self.surface, Sphere):
@@ -527,6 +525,22 @@ class IntrinsicGradientCurvatureCalculator:
527
525
  if theory != 0.0:
528
526
  H = math.copysign(abs(H), theory)
529
527
 
528
+ return K, H
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)
536
+ return K
537
+
538
+ def compute_mean_curvature(self, u: float, v: float) -> float:
539
+ """
540
+ Compute mean curvature H = (EN - 2FM + GL) / (2*det(I)).
541
+ """
542
+ terms = self._get_terms_cached(u, v)
543
+ _, H = self._compute_curvatures_from_terms(u, terms)
530
544
  return H
531
545
 
532
546
  def compute_riemann_curvature(self, u: float, v: float) -> float:
@@ -535,17 +549,9 @@ class IntrinsicGradientCurvatureCalculator:
535
549
 
536
550
  For 2D surfaces: R^1_212 = K * det(g) = LN - M^2
537
551
  """
538
- G_u, G_v, _ = self.grad_op.compute_both(u, v)
539
- r_u, r_v = self._get_tangent_vectors(u, v)
540
-
541
- dn_du = G_u.dn
542
- dn_dv = G_v.dn
543
-
544
- L = -dn_du.dot(r_u)
545
- M = -dn_du.dot(r_v)
546
- N = -dn_dv.dot(r_v)
547
-
548
- 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
549
555
  return R_1212
550
556
 
551
557
  def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float]:
@@ -554,8 +560,8 @@ class IntrinsicGradientCurvatureCalculator:
554
560
 
555
561
  Uses: k1,k2 = H +/- sqrt(H^2 - K)
556
562
  """
557
- K = self.compute_gaussian_curvature(u, v)
558
- 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)
559
565
 
560
566
  discriminant = max(0, H * H - K)
561
567
  sqrt_disc = discriminant ** 0.5
@@ -569,8 +575,8 @@ class IntrinsicGradientCurvatureCalculator:
569
575
  """
570
576
  Compute all curvature quantities at once.
571
577
  """
572
- K = self.compute_gaussian_curvature(u, v)
573
- 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)
574
580
 
575
581
  discriminant = max(0, H * H - K)
576
582
  sqrt_disc = discriminant ** 0.5
@@ -599,6 +605,28 @@ class CurvatureCalculator:
599
605
  def __init__(self, surface: Surface, step_size: float = 1e-3):
600
606
  self.surface = surface
601
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
602
630
 
603
631
  def _position_array(self, u: float, v: float) -> np.ndarray:
604
632
  """Convert vec3 position to numpy array."""
@@ -608,31 +636,62 @@ class CurvatureCalculator:
608
636
  def _compute_derivatives(self, u: float, v: float) -> Dict[str, np.ndarray]:
609
637
  """Compute surface derivatives using high-order finite differences."""
610
638
  effective_h = max(self.h, 1e-6)
611
-
612
- r_u = derivative_5pt(lambda uu: self._position_array(uu, v), u, effective_h)
613
- r_v = derivative_5pt(lambda vv: self._position_array(u, vv), v, effective_h)
614
-
615
- r_uu = derivative_2nd_5pt(lambda uu: self._position_array(uu, v), u, effective_h)
616
- r_vv = derivative_2nd_5pt(lambda vv: self._position_array(u, vv), v, effective_h)
617
- r_uv = derivative_5pt(
618
- lambda vv: derivative_5pt(
619
- lambda uu: self._position_array(uu, vv), u, effective_h
620
- ), v, effective_h
621
- )
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)
622
675
 
623
676
  return {
624
677
  'r_u': r_u, 'r_v': r_v,
625
678
  'r_uu': r_uu, 'r_vv': r_vv, 'r_uv': r_uv
626
679
  }
627
680
 
628
- 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]:
629
687
  """
630
688
  Compute first and second fundamental forms.
631
689
 
632
690
  Returns:
633
691
  (g, h, n): First form, second form, normal vector
634
692
  """
635
- derivs = self._compute_derivatives(u, v)
693
+ if derivs is None:
694
+ derivs = self._compute_derivatives(u, v)
636
695
  r_u = derivs['r_u']
637
696
  r_v = derivs['r_v']
638
697
  r_uu = derivs['r_uu']
@@ -661,40 +720,31 @@ class CurvatureCalculator:
661
720
 
662
721
  return g, h, n
663
722
 
664
- def compute_gaussian_curvature(self, u: float, v: float) -> float:
665
- """Compute Gaussian curvature K = det(II) / det(I)."""
666
- g, h, _ = self.compute_fundamental_forms(u, v)
667
-
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."""
668
729
  det_g = np.linalg.det(g)
669
- det_h = np.linalg.det(h)
670
-
671
730
  if abs(det_g) < 1e-14:
672
- return 0.0
673
-
674
- return det_h / det_g
731
+ return 0.0, 0.0
675
732
 
676
- def compute_mean_curvature(self, u: float, v: float) -> float:
677
- """Compute mean curvature H."""
678
- g, h, _ = self.compute_fundamental_forms(u, v)
679
-
680
- det_g = np.linalg.det(g)
681
- if abs(det_g) < 1e-14:
682
- return 0.0
733
+ det_h = np.linalg.det(h)
734
+ K = det_h / det_g
683
735
 
684
736
  trace_term = g[1, 1] * h[0, 0] - 2 * g[0, 1] * h[0, 1] + g[0, 0] * h[1, 1]
685
737
  H = trace_term / (2 * det_g)
686
738
 
687
- return abs(H)
739
+ return K, abs(H)
688
740
 
689
- def compute_principal_curvatures(self, u: float, v: float) -> Tuple[float, float, np.ndarray, np.ndarray]:
690
- """
691
- Compute principal curvatures and principal directions.
692
-
693
- Returns:
694
- (k1, k2, dir1, dir2): Principal curvatures and directions
695
- """
696
- g, h, _ = self.compute_fundamental_forms(u, v)
697
- 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."""
698
748
  r_u = derivs['r_u']
699
749
  r_v = derivs['r_v']
700
750
 
@@ -702,20 +752,16 @@ class CurvatureCalculator:
702
752
  if abs(det_g) < 1e-14:
703
753
  return 0.0, 0.0, np.array([1., 0., 0.]), np.array([0., 1., 0.])
704
754
 
705
- # Shape operator S = g^{-1} h
706
755
  g_inv = np.linalg.inv(g)
707
756
  S = g_inv @ h
708
757
 
709
- # Eigenvalue decomposition
710
758
  eigenvalues, eigenvectors = np.linalg.eig(S)
711
759
  k1, k2 = eigenvalues.real
712
760
 
713
- # Ensure k1 >= k2 by absolute value
714
761
  if abs(k1) < abs(k2):
715
762
  k1, k2 = k2, k1
716
763
  eigenvectors = eigenvectors[:, [1, 0]]
717
764
 
718
- # Convert to 3D directions
719
765
  dir1_2d = eigenvectors[:, 0]
720
766
  dir2_2d = eigenvectors[:, 1]
721
767
 
@@ -727,12 +773,35 @@ class CurvatureCalculator:
727
773
 
728
774
  return k1, k2, dir1_3d, dir2_3d
729
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
+
730
799
  def compute_all_curvatures(self, u: float, v: float) -> Dict[str, Union[float, np.ndarray]]:
731
800
  """Compute all curvature quantities at once."""
732
- g, h, n = self.compute_fundamental_forms(u, v)
733
- K = self.compute_gaussian_curvature(u, v)
734
- H = self.compute_mean_curvature(u, v)
735
- 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)
736
805
 
737
806
  return {
738
807
  'K': K,
@@ -751,6 +820,102 @@ class CurvatureCalculator:
751
820
  # Convenience Functions - Intrinsic Gradient Method (Default)
752
821
  # ============================================================
753
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
+
754
919
  def compute_gaussian_curvature(
755
920
  surface: Surface,
756
921
  u: float,
@@ -760,8 +925,24 @@ def compute_gaussian_curvature(
760
925
  """
761
926
  Compute Gaussian curvature using intrinsic gradient method.
762
927
  """
763
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
764
- 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
765
946
 
766
947
 
767
948
  def compute_mean_curvature(
@@ -773,8 +954,25 @@ def compute_mean_curvature(
773
954
  """
774
955
  Compute mean curvature using intrinsic gradient method.
775
956
  """
776
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
777
- 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
778
976
 
779
977
 
780
978
  def compute_riemann_curvature(
@@ -786,7 +984,7 @@ def compute_riemann_curvature(
786
984
  """
787
985
  Compute Riemann curvature tensor component R^1_212.
788
986
  """
789
- calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
987
+ calc = _get_cached_calc(_intrinsic_calc_cache, surface, step_size, IntrinsicGradientCurvatureCalculator, "_cs_intrinsic_calc")
790
988
  return calc.compute_riemann_curvature(u, v)
791
989
 
792
990
 
@@ -799,7 +997,20 @@ def compute_all_curvatures(
799
997
  """
800
998
  Compute all curvature quantities using intrinsic gradient method.
801
999
  """
802
- 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")
803
1014
  return calc.compute_all_curvatures(u, v)
804
1015
 
805
1016
 
@@ -822,7 +1033,7 @@ def compute_intrinsic_gradient(
822
1033
  Returns:
823
1034
  GradientResult object
824
1035
  """
825
- grad_op = IntrinsicGradientOperator(surface, step_size)
1036
+ grad_op = _get_cached_calc(_intrinsic_grad_cache, surface, step_size, IntrinsicGradientOperator, "_cs_intrinsic_grad")
826
1037
 
827
1038
  if direction == 'u':
828
1039
  return grad_op.compute_u(u, v)
@@ -843,8 +1054,24 @@ def gaussian_curvature_classical(
843
1054
  step_size: float = 1e-3
844
1055
  ) -> float:
845
1056
  """Compute Gaussian curvature using classical method."""
846
- calc = CurvatureCalculator(surface, step_size)
847
- 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
848
1075
 
849
1076
 
850
1077
  def mean_curvature_classical(
@@ -854,8 +1081,25 @@ def mean_curvature_classical(
854
1081
  step_size: float = 1e-3
855
1082
  ) -> float:
856
1083
  """Compute mean curvature using classical method."""
857
- calc = CurvatureCalculator(surface, step_size)
858
- 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
859
1103
 
860
1104
 
861
1105
  def principal_curvatures_classical(
@@ -865,7 +1109,7 @@ def principal_curvatures_classical(
865
1109
  step_size: float = 1e-3
866
1110
  ) -> Tuple[float, float]:
867
1111
  """Compute principal curvatures using classical method."""
868
- calc = CurvatureCalculator(surface, step_size)
1112
+ calc = _get_cached_calc(_classical_calc_cache, surface, step_size, CurvatureCalculator, "_cs_classical_calc")
869
1113
  k1, k2, _, _ = calc.compute_principal_curvatures(u, v)
870
1114
  return k1, k2
871
1115
 
@@ -877,7 +1121,7 @@ def all_curvatures_classical(
877
1121
  step_size: float = 1e-3
878
1122
  ) -> Dict[str, Union[float, np.ndarray]]:
879
1123
  """Compute all curvature quantities using classical method."""
880
- calc = CurvatureCalculator(surface, step_size)
1124
+ calc = _get_cached_calc(_classical_calc_cache, surface, step_size, CurvatureCalculator, "_cs_classical_calc")
881
1125
  return calc.compute_all_curvatures(u, v)
882
1126
 
883
1127
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: coordinate_system
3
- Version: 7.1.0
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,12 +51,19 @@ 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.1.0
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.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
+
60
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)
@@ -236,6 +243,12 @@ S_YM = F_xy.yang_mills_action()
236
243
 
237
244
  ## Changelog
238
245
 
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
+
239
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
@@ -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=4uBZ1HbHqBkaC8fbAmwOzV05MRX_DED_yv33aRpDkdI,11136
2
- coordinate_system/complex_geometric_physics.py,sha256=6BjYorC2LanmSe6_9hMoUcvpDuUJOAUtbi03-QzXBwU,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=7-82wvnL5czyBQw319jgNHBHUamSSK4PBlSdhBsfxYA,32292
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.0.dist-info/LICENSE,sha256=tDnRkJxBYPzWdfh2gArRqrUPJxQZRZHJVs68qqBHIq4,1083
10
- coordinate_system-7.1.0.dist-info/METADATA,sha256=gimpzb3wzTYLlSWE-uogZxP-zJ2HxywpL1Ei-DZ3L0Y,10539
11
- coordinate_system-7.1.0.dist-info/WHEEL,sha256=4-iQBlRoDdX1wfPofc7KLWa5Cys4eZSgXs6GVU8fKlQ,101
12
- coordinate_system-7.1.0.dist-info/top_level.txt,sha256=R6LguuPPZ5esrIsDTqPGi9UxCvZPIXwn7KRKX87c79M,18
13
- coordinate_system-7.1.0.dist-info/RECORD,,