Skip to content
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

Charpoly computation improved #804

Merged
merged 1 commit into from
Feb 21, 2023

Conversation

stepanoslejsek
Copy link
Contributor

@stepanoslejsek stepanoslejsek commented Feb 5, 2023

I took a task for improving computation of characteristic polynomial as it was mentioned in the #TODO note using the provided article. Testing withing runtest.jl was successful.

@baggepinnen
Copy link
Member

Hello and thanks for your contribution :)

Would you mind submitting a version of the PR without any automatic formatting changes applied?

@stepanoslejsek
Copy link
Contributor Author

Yeah for sure, I apologize for that formatting stuff.

@codecov
Copy link

codecov bot commented Feb 6, 2023

Codecov Report

Merging #804 (d0e0e58) into master (0d06b70) will not change coverage.
The diff coverage is n/a.

❗ Current head d0e0e58 differs from pull request most recent head 21e35b2. Consider uploading reports for the commit 21e35b2 to get more accurate results

@@           Coverage Diff           @@
##           master     #804   +/-   ##
=======================================
  Coverage   97.14%   97.14%           
=======================================
  Files           4        4           
  Lines         315      315           
=======================================
  Hits          306      306           
  Misses          9        9           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@baggepinnen
Copy link
Member

No worries, thanks for updating! It seems like github has been thrown off a bit by some of your changes, it shows the entire file as affected by the diff. I think the PR needs a squash, are you familiar with how to perform one?

@stepanoslejsek
Copy link
Contributor Author

the PR needs a

No, I'm not familiar with PR squashing, but I'm looking forward to learn about it.

@baggepinnen
Copy link
Member

hehe, it's quite simple, but it can be come messy at times. It boils down to combining several different commits so that they appear as a single commit. It can be done from the command-line interface, but I usually use a tool for the purpose, in my case, I use "git lens" in vscode.

@stepanoslejsek
Copy link
Contributor Author

Thank you for the explanation, I'm going to squash the PR then. Hopefully it won't mess up.

@baggepinnen
Copy link
Member

Hmm, your squash worked (nice ;) but there's still a whole-file diff. Base on this article
https://stackoverflow.com/questions/19593909/git-diff-sees-whole-file-as-changed-when-its-not
it seems like your editor has changed the whole file, are you by any chance using windows?

@baggepinnen
Copy link
Member

Yeah, running this command git diff --ignore-space-at-eol master shows that your editor has inserted a bunch of invisible characters at the end of each line (all the ^Ms).

image

I'm not a windows person so I'm not sure how to fix that :/ I suggest googling how to turn that behavior off in your editor

@stepanoslejsek
Copy link
Contributor Author

Oh I see now, I'm using linux, let me fix those invisible characters and squash it once again.

@stepanoslejsek
Copy link
Contributor Author

It's still there, not exactly sure why, I'll try something else to deal with it.

@baggepinnen
Copy link
Member

I think that your editor might be trying too hard to force its settings upon the file by auto formatting. I saw that you had one commit that did away with the line-ending edits, but it had instead inserted tabs instead of spaces for indentation 😃 Generally, these features are best left turned off when working on open-source packages, unless the package happens to follow exactly the same convention that your editor happens to be configured with.

Please don't let this discourage you, I do appreciate your PR and I'm sure you'll find the proper workflow soon!

@mzaffalon
Copy link
Contributor

@stepanoslejsek What editor are you using? I found that Emacs + julia-mode + magit usually work, although I have never tried on Linux.

@stepanoslejsek
Copy link
Contributor Author

I am using neovim, but I found the solution. It'll be without those invisible characters. I checked it with cat -A command.

Copy link
Member

@baggepinnen baggepinnen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks good, thanks for this PR!

What I'm missing is some form of evaluation whether or not this implementation is actually faster and more accurate than the existing one.

Benchmarking the speed should be straightforward using BenchmarkTools.jl, and benchmarking the accuracy could potentially be done by comparing to the result computed using BigFloat

@@ -298,18 +299,40 @@ function siso_ss_to_zpk(sys, i, j)
return z, eigvals(A), k
end

