How to avoid type instability in the return type of my function?

I have a struct and a simple function:

struct SU2Element <: AbstractMatrix{ComplexF64}
	t₁::ComplexF64
	t₂::ComplexF64

	function SU2Element(t₁::Number, t₂::Number)
		SU2Element(ComplexF64(t₁), ComplexF64(t₂))
	end

	function SU2Element(t₁::ComplexF64, t₂::ComplexF64)
		new(t₁, t₂)
	end
end

function normalizeSU2(S::SU2Element)
	sqrtΔ = √det(S)
	t₁ = S.t₁ / sqrtΔ
	t₂ = S.t₂ / sqrtΔ
	SU2Element(t₁, t₂)
end

This basically takes an SU2Element <: AbstractMatrix{Complex} and normalizes it to have determinant 1.
In an external function, I need to construct an SU2Element and normalize it, but I also need sqrtΔ before the normalization. Since I think it is pointless to calculate sqrtΔ in normalizeSU2 just to have it dropped and recalculated outside, I though of returning sqrtΔ from normalizeSU2 in a tuple, like this:

function normalizeSU2(S::SU2Element, returndet = false)
	sqrtΔ = √det(S)
	t₁ = S.t₁ / sqrtΔ
	t₂ = S.t₂ / sqrtΔ
	if returndet
		SU2Element(t₁, t₂), sqrtΔ
	else
		SU2Element(t₁, t₂)
	end
end

The problem with this approach is that it is not type stable. I know I shouldn’t optimize prematurely, but this function gets called a lot in a hot loop, and having already recognized the type instability, I though of trying to solve it.

One solution is to always return SU2Element(t₁, t₂), sqrtΔ and just not use sqrtΔ in places I don’t need it. But this seems a bit off, I would prefer for the function to return sqrtΔ when I’m explicit about it.

Another approach is to just have two different functions with different names, which works and it is explicit, but also knowing how powerful Julia is, I would like to at least try to find a more Julian solution.

Finally, I could pass Val(true) or Val(false) and dispatch on that, but that would require me to pass Val(false) every time I don’t need sqrtΔ which is a bit annoying.

Is there a nice way of solving this, that lets me call normalizeSU2(S) and have only SU2Element returned, and be explicit when I need sqrtΔ?

I would go for this. (Or just write a, _ = f(...) to ignore the second return value)

2 Likes

what about having 2 methods of the function, where the second one lets the caller pass in the sqrt?

2 Likes

Yeah, it’s a little awkward.

I would probably always return the determinant, and just let the caller throw it away if needs be, ie. su2, _ = normlizeSU2(su2). Seems simple enough.