A small two-rotor drone, seen from the front, must hold still at a marked point in a hall while doors open and gusts push it sideways. It can only push along its own axis, so to move sideways it must tilt; to tilt it must run one rotor harder than the other. Every 0.02 seconds the simulator calls
controller(t, x, z, angle, vx, vz, omega) -> (left, right)
with the time t (0, 0.02, ...), the drone's sideways position x and height z measured from the hold point (m; x positive to the right, z positive up), its tilt angle (rad, positive when it leans right, so its thrust pushes it right), and their rates vx, vz (m/s) and omega (rad/s). Return the thrusts of the left and right rotors in newtons; each is limited to 0..6 N.
The physics. Mass m = 0.5 kg, moment of inertia I = 0.004 kg m², rotor arm L = 0.12 m, g = 9.81, air drag 0.1 N per m/s. Each test changes m and I by up to 10 % (you are not told). With T = left + right and the wind force F (N), the simulator computes
x_acc = (T * sin(angle) + F - 0.1 * vx) / m
z_acc = (T * cos(angle) - 0.1 * vz) / m - g
angle_acc = L * (left - right) / I
and moves the state on in four steps of 0.005 s, each vx += 0.005*x_acc; vz += 0.005*z_acc; omega += 0.005*angle_acc, then x += 0.005*vx; z += 0.005*vz; angle += 0.005*omega, holding your thrusts for the whole 0.02 s. Hovering takes m * g / 2, about 2.45 N, on each rotor. The readings are noisy (about 5 mm on the positions, 0.002 rad on the tilt, 0.01 on the rates). The drone starts at rest up to 0.5 m to the side, 0.3 m above or below the point and 0.1 rad from level. Two gusts blow sideways: one of 0.3 to 0.6 N starting between 2 and 4 s and lasting 1 to 2 s, and one of 0.2 to 0.5 N starting between 6 and 8 s and lasting 0.5 to 1.5 s.
The run lasts 10 s (500 calls). It ends early if the drone tips over (|angle| > 1 rad) or leaves the flying area (|x| > 3 m, z < -1 m, which is the floor, or z > 3 m).
Each test calls run_hover(controller, seed) (defined for you; try it with Run), which returns a summary such as
{"crashed_at": None, "rms_pos": 0.1288, "rms_effort": 0.1037, "cost": 1.6709, "max_tilt_deg": 5.85}
for the hand-tuned controller described below on seed 1. Your function may keep state in global variables; t == 0 marks the start of a new run. numpy is loaded for this problem.
How this problem is scored
- A test passes when the drone neither tips over nor leaves the flying area.
- The run's cost is
100 * rms_pos ** 2 + rms_effort ** 2, whererms_posis the root mean square over all steps of the distance from the hold point (sqrt(x**2 + z**2), in m) andrms_effortthe root mean square of how far the thrusts are from hovering (sqrt((left - h)**2 + (right - h)**2)withh = m * g / 2for this test's mass, in N). So 10 cm off counts like 1 N of extra thrust. - Its quality is
100 * ln(15 / cost) / ln(75), kept between 0 and 100: a cost of 15 scores 0, a cost of 0.2 scores 100. - A hand-tuned state feedback,
total = 0.5*9.81 - 4.5*z - 2.4*vzanddifference = -(3*x + 2.5*vx + 7.5*angle + 0.7*omega)shared asleft = (total + difference) / 2,right = (total - difference) / 2, averages about 55. An LQR design with weights that match the cost reaches the mid 80s.
Constraints
- return a pair of numbers; the result must not depend on the clock; compute any gains once (at
t == 0, or when your code loads), not in every call
Goals
- Stabilise a drone that has to tilt to move sideways, using feedback from all six states
- Linearise the stated equations about hover and turn them into a sampled model x[k+1] = A x[k] + B u[k]
- Design an LQR gain whose weights match the score, and beat a hand-tuned design clearly