coordinate-system 5.2.2__cp313-cp313-win_amd64.whl → 6.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.
@@ -1,33 +1,94 @@
1
1
  """
2
- Differential Geometry Module for Coordinate System Package
3
- ===========================================================
2
+ Differential Geometry Module
3
+ ============================
4
4
 
5
- This module provides tools for discrete differential geometry computations on surfaces,
6
- using the CORRECT Intrinsic Gradient Operator framework based on the proven algorithm.
5
+ Comprehensive discrete differential geometry computations on surfaces,
6
+ combining classical methods and intrinsic gradient operator framework.
7
7
 
8
- Key Formula:
9
- G_μ = (c(u+h) - c(u-h)) / (2h) / c(u) then extract normal derivative using .VZ()
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
10
14
 
11
- Author: PanGuoJun
12
- Date: 2025-10-31
15
+ Author: Coordinate System Package
16
+ Date: 2025-12-03
13
17
  """
14
18
 
15
19
  import math
16
20
  import numpy as np
17
- from typing import Tuple, Optional, Callable, Union
21
+ from typing import Tuple, Optional, Callable, Union, Dict, List
18
22
  from .coordinate_system import coord3, vec3
19
23
 
20
24
 
21
- # ========== Surface Base Class ==========
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
+ # ============================================================
22
81
 
23
82
  class Surface:
24
83
  """
25
- Base class for parametric surfaces r(u, v)
84
+ Base class for parametric surfaces r(u, v).
85
+
86
+ Subclasses must implement the position(u, v) method.
26
87
  """
27
88
 
28
89
  def __init__(self, h: float = 1e-6):
29
90
  """
30
- Initialize surface
91
+ Initialize surface.
31
92
 
32
93
  Args:
33
94
  h: Step size for numerical differentiation
@@ -35,23 +96,27 @@ class Surface:
35
96
  self.h = h
36
97
 
37
98
  def position(self, u: float, v: float) -> vec3:
38
- """Compute position on surface at parameters (u, v)"""
99
+ """
100
+ Compute position on surface at parameters (u, v).
101
+
102
+ Must be implemented by subclasses.
103
+ """
39
104
  raise NotImplementedError("Subclass must implement position(u, v)")
40
105
 
41
106
  def tangent_u(self, u: float, v: float) -> vec3:
42
- """Compute tangent vector in u direction"""
107
+ """Compute tangent vector in u direction using central difference."""
43
108
  r_plus = self.position(u + self.h, v)
44
109
  r_minus = self.position(u - self.h, v)
45
110
  return (r_plus - r_minus) * (1.0 / (2.0 * self.h))
46
111
 
47
112
  def tangent_v(self, u: float, v: float) -> vec3:
48
- """Compute tangent vector in v direction"""
113
+ """Compute tangent vector in v direction using central difference."""
49
114
  r_plus = self.position(u, v + self.h)
50
115
  r_minus = self.position(u, v - self.h)
51
116
  return (r_plus - r_minus) * (1.0 / (2.0 * self.h))
52
117
 
53
118
  def normal(self, u: float, v: float) -> vec3:
54
- """Compute unit normal vector"""
119
+ """Compute unit normal vector."""
55
120
  r_u = self.tangent_u(u, v)
56
121
  r_v = self.tangent_v(u, v)
57
122
  n = r_u.cross(r_v)
@@ -62,13 +127,16 @@ class Surface:
62
127
  return vec3(0.0, 0.0, 1.0)
63
128
 
64
129
 
65
- # ========== Common Surface Types ==========
66
-
67
130
  class Sphere(Surface):
68
131
  """
69
- Sphere surface
70
- Parametrization: r(θ, φ) = R(sin θ cos φ, sin θ sin φ, cos θ)
71
- where θ ∈ [0, π] is polar angle, φ ∈ [0, 2π] is azimuthal angle
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
72
140
  """
73
141
 
74
142
  def __init__(self, radius: float = 1.0, h: float = 1e-6):
@@ -76,43 +144,75 @@ class Sphere(Surface):
76
144
  self.R = radius
77
145
 
78
146
  def position(self, theta: float, phi: float) -> vec3:
79
- """Position on sphere"""
147
+ """Position on sphere."""
80
148
  x = self.R * math.sin(theta) * math.cos(phi)
81
149
  y = self.R * math.sin(theta) * math.sin(phi)
82
150
  z = self.R * math.cos(theta)
83
151
  return vec3(x, y, z)
84
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
+
85
163
 
86
164
  class Torus(Surface):
87
165
  """
