Function argument Array{Array{ComplexF64,2},1} seems to be ignored

I have a function definition:

function simBiquad(xin::Array{ComplexF64,1}, A::Array{Array{Complex{Float64},2},1},
                    B::Array{Array{Complex{Float64},2},1},
                    C::Array{Array{Complex{Float64},2},1},
                    D::Array{Array{Complex{Float64},2},1})

I call the function in the REPL using:

@enter simBiquad(xin,A,B,C,D);

and get the error message:

ERROR: MethodError: no method matching simBiquad(::Array{Complex{Float64},1}, ::Array{Array{Complex{Float64},2},1}, ::Array{Array{Complex{Float64},2},1}, ::Array{Array{Complex{Float64},2},1}, ::Array{Array{Complex{Float64},2},1})
Closest candidates are:
  simBiquad(::Array{Complex{Float64},1}, ::Array{Complex{Float64},1}, ::Array{Complex{Float64},1}, ::Array{Complex{Float64},1}, ::Array{Complex{Float64},1}) at /home/martin/Dropbox/Matlab/Complex/KM_ComplexFilterToolbox/juliaFiles/LpFlt.jl:39

As you can see the first argument has type ::Array{Complex{Float64},1} which matches, and the other arguments (A,B,C, and D) have the types ::Array{Array{Complex{Float64},2},1} which matches the function declaration line, but the error message states that types ::Array{Complex{Float64},1} are expected even though the function declaration states Arrays of Arrays are expected. I canā€™t see what I am doing wrong; if someone else sees what my error is, any help would be appreciated. Iā€™m hoping this might be something of general interest as Iā€™m guessing itā€™s a scenario that will be common in realizing filter simulations. Thank you.

Sounds like you edited your function definition and didnā€™t re-evaluate it?

PS. Your function seems over-typed ā€” making the function signature so specific has no performance benefits whatsoever, but makes your code much less generic.

2 Likes

stevengj, can you explain how to force a function to be re-evaluated. I currently have the file at the top level, and the function is in a module. Do I have to quit julia and restart it every time I change the function during debug.
I might also mention that my algorithm for running the filter (itā€™s a complex cascade filter where each section can be a first or second-order state-space realization does require the A,B,C, and D arrays to be as specified; if they donā€™t have this form, the filter will not function correctly.

You can define as many versions of the function as you want in a single julia session. To see all versions of your function, you can use

methods(function_name)

Also you really want to be using Revise.jl. This will let you pick up and reevaluate any changes that you make in development.

3 Likes

Follow the instructions here: Configuration Ā· Revise.jl
Revise.jl is your ticket to bliss.

1 Like

They have to be double-precision complex? (They canā€™t be any other number type, e.g. single-precision complex, or unitful values?) You canā€™t use any other array type, only the built-in Array type?

A major advantage of using Julia is code that can be generic over many different number and container types. Even if you require arrays of arrays of numbers, there are still many different choices.

1 Like

Stevengj, I guess there are always many different ways of doing things. I am new to Julia (hence the reason for posting in First Steps), so currently I am just trying whatever I can to get things working. I am slowly trying to figure out how to get julia to re-compile when I make changes, but this is challenging for me. For example, I did make a package and am trying to use Revise, but I still found that a number of times, I needed to do a >git commit -am ā€œā€ in a terminal to get julia to see the changes. I also am trying to not have to make a new package every time I try something different, and to use includet(), and this seems to work a lot, but many times I need to quit julia and restart. I still have not been able to come up with a clean work flow that I understand; again probably because I am new to Julia, and perhaps my incompetence. It is not due to not reading the documentation as much as possible and going back time and time again.
The application I am looking at is using a 1024 channel Filter bank running on a Pi4 to analyze ECG signals. This project is mostly a hobby, although I am working on getting some 4ā€™th year students interested in the application as a project next year. The filter architecture I am looking at is based on using OFDM filters. The current architecture is equivalent to first taking 4096 point FFTā€™s and then adding 7 adjacent channels with appropriate coefficients (with a bit more complication involved as Iā€™m using Frame processing and I also differentiate the signal which I can get for free with the filter-bank approach). This inherently needs array multiplications. Iā€™m not sure what other array type I could use besides the built-in Array type; mayhbe you could be more specific? The stop bands have a lot of attenation (over 100dB as one gets away from a particular channel). I also have some low-pass filtering using a cascade state-space approach, and if I want to use the same code for both first and second-order complex stages; I do need matrix multiplications. I am guessing ComplexF32 could cause some degradation but Iā€™m not sure. I developed the Filters using Matlab, so for now I want to match my Matlab results before optimizing (I have enough complication as it is, and matching the Matlab results makes debugging easier). As long as the speed is say 4 times faster than a personā€™s heart rate, then I currently think the processing speed is not an issue. Anyways, in summary, yes there are better ways, but right now, Iā€™m just trying to get things working.

This should not be necessary.

I would suggest that you get Revise working first. It is an indispensable tool for practical work in Julia.

I think the suggestion is just about not specifying a narrow type unless it is necessary. Generic Julia code should work with any kind of array.

Unless you are implementing various versions of simBiquad which do different things (in the sense of requiring different Julia code) for various types, I would just go with AbstractVector etc, or even no explicit types, and not bother about figuring out types at this stage.

If your code will only work for arrays of arrays, just specify the function signature like this:

function simBiquad(xin, A, B, C, D)

Thereā€™s no need to mess with type specs unless you need to control dispatch, or you really need this for error checking.

Refusing to allow other types of arrays ruins all the fun :wink:

BTW, itā€™s much easier to read and less error prone to use Vector{Matrix{ComplexF64}} rather than Array{Array{Complex{Float64},2},1}

Tamas, thank you for the suggestions. I have taken your (and others) suggestions to use Revise; currently for initial debug of a new module with the file in the same directory, and using

julia> includet("LpFlt.jl")
julia> using .LpFlt

When I get the module mostly working than I plan on making it a Pkg. One thing I have not been able to understand from looking at the documentation for Pkg is how to deal with multiple files. Do they all go in the same directory (./LpFlt/src) and then use ā€œincludeā€ statements, or should there be a separate package (and therefore directory) for each file, and then inside the main file, we have ā€œusingā€ statements?
Regarding you suggestion for using AbstractVector. For a cascade filter, I need A,B,C, and D arrays? for each stage. For example, if a stage is a complex biquad, then A is 2x2 complex, B is 2x1 complex, C is 1x2 complex, and D is 1x1 complex. So I have been using an Nx1 array for the cascade filter, with nxn array for the A,B,C, and D matrices of each stage. For A, I have used

A = Array{Array{ComplexF64,2}}(undef,N)

I donā€™t think I can use an AbstractVector, as my understanding is a vector is always nx1 or 1xn? If I understand correctly, you are suggesting that instead of

function simBiquad(xi::Array{ComplexF64,1}, A::Array{Array{Complex{Float64},2},1},
                    B::Array{Array{Complex{Float64},2},1},
                    C::Array{Array{Complex{Float64},2},1},
                    D::Array{Array{Complex{Float64},2},1})

I use something like?

function simBiquad(xi::AbstractArray, 
                    A::AbstractArray{AbstractArray},
                    B::AbstractArray{AbstractArray},
                    C::AbstractArray{AbstractArray},
                    D::AbstractArray{AbstractArray})

The array of array stuff is not easy for me. For example, in order to initialize the value of D[i] which I have as an Array{ComplexF64,2}, the only way I could figure out was to use:

D[i] = fill(parse(ComplexF64, filt["sections"][i]["d"]), (1,1))

Note: the value is coming from a Yaml file I read in, which puts the A,B,C, and D matrices into an array of Dicts. Anyways, I couldnā€™t figure out a better way of specifying the value of a 1x1 array without using fill(); this is messy to me.

I believe the suggestion is to use

function simBiquad(xi::T, 
                    A::F,
                    B::F,
                    C::F,
                    D::F) where {T<:AbstractArray, F<:AbstractArray}

This is what is meant by ā€œgenericā€ code. You are allowing the flexibility for the type of the argument to be any subtype of AbstractArray. Below is an example of what a code similar to the above would do:

julia> A
1-element Array{Array{Float64,1},1}:
 [1.0, 2.0]

julia> B
2-element Array{Float64,1}:
 1.0
 2.0

julia> function test(g::T, h::F) where {T<:AbstractArray, F<:AbstractArray}
       println(1)
       end
test (generic function with 1 method)

julia> test(A, B)
1
1 Like

This is a very good reason to allow AbstractArray and not just Array. For those filters StaticArrays would be much more efficient than an ordinary Matrix

No, a vector is never nx1 or 1xn, only matrices are nx1 or 1xn, as they have two dimensions. A vector is ā€˜length-nā€™ or size (n,), since it has only one single dimension.

Yes, thatā€™s a common way to organize packages. Just look at some existing Julia packages, or use a template generator (search for suggestions on this forum).

I think you might benefit a lot from

Donā€™t use

A::AbstractArray{AbstractArray}

as it does not do what you think it does. Again, I would not parametrize these at all unless necessary.

I would just go with specifying no types at all (i.e. duck typing). As a beginner, itā€™s too easy to get type specifications wrong, especially for nested types. There is generally no performance benefit to specifying a type in a function call.

Later on, you can narrow the type declaration without changing anything else. The reasons to do this are (1) as filters for dispatch (i.e. doing different things for different types), (2) as a form of documentation, and (3) using type checks to give clearer errors for usage bugs. (But if you pass the wrong type you will typically get an error from elsewhere in the function call anyway.)

2 Likes

BTW, why use an array here at all, instead of a scalar?

You are needlessly forcing yourself to awkwardly create a 1x1 matrix instead of just using a scalar.

DNF, I still donā€™t understand as my A matrices for a biquad must be 2x2 so they canā€™t be vectors? I havenā€™t heard of StaticArrays previously; I am looking into it and they look interesting. Thank you.

What are you replying to, specifically?

Sorry, I should have included it; this has to do with not understanding:
ā€œNo, a vector is never nx1 or 1xn, only matrices are nx1 or 1xn, as they have two dimensions. A vector is ā€˜length-nā€™ or size (n,), since it has only one single dimension.ā€
As far as I understand, I need to use matries (i.e. arrays and not vectors), but again, my background is Matlab and I am very new to Julian hoping to get faster performance so itā€™s hard to get familiar with a lot of the concepts.

A 2x2 array is a matrix, but a nx1 array is not a vector. I was just correcting or answering that statement/question, not saying anything about A.

Edit: this is what I was addressing.