Skip to content

Conversation

GianmarcoCuppari
Copy link

Fixes #1438

This PR implements hermitianpart for Number types as requested in the issue.

Changes:

  • Added hermitianpart(x::Number) = real(x) method in src/symmetric.jl
  • Updated docstring to document the behavior for numbers
  • Added test cases for complex number inputs in test/symmetric.jl

This resolves the MethodError when calling hermitianpart on complex numbers and enables the generic code usage mentioned in the issue.

Copy link

codecov bot commented Sep 16, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.89%. Comparing base (98723df) to head (2839e35).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1445   +/-   ##
=======================================
  Coverage   93.89%   93.89%           
=======================================
  Files          34       34           
  Lines       15920    15921    +1     
=======================================
+ Hits        14948    14949    +1     
  Misses        972      972           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Member

@dkarrasch dkarrasch left a comment

Choose a reason for hiding this comment

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

Thanks for your PR!

Co-authored-by: Daniel Karrasch <[email protected]>
Co-authored-by: Steven G. Johnson <[email protected]>
@araujoms
Copy link
Collaborator

I think we should have hermitianpart(x::Number) = (x + conj(x))/2 in order to match the type behaviour of the matrix case. Then we have hermitianpart(im) === 0.0 + 0.0im, hermitianpart(1) === 1.0 and hermitianpart(1//1) === 1//1.

@GianmarcoCuppari
Copy link
Author

I see the point, but I’d lean towards keeping hermitianpart(x::Number) = real(x) for scalars.
This way we avoid unnecessary operations and preserve the exact type (Int, Rational, etc.), which feels more natural for numbers.
Matrices already use (A + A')/2, so having slightly different behavior for arrays vs. scalars might actually be fine here.

What do you think @stevengj ? Could this make sense?

@stevengj
Copy link
Member

I think the general principle here is that we shouldn't change the type unless it is required to represent the output, since type conversions may incur a loss of data.

@araujoms
Copy link
Collaborator

araujoms commented Sep 17, 2025

real(x) does change the type of x, that's what bothers me. If we use hermitianpart to handle both matrices and scalars - which is the whole point of this function - we want the typing between both cases to be consistent.

I think it would be the perfect solution for the sister PR #1439, which currently has an unsatisfactory behavior:

julia> Z = complex([1 0;0 2]);

julia> inv(Diagonal(Z))
2×2 Diagonal{ComplexF64, Vector{ComplexF64}}:
 1.0-0.0im          
           0.5-0.0im

julia> inv(Hermitian(Diagonal(Z)))
2×2 Hermitian{Float64, Diagonal{Float64, Vector{Float64}}}:
 1.0    
     0.5

@stevengj
Copy link
Member

stevengj commented Sep 17, 2025

real(x) does change the type of x

It doesn't change the underlying numeric type — it just extracts a component, like a[i] for an array, without doing any conversion or losing any data from that component.

If we use hermitianpart to handle both matrices and scalars - which is the whole point of this function - we want the typing between both cases to be consistent.

Why? You want the meaning between both cases to be consistent, so that you can use it in generic code. But the return type is different anyway, so what does it matter if the matrix case (by necessity) also does a floating-point conversion?

@araujoms
Copy link
Collaborator

Because we'd get type instability.

@stevengj
Copy link
Member

Because we'd get type instability.

It's not a type instability to return different output types for different input types.

@araujoms
Copy link
Collaborator

araujoms commented Sep 17, 2025

It's bound to happen with any non-trivial code. It's very common to have special cases for diagonal, symmetric, Hermitian matrices in LinearAlgebra, where we wrap the matrix, do the computation, and unwrap it again. I fixed a lot of type instabilities resulting from this: #1360

Letting the inverse of a complex matrix be a real matrix is just asking for trouble.

@stevengj
Copy link
Member

stevengj commented Sep 17, 2025

It's bound to happen with any non-trivial code.

What is bound to happen?

hermitianpart(::Number) will return a Number, and hermitianpart(::AbstractMatrix) will return a Hermitian matrix. This is not a type instability. What does it matter if the latter is floating-point and the former is not (which is also not a type instability)?

What specific problem are you worried about?

Letting the inverse of a complex matrix be a real matrix is just asking for trouble.

I agree that it's asking for trouble, because the inverse of a complex matrix is generally not real. I don't understand what that has to do with the current PR.

@dkarrasch
Copy link
Member

I think it makes sense to have hermitianpart(x::Number) = real(x). In a "recursive" setting (like where this was originally (thought to be) needed), the real number would get promoted automatically. Also, the automatic promotion already exists as

hermitian(A::Number, ::Symbol=:U) = convert(typeof(A), real(A))

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.

missing hermitianpart(x::Number) = real(x)
4 participants