FA-93741 / Solar tracker geometry / Open access
Module edge ground clearance limit: half module width · case 01
Rotation limits are far too restrictive and clearances too small.
ROOT CAUSE
The full module width is used as the lever arm about the hub.
VERIFIED REPAIR
Use half the module width.
Unsuccessful approach: Floor division drops half a millimetre for odd module widths.
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
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: half module width', [20, 2500, 2000, 2600, -10], [-0.0, 2323.7]],
['regression: half module width (partial repair)', [-60, 1500, 1133, 500, 0], [-60, 1009.4]],
['control 1', [-45, 1200, 2384, 300, -10], [-45, 208.5]],
['control 2', [0, 1500, 1133, 2600, -10], [-0.0, 1400.1]]],
[['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: half module width', [-30, 1500, 1133, 1300, 0], [-20.674, 1300.0]],
['regression: half module width (partial repair)', [60, 2000, 1133, 500, 0], [60, 1509.4]],
['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: half module width', [45, 1200, 2256, 300, 0], [45, 402.4]],
['regression: half module width (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: half module width', [45, 2000, 4000, 500, 0], [45, 585.8]],
['regression: half module width (partial repair)', [0, 2500, 1133, 300, -5], [0, 2450.4]],
['control 1', [60, 2000, 1133, 2600, -5], [-0.0, 1950.4]],
['control 2', [45, 1200, 1133, 300, 3], [45, 778.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: half module width', [20, 2500, 2384, 300, -10], [20, 2289.8]],
['regression: half module width (partial repair)', [60, 1500, 1133, 1300, 8], [20.674, 1225.5]],
['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 | [26.312, 500.0] | [60, 523.1] | Failed |
| boundary: hub below required clearance | [-0.0, 1200.0] | [-0.0, 1200.0] | Passed |
| boundary: flat on sloped ground | [0, 1325.0] | [0, 1412.5] | Failed |
| regression: half module width | [-0.0, 2147.3] | [-0.0, 2323.7] | Failed |
| regression: half module width (partial repair) | [-60, 518.8] | [-60, 1009.4] | Failed |
| control 1 | [-22.18, -89.3] | [-45, 208.5] | Failed |
| control 2 | [-0.0, 1300.2] | [-0.0, 1400.1] | Failed |
SHA-256 / 1e77d782e328380f5fda6a07122ac0d9a83a169efde9c9080e3039da39a067f9
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 = 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: half module width', [20, 2500, 2000, 2600, -10], [-0.0, 2323.7]],
['regression: half module width (partial repair)', [-60, 1500, 1133, 500, 0], [-60, 1009.4]],
['control 1', [-45, 1200, 2384, 300, -10], [-45, 208.5]],
['control 2', [0, 1500, 1133, 2600, -10], [-0.0, 1400.1]]],
[['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: half module width', [-30, 1500, 1133, 1300, 0], [-20.674, 1300.0]],
['regression: half module width (partial repair)', [60, 2000, 1133, 500, 0], [60, 1509.4]],
['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: half module width', [45, 1200, 2256, 300, 0], [45, 402.4]],
['regression: half module width (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: half module width', [45, 2000, 4000, 500, 0], [45, 585.8]],
['regression: half module width (partial repair)', [0, 2500, 1133, 300, -5], [0, 2450.4]],
['control 1', [60, 2000, 1133, 2600, -5], [-0.0, 1950.4]],
['control 2', [45, 1200, 1133, 300, 3], [45, 778.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: half module width', [20, 2500, 2384, 300, -10], [20, 2289.8]],
['regression: half module width (partial repair)', [60, 1500, 1133, 1300, 8], [20.674, 1225.5]],
['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: half module width | [-0.0, 2323.7] | [-0.0, 2323.7] | Passed |
| regression: half module width (partial repair) | [-60, 1009.8] | [-60, 1009.4] | Failed |
| control 1 | [-45, 208.5] | [-45, 208.5] | Passed |
| control 2 | [-0.0, 1400.2] | [-0.0, 1400.1] | Failed |
SHA-256 / 46166dd0cc8a9eea41678f17940a4ec05fd25dd0b3c4406b392c5c6942ff7aa2
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: half module width', [20, 2500, 2000, 2600, -10], [-0.0, 2323.7]],
['regression: half module width (partial repair)', [-60, 1500, 1133, 500, 0], [-60, 1009.4]],
['control 1', [-45, 1200, 2384, 300, -10], [-45, 208.5]],
['control 2', [0, 1500, 1133, 2600, -10], [-0.0, 1400.1]]],
[['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: half module width', [-30, 1500, 1133, 1300, 0], [-20.674, 1300.0]],
['regression: half module width (partial repair)', [60, 2000, 1133, 500, 0], [60, 1509.4]],
['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: half module width', [45, 1200, 2256, 300, 0], [45, 402.4]],
['regression: half module width (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: half module width', [45, 2000, 4000, 500, 0], [45, 585.8]],
['regression: half module width (partial repair)', [0, 2500, 1133, 300, -5], [0, 2450.4]],
['control 1', [60, 2000, 1133, 2600, -5], [-0.0, 1950.4]],
['control 2', [45, 1200, 1133, 300, 3], [45, 778.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: half module width', [20, 2500, 2384, 300, -10], [20, 2289.8]],
['regression: half module width (partial repair)', [60, 1500, 1133, 1300, 8], [20.674, 1225.5]],
['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: half module width | [-0.0, 2323.7] | [-0.0, 2323.7] | Passed |
| regression: half module width (partial repair) | [-60, 1009.4] | [-60, 1009.4] | Passed |
| control 1 | [-45, 208.5] | [-45, 208.5] | Passed |
| control 2 | [-0.0, 1400.1] | [-0.0, 1400.1] | Passed |
SHA-256 / f479bbb93ed31cd347f81da9f6a48442b05ccdb8f8486b270b196b897db6f487
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.059690+00:00.
Case digest / cabec349c1aedaba7bec21a8c32c2e2929f67cf4bcff59c17ad4ae6050ac8758