88
- Torus surface
89
- Parametrization: r(u, v) = ((R + r cos u) cos v, (R + r cos u) sin v, r sin u)
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)))
90
174
  """
91
175
 
92
176
  def __init__(self, major_radius: float = 3.0, minor_radius: float = 1.0, h: float = 1e-6):
93
177
  super().__init__(h)
94
- self.R = major_radius # Major radius
95
- self.r = minor_radius # Minor radius
178
+ self.R = major_radius
179
+ self.r = minor_radius
96
180
 
97
181
  def position(self, u: float, v: float) -> vec3:
98
- """Position on torus"""
182
+ """Position on torus."""
99
183
  x = (self.R + self.r * math.cos(u)) * math.cos(v)
100
184
  y = (self.R + self.r * math.cos(u)) * math.sin(v)
101
185
  z = self.r * math.sin(u)
102
186
  return vec3(x, y, z)
103
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)))
104
195
 
105
- # ========== Metric Tensor ==========
196
+
197
+ # ============================================================
198
+ # Metric Tensor
199
+ # ============================================================
106
200
 
107
201
  class MetricTensor:
108
202
  """
109
- First fundamental form (metric tensor) of a surface
110
- g_ij = <∂r/∂u^i, ∂r/∂u^j>
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>
111
211
  """
112
212
 
113
213
  def __init__(self, E: float, F: float, G: float):
114
214
  """
115
- Initialize metric tensor
215
+ Initialize metric tensor.
116
216
 
117
217
  Args:
118
218
  E: g_11 = <r_u, r_u>
@@ -126,7 +226,7 @@ class MetricTensor:
126
226
 
127
227
  @classmethod
128
228
  def from_surface(cls, surface: Surface, u: float, v: float) -> 'MetricTensor':
129
- """Create metric tensor from surface at point (u, v)"""
229
+ """Create metric tensor from surface at point (u, v)."""
130
230
  r_u = surface.tangent_u(u, v)
131
231
  r_v = surface.tangent_v(u, v)
132
232
  E = r_u.dot(r_u)
@@ -135,171 +235,193 @@ class MetricTensor:
135
235
  return cls(E, F, G)
136
236
 
137
237
  def determinant(self) -> float:
138
- """Get determinant of metric tensor"""
238
+ """Get determinant of metric tensor."""
139
239
  return self.det
140
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
+
141
251
  def __repr__(self) -> str:
142
252
  return f"MetricTensor(E={self.E:.6f}, F={self.F:.6f}, G={self.G:.6f}, det={self.det:.6f})"
143
253
 
144
254
 
145
- # ========== Intrinsic Gradient Operator ==========
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
+ # ============================================================
146
282
 
147
283
  class IntrinsicGradientOperator:
148
284
  """
149
- CORRECT implementation based on proven test code
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()
150
290
  """
151
-
291
+
152
292
  def __init__(self, surface: Surface, step_size: float = 1e-3):
153
293
  self.surface = surface
154
294
  self.h = step_size
155
295
 
156
296
  def calc_intrinsic_frame(self, u: float, v: float) -> coord3:
157
297
  """
158
- Calculate intrinsic frame - EXACTLY like the working test code
298
+ Calculate intrinsic frame at point (u, v).
299
+
300
+ For Sphere and Torus, uses analytical expressions.
301
+ For other surfaces, uses numerical derivatives.
159
302
  """
160
- # 对于球面,使用解析表达式(与测试代码一致)
161
303
  if isinstance(self.surface, Sphere):
162
304
  R = self.surface.R
163
305
  theta, phi = u, v
164
-
165
- # 位置向量
306
+
166
307
  pos = self.surface.position(theta, phi)
167
-
168
- # 切向量(解析表达式,与测试代码一致)
308
+
309
+ # Analytical tangent vectors
169
310
  r_theta = vec3(
170
311
  R * math.cos(theta) * math.cos(phi),
171
312
  R * math.cos(theta) * math.sin(phi),
172
313
  -R * math.sin(theta)
173
314
  )
