Negative impulse input shaper

As I am experimenting with custom input shapers, one idea that I saw from Singhose et al., 2004 was the use of negative impulse to reduce shaper duration and improve settling time. In theory, as long as the difference between positive and negative impulse is limited, it should be applicable since the stepper can accelerate in either direction.

I wonder if anyone else have done similar experiment in Klipper and would like to share their experience?


Yes, I’ve experimented a bit with this. The general thing about these input shapers is that they become a lot more sensitive to correct measurement and configuration of the resonance frequency (and damping ratio for that matter). They also amplify the vibrations at some frequencies, and so may increase the secondary resonances on some printers.

If you think about it, ZV shaper already fairly poorly negates vibrations, and this is, arguably, the shortest shaper with all-positive coefficients. Negative-ZV is shorter, but it then negates the resonances in an even more narrow band. Then there is Negative-ZVD, but for reasonable practical values of the negative coefficient the MZV shaper is shorter (AFAIR) and may arguably be better (though negative-ZVD may be better for some printers than MZV). So, I think these are the shapers not for a general use. However, they may be a good fit for some printers, and even companies and groups who design the printers themselves, because they may be able to design them in such a way that negative-valued shapers will behave predictably well for them, and they may be able to adapt the tuning process to produce good results. And they may be a good playground for knowledgeable individuals who know what they are doing.

I was mainly intrigued by the possibility of designing a shaper with duration of MZV or less, maybe 0.6 to 0.65 resonance frequency, but with the insensitivity of MZV or close to ZVD. The possibility of exciting high frequency is something that I’m not sure can become a problem in practice, hence the question.

I am still running simulations of different ideas and have not landed on a good candidate yet.

So I simulated a few variants of negative impulse ZV, but to my surprise the “smoothing” score is worse than reference ZV implementation even though the negative impulse ZV is of smaller total duration. Maybe I am misunderstanding something here. If you don’t mind, could you share some intuition behind get_shaper_smoothing?

Here’s the test shaper for f=55Hz and zeta=0.065. This is manually approximated, so the residual vibration is not exactly zero but the curve looks correct.

def get_nzv_shaper(shaper_freq, damping_ratio):
    df = math.sqrt(1. - damping_ratio**2)
    K2 = math.exp(-.33 * damping_ratio * math.pi / df)
    K3 = math.exp(-.66 * damping_ratio * math.pi / df)
    t_d = 1. / (shaper_freq * df)

    a1 = 1.
    a2 = - K2
    a3 = K3

    A = [a1, a2, a3]
    T = [0., (0.33/2 + 0.0023)*t_d, (.66/2 + 0.0035)*t_d]
    return (A, T)

Looks like it is trying to shift the impulses to center the response at t=0. Then calculate the distance traveled between t=0 and the last impulse?

get_shaper_smoothing calculates the maximum deviation of the commanded vs the expected trajectory on 90 degrees and 180 degrees turns, giving kind of an estimate on how much the toolhead could deviate from the expected trajectory. Edit: maximum deviation - what I mean here is the distance between the angle tip and the closest point of the commanded trajectory for it. For 180 degrees turn it is easy to show that the closest point is at t=0 (with IS shifted accordingly). Strictly speaking, for 90 degrees angle the closest point is not at t=0, but the difference isn’t very large. Still, the precise time point could be calculated (it will depend on scv, accel, and the shaper pulses themselves), it’s just wasn’t done.

The code calculates the middle time point for the shaper and offsets the pulses accordingly, and it also normalizes the coefficients, so that should be all good. However, I think the current code in Klipper mainline does not handle negative coefficients correctly. The correct snippet would be (currently fixed only in a branch on my github) :

        # Calculate offset for 90 and 180 degrees turn
        offset_90_x = offset_90_y = offset_180 = 0.
        for i in range(n):
            if T[i] >= ts:
                # Calculate offset for one of the axes
                offset_90_x += A[i] * (scv + half_accel * (T[i]-ts)) * (T[i]-ts)
                offset_90_y += A[i] * (scv - half_accel * (T[i]-ts)) * (T[i]-ts)
            offset_180 += A[i] * half_accel * (T[i]-ts)**2
        offset_90 = inv_D * math.sqrt(offset_90_x**2 + offset_90_y**2)
        offset_180 *= inv_D
        return max(offset_90, abs(offset_180))

So, I think it should work, but it wasn’t very well tested. But do give it a try.

FWIW, there are formulas for NZV in the closed form, so no need for an approximation (although you could do a numeric optimization to get a bit shorter shaper, but in practice I’d say it doesn’t make a lot of sense). BTW, MZV is basically a ZV shaper with a duration of 0.75 * t_d.

Thanks for the updated smoothing formula, the results look good.

Using scipy.optimize, I have been able to calculate the shaper coefficient for SNA (specified negative amplitude) shapers. This way, it should be possible to experiment with different trade-off between shaper duration and high-mode excitation. I’m also not sure if larger negative impulse can interact with the stepper in a bad way.

So far, I have been having good results near the limit of the ZV shaper. I think it only works for relatively rigid motion system with consistent resonance across time and space. Also, it looks like for anything faster than MZV, damping factor estimation is no longer optional.

If anyone is interested, here’s the code. No support will be provided, and I don’t expect this in mainline. Also, I have only run simulations, not physically tested.

def get_sna_shaper(na, shaper_freq, damping_ratio):
    df = math.sqrt(1. - damping_ratio**2)
    omega_n = 2 * math.pi * shaper_freq
    omega_d = omega_n * df
    zon = damping_ratio * omega_n # zeta omega n
    t_d = 1. / (shaper_freq * df)

    a = (1 + na) / 2
    A = [a, -na, a]

    fun = lambda x: x[2] # optimize for shortest duration
    guess = [0, 0.2*t_d, 0.4*t_d]
    bnds = ((0,0), (0, t_d), (0, t_d))
    # sin of first impulse is always zero, cos of first impulse = a
    cons = ({'type': 'eq', 'fun': lambda x: - na * math.exp(zon * x[1]) * math.sin(x[1] * omega_d) + a * math.exp(zon * x[2]) * math.sin(x[2] * omega_d)},
            {'type': 'eq', 'fun': lambda x: a  - na * math.exp(zon * x[1]) * math.cos(x[1] * omega_d) + a * math.exp(zon * x[2]) * math.cos(x[2] * omega_d)})
    res = scipy.optimize.minimize(fun, guess, method='SLSQP', bounds=bnds, constraints=cons)

    (t0, t1, t2) = res['x']
    T = [t0, t1, t2]
    return (A, T)

def get_sna05_shaper(shaper_freq, damping_ratio):
    return get_sna_shaper(0.5, shaper_freq, damping_ratio)

def get_umzv_shaper(shaper_freq, damping_ratio):
    return get_sna_shaper(1.0, shaper_freq, damping_ratio)


I had been able to test the SNA -0.5 shaper physically and so far I have not seen any issue that can be attributed to high frequency excitation.

However, I have not been able to conclusively prove that it reduces smoothing. I find the testing methodology using ringing tower stl with 0.15 gap is difficult to interpret. Around 10k acceleration, the test becomes very sensitive to pressure advance and flow rate changes and not so much on the type of input shaper used.

Perhaps the gantry is now much stiffer than the extrusion system…