-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: filt improvements #7513
RFC: filt improvements #7513
Conversation
This redefines `filt` as `filt!` with a few modifications to perform its filtering in-place. `filt` is defined as a simple wrapper, copying the vectors that `filt!` modifies. Further, both methods now allow an optional argument for the initial filter state vector. This vector is modified in-place in `filt!`.
Compute an initial state for filt such that its response to a step function is steady state.
I haven't looked this over in detail, but just a couple of comments:
|
Ah, yes, I got a little overzealous in setting the initial conditions. Setting them like this is a common thing to do in |
This reverts commit 19ba34c.
@mbauman, this is off-topic, but what are your in-terminal Gadfly settings? These plots look great. I have the iTerm plotting setup but I haven't tweaked the settings at all so it still has a light background. |
@mbauman, absolutely, setting filter state would be a big improvement. I would just use a default that preserves current behavior. |
PR updated, should be good to review at this point. @StefanKarpinski Here's my .juliarc.jl. The Gadfly theme and its swapping are pilfered from @Keno. It's a little messy, but it works for now. |
This looks good to me, but I think there may be some value to @timholy's suggestion of making |
Exactly. The point being that calling When the output array is different from the input array, you might think there should be two cache misses because you're traversing two arrays. At least for the experiments I've run, that doesn't seem to cost you any extra time---I think hardware prefetching is getting pretty good 😄, and it fetches the two regions in parallel, so you only pay the price of a cache miss once. Might be worth running some timing experiments to check this explicitly, though, before you go to a lot of effort to rework this. |
Just like as scale! allows a pre-allocated argument for scaling a matrix by a vector, this allows specifying a third array as the output for scaling by scalars.
- No longer modifies the filter state in place, instead uses a copy
Ah, very clever. I didn't quite catch on to the power of using two arguments for this from the first comment. Thank you both for running the performance tests and your advice. I have updated the PR with your suggestions, and it works wonderfully. I no longer modify the state vector in-place to minimize surprises (and it makes for a simpler API/doc). |
If we get it along with this PR, I don't see a need to make it a separate PR. |
# (and does so by default) | ||
function filt!{T<:Number}(b::Union(AbstractVector{T}, T), a::Union(AbstractVector{T}, T), | ||
x::AbstractVector{T}, si::AbstractVector{T}=zeros(T, max(length(a), length(b))-1), | ||
out::AbstractVector{T}=x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move out
to be the first argument? I understand that then it can't be optional, but I think it's worth it for consistency, since most mutating functions in Base take the output as their first argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing. This strikes me as a good use-case for keyword arguments as their overhead will likely be insignificant compared to the actual filtering. I'd like to preserve the ability to call filt!(b,a,x)
(which I realize bucks that convention… we can ax it if you like). See my update — does that look good to you?
Add a secondary method with keyword arguments to allow si and the output array to be optional.
Thanks for the guidance, @simonster and @timholy. |
Thanks for implementing this! |
👍 |
Could we bring that feature back? It would be useful to be able to chain function calls like such:
In linear predictive coding, one typically varies the vocal tract parameters (and the resulting IIR filter) every 10-20ms. If you don't tell me what to put in filter_state, I have to reproduce it from scratch by refiltering x[n] for the last filter_order samples. Alternatively, instead of having By the way, excellent work. :) |
Interesting, and sounds reasonable. Do you mind opening a new issue with this request? I'm not sure if anyone is depending upon the initial state vector not getting modified, and I've not done much with DSP lately. DSP.jl has |
I've made a handful of improvements and extensions to the
filt
function on the way to addingfiltfilt
to DSP.jl.filt
asfilt!
with a few modifications to perform its filtering in-place.filt
is defined as a simple wrapper, copying the vectors thatfilt!
modifies.filt
andfilt!
now accept an optional fourth argument argument for the initial filter state vector.This vector is modified in-place infilt!
filt!
takes an optional fifth argument for its output array. This can alias the input, and it does so by default.The default filter state is now automatically computed such that its response to a step function is steady state.Here's an example of why allowing that third part is important. This is a 5 pole low-pass butterworth filter from DSP.jl. In red is
the old implementationan initial state of 0s, and in blue isthe new defaulta matched initial state:A few questions:
This changes the output of filt. I think that this is the "right" default. But it will change results. Is that ok? I am not a digital filter guru; I just use them a lot. Does this make sense to do in other applications? Edit: Hm. Thinking about this more, I don't think this should happen in the base filt function. I can roll back the second commit, and it can become part of a more domain-specific package, like DSP.jl.Licensing: I was looking at SciPy's implementation as I worked on this. But the only part that actually comes from SciPy is the 3-line algorithm for solving the initial state. SciPy is 3-clause BSD, but surely that algorithm is published somewhere and/or not copyrightable?(only applies to 19ba34c.)copy
.