function charpoly(A::AbstractMatrix{T}) where {T}
N = size(A, 1)
poly_factors::Vector{T} = vec(ones(T, N+1, 1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
poly_factors::Vector{T} = vec(ones(T, N+1, 1))
poly_factors = vec(ones(T, N+1))

the type assertion is only beneficial if type inference fails

function charpoly(A::AbstractMatrix{<:Real})
Λ = eigvalsnosort(A)
return prod(roots2real_poly_factors(Λ))
t::Matrix{T} = zeros(N, N) # Preallocation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
t::Matrix{T} = zeros(N, N) # Preallocation
t = zeros(T, N, N) # Preallocation

the element type of a zero matrix is specified like this

Λ = eigvalsnosort(A)
return prod(roots2real_poly_factors(Λ))
t::Matrix{T} = zeros(N, N) # Preallocation
P::Matrix{T}, H::Matrix{T} = hessenberg(A)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
P::Matrix{T}, H::Matrix{T} = hessenberg(A)
P, H = hessenberg(A)

Comment on lines 311 to 318
@simd for j=N:-1:1
@simd for i=1:j
@simd for k=N-j:-1:1
t[k+1, i] = H[i, j]*t[k, j+1]-H[j+1, j]*t[k, i]
end
t[1, i] = H[i, j]
end
@simd for k=1:N-j
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@simd for j=N:-1:1
@simd for i=1:j
@simd for k=N-j:-1:1
t[k+1, i] = H[i, j]*t[k, j+1]-H[j+1, j]*t[k, i]
end
t[1, i] = H[i, j]
end
@simd for k=1:N-j
for j=N:-1:1
for i=1:j
for k=N-j:-1:1
t[k+1, i] = H[i, j]*t[k, j+1]-H[j+1, j]*t[k, i]
end
t[1, i] = H[i, j]
end
for k=1:N-j

@simd is unlikely to do anything here since you are not using @inbounds annotations

end
end
i = N-1:-1:0
poly_factors[2:N+1, 1] = (-1).^(N.-i).*t[N.-i, 1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
poly_factors[2:N+1, 1] = (-1).^(N.-i).*t[N.-i, 1]
@. poly_factors[2:N+1, 1] = (-1)^(N-i)*t[N-i, 1]

@@ -298,18 +299,40 @@ function siso_ss_to_zpk(sys, i, j)
return z, eigvals(A), k
end

function charpoly(A::AbstractMatrix{T}) where {T}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function charpoly(A::AbstractMatrix{T}) where {T}
"""
Implements: "An accurate and efficient algorithm for the computation of the characteristic polynomial of a general square matrix."
"""
function charpoly(A::AbstractMatrix{T}) where {T}

@stepanoslejsek
Copy link
Contributor Author

I did a benchmarking on BigFloat matrix using the old and new method and here is the result of benchmarking using @Btime
image

Returning reverse array deleted

Formatting fixed, handling case when matrix has 0x0 dimensions

Commited conversion.jl

Invisible characters deleted

Truly deleted invisible characters

Removed @simd macros, added docstring
@baggepinnen
Copy link
Member

Wonderful, that's a solid 10x improvement :) I think you might have misunderstood slightly what I meant with using BigFloat though, BigFloats are for comparing accuracy, while performance benchmarks are likely better run using the standard Float64. When you compute using BigFloats, you get an extremely accurate answer which can be considered "the truth", the old and new algorithms are then computed in Float64 and compared to the BigFloat result to see which one is closer to the truth.

@stepanoslejsek
Copy link
Contributor Author

I see, sorry for misunderstanding, I took a random 10x10 Float64 matrix and use the old and new algorithm and pass trough a BigFloat representation and Float64 representation and then took a L2 norm on each coefficient and here is the result.

image

@baggepinnen
Copy link
Member

That looks amazing :) Thanks for your nice contribution!

@baggepinnen baggepinnen merged commit 563516e into JuliaControl:master Feb 21, 2023
@baggepinnen
Copy link
Member

May I ask how you performed the BigFloat computations? When I try to use any floating-point type that is not recognized by BLAS I get

julia> charpoly(big.(A))
ERROR: MethodError: no method matching hessenberg!(::Matrix{BigFloat})

@stepanoslejsek
Copy link
Contributor Author

May I ask how you performed the BigFloat computations? When I try to use any floating-point type that is not recognized by BLAS I get

julia> charpoly(big.(A))
ERROR: MethodError: no method matching hessenberg!(::Matrix{BigFloat})

I ran into same problem with only using ControlSystemsBase, however I found out that in order to work, it is necessary to write using OrdinaryDiffEq, which is somewhat weird. I tested it with only using ControlSystems and then call ControlSystemsBase.charpoly(big.(A)).

@baggepinnen
Copy link
Member

Thanks for the hint, OrdinaryDiffEq loads GenericSchur.jl which contains an implementation of hessenberg that works for BigFloat. I tried with GenericLinearAlgebra which has much poorer compatibility than GenericSchur, but now I know what to use instead :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants