File size: 8,446 Bytes
fb867c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9f6ae17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb867c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
"""
Helix geometry calculations for the Felix Framework.

This module implements the core mathematical model for the helical agent path,
translating the 3D geometric model from thefelix.md into computational form.

Mathematical Foundation:
- Parametric helix with exponential radius tapering
- Position vector r(t) = (R(t)cos(θ(t)), R(t)sin(θ(t)), Ht)
- Parameter t ∈ [0,1] where t=0 is bottom, t=1 is top
- Tapering function R(t) = R_bottom * (R_top/R_bottom)^t
- Angular function θ(t) = 2πnt where n is number of turns

For complete mathematical specification, see:
- docs/mathematical_model.md: Formal parametric equations and geometric properties
- docs/hypothesis_mathematics.md: Statistical formulations for research hypotheses
- thefelix.md: Original OpenSCAD geometric prototype
- validate_openscad.py: Numerical validation against OpenSCAD (<1e-12 precision)

Implementation validates against OpenSCAD model with mathematical precision.
"""

import math
from typing import Tuple


class HelixGeometry:
    """
    Core helix mathematical model for agent positioning.
    
    Implements the same parametric equations as the OpenSCAD prototype,
    allowing validation against the geometric visualization.
    """
    
    def __init__(self, top_radius: float, bottom_radius: float, height: float, turns: int):
        """
        Initialize helix with geometric parameters.
        
        Args:
            top_radius: Radius at the top of the helix (t=0)
            bottom_radius: Radius at the bottom of the helix (t=1)  
            height: Total vertical height of the helix
            turns: Number of complete rotations
            
        Raises:
            ValueError: If parameters are invalid
        """
        self._validate_parameters(top_radius, bottom_radius, height, turns)
        
        self.top_radius = top_radius
        self.bottom_radius = bottom_radius
        self.height = height
        self.turns = turns
    
    def _validate_parameters(self, top_radius: float, bottom_radius: float, 
                           height: float, turns: int) -> None:
        """Validate helix parameters for mathematical consistency."""
        if top_radius <= bottom_radius:
            raise ValueError("top_radius must be greater than bottom_radius")
        
        if height <= 0:
            raise ValueError("height must be positive")
            
        if turns <= 0:
            raise ValueError("turns must be positive")
    
    def get_position(self, t: float) -> Tuple[float, float, float]:
        """
        Calculate 3D position along helix path.
        
        Implements the parametric helix equation:
        r(t) = (R(t)cos(θ(t)), R(t)sin(θ(t)), Ht)
        
        Where:
        - R(t) = R_bottom * (R_top/R_bottom)^t (exponential tapering)
        - θ(t) = 2πnt (angular progression)
        - z(t) = Ht (linear height progression)
        
        Mathematical reference: docs/mathematical_model.md, Section 1.2
        
        Args:
            t: Parameter value between 0 (bottom) and 1 (top)
            
        Returns:
            Tuple of (x, y, z) coordinates
            
        Raises:
            ValueError: If t is outside [0,1] range
        """
        if not (0.0 <= t <= 1.0):
            raise ValueError("t must be between 0 and 1")
        
        # Calculate height (linear interpolation: t=0 is top, t=1 is bottom)
        # Invert so agents start at wide top and descend to narrow bottom
        z = self.height * (1.0 - t)
        
        # Calculate radius at this height (exponential tapering)
        radius = self.get_radius(z)
        
        # Calculate angle (linear progression through turns)
        # Total rotation: turns * 360 degrees = turns * 2π radians
        angle_radians = t * self.turns * 2.0 * math.pi
        
        # Calculate Cartesian coordinates
        x = radius * math.cos(angle_radians)
        y = radius * math.sin(angle_radians)
        
        return (x, y, z)

    def get_position_at_t(self, t: float) -> Tuple[float, float, float]:
        """
        Alias for get_position method to maintain API consistency.

        Calculate 3D position along helix path at parameter t.

        Args:
            t: Parameter value between 0 (bottom) and 1 (top)

        Returns:
            Tuple of (x, y, z) coordinates
        """
        return self.get_position(t)

    def get_radius(self, z: float) -> float:
        """
        Calculate radius at given height using exponential tapering.
        
        Implements the tapering function:
        R(z) = R_bottom * (R_top/R_bottom)^(z/height)
        
        This creates exponential tapering that naturally focuses agent density
        toward the narrow end, supporting Hypothesis H3 (attention focusing).
        
        Mathematical reference: docs/mathematical_model.md, Section 2
        Hypothesis reference: docs/hypothesis_mathematics.md, Section H3.2
        
        Args:
            z: Height value (0 = bottom, height = top)
            
        Returns:
            Radius at the specified height
        """
        # Ensure z is within valid range
        z = max(0.0, min(z, self.height))
        
        # Exponential tapering formula from OpenSCAD
        radius_ratio = self.top_radius / self.bottom_radius
        height_fraction = z / self.height
        radius = self.bottom_radius * pow(radius_ratio, height_fraction)
        
        return radius
    
    def get_angle_at_t(self, t: float) -> float:
        """
        Calculate rotation angle (in radians) at parameter t.
        
        Args:
            t: Parameter value between 0 and 1
            
        Returns:
            Angle in radians
        """
        if not (0.0 <= t <= 1.0):
            raise ValueError("t must be between 0 and 1")
            
        return t * self.turns * 2.0 * math.pi
    
    def get_tangent_vector(self, t: float) -> Tuple[float, float, float]:
        """
        Calculate tangent vector to helix at parameter t.
        
        Useful for agent orientation and movement direction.
        
        Args:
            t: Parameter value between 0 and 1
            
        Returns:
            Normalized tangent vector (dx/dt, dy/dt, dz/dt)
        """
        if not (0.0 <= t <= 1.0):
            raise ValueError("t must be between 0 and 1")
        
        # Small epsilon for numerical differentiation
        eps = 1e-8
        t1 = max(0.0, t - eps)
        t2 = min(1.0, t + eps)
        
        x1, y1, z1 = self.get_position(t1)
        x2, y2, z2 = self.get_position(t2)
        
        # Calculate direction vector
        dx = x2 - x1
        dy = y2 - y1
        dz = z2 - z1
        
        # Normalize
        length = math.sqrt(dx*dx + dy*dy + dz*dz)
        if length > 0:
            dx /= length
            dy /= length
            dz /= length
        
        return (dx, dy, dz)
    
    def approximate_arc_length(self, t_start: float = 0.0, t_end: float = 1.0, 
                              segments: int = 1000) -> float:
        """
        Approximate arc length of helix segment using linear interpolation.
        
        Args:
            t_start: Starting parameter value
            t_end: Ending parameter value
            segments: Number of segments for approximation
            
        Returns:
            Approximate arc length
        """
        if not (0.0 <= t_start <= t_end <= 1.0):
            raise ValueError("Invalid t_start or t_end values")
        
        if segments < 1:
            raise ValueError("segments must be positive")
        
        total_length = 0.0
        dt = (t_end - t_start) / segments
        
        prev_x, prev_y, prev_z = self.get_position(t_start)
        
        for i in range(1, segments + 1):
            t = t_start + i * dt
            x, y, z = self.get_position(t)
            
            # Calculate distance from previous point
            distance = math.sqrt((x - prev_x)**2 + (y - prev_y)**2 + (z - prev_z)**2)
            total_length += distance
            
            prev_x, prev_y, prev_z = x, y, z
        
        return total_length
    
    def __repr__(self) -> str:
        """String representation for debugging."""
        return (f"HelixGeometry(top_radius={self.top_radius}, "
                f"bottom_radius={self.bottom_radius}, "
                f"height={self.height}, turns={self.turns})")