174
-
175
315
  r_phi = vec3(
176
316
  -R * math.sin(theta) * math.sin(phi),
177
317
  R * math.sin(theta) * math.cos(phi),
178
318
  0
179
319
  )
180
-
181
- # 单位法向量
320
+
182
321
  n = r_theta.cross(r_phi).normalized()
183
-
184
- # 单位切向量
185
322
  e1 = r_theta.normalized()
186
323
  e2 = r_phi.normalized()
187
-
324
+
188
325
  elif isinstance(self.surface, Torus):
189
- # 对于环面,使用解析表达式
190
- R = self.surface.R # 主半径
191
- r = self.surface.r # 副半径
326
+ R = self.surface.R
327
+ r = self.surface.r
192
328
  u_param, v_param = u, v
193
-
194
- # 位置向量
329
+
195
330
  pos = self.surface.position(u_param, v_param)
196
-
197
- # 切向量(解析表达式)
331
+
332
+ # Analytical tangent vectors
198
333
  r_u = vec3(
199
334
  -r * math.sin(u_param) * math.cos(v_param),
200
335
  -r * math.sin(u_param) * math.sin(v_param),
201
336
  r * math.cos(u_param)
202
337
  )
203
-
204
338
  r_v = vec3(
205
339
  -(R + r * math.cos(u_param)) * math.sin(v_param),
206
340
  (R + r * math.cos(u_param)) * math.cos(v_param),
207
341
  0
208
342
  )
209
-
210
- # 单位法向量
343
+
211
344
  n = r_u.cross(r_v).normalized()
212
-
213
- # 单位切向量
214
345
  e1 = r_u.normalized()
215
346
  e2 = r_v.normalized()
216
-
347
+
217
348
  else:
218
- # 对于其他曲面,使用数值方法
349
+ # Numerical method for general surfaces
219
350
  pos = self.surface.position(u, v)
220
351
  r_u = self.surface.tangent_u(u, v)
221
352
  r_v = self.surface.tangent_v(u, v)
222
-
223
- # 单位法向量
353
+
224
354
  n = r_u.cross(r_v).normalized()
225
-
226
- # 单位切向量
227
355
  e1 = r_u.normalized()
228
356
  e2 = r_v.normalized()
229
-
230
- # 创建内禀标架(与测试代码一致)
357
+
358
+ # Create intrinsic frame
231
359
  frame = coord3()
232
360
  frame.o = pos
233
361
  frame.ux = e1
234
362
  frame.uy = e2
235
363
  frame.uz = n
236
-
364
+
237
365
  return frame
238
366
 
239
- def compute_both(self, u: float, v: float) -> Tuple['GradientResult', 'GradientResult', coord3]:
367
+ def compute_both(self, u: float, v: float) -> Tuple[GradientResult, GradientResult, coord3]:
240
368
  """
241
- Compute gradients using CENTRAL DIFFERENCES - like test code
369
+ Compute gradients in both u and v directions using central differences.
370
+
371
+ Returns:
372
+ Tuple of (G_u, G_v, center_frame)
242
373
  """
243
- # 计算中心点和偏移点的标架
244
374
  c_center = self.calc_intrinsic_frame(u, v)
245
-
246
- # u方向:中心差分
375
+
376
+ # u direction: central difference
247
377
  c_u_plus = self.calc_intrinsic_frame(u + self.h, v)
248
378
  c_u_minus = self.calc_intrinsic_frame(u - self.h, v)
249
379
  dn_du = ((c_u_plus - c_u_minus) / (2 * self.h)).VZ()
250
-
251
- # v方向:中心差分
380
+
381
+ # v direction: central difference
252
382
  c_v_plus = self.calc_intrinsic_frame(u, v + self.h)
253
383
  c_v_minus = self.calc_intrinsic_frame(u, v - self.h)
254
384
  dn_dv = ((c_v_plus - c_v_minus) / (2 * self.h)).VZ()
255
-
256
- # 创建梯度结果
385
+
257
386
  G_u = GradientResult(dn_du, "u")
258
387
  G_v = GradientResult(dn_dv, "v")
259
-
388
+
260
389
  return G_u, G_v, c_center
261
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")
262
397
 
263
- class GradientResult:
264
- """
265
- Gradient result containing normal vector derivative
266
- """
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")
267
404
 
268
- def __init__(self, dn: vec3, direction: str):
269
- """
270
- Initialize gradient result
271
405
 
272
- Args:
273
- dn: Normal vector derivative (computed using proven algorithm)
274
- direction: Parameter direction ('u' or 'v')
275
- """
276
- self.dn = dn
277
- self.direction = direction
278
-
279
- def __repr__(self) -> str:
280
- return f"GradientResult({self.direction}: [{self.dn.x:.6f}, {self.dn.y:.6f}, {self.dn.z:.6f}])"
281
-
282
-
283
- # ========== Curvature Calculator ==========
406
+ # ============================================================
407
+ # Intrinsic Gradient Curvature Calculator
408
+ # ============================================================
284
409
 
285
410
  class IntrinsicGradientCurvatureCalculator:
286
411
  """
287
- Curvature calculator using the CORRECTED intrinsic gradient method
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.
288
416
  """
289
-
417
+
290
418
  def __init__(self, surface: Surface, step_size: float = 1e-3):
291
419
  self.surface = surface
292
420
  self.h = step_size
293
421
  self.grad_op = IntrinsicGradientOperator(surface, step_size)
294
422
 
295
- def compute_gaussian_curvature(self, u: float, v: float) -> float:
296
- """
297
- Compute Gaussian curvature - CORRECTED implementation
298
- """
299
- # 计算梯度算子
300
- G_u, G_v, _ = self.grad_op.compute_both(u, v)
301
-
302
- # 计算切向量(使用解析表达式)
423
+ def _get_tangent_vectors(self, u: float, v: float) -> Tuple[vec3, vec3]:
424
+ """Get tangent vectors (analytical for known surfaces, numerical otherwise)."""
303
425
  if isinstance(self.surface, Sphere):
304
426
  R = self.surface.R
305
427
  theta, phi = u, v
@@ -314,42 +436,48 @@ class IntrinsicGradientCurvatureCalculator:
314
436
  0
315
437
  )
316
438
  elif isinstance(self.surface, Torus):
317
- R = self.surface.R # 主半径
318
- r = self.surface.r # 副半径
319
- u_param, v_param = u, v
439
+ R = self.surface.R
440
+ r = self.surface.r
320
441
  r_u = vec3(
321
- -r * math.sin(u_param) * math.cos(v_param),
322
- -r * math.sin(u_param) * math.sin(v_param),
323
- r * math.cos(u_param)
442
+ -r * math.sin(u) * math.cos(v),
443
+ -r * math.sin(u) * math.sin(v),
444
+ r * math.cos(u)
324
445
  )
325
446
  r_v = vec3(
326
- -(R + r * math.cos(u_param)) * math.sin(v_param),
327
- (R + r * math.cos(u_param)) * math.cos(v_param),
447
+ -(R + r * math.cos(u)) * math.sin(v),
448
+ (R + r * math.cos(u)) * math.cos(v),
328
449
  0
329
450
  )
330
451
  else:
331
- # 对于其他曲面,使用数值导数
332
452
  r_u = self.surface.tangent_u(u, v)
333
453
  r_v = self.surface.tangent_v(u, v)
334
454
 
335
- # 提取法向量导数
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
+
336
464
  dn_du = G_u.dn
337
465
  dn_dv = G_v.dn
338
466
 
339
- # 计算度量张量
467
+ # First fundamental form
340
468
  E = r_u.dot(r_u)
341
469
  F = r_u.dot(r_v)
342
470
  G = r_v.dot(r_v)
343
471
  metric_det = E * G - F * F
344
472
 
345
- # 计算第二基本形式
473
+ # Second fundamental form
346
474
  L = -dn_du.dot(r_u)
347
475
  M1 = -dn_du.dot(r_v)
348
476
  M2 = -dn_dv.dot(r_u)
349
477
  N = -dn_dv.dot(r_v)
350
478
  M = (M1 + M2) / 2.0
351
479
 
352
- # 高斯曲率
480
+ # Gaussian curvature
353
481
  if abs(metric_det) > 1e-14:
354
482
  K = (L * N - M * M) / metric_det
355
483
  else:
@@ -357,142 +485,12 @@ class IntrinsicGradientCurvatureCalculator:
357
485
 
358
486
  return K
359
487
 
360
- def compute_riemann_curvature(self, u: float, v: float, proportional_correction: bool = True) -> float:
361
- """
362
- Compute Riemann curvature tensor component R^1_212
363
- Based on the intrinsic gradient operator method with correct implementation
364
- """
365
- delta = self.h
366
-
367
- # 计算中心标架
368
- c_center = self.grad_op.calc_intrinsic_frame(u, v)
369
-
370
- # 计算u方向的内禀梯度算子(使用中心差分)
371
- c_u_plus = self.grad_op.calc_intrinsic_frame(u + delta, v)
372
- c_u_minus = self.grad_op.calc_intrinsic_frame(u - delta, v)
373
- G_u = (c_u_plus - c_u_minus) / (2 * delta)
374
-
375
- # 计算v方向的内禀梯度算子(使用中心差分)
376
- c_v_plus = self.grad_op.calc_intrinsic_frame(u, v + delta)
377
- c_v_minus = self.grad_op.calc_intrinsic_frame(u, v - delta)
378
- G_v = (c_v_plus - c_v_minus) / (2 * delta)
379
-
380
- # 计算李括号 [G_u, G_v] = G_u ∘ G_v - G_v ∘ G_u
381
- # 这需要计算二阶混合偏导数
382
-
383
- # 计算 ∂²c/∂u∂v 和 ∂²c/∂v∂u
384
- c_uv_pp = self.grad_op.calc_intrinsic_frame(u + delta, v + delta)
385
- c_uv_pm = self.grad_op.calc_intrinsic_frame(u + delta, v - delta)
386
- c_uv_mp = self.grad_op.calc_intrinsic_frame(u - delta, v + delta)
387
- c_uv_mm = self.grad_op.calc_intrinsic_frame(u - delta, v - delta)
388
-
389
- # 二阶混合偏导数 ∂²c/∂u∂v
390
- d2c_dudv = (c_uv_pp - c_uv_pm - c_uv_mp + c_uv_mm) / (4 * delta * delta)
391
-
392
- # 对于对称的联络,∂²c/∂v∂u = ∂²c/∂u∂v,所以李括号简化为零
393
- # 但是我们需要考虑标架的非对易性
394
-
395
- # 更精确的方法:直接计算联络系数的变化
396
- # 提取法向量的导数(这是关键)
397
- dn_du = G_u.VZ() # 法向量在u方向的导数
398
- dn_dv = G_v.VZ() # 法向量在v方向的导数
399
-
400
- # 计算切向量(根据曲面类型)
401
- if isinstance(self.surface, Sphere):
402
- R = self.surface.R
403
- theta, phi = u, v
404
- r_u = vec3(
405
- R * math.cos(theta) * math.cos(phi),
406
- R * math.cos(theta) * math.sin(phi),
407
- -R * math.sin(theta)
408
- )
409
- r_v = vec3(
410
- -R * math.sin(theta) * math.sin(phi),
411
- R * math.sin(theta) * math.cos(phi),
412
- 0
413
- )
414
- elif isinstance(self.surface, Torus):
415
- R = self.surface.R
416
- r = self.surface.r
417
- u_param, v_param = u, v
418
- r_u = vec3(
419
- -r * math.sin(u_param) * math.cos(v_param),
420
- -r * math.sin(u_param) * math.sin(v_param),
421
- r * math.cos(u_param)
422
- )
423
- r_v = vec3(
424
- -(R + r * math.cos(u_param)) * math.sin(v_param),
425
- (R + r * math.cos(u_param)) * math.cos(v_param),
426
- 0
427
- )
428
- else:
429
- r_u = self.surface.tangent_u(u, v)
430
- r_v = self.surface.tangent_v(u, v)
431
-
432
- # 计算第二基本形式系数
433
- L = -dn_du.dot(r_u)
434
- M = -dn_du.dot(r_v)
435
- N = -dn_dv.dot(r_v)
436
-
437
- # 计算度量张量
438
- E = r_u.dot(r_u)
439
- F = r_u.dot(r_v)
440
- G_metric = r_v.dot(r_v)
441
- det_g = E * G_metric - F * F
442
-
443
- # 对于2D曲面,黎曼曲率张量只有一个独立分量
444
- # R^1_212 与高斯曲率的关系为:K = R^1_212 / det(g)
445
- # 所以 R^1_212 = K * det(g) = (LN - M²)
446
-
447
- R_1212 = L * N - M * M
448
-
449
- # 比例修正(对于某些参数化可能需要)
450
- if proportional_correction and isinstance(self.surface, Sphere):
451
- # 对于球面的某些参数化,可能需要额外的修正
452
- # 但是基于第二基本形式的计算通常不需要
453
- pass
454
-
455
- return R_1212
456
-
457
488
  def compute_mean_curvature(self, u: float, v: float) -> float:
