research(R4): L38 wall "ripple" is a coil-ownership step, not a wave

Offline replay of the L37 and L38 tube fields on the FI Style 2 helix shows no 7.8 m ripple from the
tube distance itself (< 0.3 cm). The real signal is a step: the upper coil's 1 m fallback floor strip,
composed independently, puts a support root 0.44 m from the lower bore's centreline at head height;
the 7.8 m "period" is a sinusoid fitted to that step. Proposes pairing the floor cap with its bore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-23 08:30:59 +02:00
1 parent 945225d942
commit cf57c87daf
5 files changed
+776

No files matched your search

@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Audit a route-only model of fallback support intersecting a neighboring bore."""
import json
import math
from r4_tunnel_wall_ripple import (BORE, LIFT, SPACING, WINDOW, Helix,
detrend_profile, ripple_amplitude)
def load_route():
with open('Saved/Linux/l38/owner-fi-style2-layout.json', encoding='utf-8') as source:
passage = json.load(source)['passages'][0]
points = [tuple(value/1024.0 for value in point['position_q'])
for point in passage['control_points']]
return Helix(points)
def rounded_box(side, vertical):
outside = math.hypot(max(side, 0.0), max(vertical, 0.0))
inside = min(max(side, vertical), 0.0)
return outside+inside
def fallback_field(route, x, y, z, mode):
"""Use tube-only native density and exact route geometry, omitting FI density."""
bore_d, bore_i, bore_t = route.tube(x, y, z, True)
bore_center = route.evaluate(bore_i, bore_t)
bore_floor = bore_center[2]-BORE+LIFT
_, _, floor_i = route.l38_field(x, y, z, 'owner')
if floor_i < 0:
return -bore_d, bore_i, floor_i
floor_t = route.project(floor_i, x, y, z, True, True)
floor_curve = route.evaluate(floor_i, floor_t, True)
floor_q = floor_curve[2]+LIFT
horizontal = math.hypot(x-floor_curve[0], y-floor_curve[1])
side = horizontal-0.55
top = z-floor_q
fallback_depth = 1.0
cap_floor = floor_q
if mode == 'paired':
cap_depth = 0.375
if (horizontal > 0.55 or top < -cap_depth or top > 0.125):
cap_floor = bore_floor
d = max(bore_d, cap_floor-z)
native = -bore_d
composed = max(native, -d)
support = rounded_box(side, max(top, floor_q-fallback_depth-z))
if mode in ('shared-owner', 'paired'):
support = max(support, -d)
if native >= 0.0 and support < 0.001:
composed = min(composed, support)
return composed, bore_i, floor_i
def audit_failure_window(route, mode):
start = (route.total-WINDOW)*0.31
radii = []
for sample in range(round(WINDOW/SPACING)+1):
distance = start+sample*SPACING
pose = route.route_pose(distance)
before = route.route_pose(max(0.0, distance-0.25))
after = route.route_pose(min(route.total, distance+0.25))
tx, ty = after[0]-before[0], after[1]-before[1]
length = math.hypot(tx, ty)
nx, ny = -ty/length, tx/length
roots = []
for sx, sy in ((nx, ny), (-nx, -ny)):
prev_r = 0.0
prev_d = fallback_field(route, pose[0], pose[1], pose[2], mode)[0]
best_r, best_error = float('nan'), float('inf')
radius = SPACING
while radius <= max(8.0, pose[3]+4.0)+1.0e-6:
value = fallback_field(route, pose[0]+sx*radius,
pose[1]+sy*radius, pose[2], mode)[0]
if (prev_d < 0.0) != (value < 0.0):
denominator = prev_d-value
crossing = (prev_r+(radius-prev_r)*prev_d/denominator
if denominator > 1.0e-12 else 0.5*(prev_r+radius))
error = abs(crossing-pose[3])
if error < best_error:
best_r, best_error = crossing, error
prev_d, prev_r = value, radius
radius += SPACING
roots.append(best_r)
if all(math.isfinite(root) for root in roots):
radii.append((distance, 0.5*(roots[0]+roots[1])))
if len(radii) < 192:
return len(radii), float('nan'), float('nan'), float('nan'), float('nan')
detrended = detrend_profile([radius for _, radius in radii], start)
amp, period = ripple_amplitude(detrended)
return len(radii), amp, period, min(r for _, r in radii), max(r for _, r in radii)
def main():
actual = load_route()
print(f'[saved FI Style 2 failure window] controls={actual.count+1} '
f'length={actual.total:.3f}m window={(actual.total-WINDOW)*0.31:.3f}m')
variants = [
('independent 1 m fallback', 'owner'),
('owner fallback + shared domain only', 'shared-owner'),
('0.375 m floor band + paired bore domain', 'paired'),
]
for description, mode in variants:
samples, amplitude, period, low, high = audit_failure_window(actual, mode)
print(f' {description}: local={amplitude*100:.3f}cm period={period:.4f}m '
f'radius_range={low:.4f}..{high:.4f}m valid={samples}')
if __name__ == '__main__':
main()
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Probe the L38 rounded fallback strip with and without a bore-domain clip."""
import json
import math
from r4_tunnel_wall_ripple import Helix, LIFT, SPACING
def load_route():
with open('Saved/Linux/l38/owner-fi-style2-layout.json', encoding='utf-8') as source:
passage = json.load(source)['passages'][0]
points = [tuple(value/1024.0 for value in point['position_q'])
for point in passage['control_points']]
return Helix(points)
def fallback_density(route, x, y, z, mode):
carve_density, bore_index, floor_index = route.l38_field(x, y, z, 'supportband')
if floor_index < 0:
return 1.0, bore_index, floor_index, 0.0, 0.0
t = route.project(floor_index, x, y, z, True, True)
floor_curve = route.evaluate(floor_index, t, True)
floor_q = floor_curve[2]+LIFT
horizontal = math.hypot(x-floor_curve[0], y-floor_curve[1])
side = horizontal-0.55
top = z-floor_q
depth = 1.0
bore_distance, bore_index, bore_t = route.tube(x, y, z, True)
bore_center_z = route.evaluate(bore_index, bore_t)[2]
bore_floor = bore_center_z-3.0+LIFT
floor_to_bore_center = floor_q-bore_center_z
if mode == 'neighbor-limited' and floor_to_bore_center > 0.0:
depth = min(depth, max(0.0, floor_to_bore_center-0.01))
bottom = floor_q-depth-z
vertical = max(top, bottom)
outside = math.hypot(max(side, 0.0), max(vertical, 0.0))
inside = min(max(side, vertical), 0.0)
support = outside+inside
carve_distance = -carve_density
if mode == 'shared-domain':
support = max(support, -carve_distance)
elif mode == 'paired':
if horizontal <= 0.55 and -0.375 <= top <= 0.125:
cap_floor = floor_q
else:
cap_floor = bore_floor
carve_distance = max(bore_distance, cap_floor-z)
support = max(support, -carve_distance)
# Native air is positive. In L38 the fallback strip is min-composed into it.
return min(1.0, support), bore_index, floor_index, support, carve_distance, depth, floor_q
def nearest_wall_root(route, pose, nx, ny, mode):
previous_r = 0.0
previous = fallback_density(route, pose[0], pose[1], pose[2], mode)[0]
crossings = []
for step in range(1, 65):
radius = step*SPACING
x, y = pose[0]+nx*radius, pose[1]+ny*radius
value = fallback_density(route, x, y, pose[2], mode)[0]
if (previous < 0.0) != (value < 0.0):
den = previous-value
crossing = (previous_r+(radius-previous_r)*previous/den
if den > 1.0e-12 else 0.5*(previous_r+radius))
crossings.append(crossing)
previous_r, previous = radius, value
return min(crossings, key=lambda value: abs(value-3.0)) if crossings else float('nan'), crossings
def main():
route = load_route()
for distance in (150.744, 150.869):
pose = route.route_pose(distance)
before = route.route_pose(max(0.0, distance-0.25))
after = route.route_pose(min(route.total, distance+0.25))
tx, ty = after[0]-before[0], after[1]-before[1]
length = math.hypot(tx, ty)
nx, ny = -ty/length, tx/length
print(f's={distance:.3f}m segment={pose[4]} t={pose[5]:.3f} z={pose[2]:.4f}m')
for side, sx, sy in (('left', nx, ny), ('right', -nx, -ny)):
for mode in ('current', 'shared-domain', 'paired', 'neighbor-limited'):
root, crossings = nearest_wall_root(route, pose, sx, sy, mode)
at_center = fallback_density(route, pose[0], pose[1], pose[2], mode)
label = mode
print(f' {side:5s} {label:19s} nearest3_root={root:.4f}m '
f'all_roots={[round(r,4) for r in crossings]} '
f'bore={at_center[1]} floor={at_center[2]} '
f'support={at_center[3]:.4f} cap_d={at_center[4]:.4f} '
f'depth={at_center[5]:.4f} floor_z={at_center[6]:.4f}')
floor_index = 134
floor_distance = route.arc[floor_index] + 0.5*(route.arc[floor_index+1]-route.arc[floor_index])
floor_pose = route.route_pose(floor_distance)
intended_floor = floor_pose[2]-floor_pose[3]+LIFT
current_floor = fallback_density(route, floor_pose[0], floor_pose[1], intended_floor, 'current')
paired_floor = fallback_density(route, floor_pose[0], floor_pose[1], intended_floor, 'paired')
paired_below = fallback_density(route, floor_pose[0], floor_pose[1], intended_floor-0.25, 'paired')
limited_floor = fallback_density(route, floor_pose[0], floor_pose[1], intended_floor, 'neighbor-limited')
print(f' floor owner segment={floor_index} intended_z={intended_floor:.4f}m '
f'current_support={current_floor[3]:.4f}m paired_top={paired_floor[3]:.4f}m '
f'paired_0.25m_below={paired_below[3]:.4f}m '
f'limited_support={limited_floor[3]:.4f}m '
f'limited_depth={limited_floor[5]:.4f}m')
if __name__ == '__main__':
main()
@@ -0,0 +1,440 @@
#!/usr/bin/env python3
"""Small synthetic reproduction of the L37/L38 FI helix wall audit."""
import math
import json
import bisect
import argparse
R = 3.0
PITCH = 3.31
TURNS = 12.95
N_PER_TURN = 20
BORE = 3.0
LIFT = 0.15
SPACING = 0.125
WINDOW = 32.0
PERIODS = [0.5 + 0.0625 * i for i in range(121)]
def catmull(v0, v1, v2, v3, t):
t2, t3 = t*t, t*t*t
return 0.5*((2*v1)+(-v0+v2)*t+(2*v0-5*v1+4*v2-v3)*t2
+(-v0+3*v1-3*v2+v3)*t3)
def catmull_d(v0, v1, v2, v3, t):
return 0.5*((-v0+v2)+2*(2*v0-5*v1+4*v2-v3)*t
+3*(-v0+3*v1-3*v2+v3)*t*t)
def catmull_dd(v0, v1, v2, v3, t):
return (2*v0-5*v1+4*v2-v3)+3*(-v0+3*v1-3*v2+v3)*t
class Helix:
def __init__(self, points=None):
self.count = math.ceil(TURNS * N_PER_TURN) if points is None else len(points)-1
self.end_theta = 2*math.pi*TURNS
self.dtheta = self.end_theta/self.count
self.k = PITCH/(2*math.pi)
self.pts = ([(R*math.cos(i*self.dtheta), R*math.sin(i*self.dtheta),
self.k*i*self.dtheta) for i in range(self.count+1)]
if points is None else [tuple(p) for p in points])
self.sorted_z = sorted((p[2], i) for i, p in enumerate(self.pts))
self.segs = []
for i in range(self.count):
a, b = self.pts[i], self.pts[i+1]
dx, dy, dz = b[0]-a[0], b[1]-a[1], b[2]-a[2]
length = math.sqrt(dx*dx+dy*dy+dz*dz)
smooth = i > 2 and i+12 < self.count and length <= 8.0
p0, p3 = self.pts[max(0, i-1)], self.pts[min(self.count, i+2)]
self.segs.append((a, b, p0, p3, smooth, length))
self.arc = [0.0]
for seg in self.segs:
self.arc.append(self.arc[-1]+seg[5])
self.total = self.arc[-1]
def route_pose(self, distance):
distance = max(0.0, min(self.total, distance))
i = min(self.count-1, max(0, int(distance/(self.total/self.count))))
while i+1 < self.count and self.arc[i+1] < distance:
i += 1
while i > 0 and self.arc[i] > distance:
i -= 1
a, b, p0, p3, smooth, _ = self.segs[i]
t = (distance-self.arc[i])/(self.arc[i+1]-self.arc[i])
x = a[0]+t*(b[0]-a[0]); y = a[1]+t*(b[1]-a[1])
if smooth:
x = catmull(p0[0], a[0], b[0], p3[0], t)
y = catmull(p0[1], a[1], b[1], p3[1], t)
z = a[2]+t*(b[2]-a[2])
return (x, y, z, BORE, i, t)
def candidate_indices(self, x, y, target_z):
"""Segments around the height owner and its adjacent overlapping coils."""
j = bisect.bisect_left(self.sorted_z, (target_z, -1))
near = self.sorted_z[max(0, min(len(self.sorted_z)-1, j))][1]
if j > 0 and abs(self.sorted_z[j-1][0]-target_z) < abs(self.sorted_z[j][0]-target_z):
near = self.sorted_z[j-1][1]
found = set()
for center in (near, near-N_PER_TURN, near+N_PER_TURN):
for j in range(center-4, center+5):
if 0 <= j < self.count:
found.add(j)
return sorted(found)
def evaluate(self, index, t, floor=False):
a, b, p0, p3, smooth, _ = self.segs[index]
z = a[2]+t*(b[2]-a[2])
dz = b[2]-a[2]
if smooth:
x = catmull(p0[0], a[0], b[0], p3[0], t)
y = catmull(p0[1], a[1], b[1], p3[1], t)
dx = catmull_d(p0[0], a[0], b[0], p3[0], t)
dy = catmull_d(p0[1], a[1], b[1], p3[1], t)
ddx = catmull_dd(p0[0], a[0], b[0], p3[0], t)
ddy = catmull_dd(p0[1], a[1], b[1], p3[1], t)
else:
x = a[0]+t*(b[0]-a[0]); y = a[1]+t*(b[1]-a[1])
dx = b[0]-a[0]; dy = b[1]-a[1]
ddx = ddy = 0.0
if floor:
z -= BORE
return (x, y, z, dx, dy, dz, ddx, ddy)
def project(self, index, x, y, z, l38, floor=False):
a, b, _, _, _, _ = self.segs[index]
az = a[2] - (BORE-LIFT if floor else 0.0)
dx, dy, dz = b[0]-a[0], b[1]-a[1], b[2]-a[2]
chord_z = z
den = dx*dx+dy*dy+dz*dz
t = max(0.0, min(1.0, ((x-a[0])*dx+(y-a[1])*dy+(chord_z-az)*dz)/den))
if not l38:
return t
def dist2(u):
c = self.evaluate(index, u, floor)
ez = (c[2]+LIFT)-z if floor else c[2]-z
ex, ey = c[0]-x, c[1]-y
return ex*ex+ey*ey+ez*ez
for _ in range(4):
c = self.evaluate(index, t, floor)
ex, ey = c[0]-x, c[1]-y
ez = (c[2]+LIFT)-z if floor else c[2]-z
grad = ex*c[3]+ey*c[4]+ez*c[5]
curv = c[3]*c[3]+c[4]*c[4]+c[5]*c[5]+ex*c[6]+ey*c[7]
if curv <= 1.0e-9:
break
nt = max(0.0, min(1.0, t-grad/curv))
if abs(nt-t) <= 1.0e-10:
t = nt
break
t = nt
if dist2(0.0) < dist2(t):
t = 0.0
if dist2(1.0) < dist2(t):
t = 1.0
return t
def tube(self, x, y, z, l38):
best = (float('inf'), -1, 0.0)
for i in self.candidate_indices(x, y, z):
a, b, _, _, _, _ = self.segs[i]
t = self.project(i, x, y, z, l38)
c = self.evaluate(i, t)
radius = BORE
d = math.sqrt((x-c[0])**2+(y-c[1])**2+(z-c[2])**2)-radius
if d < best[0] or (d == best[0] and i < best[1]):
best = (d, i, t)
return best
def l38_field(self, x, y, z, cap_mode='owner'):
"""Return sign-inverted L38 carve field, optionally without its floor cap."""
bore_d, bore_i, bore_t = self.tube(x, y, z, True)
if cap_mode == 'none':
return -bore_d, bore_i, bore_i
candidates = set(self.candidate_indices(x, y, z))
candidates.update(self.candidate_indices(x, y, z+BORE-LIFT))
candidates.update(self.candidate_indices(x, y, z+BORE-LIFT-0.98))
nearest_floor = (float('inf'), -1, 0.0, 0.0, 0.0, 0.0)
nearest_player = (float('inf'), -1, 0.0, 0.0, 0.0, 0.0)
nearest_band = None
for i in sorted(candidates):
t = self.project(i, x, y, z, True, True)
c = self.evaluate(i, t, True)
fx, fy = c[0], c[1]
floor_q = c[2]+LIFT
horiz2 = (x-fx)**2+(y-fy)**2
floor_score = horiz2+(z-floor_q)**2
player_score = horiz2+(z-(floor_q+0.98))**2
if (floor_score, i) < (nearest_floor[0], nearest_floor[1]):
nearest_floor = (floor_score, i, t, floor_q, horiz2, BORE)
if (player_score, i) < (nearest_player[0], nearest_player[1]):
nearest_player = (player_score, i, t, floor_q, horiz2, BORE)
if abs(floor_q-z) <= 0.125 and horiz2 <= BORE*BORE:
if nearest_band is None or (floor_score, i) < (nearest_band[0], nearest_band[1]):
nearest_band = (floor_score, i, t, floor_q, horiz2, BORE)
bore_c = self.evaluate(bore_i, bore_t)
bore_floor = bore_c[2]-BORE+LIFT
bore_horiz2 = (x-bore_c[0])**2+(y-bore_c[1])**2
near_bore_floor = abs(bore_floor-z) <= 0.125 and bore_horiz2 <= BORE*BORE
if cap_mode == 'matched':
# Preserve the floor owner where the nearest bore itself is in its floor band.
floor_owner = nearest_band if near_bore_floor and nearest_band else nearest_floor
cap_floor = floor_owner[3] if near_bore_floor else bore_floor
elif cap_mode == 'paired':
# Keep the local route-floor owner only inside its narrow ownership band;
# outside that band, pair the floor cap with the same bore as the tube.
if near_bore_floor and nearest_band:
floor_owner = nearest_band
cap_floor = nearest_band[3]
elif nearest_floor[0] <= 0.65**2:
floor_owner = nearest_floor
cap_floor = nearest_floor[3]
else:
floor_owner = nearest_floor
cap_floor = bore_floor
elif cap_mode == 'band':
floor_owner = nearest_band if nearest_band else nearest_floor
cap_floor = nearest_band[3] if nearest_band else bore_floor
elif cap_mode == 'walkband':
floor_owner = nearest_band if nearest_band else nearest_floor
if nearest_band and nearest_band[4] <= 0.55**2:
cap_floor = nearest_band[3]
else:
cap_floor = bore_floor
elif cap_mode == 'supportband':
floor_owner = nearest_floor
vertical_offset = z-nearest_floor[3]
if -1.0 <= vertical_offset <= 0.125 and nearest_floor[4] <= 0.55**2:
cap_floor = nearest_floor[3]
else:
cap_floor = bore_floor
else:
if near_bore_floor and nearest_band:
floor_owner = nearest_band
elif nearest_floor[0] <= 0.65**2:
floor_owner = nearest_floor
elif nearest_player[0] <= 1.50**2:
floor_owner = nearest_player
elif bore_d <= 0:
floor_owner = (bore_d, bore_i, bore_t, bore_floor, bore_horiz2, BORE)
elif nearest_player[1] >= 0:
floor_owner = nearest_player
else:
floor_owner = nearest_floor
cap_floor = floor_owner[3]
return -max(bore_d, cap_floor-z), bore_i, floor_owner[1]
def ripple_amplitude(samples):
best_amp, best_period = 0.0, 0.0
for period in PERIODS:
omega = 2*math.pi/period
cc = ss = cs = yc = ys = 0.0
for distance, value in samples:
c, s = math.cos(omega*distance), math.sin(omega*distance)
cc += c*c; ss += s*s; cs += c*s
yc += value*c; ys += value*s
det = cc*ss-cs*cs
if det <= 1.0e-9:
continue
co = (yc*ss-ys*cs)/det
si = (ys*cc-yc*cs)/det
amp = math.hypot(co, si)
if amp > best_amp:
best_amp, best_period = amp, period
return best_amp, best_period
def detrend_profile(values, start):
n = len(values)
mean_i = 0.5*(n-1)
mean = sum(values)/n
xx = xy = 0.0
for i, value in enumerate(values):
x = (i-mean_i)*SPACING
xy += x*(value-mean)
xx += x*x
slope = xy/xx if xx else 0.0
return [(start+i*SPACING, value-mean-((i-mean_i)*SPACING)*slope)
for i, value in enumerate(values)]
def interpolate_audit_path(h, distance):
return h.route_pose(distance)
def wall_crossing(h, pose, nx, ny, field, variant):
px, py, pz = pose[:3]
prev_r, prev_d = 0.0, field(px, py, pz, variant)[0]
best_r, best_err = float('nan'), float('inf')
max_r = max(8.0, pose[3]+4.0)
r = SPACING
while r <= max_r+1.0e-6:
d = field(px+nx*r, py+ny*r, pz, variant)[0]
if (prev_d < 0.0) != (d < 0.0):
den = prev_d-d
cross = prev_r+(r-prev_r)*prev_d/den if den > 1.0e-12 else 0.5*(prev_r+r)
err = abs(cross-pose[3])
if err < best_err:
best_r, best_err = cross, err
prev_d, prev_r = d, r
r += SPACING
return best_r
def audit(h, field, variant):
starts = [0.03, 0.31, 0.457, 0.61, 0.91]
local = []
coherent = []
owner_switches = 0
last_bore = last_floor = None
count = 0
for fraction in starts:
start = (h.total-WINDOW)*fraction
radii = []
for sample in range(round(WINDOW/SPACING)+1):
distance = start+sample*SPACING
pose = interpolate_audit_path(h, distance)
before = interpolate_audit_path(h, max(0.0, distance-0.25))
after = interpolate_audit_path(h, min(h.total, distance+0.25))
tx, ty = after[0]-before[0], after[1]-before[1]
tl = math.hypot(tx, ty)
if tl <= 1.0e-6:
continue
nx, ny = -ty/tl, tx/tl
left = wall_crossing(h, pose, nx, ny, field, variant)
right = wall_crossing(h, pose, -nx, -ny, field, variant)
if not math.isfinite(left) or not math.isfinite(right):
continue
radii.append(0.5*(left+right))
_, bi, fi = field(pose[0], pose[1], pose[2], variant)
if last_bore is not None and bi != last_bore:
owner_switches += 1
if last_floor is not None and fi != last_floor:
owner_switches += 1
last_bore, last_floor = bi, fi
count += 1
if len(radii) < 192:
continue
vals = detrend_profile(radii, start)
coherent.extend(vals)
amp, period = ripple_amplitude(vals)
local.append((amp, period, start, min(radii), max(radii)))
peak = max(local, default=(0, 0, 0, 0, 0))
coh_amp, coh_period = ripple_amplitude(coherent)
return peak, coh_amp, coh_period, owner_switches, count
def floor_audit(h, field, variant):
max_abs = 0.0
peak_to_peak = 0.0
for fraction in (0.03, 0.31, 0.457, 0.61, 0.91):
start = (h.total-WINDOW)*fraction
residuals = []
for sample in range(round(WINDOW/SPACING)+1):
distance = start+sample*SPACING
pose = h.route_pose(distance)
expected = pose[2]-pose[3]+LIFT
previous = None
hits = []
for vertical in range(7):
z = expected-1.0+vertical*0.25
density = field(pose[0], pose[1], z, variant)[0]
if previous is not None and previous[1] < 0.0 and density >= 0.0:
previous_z, previous_d = previous
den = density-previous_d
crossing = (previous_z+(z-previous_z)*(-previous_d)/den
if den > 1.0e-12 else z)
hits.append((abs(crossing-expected), crossing-expected))
previous = (z, density)
if hits:
residuals.append(min(hits)[1])
if residuals:
max_abs = max(max_abs, max(abs(v) for v in residuals))
peak_to_peak = max(peak_to_peak, max(residuals)-min(residuals))
return max_abs, peak_to_peak
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--actual-only', action='store_true')
parser.add_argument('--synthetic-only', action='store_true')
parser.add_argument('--paired-only', action='store_true')
parser.add_argument('--band-only', action='store_true')
parser.add_argument('--walkband-only', action='store_true')
args = parser.parse_args()
h = Helix()
seg = h.segs[40][5]
turn_len = math.sqrt((2*math.pi*R)**2+PITCH**2)
period = 7.812
print(f'controls={h.count+1} segments={h.count} turns={TURNS:.2f} total_arc={h.total:.3f}m')
print(f'control_chord={seg:.5f}m; exact_helix_turn_arc={turn_len:.5f}m; '
f'{period:.3f}m={period/seg:.3f} control chords={period/turn_len:.3f} turns '
f'={period/PITCH:.3f} pitches')
def l37(x, y, z, _variant):
d, i, _t = h.tube(x, y, z, False)
return -d, i, i
def l38_tube(x, y, z, _variant):
d, i, _t = h.tube(x, y, z, True)
return -d, i, i
if not args.actual_only and not args.paired_only and not args.band_only and not args.walkband_only:
for label, fun, variant in [
('L37 smoothed tube', l37, 'none'),
('L38 nearest smoothed tube only', l38_tube, 'none'),
('L38 tube + independently selected floor owner', h.l38_field, 'owner'),
('L38 tube + paired cap with a narrow route-floor band', h.l38_field, 'paired'),
]:
peak, coh_amp, coh_period, switches, samples = audit(h, fun, variant)
print(f'{label}: wall_period={peak[1]:.4f}m local_amp={peak[0]:.5f}m '
f'coherent_period={coh_period:.4f}m coherent_amp={coh_amp:.5f}m '
f'radius_range={peak[3]:.5f}..{peak[4]:.5f}m '
f'owner_switches={switches} samples={samples}')
if not args.synthetic_only:
route_path = 'Saved/Linux/l38/owner-fi-style2-layout.json'
with open(route_path, encoding='utf-8') as source:
passage = json.load(source)['passages'][0]
points = [tuple(v/1024.0 for v in item['position_q'])
for item in passage['control_points']]
actual = Helix(points)
print(f'logged FI Style 2 controls: {len(points)} points, '
f'{actual.total:.3f} m chord arc; audited windows at '
f'{[(round((actual.total-WINDOW)*f, 3)) for f in (0.03, 0.31, 0.457, 0.61, 0.91)]} m')
if not args.paired_only and not args.band_only and not args.walkband_only:
for label, smooth in [('L37 raw tube', False), ('L38 raw tube', True)]:
def actual_field(x, y, z, _variant):
distance, index, _t = actual.tube(x, y, z, smooth)
return -distance, index, index
peak, coh_amp, coh_period, switches, samples = audit(actual, actual_field, 'none')
print(f'actual controls {label}: wall_period={peak[1]:.4f}m '
f'local_amp={peak[0]*100:.3f}cm coherent_period={coh_period:.4f}m '
f'coherent_amp={coh_amp*100:.3f}cm radius_range='
f'{peak[3]:.4f}..{peak[4]:.4f}m samples={samples}')
cap_variants = ([('L38 cap floor owner restricted to 0.55 m walk band', 'walkband')]
if args.walkband_only else
[('L38 cap uses a route floor owner only in its narrow floor band', 'band')]
if args.band_only else
[('L38 cap paired outside route-floor ownership band', 'paired')]
if args.paired_only else
[('L38 cap with independent floor owner', 'owner'),
('L38 cap paired outside route-floor ownership band', 'paired')])
for label, mode in cap_variants:
peak, coh_amp, coh_period, switches, samples = audit(
actual, actual.l38_field, mode)
print(f'actual controls {label}: wall_period={peak[1]:.4f}m '
f'local_amp={peak[0]*100:.3f}cm coherent_period={coh_period:.4f}m '
f'coherent_amp={coh_amp*100:.3f}cm radius_range='
f'{peak[3]:.4f}..{peak[4]:.4f}m samples={samples}')
if args.walkband_only:
floor_max, floor_range = floor_audit(actual, actual.l38_field, 'walkband')
print(f'actual controls narrow-band floor: max_abs_error={floor_max*100:.5f}cm '
f'window_peak_to_peak={floor_range*100:.5f}cm')
if __name__ == '__main__':
main()