FA-93746 / Solar tracker geometry / Open access
Module edge ground clearance limit: hub below clearance · case 01
A tracker mounted too low is forced to a steep angle.
ROOT CAUSE
The case where the hub is below the required clearance is not special-cased, so a negative limit inverts the clamp.
VERIFIED REPAIR
Hold the tracker flat when h <= clear.
Unsuccessful approach: Treating it as unconstrained drives the edge into the ground.
Case contract
Hub height h and module width w in mm. Maximum rotation keeps the low edge at least clear above flat ground: asin((h - clear)/(w/2)), 90 when that ratio >= 1, 0 when <= 0. The command is theta clamped to +-max. Reported clearance is h - (w/2)|sin cmd| minus the ground rise under the low edge, side*(w/2)cos(cmd)tan(slope), where side is the sign of cmd or, at 0, the sign of slope (>= 0 counts positive). Return [cmd rounded 3, clearance rounded 1].
Why this case matters
Single-axis and dual-axis solar trackers turn a sun direction into actuator commands; a sign, frame or limit mistake points a whole plant away from the sun or into a mechanical stop.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(theta, h, w, clear, slope):
half = w / 2
lim = (h - clear) / half
if lim >= 1:
amax = 90.0
else:
amax = math.degrees(math.asin(lim))
cmd = max(-amax, min(amax, theta))
r = math.radians(cmd)
low = h - half * abs(math.sin(r))
if cmd != 0:
side = 1 if cmd > 0 else -1
else:
side = 1 if slope >= 0 else -1
ground = side * half * math.cos(r) * math.tan(math.radians(slope))
return [round(cmd, 3), round(low - ground, 1)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [20, 2500, 2000, 2600, -10], [-0.0, 2323.7]],
['regression: hub below clearance (partial repair)', [20, 1200, 2000, 1300, 3], [-0.0, 1147.6]],
['control 1', [-45, 1200, 2384, 300, -10], [-45, 208.5]],
['control 2', [45, 1500, 2256, 2600, 0], [-0.0, 1500.0]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [20, 2500, 2000, 2600, 0], [-0.0, 2500.0]],
['regression: hub below clearance (partial repair)', [20, 2500, 2256, 2600, 8], [-0.0, 2341.5]],
['control 1', [20, 2000, 2256, 500, -10], [20, 1801.1]],
['control 2', [0, 2500, 2000, 300, -5], [0, 2412.5]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [0, 2000, 2384, 2600, 3], [-0.0, 1937.5]],
['regression: hub below clearance (partial repair)', [20, 2500, 1133, 2600, 8], [-0.0, 2420.4]],
['control 1', [60, 2500, 1133, 1300, 8], [60, 1969.6]],
['control 2', [20, 1200, 2000, 1300, 3], [-0.0, 1147.6]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [45, 1200, 2384, 1300, 0], [-0.0, 1200.0]],
['regression: hub below clearance (partial repair)', [-30, 1200, 4000, 2600, 0], [-0.0, 1200.0]],
['control 1', [45, 1200, 1133, 300, 3], [45, 778.4]],
['control 2', [-45, 2000, 2000, 1300, 8], [-44.427, 1400.4]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [60, 1200, 4000, 1300, -5], [-0.0, 1025.0]],
['regression: hub below clearance (partial repair)', [-45, 2500, 1133, 2600, 0], [-0.0, 2500.0]],
['control 1', [-30, 1500, 2384, 500, -5], [-30, 813.7]],
['control 2', [-30, 1200, 4000, 2600, 0], [-0.0, 1200.0]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| normal: rotation limited by clearance | [60, 523.1] | [60, 523.1] | Passed |
| boundary: hub below required clearance | [5.739, 1100.0] | [-0.0, 1200.0] | Failed |
| boundary: flat on sloped ground | [0, 1412.5] | [0, 1412.5] | Passed |
| regression: hub below clearance | [5.739, 2575.4] | [-0.0, 2323.7] | Failed |
| regression: hub below clearance (partial repair) | [5.739, 1047.9] | [-0.0, 1147.6] | Failed |
| control 1 | [-45, 208.5] | [-45, 208.5] | Passed |
| control 2 | [77.207, 400.0] | [-0.0, 1500.0] | Failed |
SHA-256 / cef44f0b8180d2bfa6928d2df8dd39e220138cceed0bc4b1530ba3f376328b6f
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(theta, h, w, clear, slope):
half = w / 2
lim = (h - clear) / half
if lim >= 1:
amax = 90.0
elif lim <= 0:
amax = 90.0
else:
amax = math.degrees(math.asin(lim))
cmd = max(-amax, min(amax, theta))
r = math.radians(cmd)
low = h - half * abs(math.sin(r))
if cmd != 0:
side = 1 if cmd > 0 else -1
else:
side = 1 if slope >= 0 else -1
ground = side * half * math.cos(r) * math.tan(math.radians(slope))
return [round(cmd, 3), round(low - ground, 1)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [20, 2500, 2000, 2600, -10], [-0.0, 2323.7]],
['regression: hub below clearance (partial repair)', [20, 1200, 2000, 1300, 3], [-0.0, 1147.6]],
['control 1', [-45, 1200, 2384, 300, -10], [-45, 208.5]],
['control 2', [45, 1500, 2256, 2600, 0], [-0.0, 1500.0]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [20, 2500, 2000, 2600, 0], [-0.0, 2500.0]],
['regression: hub below clearance (partial repair)', [20, 2500, 2256, 2600, 8], [-0.0, 2341.5]],
['control 1', [20, 2000, 2256, 500, -10], [20, 1801.1]],
['control 2', [0, 2500, 2000, 300, -5], [0, 2412.5]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [0, 2000, 2384, 2600, 3], [-0.0, 1937.5]],
['regression: hub below clearance (partial repair)', [20, 2500, 1133, 2600, 8], [-0.0, 2420.4]],
['control 1', [60, 2500, 1133, 1300, 8], [60, 1969.6]],
['control 2', [20, 1200, 2000, 1300, 3], [-0.0, 1147.6]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [45, 1200, 2384, 1300, 0], [-0.0, 1200.0]],
['regression: hub below clearance (partial repair)', [-30, 1200, 4000, 2600, 0], [-0.0, 1200.0]],
['control 1', [45, 1200, 1133, 300, 3], [45, 778.4]],
['control 2', [-45, 2000, 2000, 1300, 8], [-44.427, 1400.4]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [60, 1200, 4000, 1300, -5], [-0.0, 1025.0]],
['regression: hub below clearance (partial repair)', [-45, 2500, 1133, 2600, 0], [-0.0, 2500.0]],
['control 1', [-30, 1500, 2384, 500, -5], [-30, 813.7]],
['control 2', [-30, 1200, 4000, 2600, 0], [-0.0, 1200.0]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| normal: rotation limited by clearance | [60, 523.1] | [60, 523.1] | Passed |
| boundary: hub below required clearance | [30, 700.0] | [-0.0, 1200.0] | Failed |
| boundary: flat on sloped ground | [0, 1412.5] | [0, 1412.5] | Passed |
| regression: hub below clearance | [20, 2323.7] | [-0.0, 2323.7] | Failed |
| regression: hub below clearance (partial repair) | [20, 808.7] | [-0.0, 1147.6] | Failed |
| control 1 | [-45, 208.5] | [-45, 208.5] | Passed |
| control 2 | [45, 702.4] | [-0.0, 1500.0] | Failed |
SHA-256 / 5e852c402fff666623f0880a3182ec9d197af5f359d1543684a3284399747264
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(theta, h, w, clear, slope):
half = w / 2
lim = (h - clear) / half
if lim >= 1:
amax = 90.0
elif lim <= 0:
amax = 0.0
else:
amax = math.degrees(math.asin(lim))
cmd = max(-amax, min(amax, theta))
r = math.radians(cmd)
low = h - half * abs(math.sin(r))
if cmd != 0:
side = 1 if cmd > 0 else -1
else:
side = 1 if slope >= 0 else -1
ground = side * half * math.cos(r) * math.tan(math.radians(slope))
return [round(cmd, 3), round(low - ground, 1)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [20, 2500, 2000, 2600, -10], [-0.0, 2323.7]],
['regression: hub below clearance (partial repair)', [20, 1200, 2000, 1300, 3], [-0.0, 1147.6]],
['control 1', [-45, 1200, 2384, 300, -10], [-45, 208.5]],
['control 2', [45, 1500, 2256, 2600, 0], [-0.0, 1500.0]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [20, 2500, 2000, 2600, 0], [-0.0, 2500.0]],
['regression: hub below clearance (partial repair)', [20, 2500, 2256, 2600, 8], [-0.0, 2341.5]],
['control 1', [20, 2000, 2256, 500, -10], [20, 1801.1]],
['control 2', [0, 2500, 2000, 300, -5], [0, 2412.5]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [0, 2000, 2384, 2600, 3], [-0.0, 1937.5]],
['regression: hub below clearance (partial repair)', [20, 2500, 1133, 2600, 8], [-0.0, 2420.4]],
['control 1', [60, 2500, 1133, 1300, 8], [60, 1969.6]],
['control 2', [20, 1200, 2000, 1300, 3], [-0.0, 1147.6]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [45, 1200, 2384, 1300, 0], [-0.0, 1200.0]],
['regression: hub below clearance (partial repair)', [-30, 1200, 4000, 2600, 0], [-0.0, 1200.0]],
['control 1', [45, 1200, 1133, 300, 3], [45, 778.4]],
['control 2', [-45, 2000, 2000, 1300, 8], [-44.427, 1400.4]]],
[['normal: rotation limited by clearance', [60, 1500, 2256, 500, 0], [60, 523.1]],
['boundary: hub below required clearance', [30, 1200, 2000, 1300, 0], [-0.0, 1200.0]],
['boundary: flat on sloped ground', [0, 1500, 2000, 500, -5], [0, 1412.5]],
['regression: hub below clearance', [60, 1200, 4000, 1300, -5], [-0.0, 1025.0]],
['regression: hub below clearance (partial repair)', [-45, 2500, 1133, 2600, 0], [-0.0, 2500.0]],
['control 1', [-30, 1500, 2384, 500, -5], [-30, 813.7]],
['control 2', [-30, 1200, 4000, 2600, 0], [-0.0, 1200.0]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| normal: rotation limited by clearance | [60, 523.1] | [60, 523.1] | Passed |
| boundary: hub below required clearance | [-0.0, 1200.0] | [-0.0, 1200.0] | Passed |
| boundary: flat on sloped ground | [0, 1412.5] | [0, 1412.5] | Passed |
| regression: hub below clearance | [-0.0, 2323.7] | [-0.0, 2323.7] | Passed |
| regression: hub below clearance (partial repair) | [-0.0, 1147.6] | [-0.0, 1147.6] | Passed |
| control 1 | [-45, 208.5] | [-45, 208.5] | Passed |
| control 2 | [-0.0, 1500.0] | [-0.0, 1500.0] | Passed |
SHA-256 / e34ddd64f193b7b445faca9a05336d39fd4fdb987045b59c9c1c0a6c08a8568a
Verification & scope
Deterministic stipulated toy contract for teaching; no claim of conformance with any standard, vendor protocol or production controller. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:51:58.058299+00:00.
Case digest / fca66a8ccb7774a1823089b1baa3688a5dc41a839df8ef3357aba38812678370