458
489
  """
459
- Compute mean curvature
490
+ Compute mean curvature H = (EN - 2FM + GL) / (2*det(I)).
460
491
  """
461
- # 使用与高斯曲率相同的计算方法
462
492
  G_u, G_v, _ = self.grad_op.compute_both(u, v)
463
-
464
- # 计算切向量(使用解析表达式)
465
- if isinstance(self.surface, Sphere):
466
- R = self.surface.R
467
- theta, phi = u, v
468
- r_u = vec3(
469
- R * math.cos(theta) * math.cos(phi),
470
- R * math.cos(theta) * math.sin(phi),
471
- -R * math.sin(theta)
472
- )
473
- r_v = vec3(
474
- -R * math.sin(theta) * math.sin(phi),
475
- R * math.sin(theta) * math.cos(phi),
476
- 0
477
- )
478
- elif isinstance(self.surface, Torus):
479
- R = self.surface.R # 主半径
480
- r = self.surface.r # 副半径
481
- u_param, v_param = u, v
482
- r_u = vec3(
483
- -r * math.sin(u_param) * math.cos(v_param),
484
- -r * math.sin(u_param) * math.sin(v_param),
485
- r * math.cos(u_param)
486
- )
487
- r_v = vec3(
488
- -(R + r * math.cos(u_param)) * math.sin(v_param),
489
- (R + r * math.cos(u_param)) * math.cos(v_param),
490
- 0
491
- )
492
- else:
493
- # 对于其他曲面,使用数值导数
494
- r_u = self.surface.tangent_u(u, v)
495
- r_v = self.surface.tangent_v(u, v)
493
+ r_u, r_v = self._get_tangent_vectors(u, v)
496
494
 
497
495
  dn_du = G_u.dn
498
496
  dn_dv = G_v.dn
@@ -508,7 +506,6 @@ class IntrinsicGradientCurvatureCalculator:
508
506
  N = -dn_dv.dot(r_v)
509
507
  M = (M1 + M2) / 2.0
510
508
 
511
- # 平均曲率
512
509
  if abs(metric_det) > 1e-14:
513
510
  H = (G * L - 2 * F * M + E * N) / (2 * metric_det)
514
511
  else:
@@ -516,14 +513,49 @@ class IntrinsicGradientCurvatureCalculator:
516
513
 
517
514
  return H
518
515
 
519
- def compute_all_curvatures(self, u: float, v: float) -> dict:
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]]]:
520
553
  """
521
- Compute all curvature quantities
554
+ Compute all curvature quantities at once.
522
555
  """
523
556
  K = self.compute_gaussian_curvature(u, v)
524
557
  H = self.compute_mean_curvature(u, v)
525
-
526
- # 主曲率
558
+
527
559
  discriminant = max(0, H * H - K)
528
560
  sqrt_disc = discriminant ** 0.5
529
561
  k1 = H + sqrt_disc
@@ -536,7 +568,172 @@ class IntrinsicGradientCurvatureCalculator:
536
568
  }
537
569
 
538
570
 
539
- # ========== Convenience Functions ==========
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
+ # ============================================================
540
737
 
