I have written a few "trackers" in my time, the simplest probably being a solar MPPT algo. They come up from time to time.
One aspect of them is their "stability" if that is the right term. Like an opamp, for example, an MPPT tracker algo will "hunt" and keep hunting until it gets the response it expects or it hits it's limiter.
Today I'm bored and trying to solve a control algo which cannot behave that way. If the metric in question is offset from target a single response should be made. That response should be deterministic.
Ideally, but throwing a gantlet for myself, it should also be stateless, idempotent and thread safe.
Problem:
In a game, "Nucleares", I have a super-simplified nuclear reactor. A key metric is "Iodine Generation". It is the closest thing we have on the game webservice to actual "power factor" or "flux level". Iodine generation is proportional to reactivity. Layman... it's the power the engine is delivering. For purposes here, if iodine generation is above target, the reactor is reacting too much and the algo should respond to lower it and vice versa.
The throttle, in this specific case, is the control rods. Fairly unsurprising that. There are alternatives, but the only thing that would change, in context of the algo with those are the "feedback delay" time.
Solution:
For a given target iodine generation rate the algo will compare this target to the current generation rate. It shall then produce a "single response" with a corresponding rod movement to correct the difference. It should not "chase" or "hunt". A single movement of "Iodine generation" should result in a single move of the rods.
Scrap book attempt 1:
def calculate_rod_delta(iodine):
offset = iodine - iodine_tgt
offset = offset * 5 # rods move 0.1% for each 0.02 iodine offset
return offset
This gives the correct rod delta. When looped however it highlights a problem. As it deals in "rod delta" only and there is a lag time from commanding the rod move, to them moving, to the reactor responding, to the iodine increase being detected... it will continue to send "-0.1"..."-0.1"..."-0.1" until the reactor lag time completes, by which time it will have moved the rods 2 steps too far. It will recoil back by "+0.2"... "+0.2" and ... we have a rapidly accelerating oscillation. It's an opamp turns RF emitter bouncing rail to rail.
The practicalist in me says to just increase the period to at least twice the maximum lag time. Make the algo wait for 20 seconds following any action. I call this "termporal hysteresis" and it's a cheap cheat I have used many times. I want to explore other ways.
I figured here would be a good place to get advise as people here understand things like "signal theory" and "impulse response", "damping" etc from EE degrees.
I believe I am looking for a highly damped algo or is that "critically damped" in that it will attempt to correct without any oscillation within a single "period".
Anyway. One approach is to split the algo into three phases. "Wait offset", "Make change", "Await response"
So if the iodine drops by the minimum amount for a control rod step (0.02), the algo sends the "rods-0.1%" command and then goes into a new loop awaiting change. If that change takes longer than a timeout or if that change occurs in the wrong direction, flow will be released back to "Make Change". If it occurs in the expected magnitude (required rod delta goes to 0) flow is returned to "Wait offset".
Pigeon code:
BEGIN
WHILE offset = 0:
read iodine
offset = iodine - iodine_target * scale_alignment_value
WRITE rod_movement with offset
WHILE offset != 0:
read iodine
offset = iodine - iodine_target * scale_alignment_value
LOOP
It is making the assumption its at 0 offset when started, but that can be addressed.
Now... is there a fancy way to do this mathematically instead of iteratively.... or a way to combine the two loops into one with some fancy FIR/IIR filter wizardry?