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.

@codecov
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!

@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))

@GianmarcoCuppari
Copy link
Author

I just saw that the tests on this PR aren't working.

Do you think this could be due to code changes @dkarrasch ?

Also, I'm still not sure if you prefer to keep "real." Taking a closer look, it seems like having "real" makes the most sense.

Looking forward to hearing from you,
Thanks!

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.

Here are a few comments. Once that is settled, we should be able to merge.

Comment on lines +3 to +21
---
## 🚀 About this fork

This is my fork of LinearAlgebra.jl for contributing to Julia as part of my GSoC 2026 preparation.

**My contributions:**
- [PR #1445](https://github.com/JuliaLang/LinearAlgebra.jl/pull/1445) - Add hermitianpart method for Number types
- Active in issues: Coming

**Original repository:** [JuliaLang/LinearAlgebra.jl](https://github.com/JuliaLang/LinearAlgebra.jl)

---

# LinearAlgebra

This package is part of the Julia standard library (stdlib).

LinearAlgebra.jl provides functionality for performing linear algebra operations in Julia.

Copy link
Member

Choose a reason for hiding this comment

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

Can you please remove all these changes?

Comment on lines +1014 to +1017
For scalar inputs `x`, the function returns the real part of `x`. Standard integer scalars
are promoted to `Float64` to align with the behavior of 1×1 Hermitian matrices, while other numeric types
(`Float32`, `BigInt`, `Rational`, `BigFloat`) are preserved.
Copy link
Member

Choose a reason for hiding this comment

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

I'd suggest to leave this out, too.

!!! compat "Julia 1.10"
This function requires Julia 1.10 or later.
"""

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

The docstring needs to be attached to the function definition, otherwise Julia doesn't know where it belongs.

"""

hermitianpart(A::AbstractMatrix, uplo::Symbol=:U) = Hermitian(_hermitianpart(A), uplo)
hermitianpart(x::Number) = float(real(x))
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
hermitianpart(x::Number) = float(real(x))
hermitianpart(x::Number) = real(x)

I believe this will get promoted if necessary by other mechanisms, so there is no need to enforce it per se.

inds = A.uplo == 'U' ? ijminmax : reverse(ijminmax)
Base.replace_in_print_matrix(parent(A), inds..., s)
end
end
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
end
end

It's common style to have an empty line at the end. No need to modify this.

Comment on lines +1041 to +1046
@test typeof(hermitianpart(5)) == Float64
@test hermitianpart(2.5) == 2.5
@test hermitianpart(-1 + 0im) == -1
@test typeof(hermitianpart(3 + 4im)) == 3.0
@test typeof(hermitianpart(3 + 4im)) == Float64
@test typeof(hermitianpart(2.5 + 3im)) == Float64
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
@test typeof(hermitianpart(5)) == Float64
@test hermitianpart(2.5) == 2.5
@test hermitianpart(-1 + 0im) == -1
@test typeof(hermitianpart(3 + 4im)) == 3.0
@test typeof(hermitianpart(3 + 4im)) == Float64
@test typeof(hermitianpart(2.5 + 3im)) == Float64
@test hermitianpart(2.5) == 2.5
@test hermitianpart(-1 + 0im) == -1
@test typeof(hermitianpart(3 + 4im)) == 3.0

These type tests wouldn't be needed anymore.

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