541
738
  def compute_gaussian_curvature(
542
739
  surface: Surface,
@@ -545,15 +742,7 @@ def compute_gaussian_curvature(
545
742
  step_size: float = 1e-3
546
743
  ) -> float:
547
744
  """
548
- Compute Gaussian curvature
549
-
550
- Args:
551
- surface: Surface object
552
- u, v: Parameter values
553
- step_size: Step size for numerical differentiation
554
-
555
- Returns:
556
- Gaussian curvature value
745
+ Compute Gaussian curvature using intrinsic gradient method.
557
746
  """
558
747
  calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
559
748
  return calc.compute_gaussian_curvature(u, v)
@@ -566,7 +755,7 @@ def compute_mean_curvature(
566
755
  step_size: float = 1e-3
567
756
  ) -> float:
568
757
  """
569
- Compute mean curvature
758
+ Compute mean curvature using intrinsic gradient method.
570
759
  """
571
760
  calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
572
761
  return calc.compute_mean_curvature(u, v)
@@ -576,23 +765,13 @@ def compute_riemann_curvature(
576
765
  surface: Surface,
577
766
  u: float,
578
767
  v: float,
579
- step_size: float = 1e-3,
580
- proportional_correction: bool = True
768
+ step_size: float = 1e-3
581
769
  ) -> float:
582
770
  """
583
- Compute Riemann curvature tensor component R^1_212
584
-
585
- Args:
586
- surface: Surface object
587
- u, v: Parameter values
588
- step_size: Step size for numerical differentiation
589
- proportional_correction: Apply proportional correction for spherical coordinates
590
-
591
- Returns:
592
- Riemann curvature tensor component R^1_212
771
+ Compute Riemann curvature tensor component R^1_212.
593
772
  """
594
773
  calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
595
- return calc.compute_riemann_curvature(u, v, proportional_correction)
774
+ return calc.compute_riemann_curvature(u, v)
596
775
 
597
776
 
598
777
  def compute_all_curvatures(
@@ -600,9 +779,9 @@ def compute_all_curvatures(
600
779
  u: float,
601
780
  v: float,
602
781
  step_size: float = 1e-3
603
- ) -> dict:
782
+ ) -> Dict[str, Union[float, Tuple[float, float]]]:
604
783
  """
605
- Compute all curvature quantities
784
+ Compute all curvature quantities using intrinsic gradient method.
606
785
  """
607
786
  calc = IntrinsicGradientCurvatureCalculator(surface, step_size)
608
787
  return calc.compute_all_curvatures(u, v)
@@ -616,7 +795,7 @@ def compute_intrinsic_gradient(
616
795
  step_size: float = 1e-3
617
796
  ) -> GradientResult:
618
797
  """
619
- Compute intrinsic gradient in specified direction
798
+ Compute intrinsic gradient in specified direction.
620
799
 
621
800
  Args:
622
801
  surface: Surface object
@@ -628,24 +807,142 @@ def compute_intrinsic_gradient(
628
807
  GradientResult object
629
808
  """
630
809
  grad_op = IntrinsicGradientOperator(surface, step_size)
631
-
810
+
632
811
  if direction == 'u':
633
- # Compute central difference for u direction
634
- c_plus = grad_op.calc_intrinsic_frame(u + step_size, v)
635
- c_minus = grad_op.calc_intrinsic_frame(u - step_size, v)
636
- dn = ((c_plus - c_minus) / (2 * step_size)).VZ()
812
+ return grad_op.compute_u(u, v)
637
813
  elif direction == 'v':
638
- # Compute central difference for v direction
639
- c_plus = grad_op.calc_intrinsic_frame(u, v + step_size)
640
- c_minus = grad_op.calc_intrinsic_frame(u, v - step_size)
641
- dn = ((c_plus - c_minus) / (2 * step_size)).VZ()
814
+ return grad_op.compute_v(u, v)
642
815
  else:
643
816
  raise ValueError(f"direction must be 'u' or 'v', got: {direction}")
644
817
 
645
- return GradientResult(dn, direction)
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)
646
911
 
647
912
 
648
- # ========== Export ==========
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
+ # ============================================================
649
946
 
650
947
  __all__ = [
651
948
  # Surface classes
@@ -655,12 +952,35 @@ __all__ = [
655
952
 
656
953
  # Core classes
657
954
  'MetricTensor',
955
+ 'GradientResult',
658
956
  'IntrinsicGradientOperator',
659
957
  'IntrinsicGradientCurvatureCalculator',
958
+ 'CurvatureCalculator',
660
959
 
661
- # Functions
960
+ # Intrinsic gradient method functions (default)
662
961
  'compute_gaussian_curvature',
663
962
  'compute_mean_curvature',
963
+ 'compute_riemann_curvature',
664
964
  'compute_all_curvatures',
665
965
  'compute_intrinsic_gradient',
666
- ]
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
+ ]