FA-93831 / Solar tracker geometry / Open access
Dual-axis pointing error: angular distance metric · case 01
Small commanded offsets across north read as nearly 360 degrees of error.
ROOT CAUSE
The error adds raw azimuth and elevation differences.
VERIFIED REPAIR
Use the great-circle angle.
Unsuccessful approach: A planar hypotenuse still ignores azimuth wrap and convergence toward the zenith.
Case contract
Pointing error is the great-circle angle between the commanded direction and the sun: cos(err) = sin e1 sin e2 + cos e1 cos e2 cos(az1 - az2), clamped to [-1, 1]. Return [err degrees rounded 3, err <= tol].
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(cmd_az, cmd_el, sun_az, sun_el, tol):
e1 = math.radians(cmd_el)
e2 = math.radians(sun_el)
d = math.radians(cmd_az - sun_az)
c = math.sin(e1) * math.sin(e2) + math.cos(e1) * math.cos(e2) * math.cos(d)
c = max(-1.0, min(1.0, c))
err = abs(cmd_az - sun_az) + abs(cmd_el - sun_el)
return [round(err, 3), err <= tol]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [340, 45, 60, 80, 0.5], [44.136, False]],
['regression: angular distance metric (partial repair)', [340, 30, 270, 45, 1], [55.737, False]],
['control 1', [90, 10, 225, 30, 0.5], [121.081, False]],
['control 2', [340, 80, 225, 45, 0.5], [49.874, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [200, 60, 60, 45, 5], [70.03, False]],
['regression: angular distance metric (partial repair)', [180, 60, 0, 60, 0.5], [60.0, False]],
['control 1', [180, 80, 200, 10, 5], [70.628, False]],
['control 2', [45, 60, 60, 80, 1], [20.49, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [135, 60, 45, 45, 5], [52.239, False]],
['regression: angular distance metric (partial repair)', [270, 30, 0, 80, 5], [60.501, False]],
['control 1', [135, 80, 60, 60, 5], [28.915, False]],
['control 2', [60, 30, 315, 10, 0.5], [97.696, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [200, 30, 60, 80, 0.5], [67.839, False]],
['regression: angular distance metric (partial repair)', [0, 45, 250, 60, 2], [60.564, False]],
['control 1', [180, 45, 45, 60, 1], [68.754, False]],
['control 2', [300, 10, 135, 45, 5], [123.357, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [300, 30, 135, 60, 1], [89.155, False]],
['regression: angular distance metric (partial repair)', [270, 30, 250, 30, 5], [17.298, False]],
['control 1', [270, 10, 250, 60, 0.5], [52.187, False]],
['control 2', [300, 30, 180, 30, 2], [97.181, False]]]]
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: on target | [0, True] | [0.0, True] | Passed |
| boundary: across north | [350, False] | [8.658, False] | Failed |
| normal: elevation only | [10, False] | [10.0, False] | Passed |
| regression: angular distance metric | [315, False] | [44.136, False] | Failed |
| regression: angular distance metric (partial repair) | [85, False] | [55.737, False] | Failed |
| control 1 | [155, False] | [121.081, False] | Failed |
| control 2 | [150, False] | [49.874, False] | Failed |
SHA-256 / ef5e92e27a4962b0de412891d0d2cdd1ec1d73de00d09ad641799bc775bb5391
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cmd_az, cmd_el, sun_az, sun_el, tol):
e1 = math.radians(cmd_el)
e2 = math.radians(sun_el)
d = math.radians(cmd_az - sun_az)
c = math.sin(e1) * math.sin(e2) + math.cos(e1) * math.cos(e2) * math.cos(d)
c = max(-1.0, min(1.0, c))
err = math.hypot(cmd_az - sun_az, cmd_el - sun_el)
return [round(err, 3), err <= tol]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [340, 45, 60, 80, 0.5], [44.136, False]],
['regression: angular distance metric (partial repair)', [340, 30, 270, 45, 1], [55.737, False]],
['control 1', [90, 10, 225, 30, 0.5], [121.081, False]],
['control 2', [340, 80, 225, 45, 0.5], [49.874, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [200, 60, 60, 45, 5], [70.03, False]],
['regression: angular distance metric (partial repair)', [180, 60, 0, 60, 0.5], [60.0, False]],
['control 1', [180, 80, 200, 10, 5], [70.628, False]],
['control 2', [45, 60, 60, 80, 1], [20.49, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [135, 60, 45, 45, 5], [52.239, False]],
['regression: angular distance metric (partial repair)', [270, 30, 0, 80, 5], [60.501, False]],
['control 1', [135, 80, 60, 60, 5], [28.915, False]],
['control 2', [60, 30, 315, 10, 0.5], [97.696, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [200, 30, 60, 80, 0.5], [67.839, False]],
['regression: angular distance metric (partial repair)', [0, 45, 250, 60, 2], [60.564, False]],
['control 1', [180, 45, 45, 60, 1], [68.754, False]],
['control 2', [300, 10, 135, 45, 5], [123.357, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [300, 30, 135, 60, 1], [89.155, False]],
['regression: angular distance metric (partial repair)', [270, 30, 250, 30, 5], [17.298, False]],
['control 1', [270, 10, 250, 60, 0.5], [52.187, False]],
['control 2', [300, 30, 180, 30, 2], [97.181, False]]]]
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: on target | [0.0, True] | [0.0, True] | Passed |
| boundary: across north | [350.0, False] | [8.658, False] | Failed |
| normal: elevation only | [10.0, False] | [10.0, False] | Passed |
| regression: angular distance metric | [282.179, False] | [44.136, False] | Failed |
| regression: angular distance metric (partial repair) | [71.589, False] | [55.737, False] | Failed |
| control 1 | [136.473, False] | [121.081, False] | Failed |
| control 2 | [120.208, False] | [49.874, False] | Failed |
SHA-256 / e2bd277b4a5ebedcaa8ba44bfddbb86e4b56537472e3f0d0530d83f1ef3e5849
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cmd_az, cmd_el, sun_az, sun_el, tol):
e1 = math.radians(cmd_el)
e2 = math.radians(sun_el)
d = math.radians(cmd_az - sun_az)
c = math.sin(e1) * math.sin(e2) + math.cos(e1) * math.cos(e2) * math.cos(d)
c = max(-1.0, min(1.0, c))
err = math.degrees(math.acos(c))
return [round(err, 3), err <= tol]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [340, 45, 60, 80, 0.5], [44.136, False]],
['regression: angular distance metric (partial repair)', [340, 30, 270, 45, 1], [55.737, False]],
['control 1', [90, 10, 225, 30, 0.5], [121.081, False]],
['control 2', [340, 80, 225, 45, 0.5], [49.874, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [200, 60, 60, 45, 5], [70.03, False]],
['regression: angular distance metric (partial repair)', [180, 60, 0, 60, 0.5], [60.0, False]],
['control 1', [180, 80, 200, 10, 5], [70.628, False]],
['control 2', [45, 60, 60, 80, 1], [20.49, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [135, 60, 45, 45, 5], [52.239, False]],
['regression: angular distance metric (partial repair)', [270, 30, 0, 80, 5], [60.501, False]],
['control 1', [135, 80, 60, 60, 5], [28.915, False]],
['control 2', [60, 30, 315, 10, 0.5], [97.696, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [200, 30, 60, 80, 0.5], [67.839, False]],
['regression: angular distance metric (partial repair)', [0, 45, 250, 60, 2], [60.564, False]],
['control 1', [180, 45, 45, 60, 1], [68.754, False]],
['control 2', [300, 10, 135, 45, 5], [123.357, False]]],
[['normal: on target', [180, 45, 180, 45, 1], [0.0, True]],
['boundary: across north', [355, 30, 5, 30, 1], [8.658, False]],
['normal: elevation only', [90, 30, 90, 40, 5], [10.0, False]],
['regression: angular distance metric', [300, 30, 135, 60, 1], [89.155, False]],
['regression: angular distance metric (partial repair)', [270, 30, 250, 30, 5], [17.298, False]],
['control 1', [270, 10, 250, 60, 0.5], [52.187, False]],
['control 2', [300, 30, 180, 30, 2], [97.181, False]]]]
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: on target | [0.0, True] | [0.0, True] | Passed |
| boundary: across north | [8.658, False] | [8.658, False] | Passed |
| normal: elevation only | [10.0, False] | [10.0, False] | Passed |
| regression: angular distance metric | [44.136, False] | [44.136, False] | Passed |
| regression: angular distance metric (partial repair) | [55.737, False] | [55.737, False] | Passed |
| control 1 | [121.081, False] | [121.081, False] | Passed |
| control 2 | [49.874, False] | [49.874, False] | Passed |
SHA-256 / 12bb03d0c21a4981b67e4921fe29bc28d7681b5910c06e863cbd6311c1471a8e
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.747760+00:00.
Case digest / 0cc9f01ee04ef8584ae09770c2c41980ea48c8357979d18e9e7d27df6e9580ed