Spaces:
Paused
Paused
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})") |